mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-01 00:27:20 -05:00
--add-app, the result screen and a [y/N] question put the launcher of muOS, KNULLI or ROCKNIX on the card the BIOS folder is on. The files come from install/launchers/index.json at the same ref, each checked by size and SHA-256 before the first is written, and the app event reports it.
133 lines
5.3 KiB
Python
133 lines
5.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Write install/launchers/index.json: the handheld launchers as files for a card.
|
|
|
|
python scripts/generate_launcher_index.py rewrite the list
|
|
python scripts/generate_launcher_index.py --check exit 1 when it is stale
|
|
|
|
install.py --add-app reads this list from the ref its file lists come from,
|
|
and puts on a handheld's card the launcher of its system, each file checked
|
|
against the size and SHA-256 recorded here. The files are the members of the
|
|
archives scripts/build_port.py builds, from the same sources: muOS gets its
|
|
application archive, rebuilt by the installer from the members and checked
|
|
against the hash recorded for it, the others the archive's files at their
|
|
place on the card. Where each system's files go is install.py's APP_PLACES.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import importlib.util
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from build_port import (
|
|
BATOCERA_ZIP,
|
|
MUOS_APP,
|
|
REPO_ROOT,
|
|
TOOLS_ZIP,
|
|
_packs_table,
|
|
archive_members,
|
|
pinned_installer_sha256,
|
|
)
|
|
|
|
OUTPUT = REPO_ROOT / "install" / "launchers" / "index.json"
|
|
|
|
_spec = importlib.util.spec_from_file_location("install", REPO_ROOT / "install.py")
|
|
_install = importlib.util.module_from_spec(_spec)
|
|
_spec.loader.exec_module(_install)
|
|
|
|
# The archive whose members each system gets, by its key in APP_PLACES.
|
|
LAUNCHER_ARCHIVES = {"muos": MUOS_APP, "knulli": BATOCERA_ZIP, "rocknix": TOOLS_ZIP}
|
|
|
|
|
|
def _entry(field: str, name: str, source: Path | None, root: Path) -> dict:
|
|
"""A member as the list records it: where it comes from, its size and
|
|
hash, and whether it is executable in the repository."""
|
|
if source is None:
|
|
data = _packs_table(root)
|
|
origin: dict = {"content": data.decode("utf-8")}
|
|
executable = False
|
|
else:
|
|
data = source.read_bytes()
|
|
origin = {"repo_path": source.relative_to(root).as_posix()}
|
|
executable = bool(source.stat().st_mode & 0o111)
|
|
return {field: name, **origin, "size": len(data),
|
|
"sha256": hashlib.sha256(data).hexdigest(), "executable": executable}
|
|
|
|
|
|
def _member_bytes(entry: dict, root: Path) -> bytes:
|
|
if "content" in entry:
|
|
return entry["content"].encode("utf-8")
|
|
return (root / entry["repo_path"]).read_bytes()
|
|
|
|
|
|
def build_index(root: Path = REPO_ROOT) -> dict:
|
|
"""Every launcher a card can carry, from the archives' members."""
|
|
actual = hashlib.sha256((root / "install.py").read_bytes()).hexdigest()
|
|
if actual != pinned_installer_sha256(root):
|
|
raise ValueError("install.py is not the installer install.sh pins: re-pin the "
|
|
"bootstraps before writing the launcher list")
|
|
places = {place.key: place for place in _install.APP_PLACES.values()}
|
|
if set(places) != set(LAUNCHER_ARCHIVES):
|
|
raise ValueError(f"APP_PLACES names {sorted(places)}, the archives "
|
|
f"{sorted(LAUNCHER_ARCHIVES)}")
|
|
launchers: dict[str, dict] = {}
|
|
for key, archive in sorted(LAUNCHER_ARCHIVES.items()):
|
|
folder = places[key].folder
|
|
members = archive_members(archive, root)
|
|
if archive == MUOS_APP:
|
|
entries = [_entry("name", name, source, root) for name, source in members]
|
|
data = _install.app_archive([(entry, _member_bytes(entry, root))
|
|
for entry in entries])
|
|
launchers[key] = {
|
|
"archive": {"dest": f"{folder}/{archive}", "size": len(data),
|
|
"sha256": hashlib.sha256(data).hexdigest()},
|
|
"members": entries,
|
|
}
|
|
else:
|
|
# Executable only where the menu starts the script as a program:
|
|
# a mode a FAT card cannot hold is refused by its driver.
|
|
files = [_entry("dest", f"{folder}/{name}", source, root)
|
|
for name, source in members]
|
|
for entry in files:
|
|
entry["executable"] = entry["executable"] and places[key].executable
|
|
launchers[key] = {"files": files}
|
|
return {"schema": _install.APP_SCHEMA, "launchers": launchers}
|
|
|
|
|
|
def render(index: dict) -> str:
|
|
return json.dumps(index, indent=2, ensure_ascii=False) + "\n"
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = argparse.ArgumentParser(allow_abbrev=False, description=__doc__,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
parser.add_argument("--check", action="store_true",
|
|
help="exit 1 when the list differs from the launchers, writing nothing")
|
|
parser.add_argument("--output", type=Path, default=OUTPUT, help=argparse.SUPPRESS)
|
|
args = parser.parse_args(argv)
|
|
try:
|
|
text = render(build_index())
|
|
except (OSError, ValueError) as exc:
|
|
print(f"Error: {exc}", file=sys.stderr)
|
|
return 1
|
|
current = args.output.read_text(encoding="utf-8") if args.output.is_file() else None
|
|
if current == text:
|
|
return 0
|
|
if args.check:
|
|
print(f"{args.output} differs from the launchers in ports/: run "
|
|
"python scripts/generate_launcher_index.py", file=sys.stderr)
|
|
return 1
|
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
args.output.write_text(text, encoding="utf-8")
|
|
print(f"Wrote {args.output}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
from common import run_cli
|
|
|
|
run_cli(main)
|