mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
Merge remote-tracking branch 'origin/pr/331' into review/pr331
# Conflicts: # runtime/CMakeLists.txt
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
# Setup SDK BIOS assets
|
||||
|
||||
The staged game.toml selects the required BIOS profile. Retail-only recipes do not require the unused OpenBIOS image and license.
|
||||
Default OpenBIOS recipes still require their profile, image and license. The selected profile must be a file inside the staged kit.
|
||||
The gate does not bundle retail BIOS dumps and does not change the CLI's BIOS selection or generated-code readiness checks.
|
||||
|
||||
Every required asset is resolved before the containment check, including default profiles and OpenBIOS assets. A file or parent-directory symlink cannot make an external asset count as part of the kit. Symlinks whose targets remain inside the kit are allowed.
|
||||
|
||||
Run `python runtime/tests/test_setup_bios_assets.py` for ten synthetic fixture tests. Symlink controls run when the host permits symlink creation; other controls always run.
|
||||
The existing `stage_setup_sdk.sh` gate calls the same production checker. Python 3.11 uses tomllib; older Python needs tomli, as other setup tools do.
|
||||
|
||||
This gate is independently useful for retail SCPH1001 setup. Full selected-profile/SCPH5552 support also needs matching CLI, profile and generation support.
|
||||
The current upstream CLI still hardcodes SCPH1001 in its retail generate path. Passing this gate alone does not establish SCPH5552 setup support.
|
||||
@@ -566,6 +566,7 @@ if(BUILD_TESTING)
|
||||
if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
|
||||
add_test(NAME overlay_retry_c11_test COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_overlay_retry_c11.py --compiler ${CMAKE_C_COMPILER})
|
||||
endif()
|
||||
add_test(NAME setup_bios_assets_test COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_setup_bios_assets.py)
|
||||
add_test(NAME disc_companion_test COMMAND ${Python3_EXECUTABLE} ${PSXRECOMP_ROOT}/tools/tests/test_disc_companion.py)
|
||||
add_test(NAME sbi_registry_test COMMAND ${Python3_EXECUTABLE} ${PSXRECOMP_ROOT}/tools/tests/test_sbi_registry.py)
|
||||
add_test(NAME frame_interpolation_context_ownership_test
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Exercise the production SDK BIOS gate with synthetic files only."""
|
||||
import importlib.util
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
spec = importlib.util.spec_from_file_location("gate", ROOT / "tools/check_setup_bios_assets.py")
|
||||
gate = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(gate)
|
||||
|
||||
|
||||
class GateTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.tmp.cleanup)
|
||||
self.root = Path(self.tmp.name)
|
||||
(self.root / "psxrecomp/bios").mkdir(parents=True)
|
||||
|
||||
def recipe(self, text):
|
||||
(self.root / "game.toml").write_text(text)
|
||||
|
||||
def asset(self, name):
|
||||
(self.root / "psxrecomp/bios" / name).write_text("synthetic fixture")
|
||||
|
||||
def test_retail_profile_without_openbios(self):
|
||||
self.recipe('[runtime]\nopenbios=false\n[recompiler]\nbios_config="psxrecomp/bios/SCPH5552.toml"\n')
|
||||
self.asset("SCPH5552.toml")
|
||||
self.assertEqual(gate.check(self.root), [str(Path("psxrecomp/bios/SCPH5552.toml"))])
|
||||
|
||||
def test_retail_missing_profile_fails(self):
|
||||
self.recipe('[runtime]\nopenbios=false\n[recompiler]\nbios_config="psxrecomp/bios/SCPH5552.toml"\n')
|
||||
with self.assertRaisesRegex(ValueError, "SCPH5552"):
|
||||
gate.check(self.root)
|
||||
|
||||
def test_default_openbios_requires_all_assets(self):
|
||||
self.recipe('[runtime]\n')
|
||||
for name in ("OpenBIOS.toml", "openbios.bin", "OpenBIOS.LICENSE"):
|
||||
with self.assertRaises(ValueError):
|
||||
gate.check(self.root)
|
||||
self.asset(name)
|
||||
self.assertEqual(len(gate.check(self.root)), 3)
|
||||
|
||||
def test_external_profile_fails(self):
|
||||
self.recipe('[runtime]\nopenbios=false\n[recompiler]\nbios_config="../external.toml"\n')
|
||||
with self.assertRaisesRegex(ValueError, "inside"):
|
||||
gate.check(self.root)
|
||||
|
||||
def test_absent_recipe_fails(self):
|
||||
with self.assertRaisesRegex(ValueError, "game.toml"):
|
||||
gate.check(self.root)
|
||||
|
||||
def symlink(self, link, target, directory=False):
|
||||
try:
|
||||
link.symlink_to(target, target_is_directory=directory)
|
||||
except (OSError, NotImplementedError) as error:
|
||||
self.skipTest("symlink creation unavailable: " + str(error))
|
||||
|
||||
def outside(self):
|
||||
tmp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(tmp.cleanup)
|
||||
return Path(tmp.name)
|
||||
|
||||
def test_default_retail_rejects_external_profile_symlink(self):
|
||||
self.recipe('[runtime]\nopenbios=false\n')
|
||||
target = self.outside() / "SCPH1001.toml"
|
||||
target.write_text("synthetic fixture")
|
||||
self.symlink(self.root / "psxrecomp/bios/SCPH1001.toml", target)
|
||||
with self.assertRaisesRegex(ValueError, "inside"):
|
||||
gate.check(self.root)
|
||||
|
||||
def test_openbios_rejects_external_asset_symlinks(self):
|
||||
self.recipe('[runtime]\n')
|
||||
names = ("OpenBIOS.toml", "openbios.bin", "OpenBIOS.LICENSE")
|
||||
for name in names:
|
||||
self.asset(name)
|
||||
target = self.outside() / "asset"
|
||||
target.write_text("synthetic fixture")
|
||||
for name in names:
|
||||
with self.subTest(asset=name):
|
||||
link = self.root / "psxrecomp/bios" / name
|
||||
link.unlink()
|
||||
self.symlink(link, target)
|
||||
with self.assertRaisesRegex(ValueError, "inside"):
|
||||
gate.check(self.root)
|
||||
link.unlink()
|
||||
self.asset(name)
|
||||
|
||||
def test_default_retail_rejects_external_bios_directory(self):
|
||||
self.recipe('[runtime]\nopenbios=false\n')
|
||||
target = self.outside()
|
||||
(target / "SCPH1001.toml").write_text("synthetic fixture")
|
||||
link = self.root / "psxrecomp/bios"
|
||||
link.rmdir()
|
||||
self.symlink(link, target, directory=True)
|
||||
with self.assertRaisesRegex(ValueError, "inside"):
|
||||
gate.check(self.root)
|
||||
|
||||
def test_openbios_rejects_external_framework_directory(self):
|
||||
self.recipe('[runtime]\n')
|
||||
target = self.outside()
|
||||
(target / "bios").mkdir()
|
||||
for name in ("OpenBIOS.toml", "openbios.bin", "OpenBIOS.LICENSE"):
|
||||
(target / "bios" / name).write_text("synthetic fixture")
|
||||
(self.root / "psxrecomp/bios").rmdir()
|
||||
link = self.root / "psxrecomp"
|
||||
link.rmdir()
|
||||
self.symlink(link, target, directory=True)
|
||||
with self.assertRaisesRegex(ValueError, "inside"):
|
||||
gate.check(self.root)
|
||||
|
||||
def test_internal_asset_symlinks_are_accepted(self):
|
||||
self.recipe('[runtime]\n')
|
||||
target = self.root / "shared-asset"
|
||||
target.write_text("synthetic fixture")
|
||||
for name in ("OpenBIOS.toml", "openbios.bin", "OpenBIOS.LICENSE"):
|
||||
self.symlink(self.root / "psxrecomp/bios" / name, target)
|
||||
self.assertEqual(len(gate.check(self.root)), 3)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Require BIOS assets selected by the staged title recipe."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
try:
|
||||
import tomllib
|
||||
except ModuleNotFoundError:
|
||||
import tomli as tomllib
|
||||
|
||||
|
||||
def check(stage):
|
||||
stage = Path(stage).resolve()
|
||||
recipe = stage / "game.toml"
|
||||
if not recipe.is_file():
|
||||
raise ValueError("missing staged game.toml for BIOS policy")
|
||||
with recipe.open("rb") as handle:
|
||||
config = tomllib.load(handle)
|
||||
openbios = config.get("runtime", {}).get("openbios", True)
|
||||
if not isinstance(openbios, bool):
|
||||
raise ValueError("runtime.openbios must be a boolean")
|
||||
profile = config.get("recompiler", {}).get("bios_config")
|
||||
required = []
|
||||
if profile:
|
||||
required.append(stage / profile)
|
||||
elif not openbios:
|
||||
# Existing title recipes without a profile use the CLI default.
|
||||
required.append(stage / "psxrecomp/bios/SCPH1001.toml")
|
||||
if openbios:
|
||||
required.extend(stage / "psxrecomp/bios" / name for name in
|
||||
("OpenBIOS.toml", "openbios.bin", "OpenBIOS.LICENSE"))
|
||||
for path in required:
|
||||
path = path.resolve()
|
||||
try:
|
||||
path.relative_to(stage)
|
||||
except ValueError:
|
||||
raise ValueError("BIOS asset must remain inside the staged kit")
|
||||
if not path.is_file():
|
||||
raise ValueError("missing staged BIOS asset: " + str(path.relative_to(stage)))
|
||||
return [str(path.relative_to(stage)) for path in required]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
print("staged BIOS policy: " + ", ".join(check(sys.argv[1])))
|
||||
except (ValueError, OSError) as error:
|
||||
sys.exit("error: " + str(error))
|
||||
@@ -201,12 +201,15 @@ cat >"${STAGE}/psxrecomp/retcomm-sdk.json" <<'EOF'
|
||||
}
|
||||
EOF
|
||||
|
||||
for f in OpenBIOS.toml openbios.bin OpenBIOS.LICENSE SCPH1001.toml; do
|
||||
if [[ ! -f "${STAGE}/psxrecomp/bios/${f}" ]]; then
|
||||
echo "error: missing psxrecomp/bios/${f} in staged tree" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
SDK_PYTHON=python3
|
||||
elif command -v python >/dev/null 2>&1; then
|
||||
SDK_PYTHON=python
|
||||
else
|
||||
echo "error: Python is required to check the staged BIOS policy" >&2
|
||||
exit 1
|
||||
fi
|
||||
"${SDK_PYTHON}" "${SCRIPT_DIR}/check_setup_bios_assets.py" "${STAGE}"
|
||||
|
||||
if [[ "${REQUIRE_CLI}" -eq 1 && ! -f "${STAGE}/psxrecomp/psxrecomp_cli.py" ]]; then
|
||||
echo "error: missing psxrecomp/psxrecomp_cli.py in staged tree" >&2
|
||||
|
||||
Reference in New Issue
Block a user