mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 10:19:47 -04:00
57648745a3
4cc04be3("rebump") renamed the staged mod catalog from <exe>/mods/packages to <exe>/mods/bundled so that a rebuild could wipe build output without deleting the player's own installed mods. runtime.cmake followed the rename. The shared release module followed it (release_overlay_stage.ps1's Add-ModCatalog reads mods/bundled). docs/MOD_PACKAGES.md already described the destination as "the framework's mods/builtin/packages PLUS the title's mods/preloaded/packages". No title CMakeLists followed, because the second half of that sentence was implemented five times, once per repository, as a hand-written add_custom_command(TARGET psx-runtime POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory "${<TITLE>_PRELOADED_MODS}" "$<TARGET_FILE_DIR:psx-runtime>/mods") in ApeEscapeRecomp, Tomba2Recomp and MegaManX4/5/6Recomp. That names the destination as a STRING. There is no symbol, no header, no link dependency -- so renaming a staged output directory breaks nothing a configure, compile or link step can see, and the diffstat of the commit that does it looks cosmetic. The five titles went on staging into a directory neither the launcher nor any packager reads, and discovery was deferred to whichever title next ran a release packager, in a different repository, on a later day. MEASURED, on real build output. Framework origin/master22fbbfcaplus Tomba2Recomp origin/master cdf00af, both unmodified, both built: build-dir mods/ bundled/psx.enhancement.cd-speed <- framework, correct bundled/psx.enhancement.fast-loading bundled/psx.enhancement.pgxp bundled/psx.presentation.bezel packages/tomba2.debug.debug-menu <- title, stranded packages/tomba2.enhancement.skip-fmvs packages/tomba2.enhancement.widescreen packages/tomba2.experimental.interpolated-frame-rate and Add-ModCatalog run against that tree, from this commit's parent: Mod catalog is missing package(s) the sources define: tomba2.debug.debug-menu, tomba2.enhancement.skip-fmvs, tomba2.enhancement.widescreen, tomba2.experimental.interpolated-frame-rate. They exist in the source tree but did not reach ...\mods\bundled, so the release would ship a Mods page the dev build does not have. Shipped releases are NOT affected. Tomba 2 v0.0.9 pins04d9184b, which is an ancestor of4cc04be3and contains zero occurrences of migrate_legacy_root and bundled_root; its artifact is a self-consistent mods/packages layout. This is a forward break only. THE FIX PUTS THE LAYOUT IN ONE PLACE. psxrecomp_add_runtime_target() gains a one-value keyword PRELOADED_MODS_DIR. A title declares WHERE its packages live and never HOW they are laid out: set(APE_PRELOADED_MODS "${CMAKE_CURRENT_SOURCE_DIR}/mods/preloaded") psxrecomp_add_runtime_target(psx-runtime ... PRELOADED_MODS_DIR "${APE_PRELOADED_MODS}") A keyword argument rather than a positional one or a variable read out of the caller's scope, for three reasons. It is what the other fourteen one-value arguments already are, so it needs no new convention and reads the same as the LAUNCHER_BOXART line next to it. A pre-set variable would be invisible at the call site and would silently do nothing if set after the call -- the same "fails quietly in the caller's repository" shape as the bug being fixed. And it is PER TARGET, which a global variable cannot be: Tomba 2 has two runtime targets needing two different catalogs (mods/preloaded, mods/preloaded_ita). psxrecomp_v4_add_runtime_target and psxrecomp_add_game_runtime both forward their remaining arguments, so it reaches through both wrappers unchanged. _psxrt_stage_mod_catalog() now emits, in one POST_BUILD command sequence per runtime target: wipe mods/bundled; remove exactly the ids it is about to stage from any pre-existing mods/packages; copy the framework builtins; copy the title's packages; copy <dir>/README.md to mods/README.md; verify. Consequences worth stating: * mods/README.md previously reached mods/ only as a side effect of copying the whole mods/preloaded tree. Now that only packages/ is staged it is copied on purpose, or it would silently disappear. Checked per title: all five carry mods/preloaded/README.md and none carries a root mods/README.md, so mods/preloaded/README.md is the only source there ever was. * a title may still OVERRIDE a framework builtin at the same id and version, because the framework copy runs first and the title copy overwrites it. Tomba 2's Italian catalog relies on exactly this, shipping localized psx.* manifests. The id LIST is deduplicated for the assertion and the reported count while the copies keep both, or the build claims to stage 11 packages for a catalog that holds 7. * COSIM targets stage nothing. They have no launcher and no Mods page, and they land in the same output directory as the real runtime, so letting one stage had it wipe and re-stage the runtime's catalog with only the framework half whenever -DPSX_BUILD_COSIM=ON built it last. TWO GUARDS, because the failure mode being closed is silence. Configure time: a project with packages under mods/preloaded/packages that does not declare PRELOADED_MODS_DIR is a FATAL_ERROR naming the packages and the argument to add. That is exactly the missed-title state and it is detectable before any build runs. An empty packages/ is fine (the scaffold creates one), and PRELOADED_MODS_DIR NONE is the explicit opt-out. Build time: runtime/psx_check_mod_catalog.cmake fails the build if a declared package did not reach mods/bundled, or if any package this build stages turned up under mods/packages -- which after this change can only mean a CMakeLists still stages its own catalog by hand. It runs as the LAST POST_BUILD step, via cmake_language(DEFER), because POST_BUILD commands execute in registration order and the framework block registers BEFORE anything a title adds after its psxrecomp_add_runtime_target() call. Verified by inspecting build.ninja: the deferred command lands after a title-registered POST_BUILD, not before it. The deferred pass takes NO arguments and reads its work list out of global properties. cmake_language(DEFER CALL <fn> "${var}") does not carry a function-local variable through -- measured with cmake 4.2.2, the callee receives an empty argument and an add_custom_command built from it fails at directory end with "CMakeLists.txt:DEFERRED". Recorded so the next reader does not re-derive it. Ids the build does NOT stage are deliberately left alone in mods/packages. On a self-compiling setup release that directory can hold packages the player installed under the pre-split layout, and mod_packages.cpp's migrate_legacy_root() relocates those into mods/installed on the next launch. Removing only the ids this build stages is also what lets a build directory made before the split be migrated silently, so the guard's "an id turned up in mods/packages" report has no false positives. The same script is registered as the ctest psx_staged_mod_catalog_test in every project that stages a catalog, with REQUIRE_STAGED=0 so a tree where the runtime was never built reports a skip rather than a spurious failure. Where two runtime targets share an output directory it accepts either target's complete catalog, since after a full build mods/bundled holds whichever linked last -- which is correct for the packager, as it builds exactly one target per variant, and each target's own POST_BUILD guard already verified its catalog at stage time. TESTS. runtime/tests/test_mod_catalog_layout.py exercises the guard against on-disk fixtures: a reproduction of the broken state as it actually appeared (bundled/ holding the four psx.* ids, packages/ holding the four game ids), a declared package simply absent, a package directory with no manifest.toml, a FOREIGN legacy package that must NOT fail the build, the require-staged split between the two callers, and the shared-output-directory sibling case. It also pins two source invariants in runtime.cmake so the layout cannot drift back into per-title copies. Registered in runtime/CMakeLists.txt; the test-registration guard reports 123 test file(s), all registered. Framework ctest: 59/59 pass, 2 pre-existing DISABLED. Also updated so the next title does not repeat this: the project scaffold template now passes PRELOADED_MODS_DIR (it staged no catalog at all before, so a scaffolded project with mods/preloaded/packages shipped none), and docs/MOD_PACKAGES.md gains a "How bundled/ gets staged" section written at titles. Bead beads-eio.3.101. The five title-side patches must land AFTER this commit: they reference PRELOADED_MODS_DIR, which does not exist before it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>