mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 09:59:35 -04:00
Restore native POSIX setup tools and platform release validation
This commit is contained in:
@@ -492,9 +492,9 @@ static int find_python(char* out, size_t cap) {
|
||||
snprintf(out, cap, "%s", env);
|
||||
return 1;
|
||||
}
|
||||
#if defined(_WIN32)
|
||||
if (find_toolchain_python(out, cap))
|
||||
return 1;
|
||||
#if defined(_WIN32)
|
||||
/* Prefer python.org / py-launcher installs over the Microsoft Store
|
||||
* stub: Store Python redirects LocalAppData writes into LocalCache. */
|
||||
char resolved[1100];
|
||||
@@ -799,6 +799,10 @@ static int resolve_toolchain_bin(char* out, size_t cap) {
|
||||
static void activate_toolchain_path(void) {
|
||||
char pack_root[1400];
|
||||
g_toolchain_bin[0] = '\0';
|
||||
#if !defined(_WIN32)
|
||||
/* A cached portable pack must not shadow Linux/macOS native tools. */
|
||||
return;
|
||||
#endif
|
||||
if (!resolve_toolchain_bin(g_toolchain_bin, sizeof(g_toolchain_bin)))
|
||||
return;
|
||||
/* Pack root (parent of bin/) — Windows cmake-clang-v1 ships zlib here. */
|
||||
@@ -907,6 +911,9 @@ static int find_cmake(char* out, size_t cap) {
|
||||
snprintf(out, cap, "%s", env);
|
||||
return 1;
|
||||
}
|
||||
#if !defined(_WIN32)
|
||||
return find_on_path("cmake", out, cap);
|
||||
#endif
|
||||
char tc[1100], cand[1200];
|
||||
if (resolve_toolchain_bin(tc, sizeof(tc))) {
|
||||
#if defined(_WIN32)
|
||||
@@ -3064,6 +3071,44 @@ static int cmake_path_runs(const char* cmake_path) {
|
||||
return run_cmd_exit_zero(cmd);
|
||||
}
|
||||
|
||||
#if !defined(_WIN32)
|
||||
/* Native release kits probe host tools; setup never installs system packages. */
|
||||
static int posix_command_runs(const char* command) {
|
||||
char cmd[1600];
|
||||
if (!command || !command[0])
|
||||
return 0;
|
||||
snprintf(cmd, sizeof(cmd), "\"%s\" --version >/dev/null 2>&1", command);
|
||||
return run_cmd_exit_zero(cmd);
|
||||
}
|
||||
|
||||
static int host_system_toolchain_ready(void) {
|
||||
char python[1200], tool[1200];
|
||||
#if defined(__APPLE__)
|
||||
/* Avoid xcrun prompting to install tools during a readiness check. */
|
||||
if (!run_cmd_exit_zero("xcode-select -p >/dev/null 2>&1"))
|
||||
return 0;
|
||||
#endif
|
||||
if (!find_on_path("cmake", g_cmake, sizeof(g_cmake)) ||
|
||||
!posix_command_runs(g_cmake))
|
||||
return 0;
|
||||
if (!(find_on_path("python3", python, sizeof(python)) ||
|
||||
find_on_path("python", python, sizeof(python))) ||
|
||||
!posix_command_runs(python))
|
||||
return 0;
|
||||
if (!find_on_path("ninja", tool, sizeof(tool)) || !posix_command_runs(tool))
|
||||
return 0;
|
||||
if (!(find_on_path("cc", tool, sizeof(tool)) ||
|
||||
find_on_path("gcc", tool, sizeof(tool)) ||
|
||||
find_on_path("clang", tool, sizeof(tool))) || !posix_command_runs(tool))
|
||||
return 0;
|
||||
if (!(find_on_path("c++", tool, sizeof(tool)) ||
|
||||
find_on_path("g++", tool, sizeof(tool)) ||
|
||||
find_on_path("clang++", tool, sizeof(tool))) || !posix_command_runs(tool))
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* True when pack bin/ can compile+link a tiny C program (catches broken
|
||||
* ld.lld / missing libicuuc.so.* etc. that cmake --version still passes). */
|
||||
static int toolchain_bin_compiler_works(const char* bin) {
|
||||
@@ -3367,6 +3412,9 @@ static int host_toolchain_is_ready(void) {
|
||||
if (!g_project_root[0])
|
||||
return 0;
|
||||
g_tc_repair_note[0] = '\0';
|
||||
#if !defined(_WIN32)
|
||||
return host_system_toolchain_ready();
|
||||
#endif
|
||||
migrate_legacy_psxrecomp_toolchain();
|
||||
/* Wizard open: drop broken latest/ before treating the pack as ready. */
|
||||
heal_broken_toolchain_pointers();
|
||||
@@ -3538,6 +3586,9 @@ static int host_toolchain_update_available(char* local_ver, size_t local_cap,
|
||||
local_ver[0] = '\0';
|
||||
if (remote_ver && remote_cap)
|
||||
remote_ver[0] = '\0';
|
||||
#if !defined(_WIN32)
|
||||
return 0;
|
||||
#endif
|
||||
if (skip && skip[0] && skip[0] != '0')
|
||||
return 0;
|
||||
if (!g_project_root[0])
|
||||
@@ -3568,6 +3619,16 @@ static int host_ensure_toolchain_with_progress(
|
||||
snprintf(err_msg, err_cap, "Project root is not available.");
|
||||
return 0;
|
||||
}
|
||||
#if !defined(_WIN32)
|
||||
if (on_progress)
|
||||
on_progress(progress_ctx, 0.02f, "Checking native build tools…");
|
||||
if (host_system_toolchain_ready())
|
||||
return 1;
|
||||
snprintf(err_msg, err_cap,
|
||||
"Native build tools are missing. Install CMake, Ninja, Python 3, "
|
||||
"and C/C++ compilers with your package manager, then check tools again.");
|
||||
return 0;
|
||||
#endif
|
||||
migrate_legacy_psxrecomp_toolchain();
|
||||
activate_toolchain_path();
|
||||
if (!force && host_portable_cmake_ready())
|
||||
|
||||
@@ -62,6 +62,8 @@ def activate_embedded_toolchain(
|
||||
project_root: Path, progress: Optional[ProgressReporter] = None
|
||||
) -> bool:
|
||||
"""Prepend resolved toolchain bin/ to PATH for cmake/ninja/clang."""
|
||||
if sys.platform != "win32":
|
||||
return False
|
||||
log = progress.log if progress else None
|
||||
bin_dir = resolve_toolchain_bin(project_root)
|
||||
if not bin_dir or not toolchain_bin_runs(bin_dir, log=log):
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Exercise the production SDK BIOS gate with synthetic files only."""
|
||||
import importlib.util
|
||||
import ast
|
||||
import types
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest import mock
|
||||
import zipfile
|
||||
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)
|
||||
spec = importlib.util.spec_from_file_location("platform_copy", ROOT / "tools/ci/audit_setup_package_platform_copy.py")
|
||||
platform_copy = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(platform_copy)
|
||||
spec = importlib.util.spec_from_file_location("toolchain_pack", ROOT / "tools/toolchain_pack.py")
|
||||
toolchain_pack = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(toolchain_pack)
|
||||
|
||||
|
||||
class GateTests(unittest.TestCase):
|
||||
@@ -152,7 +162,9 @@ class SetupPackageTests(unittest.TestCase):
|
||||
self.write("CMakeLists.txt", "# synthetic title\n")
|
||||
self.write("VERSION", "0.0.1\n")
|
||||
self.write("build/psx_game_version.txt", "0.0.1\n")
|
||||
self.write("build/Fixture", "synthetic executable, never run\n")
|
||||
self.write("build/Fixture", "synthetic executable, never run\n"
|
||||
"1. Native build tools\n"
|
||||
"Install CMake, Ninja, Python 3, and either Clang or GCC\nCheck tools\n")
|
||||
self.write("build/replay-diagnostics.py", "# staged replay helper\n")
|
||||
# Packaging tests exercise staging, not a system compression utility.
|
||||
# Ubuntu developer images may omit zip; write a real ZIP with stdlib.
|
||||
@@ -174,7 +186,7 @@ with zipfile.ZipFile(sys.argv[-2], "w") as archive:
|
||||
self.write("game.toml", "[runtime]\noverlay_cache=true\nopenbios=" +
|
||||
("false" if retail else "true") + "\n")
|
||||
args = ["bash", str(ROOT / "tools/package_setup_host.sh"),
|
||||
"--root", str(self.root), "--build-dir", "build", "--artifact", "fixture",
|
||||
"--root", str(self.root), "--build-dir", "build", "--artifact", "linux-x64",
|
||||
"--zip-prefix", "test", "--exe-name", "Fixture", "--recompiler-build", "emitters",
|
||||
"--project-file", "CMakeLists.txt", "--project-file", "game.toml"]
|
||||
if retail:
|
||||
@@ -188,12 +200,16 @@ with zipfile.ZipFile(sys.argv[-2], "w") as archive:
|
||||
with self.subTest(retail=retail):
|
||||
result = self.package(retail)
|
||||
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
||||
stage = self.root / "dist/stage-setup-fixture"
|
||||
stage = self.root / "dist/stage-setup-linux-x64"
|
||||
self.assertTrue((stage / "replay-diagnostics.py").is_file())
|
||||
self.assertTrue((stage / "psxrecomp/bios/SCPH1001.toml").is_file())
|
||||
for name in ("OpenBIOS.toml", "openbios.bin", "OpenBIOS.LICENSE"):
|
||||
self.assertEqual((stage / "psxrecomp/bios" / name).exists(), not retail)
|
||||
readme = (stage / "README-SETUP.txt").read_text()
|
||||
self.assertIn("Install CMake, Ninja, Python 3", readme)
|
||||
self.assertIn("native build tools", readme)
|
||||
self.assertNotIn("cmake-clang-v1", readme)
|
||||
self.assertEqual(platform_copy.audit(self.root / "dist/test-0.0.1-linux-x64.zip")["status"], "pass")
|
||||
if retail:
|
||||
self.assertIn("a legal retail BIOS dump (required)", readme)
|
||||
self.assertNotIn("otherwise OpenBIOS", readme)
|
||||
@@ -216,5 +232,96 @@ with zipfile.ZipFile(sys.argv[-2], "w") as archive:
|
||||
self.assertIn("requires runtime.openbios=false", result.stderr)
|
||||
|
||||
|
||||
class PlatformCopyTests(unittest.TestCase):
|
||||
def test_gate_requires_real_platform_copy_and_controls(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
for platform in ("windows-x64", "linux-x64", "macos-arm64", "macos-x64"):
|
||||
windows = platform.startswith("windows")
|
||||
host = "Fixture.exe" if windows else "Fixture"
|
||||
readme = "1. Run {}.\n2. Provide a legal retail BIOS (required).\n".format(host)
|
||||
if windows:
|
||||
readme += "downloads cmake-clang-v1 toolchain pack\n"
|
||||
controls = "Download latest portable toolchain Select toolchain zip cmake-clang-v1"
|
||||
else:
|
||||
readme += "Install CMake, Ninja, Python 3 and a C/C++ compiler; available on PATH; native build tools\n"
|
||||
controls = "1. Native build tools Install CMake, Ninja, Python 3, and either Clang or GCC Check tools"
|
||||
archive = Path(tmp) / ("setup-" + platform + ".zip")
|
||||
|
||||
def write(extra=""):
|
||||
with zipfile.ZipFile(archive, "w") as output:
|
||||
output.writestr("README-SETUP.txt", readme)
|
||||
output.writestr(host, controls + extra)
|
||||
|
||||
write()
|
||||
self.assertEqual(platform_copy.audit(archive)["status"], "pass")
|
||||
if not windows:
|
||||
write("1. Portable toolchain")
|
||||
with self.assertRaisesRegex(ValueError, "forbidden"):
|
||||
platform_copy.audit(archive)
|
||||
|
||||
|
||||
class NativeToolchainTests(unittest.TestCase):
|
||||
def test_cli_does_not_activate_cached_packs_on_posix(self):
|
||||
tree = ast.parse((ROOT / "psxrecomp_cli.py").read_text(encoding="utf-8"))
|
||||
function = next(node for node in tree.body if isinstance(node, ast.FunctionDef)
|
||||
and node.name == "activate_embedded_toolchain")
|
||||
function.returns = None
|
||||
for argument in function.args.args:
|
||||
argument.annotation = None
|
||||
namespace = {"sys": types.SimpleNamespace(platform="linux"),
|
||||
"resolve_toolchain_bin": mock.Mock(side_effect=AssertionError("cached pack"))}
|
||||
exec(compile(ast.Module(body=[function], type_ignores=[]), "cli_activation", "exec"), namespace)
|
||||
self.assertFalse(namespace[function.name](Path(".")))
|
||||
namespace["resolve_toolchain_bin"].assert_not_called()
|
||||
|
||||
def test_host_native_guards_precede_cache_and_download_paths(self):
|
||||
source = (ROOT / "host/psxrecomp_codegen_host.c").read_text(encoding="utf-8")
|
||||
for start, native, portable in (
|
||||
("static void activate_toolchain_path(void)", "return;", "if (!resolve_toolchain_bin"),
|
||||
("static int host_toolchain_is_ready(void)", "return host_system_toolchain_ready();", "migrate_legacy_psxrecomp_toolchain();"),
|
||||
("static int host_ensure_toolchain_with_progress(", "if (host_system_toolchain_ready())", "migrate_legacy_psxrecomp_toolchain();")):
|
||||
body = source[source.index(start):]
|
||||
self.assertLess(body.index("#if !defined(_WIN32)"), body.index(native))
|
||||
self.assertLess(body.index(native), body.index(portable))
|
||||
body = source[source.index("static int host_system_toolchain_ready(void)"):
|
||||
source.index("/* True when pack bin/ can compile+link")]
|
||||
for tool in ("cmake", "ninja", "python3", "cc", "c++"):
|
||||
self.assertIn('find_on_path("' + tool + '"', body)
|
||||
self.assertIn("xcode-select -p", body)
|
||||
|
||||
def test_posix_uses_native_probes_and_never_touches_pack_cache_or_network(self):
|
||||
for platform in ("linux", "darwin"):
|
||||
with self.subTest(platform=platform), \
|
||||
mock.patch.object(toolchain_pack.sys, "platform", platform), \
|
||||
mock.patch.object(toolchain_pack.shutil, "which", side_effect=lambda name: "/native/bin/" + name), \
|
||||
mock.patch.object(toolchain_pack.subprocess, "run", return_value=types.SimpleNamespace(returncode=0)) as probe, \
|
||||
mock.patch.object(toolchain_pack, "migrate_legacy_psxrecomp_cache", side_effect=AssertionError("touched cache")):
|
||||
self.assertEqual(toolchain_pack.ensure_toolchain(download=True), Path("/native/bin"))
|
||||
self.assertEqual(len(probe.call_args_list), 5)
|
||||
self.assertTrue(all(call.args[0][-1] == "--version" for call in probe.call_args_list))
|
||||
|
||||
def test_posix_missing_and_broken_tools_fail_without_download(self):
|
||||
for broken in (False, True):
|
||||
with self.subTest(broken=broken), \
|
||||
mock.patch.object(toolchain_pack, "sys_platform_is_windows", return_value=False), \
|
||||
mock.patch.object(toolchain_pack.shutil, "which", return_value="/native/cmake" if broken else None), \
|
||||
mock.patch.object(toolchain_pack.subprocess, "run", return_value=types.SimpleNamespace(returncode=1)), \
|
||||
mock.patch.object(toolchain_pack, "migrate_legacy_psxrecomp_cache", side_effect=AssertionError("touched cache")):
|
||||
with self.assertRaisesRegex(RuntimeError, "package manager"):
|
||||
toolchain_pack.ensure_toolchain(download=True)
|
||||
|
||||
def test_windows_retains_existing_portable_pack_activation(self):
|
||||
binary = Path("fixture-pack/bin")
|
||||
with mock.patch.object(toolchain_pack, "sys_platform_is_windows", return_value=True), \
|
||||
mock.patch.object(toolchain_pack, "is_windows_store_python", return_value=False), \
|
||||
mock.patch.object(toolchain_pack, "migrate_legacy_psxrecomp_cache"), \
|
||||
mock.patch.object(toolchain_pack, "resolve_toolchain_bin", return_value=binary), \
|
||||
mock.patch.object(toolchain_pack, "toolchain_bin_runs", return_value=True), \
|
||||
mock.patch.object(toolchain_pack, "register_toolchain_user_env"), \
|
||||
mock.patch.object(toolchain_pack, "activate_toolchain_bin") as activate:
|
||||
self.assertEqual(toolchain_pack.ensure_toolchain(download=True), binary)
|
||||
activate.assert_called_once_with(binary, log=None)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Reject setup ZIPs whose toolchain copy does not match the host platform."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import zipfile
|
||||
from pathlib import Path, PurePosixPath
|
||||
|
||||
|
||||
PLATFORM_MARKERS = {
|
||||
"windows": ("windows-x64",),
|
||||
"linux": ("linux-x64",),
|
||||
"macos": ("macos-x64", "macos-arm64"),
|
||||
}
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest().upper()
|
||||
|
||||
|
||||
def infer_platform(path: Path) -> str:
|
||||
lower = path.name.lower()
|
||||
matches = [
|
||||
platform
|
||||
for platform, markers in PLATFORM_MARKERS.items()
|
||||
if any(marker in lower for marker in markers)
|
||||
]
|
||||
if len(matches) != 1:
|
||||
raise ValueError(
|
||||
"archive name must contain exactly one supported platform marker: "
|
||||
"windows-x64, linux-x64, macos-x64, or macos-arm64"
|
||||
)
|
||||
return matches[0]
|
||||
|
||||
|
||||
def root_member(names: list[str], basename: str) -> str:
|
||||
matches = [
|
||||
name
|
||||
for name in names
|
||||
if not name.endswith("/")
|
||||
and len(PurePosixPath(name).parts) == 1
|
||||
and PurePosixPath(name).name.lower() == basename.lower()
|
||||
]
|
||||
if len(matches) != 1:
|
||||
raise ValueError(f"archive must contain exactly one root {basename}")
|
||||
return matches[0]
|
||||
|
||||
|
||||
def require(text: str, tokens: tuple[str, ...], where: str) -> None:
|
||||
missing = [token for token in tokens if token not in text]
|
||||
if missing:
|
||||
raise ValueError(f"{where} is missing: {', '.join(missing)}")
|
||||
|
||||
|
||||
def reject(text: str, tokens: tuple[str, ...], where: str) -> None:
|
||||
found = [token for token in tokens if token in text]
|
||||
if found:
|
||||
raise ValueError(f"{where} contains forbidden copy: {', '.join(found)}")
|
||||
|
||||
|
||||
def audit(path: Path) -> dict[str, object]:
|
||||
platform = infer_platform(path)
|
||||
with zipfile.ZipFile(path) as archive:
|
||||
names = archive.namelist()
|
||||
readme_name = root_member(names, "README-SETUP.txt")
|
||||
readme = archive.read(readme_name).decode("utf-8")
|
||||
|
||||
run_match = re.search(r"(?m)^\s*\d+\.\s+Run\s+([^\s.]+(?:\.exe)?)\.\s*$", readme)
|
||||
if not run_match:
|
||||
raise ValueError("README-SETUP.txt has no numbered Run instruction")
|
||||
host_name = run_match.group(1)
|
||||
if platform == "windows" and not host_name.lower().endswith(".exe"):
|
||||
host_name += ".exe"
|
||||
host_member = root_member(names, host_name)
|
||||
host_copy = archive.read(host_member).decode("latin-1")
|
||||
|
||||
if platform == "windows":
|
||||
require(
|
||||
readme,
|
||||
("downloads", "cmake-clang-v1", "toolchain pack"),
|
||||
"Windows README-SETUP.txt",
|
||||
)
|
||||
require(
|
||||
host_copy,
|
||||
(
|
||||
"Download latest portable toolchain",
|
||||
"Select toolchain zip",
|
||||
"cmake-clang-v1",
|
||||
),
|
||||
"Windows setup host",
|
||||
)
|
||||
else:
|
||||
require(
|
||||
readme,
|
||||
(
|
||||
"Install CMake, Ninja, Python 3",
|
||||
"C/C++ compiler",
|
||||
"available on PATH",
|
||||
"native build tools",
|
||||
),
|
||||
"POSIX README-SETUP.txt",
|
||||
)
|
||||
reject(
|
||||
readme.lower(),
|
||||
("cmake-clang-v1", "portable toolchain", "toolchain pack", "toolchain zip"),
|
||||
"POSIX README-SETUP.txt",
|
||||
)
|
||||
require(
|
||||
host_copy,
|
||||
(
|
||||
"1. Native build tools",
|
||||
"Install CMake, Ninja, Python 3, and either Clang or GCC",
|
||||
"Check tools",
|
||||
),
|
||||
"POSIX setup host",
|
||||
)
|
||||
reject(
|
||||
host_copy,
|
||||
("Download latest portable toolchain##tc", "1. Portable toolchain"),
|
||||
"POSIX setup host",
|
||||
)
|
||||
|
||||
return {
|
||||
"archive": str(path.resolve()),
|
||||
"platform": platform,
|
||||
"sha256": sha256(path),
|
||||
"size": path.stat().st_size,
|
||||
"status": "pass",
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("archives", nargs="+", type=Path)
|
||||
parser.add_argument("--json", type=Path, dest="json_path")
|
||||
args = parser.parse_args()
|
||||
|
||||
results: list[dict[str, object]] = []
|
||||
failed = False
|
||||
for path in args.archives:
|
||||
try:
|
||||
result = audit(path)
|
||||
except (OSError, ValueError, zipfile.BadZipFile) as error:
|
||||
result = {"archive": str(path.resolve()), "status": "fail", "error": str(error)}
|
||||
failed = True
|
||||
results.append(result)
|
||||
print(f"{result['status'].upper()}: {path}: {result.get('error', result.get('platform'))}")
|
||||
|
||||
if args.json_path:
|
||||
args.json_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.json_path.write_text(
|
||||
json.dumps({"schema": 1, "results": results}, indent=2) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
return 1 if failed else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -530,9 +530,13 @@ ${DISPLAY_NAME} ${VERSION} — setup package
|
||||
Platform: ${ARTIFACT}
|
||||
|
||||
One zip for first install and updates. Does NOT include disc images, retail
|
||||
BIOS dumps, pre-generated game C, or a portable cmake/clang pack. Emitters
|
||||
BIOS dumps, pre-generated game C, or a bundled build-tool pack. Emitters
|
||||
(psxrecomp-game / psxrecomp-bios) and the CLI are inside psxrecomp/.
|
||||
EOF
|
||||
|
||||
case "${ARTIFACT}" in
|
||||
windows-*)
|
||||
cat >>"${STAGE}/README-SETUP.txt" <<EOF
|
||||
Standalone:
|
||||
1. Install Python 3.
|
||||
2. Run ${EXE_BASENAME}.
|
||||
@@ -546,6 +550,28 @@ Retro uses this same zip: it harvests emitters into a shared SDK cache,
|
||||
downloads the toolchain pack (or uses RETCOMM_TOOLCHAIN_DIR), and preserves
|
||||
saves/user config across updates.
|
||||
EOF
|
||||
;;
|
||||
linux-*|macos-*)
|
||||
cat >>"${STAGE}/README-SETUP.txt" <<EOF
|
||||
Standalone:
|
||||
1. Install CMake, Ninja, Python 3, and a C/C++ compiler with your system
|
||||
package manager.
|
||||
2. Make sure cmake, ninja, python3, and either clang/clang++ or gcc/g++ are
|
||||
available on PATH.
|
||||
3. Run ${EXE_BASENAME}.
|
||||
4. Provide ${DISC_HINT} and ${BIOS_HINT}.
|
||||
5. Follow the Generate & rebuild wizard. The host checks the native build
|
||||
tools before it continues.
|
||||
|
||||
Retro uses this same zip. It uses the same native build tools and preserves
|
||||
saves/user config across updates.
|
||||
EOF
|
||||
;;
|
||||
*)
|
||||
echo "error: unsupported setup README platform: ${ARTIFACT}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# --- Gate: the staged tree must be able to configure itself ----------------
|
||||
# The project file/dir list is an allowlist, so any path a project adds to
|
||||
|
||||
@@ -956,6 +956,30 @@ def ensure_toolchain(
|
||||
Installs always land under the shared Retro cache; the project only gets
|
||||
a stamp file pointing at that pack.
|
||||
"""
|
||||
if not sys_platform_is_windows() and from_zip is None:
|
||||
# Setup/rebuild on Linux and macOS uses native tools. Do not turn a
|
||||
# missing dependency into an unsolicited portable-pack download.
|
||||
groups = (("cmake",), ("ninja",), ("python3", "python"),
|
||||
("cc", "gcc", "clang"), ("c++", "g++", "clang++"))
|
||||
resolved = []
|
||||
for names in groups:
|
||||
command = next(filter(None, (shutil.which(name) for name in names)), None)
|
||||
if command:
|
||||
try:
|
||||
result = subprocess.run([command, "--version"], capture_output=True,
|
||||
text=True, timeout=30, check=False)
|
||||
if result.returncode == 0:
|
||||
resolved.append(command)
|
||||
continue
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
pass
|
||||
raise RuntimeError("Native build tools are missing or unusable: {}. Install CMake, "
|
||||
"Ninja, Python 3 and C/C++ compilers with your package manager."
|
||||
.format("/".join(names)))
|
||||
if log:
|
||||
log("Using native build tools on PATH")
|
||||
return Path(resolved[0]).parent
|
||||
|
||||
need = min_version or default_min_version()
|
||||
if is_windows_store_python() and log:
|
||||
log(
|
||||
|
||||
Reference in New Issue
Block a user