release: ship recomp-ui's license and asset notices in bundled zips

A bundled release links recomp-ui (MIT) into the executable and copies its
fonts and images into assets/ (OpenMoji CC BY-SA 4.0; Noto Sans Symbols 2,
Lato and the Noto Color Emoji flag sheet, OFL-1.1), but
package_game_release.sh staged only psxrecomp's own notices. The setup-host
zips carried the whole recomp-ui tree, so the move to bundled releases
dropped recomp-ui/LICENSE and assets/common/{fonts,img}/NOTICE.md from every
title's release.

The packager now stages recomp-ui's LICENSE as licenses/recomp-ui-LICENSE
and its fonts/img NOTICE.md beside the files they cover, from the tree the
build linked (RECOMP_UI_ROOT in the build's CMakeCache, else
<root>/recomp-ui), unless the build turned PSX_RECOMP_UI off. A title
cannot do this itself with --runtime-file (the path would keep recomp-ui/,
which the kit gate rejects) or --doc (staged by basename, so LICENSE would
overwrite the title's own).

Not covered: the TinyCC LGPL-2.1 notice for the Windows overlay_toolchain/tcc
(THIRD_PARTY_ATTRIBUTION.md says it must ship with it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Shokunin
2026-09-30 20:18:18 -07:00
parent c9445d00cb
commit cd4a2aced6
3 changed files with 31 additions and 2 deletions
+4 -2
View File
@@ -45,8 +45,10 @@ Template: [`templates/game-release.yml`](templates/game-release.yml).
executable, `assets/`, `bios/openbios.bin` + notice, `game.toml`,
`game_options.toml`, the mod catalog (verified against the manifest the
build published, developer-channel packages pruned), `overlay_toolchain/`,
third-party notices; refuse if anything kit-shaped is in the stage; sign on
Windows; zip.
third-party notices (psxrecomp's in `licenses/`, plus recomp-ui's license
as `licenses/recomp-ui-LICENSE` and its font/image notices as
`assets/{fonts,img}/NOTICE.md`); refuse if anything kit-shaped is in the
stage; sign on Windows; zip.
6. Verify the zip: executable + OpenBIOS + catalog present; no `psxrecomp/`,
`recomp-ui/`, CLI, emitters at the root, sources, generated C, dumps,
per-machine mod state.
+18
View File
@@ -377,6 +377,24 @@ if [[ -d "${FRAMEWORK}/runtime/licenses" ]]; then
fi
[[ -f "${FRAMEWORK}/THIRD_PARTY_ATTRIBUTION.md" ]] && cp -a "${FRAMEWORK}/THIRD_PARTY_ATTRIBUTION.md" "${STAGE}/licenses/"
[[ -f "${FRAMEWORK}/LICENSE" ]] && cp -a "${FRAMEWORK}/LICENSE" "${STAGE}/licenses/psxrecomp-LICENSE"
# recomp-ui: the executable links it (MIT) and assets/ carries its fonts and
# images (OFL-1.1, CC BY-SA 4.0), so its license and asset notices ship too.
# The build's RECOMP_UI_ROOT says which tree it linked; <root>/recomp-ui is the
# New Project Layout default.
if ! grep -qs '^PSX_RECOMP_UI:BOOL=OFF' "${BUILD_DIR}/CMakeCache.txt"; then
UI_ROOT="$(sed -n 's/^RECOMP_UI_ROOT:[A-Z]*=//p' "${BUILD_DIR}/CMakeCache.txt" 2>/dev/null | head -n1 || true)"
[[ -n "${UI_ROOT}" && -f "${UI_ROOT}/recomp_ui.cmake" ]] || UI_ROOT="${ROOT}/recomp-ui"
if [[ -f "${UI_ROOT}/recomp_ui.cmake" ]]; then
[[ -f "${UI_ROOT}/LICENSE" ]] || { echo "error: ${UI_ROOT}/LICENSE missing" >&2; exit 1; }
cp -a "${UI_ROOT}/LICENSE" "${STAGE}/licenses/recomp-ui-LICENSE"
for sub in fonts img; do
notice="${UI_ROOT}/assets/common/${sub}/NOTICE.md"
if [[ -f "${notice}" && -d "${STAGE}/assets/${sub}" ]]; then
cp -a "${notice}" "${STAGE}/assets/${sub}/NOTICE.md"
fi
done
fi
fi
if [[ -z "$(ls -A "${STAGE}/licenses" 2>/dev/null)" ]]; then
echo "error: no third-party notices staged (${FRAMEWORK}/runtime/licenses empty?)" >&2
exit 1
@@ -309,6 +309,15 @@ class PackagerScriptContract(unittest.TestCase):
self.assertNotIn("--project-file", text)
self.assertNotIn("--project-dir", text)
def test_bundled_packager_ships_recomp_ui_notices(self):
# The executable links recomp-ui and assets/ carries its fonts and
# images; their license and notices must ship with them.
text = (FW / "tools" / "package_game_release.sh").read_text(encoding="utf-8")
self.assertIn('"${STAGE}/licenses/recomp-ui-LICENSE"', text)
self.assertIn('assets/common/${sub}/NOTICE.md', text)
self.assertIn('"${STAGE}/assets/${sub}/NOTICE.md"', text)
self.assertIn("RECOMP_UI_ROOT", text, "must follow the tree the build linked")
def test_scripts_are_executable_and_parse(self):
for rel in ("tools/package_game_release.sh", "tools/ci/check_generated.sh"):
p = FW / rel