chore: merge the handheld port

This commit is contained in:
Abdessamad Derraz committed 2026-10-22 04:12:09 +02:00
commit e05844e611
19 files changed
+1862 -8

No files matched your search

+111
View File
@@ -0,0 +1,111 @@
#!/usr/bin/env python3
"""Build the PortMaster port in ports/RetroBIOS into dist/RetroBIOS.zip.
The archive has the layout PortMaster's own release build gives a port
(PortMaster-New tools/build_release.py): the launch script at the top, and
one folder holding port.json, gameinfo.xml, the README as <name>.md, the
licenses and the port's files. install.py is copied from the repository
root, and only when it is the installer the bootstraps pin, so the port
runs the same bytes as the one-line command of the same commit. Members are
written as the packs write theirs: fixed timestamps, sorted names, the
executable bit read from the source, so the same tree gives the same zip.
Usage:
python scripts/build_port.py
python scripts/build_port.py --output-dir tmp/port
"""
from __future__ import annotations
import argparse
import hashlib
import re
import sys
import zipfile
from pathlib import Path
from artifacts import ArtifactLockBusy, artifact_lock
REPO_ROOT = Path(__file__).resolve().parent.parent
PORT_SOURCE = REPO_ROOT / "ports" / "RetroBIOS"
PORT_DIR = "retrobios"
PORT_SCRIPT = "RetroBIOS.sh"
PORT_ZIP = "RetroBIOS.zip"
_PIN = re.compile(r'^DEFAULT_INSTALL_SHA256="([0-9a-f]{64})"$', re.MULTILINE)
def pinned_installer_sha256(root: Path = REPO_ROOT) -> str:
"""The SHA-256 install.sh accepts for install.py."""
found = _PIN.search((root / "install.sh").read_text(encoding="utf-8"))
if found is None:
raise ValueError("install.sh pins no installer SHA-256")
return found.group(1)
def port_members(root: Path = REPO_ROOT) -> list[tuple[str, Path]]:
"""(name in the archive, source file) for every member, sorted by name."""
source = root / "ports" / "RetroBIOS"
members = [
(PORT_SCRIPT, source / PORT_SCRIPT),
(f"{PORT_DIR}/port.json", source / "port.json"),
(f"{PORT_DIR}/gameinfo.xml", source / "gameinfo.xml"),
(f"{PORT_DIR}/{PORT_DIR}.md", source / "README.md"),
(f"{PORT_DIR}/install.py", root / "install.py"),
(f"{PORT_DIR}/licenses/LICENSE-retrobios.txt", root / "LICENSE"),
(f"{PORT_DIR}/licenses/NOTICE-retrobios.txt", root / "NOTICE"),
]
members += [
(f"{PORT_DIR}/{path.relative_to(source / PORT_DIR).as_posix()}", path)
for path in sorted((source / PORT_DIR).rglob("*"))
if path.is_file() and "__pycache__" not in path.parts
]
names = [name for name, _path in members]
if len(set(names)) != len(names):
raise ValueError("two port members share a name")
return sorted(members)
def build_port(output: Path, root: Path = REPO_ROOT) -> Path:
"""Write the port archive to *output* and return it."""
pinned = pinned_installer_sha256(root)
actual = hashlib.sha256((root / "install.py").read_bytes()).hexdigest()
if actual != pinned:
raise ValueError(
f"install.py is {actual[:12]}, install.sh pins {pinned[:12]}: "
"re-pin the bootstraps before building the port"
)
# The packs' member writer: imported here, as it loads the whole
# pack builder, which a caller reading PORT_ZIP does not need.
from generate_pack import _add_pack_member # noqa: PLC0415
output.parent.mkdir(parents=True, exist_ok=True)
scratch = output.with_name(f".{output.name}.tmp")
with zipfile.ZipFile(scratch, "w", zipfile.ZIP_DEFLATED, compresslevel=9) as zf:
for name, path in port_members(root):
_add_pack_member(zf, str(path), name)
scratch.replace(output)
return output
def main() -> int:
parser = argparse.ArgumentParser(
allow_abbrev=False, description="Build the RetroBIOS PortMaster port"
)
parser.add_argument("--output-dir", type=Path, default=REPO_ROOT / "dist",
help="where RetroBIOS.zip is written (default: dist/)")
args = parser.parse_args()
try:
with artifact_lock(str(args.output_dir)):
path = build_port(args.output_dir / PORT_ZIP)
except (ArtifactLockBusy, OSError, ValueError) as exc:
print(f"Error: {exc}", file=sys.stderr)
return 1
digest = hashlib.sha256(path.read_bytes()).hexdigest()
print(f"{path} ({path.stat().st_size} bytes, sha256 {digest})")
return 0
if __name__ == "__main__":
from common import run_cli
run_cli(main)
+6 -2
View File
@@ -3,7 +3,8 @@
Run on the draft before publishing it, and on the published release at any
time. Every pack asset release.json names must be on the release at the sha256
SHA256SUMS.txt lists for it, the parts of a pack must add up to the download
SHA256SUMS.txt lists for it, as must the handheld port (RetroBIOS.zip) when
the list names it, the parts of a pack must add up to the download
size the README prints, and nothing else may be attached. An upload that
failed in the loop, or a part re-uploaded from an older build, otherwise
publishes a release that `sha256sum --check` rejects for every user.
@@ -26,6 +27,7 @@ from dataclasses import dataclass
from pathlib import Path
sys.path.insert(0, os.path.dirname(__file__))
from build_port import PORT_ZIP
from check_release_assets import digests_of
from largefiles import LARGE_FILES_REPO
from release_record import RECORD, load_record
@@ -91,7 +93,9 @@ def compare_release(
findings.append(Finding("extra", name, "attached but neither recorded nor listed"))
for name in sorted(set(recorded) - set(sums)):
findings.append(Finding("unlisted", name, f"absent from {SUMS}"))
for name in sorted(set(sums) - set(recorded)):
# The handheld port is a release file but no pack: release.json records
# packs, and the checksum list alone vouches for the port.
for name in sorted(set(sums) - set(recorded) - {PORT_ZIP}):
findings.append(Finding("unrecorded", name, f"listed in {SUMS}, absent from release.json"))
wanted = {**sums, **(local_files or {})}
+4
View File
@@ -5283,6 +5283,10 @@ ZIP: extract them all into the same folder.
| [AmberELEC](https://amberelec.org/) | Linux OS for Anbernic handhelds: RG351P, RG351V, RG351MP, RG353, RG503 and RG552 | [AmberELEC]({rel}) | `/storage/roms/`, which the pack fills with `bios/` and `neogeo/` |
| [EmuELEC](https://emuelec.org/) | Linux OS for Amlogic TV boxes, the Odroid Go Advance, GameForce and RK356x devices | [EmuELEC]({rel}) | `/storage/roms/`, which the pack fills with `bios/`, `neogeo/` and `ports/` |
On a handheld that runs PortMaster, the RetroBIOS port checks and fills the
BIOS folder from the device, with gamepad screens:
[on a handheld](wiki/installer.md#on-a-handheld-the-retrobios-port).
### FPGA
| Setup | What it is | Pack | Extract to |