Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
9.0 KiB
Releasing a title
One framework command cuts every player release, for every title, on Windows
and Linux. A title keeps only its release config, packaging/release.toml.
Release fixes land here once instead of in a forked packager per title.
Commands
From the title repository root, with submodules checked out at their pins:
# Windows host: Windows ZIP, Linux AppImage (through WSL), or both
psxrecomp/tools/package_title_release.ps1 --channel public --platform all
psxrecomp/tools/package_title_release.ps1 --channel playtest --platform all
# Native Linux host: the AppImage
psxrecomp/tools/package_title_release.sh --channel public
psxrecomp/tools/package_title_release.sh --channel playtest
--channel is required and has no default. Without it the command fails at
once with PACKAGING FAILED. Artifacts land in dist/:
| Channel | Diagnostics default | Windows | Linux |
|---|---|---|---|
public |
off | <Name>-v<ver>-windows-x64.zip |
<Name>-v<ver>-linux-x86_64.AppImage |
playtest |
on | <Name>-v<ver>-playtest-windows-x64.zip |
<Name>-v<ver>-playtest-linux-x86_64.AppImage |
Each artifact has a .sha256 file beside it. RELEASE_MANIFEST.json inside
the package records the channel, version, title/framework/recomp-ui commits,
mod list, imports or glibc, and a hash for every file. The command never tags,
pushes or uploads.
Other options: --jobs N (default min(6, cores-2)), --out-dir,
--work-dir (default build-package/<platform>), --toolchain-bin
(Windows MinGW, default C:\msys64\mingw64\bin), and --allow-dirty.
Without --allow-dirty, uncommitted tracked changes, including a submodule
moved off its pin, are refused. With it they are recorded in the manifest, so
that package must not be published.
verify --channel C ARTIFACT... extracts finished artifacts and re-runs every
check. The command already does this after each package.
What the command does
Any failure ends with PACKAGING FAILED: <reason> and a non-zero exit code.
No step warns and then continues.
- Provenance. Reads the title, framework and recomp-ui commits and checks for tracked changes.
- Emitters. Builds
psxrecomp-gameandpsxrecomp-biosbefore any slow step. The overlay toolchain needs both. - Sources. Regenerates the recompiled OpenBIOS if it is missing, then
regenerates the game C from the disc. For AOT mode
static, it also builds the static AOT units from the original disc. On Windows,--platform linuxorallprepares the sources once there. The WSL build then compiles those same audited sources. - Build. Release,
PSX_DEBUG_TOOLS=OFF, OpenBIOS-only backend (PSXRECOMP_BIOS_STEMS=OpenBIOS),PSX_RELEASE_CHANNEL=<channel>, andPSX_GAME_VERSIONfrom the title's version file. Every flag the command owns is read back fromCMakeCache.txt. A title'scmake_argsmay not set any of them. - Stage. Stages these items:
- the executable and its version stamp
- launcher assets
- OpenBIOS and its MIT notice
game.tomlandgame_options.toml- the mod catalog, checked against the build's own catalog list. Developer-channel features are removed from public packages.
- for AOT mode
cache, the original-disc AOT cache withAOT_CACHE_AUDIT.json - the overlay toolchain, if the title ships it
- framework, recomp-ui and third-party notices
- the title's files
START_HERE.txtwith@VERSION@filled in. A template without the token is an error.- the execution contract
- Gates. Refuses the package if it contains any of these: disc images,
retail BIOS images,
.binfiles other thanbios/openbios.bin, saves, memory cards, save states, personal settings or mod state, generated or other C sources outsideoverlay_toolchain/, or build-kit trees. It also checks that the Playtest Diagnostics defaults match the channel, and runstools/ci/audit_playtest_diagnostics_package.py --mode <channel>. On Windows the executable must import only system DLLs. For AOT modestatic, every audited unit must match its receipt and be linked into the executable. A diagnostics package that the config does not declare is an error. - Package and verify. Writes a Windows ZIP with portable
/names, or builds a Linux AppImage withtools/package_appimage.sh. The AppImage uses fixed linuxdeploy1-alpha-20251107-1and appimagetool1.9.1, checked by SHA-256. The command writes the.sha256file, then extracts the artifact and verifies it.
Linux needs cmake, ninja (or make), gcc, Python 3.11+, ImageMagick,
curl, file and the SDL3/OpenGL development packages. On a Windows host,
the Linux half runs inside the default WSL distribution, using the same
checkout through /mnt/<drive>. The build tree stays on that drive. Only the
payload, AppDir and verification extraction use /tmp, and they are removed
afterwards.
The backstop: no package without a channel
runtime/psx_release_channel.cmake makes PSX_RELEASE_CHANNEL
(public | playtest) the only release input. PSX_PUBLIC_RELEASE is now
derived from it. Configure fails in these cases:
- a channel other than
publicorplaytest -DPSX_PUBLIC_RELEASE=ONwithout a channel (the old way to ask for a public build)- a
PSX_PUBLIC_RELEASEvalue that contradicts the channel
A tree configured with no channel is a dev build. The packager configures its own tree with the channel, then reads it back before staging.
packaging/release.toml
Unknown keys are errors, so a typo cannot silently drop a step.
schema = 1
[product]
name = "JerseyDevilRecomp" # artifact name prefix (and AppImage data dir token)
display_name = "Jersey Devil Recompiled"
exe_name = "JerseyDevil_Recompiled" # staged executable; default = CMake OUTPUT_NAME
version_file = "packaging/release/VERSION" # raw text = PSX_GAME_VERSION and @VERSION@
# game_toml = "game.toml" # codegen config (default)
# player_game_toml = "game.toml" # shipped config (default = game_toml)
# runtime_target = "psx-runtime"
# zip_folder = "MediEvilIIRecomp" # optional single top-level ZIP folder
launcher_boxart = "launcher_assets/img/boxart.tga" # staged copy must match
[build]
cmake_args = ["-DPSX_SDL_BACKEND=SDL3", "-DPSX_PGXP_VARIANT=OFF"]
# windows_cmake_args / linux_cmake_args: per-platform extras
# regenerate = true # psxrecomp-game --config game.toml first
# binary_identity = "{version}+{commit12}" # bytes the executable must contain
[aot]
mode = "cache" # cache: ship the original-disc AOT cache
# static: compile audited units into the exe
# none
profile = "aot/overlays.json"
# cps = true # default: true for static, false for cache
[overlay_toolchain]
ship = true # bundle the runtime overlay compiler
[diagnostics] # required if the catalog has a *.diagnostics package
game_id = "SCUS-94907"
package_id = "jerseydevil.diagnostics"
# replay_helper = false # title records with its own tool (no replay-diagnostics.py)
[payload]
start_here = "packaging/release/START_HERE.txt" # must contain @VERSION@
files = ["README.md", { from = "packaging/release/input.ini", to = "input.ini" }]
# build_files = ["..."] # extra files from beside the built executable
[linux]
app_run = "packaging/linux/AppRun" # @VERSION@ @APP_NAME@ @EXE_NAME@ @PAYLOAD_DIR@
# @ENV_PREFIX@ @ARTIFACT_NAME@ are filled in
desktop_file = "packaging/linux/io.github.mstan.JerseyDevilRecomp.desktop"
icon = "launcher_assets/img/boxart.tga" # PNG, or converted with ImageMagick
payload_dir = "jerseydevilrecomp" # usr/share/<payload_dir>
env_prefix = "JERSEYDEVIL_RECOMP"
# prune_unreferenced_libs = true # drop usr/lib entries the exe does not load
# Optional title steps, run at prepare | post_build | post_stage. Tokens:
# {python} {root} {framework} {channel} {platform} {version} {build_dir}
# {work_dir}, plus {exe} (post_build) and {stage} (post_stage). A non-zero
# exit fails the release.
# [[hooks.post_stage]]
# command = ["{python}", "tools/check_my_payload.py", "{stage}"]
# platforms = ["windows", "linux"]
aot.mode = "static" checks generated/AOT_STATIC_AUDIT.json in these ways:
every pair is valid, the recipe count matches expected_records, the
game.toml and profile hashes match, each unit's hash matches its receipt,
and each unit appears in the executable's link line.
Migrating a title
Rules for every migrated title:
- Delete the forked packagers (
tools/package_release.ps1,tools/package_appimage.sh,tools/package_release.py, prepare scripts,scripts/package_setup_release.sh) and their tests. - Write
packaging/release.toml. - Put
@VERSION@inSTART_HERE.txt. - Remove any title CMake that forces
PSX_PUBLIC_RELEASE. - Pin the latest psxrecomp and recomp-ui.
Prove the migration with both channels on both platforms, then verify the
artifacts. Titles migrated so far: Jersey Devil and MediEvil II. The backlog
is tracked in bead beads-eio.3.369.