From 289c180bfa794137ddf14f33096956e3ece0c5dd Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Thu, 5 Nov 2026 19:18:29 +0100 Subject: [PATCH] feat: narrow a knulli install to its board --- install.ps1 | 2 +- install.py | 46 ++++++++++++++---------------------- install.sh | 2 +- install/launchers/index.json | 16 ++++++------- tests/test_install.py | 30 ++++++++++++++++------- wiki/installer.md | 12 +++++----- 6 files changed, 56 insertions(+), 52 deletions(-) diff --git a/install.ps1 b/install.ps1 index e686a65f3..f12f5ffb2 100644 --- a/install.ps1 +++ b/install.ps1 @@ -16,7 +16,7 @@ # and Invoke-WebRequest throws on a cut transfer before the hash is read: # the site's build rewrites this line there (scripts/sitebootstrap.py). $mismatchAdvice = "The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages. Waiting five minutes settles that." - $defaultInstallSha256 = "8760870a1e7e1f1b72379cfec88d595ac8d7729880af905e266b424e3ee306bc" + $defaultInstallSha256 = "ec117f8ed92e101b1adc51b1f07bf002840fb8e36c4c492617e1b48c47c6ee92" $maximumInstallerBytes = 2MB $installerExit = 0 $installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null } diff --git a/install.py b/install.py index 54ce783cf..546d9d04f 100644 --- a/install.py +++ b/install.py @@ -1450,36 +1450,28 @@ def _parse_bash_var(path: Path, key: str) -> str | None: # recalbox-system.mk RECALBOX_SYSTEM_PLATFORM, ROCKNIX's scripts/image puts # HW_DEVICE in os-release (RK3326, our rk3326). Lakka's scripts/image writes # LIBREELEC_DEVICE when the image is built for a device (RPi4) and always -# LIBREELEC_PROJECT (Generic): the first one set is the board. +# LIBREELEC_PROJECT (Generic): the first one set is the board. KNULLI moves +# /usr/share/batocera to /usr/share/knulli (knulli-linux 57afa662, +# board/scripts/post-build-script.sh:264-278) and writes KNULLI_SYSTEM_ARCH +# to knulli.arch (package/system/knulli-system/knulli-system.mk:98-99). _BOARD_SOURCES: dict[str, tuple[str, str]] = { "batocera": ("file", "/usr/share/batocera/batocera.arch"), "recalbox": ("file", "/recalbox/recalbox.arch"), "rocknix": ("os_release", "HW_DEVICE"), "lakka": ("os_release", "LIBREELEC_DEVICE|LIBREELEC_PROJECT"), + "knulli": ("file", "/usr/share/knulli/knulli.arch"), } - -# A platform that names its board without publishing a core list per board. -# Knulli moves /usr/share/batocera to /usr/share/knulli (knulli-linux -# 57afa662, board/scripts/post-build-script.sh:264-278) and writes its board -# to knulli.arch (package/system/knulli-system/knulli-system.mk:98-99). -_DERIVATIVE_BOARDS: dict[str, tuple[str, str]] = { - "knulli": ("Knulli", "/usr/share/knulli/knulli.arch"), +# The board a system's own name stands for, where the two differ: the +# target lists are named for the board files (configs/knulli-rk3566.board), +# and the arch file for the TARGET flag the board sets. knulli-rk3566.board +# sets BR2_PACKAGE_BATOCERA_TARGET_RK3568, which knulli-system.mk:22-23 +# (20260511) writes as rk3568. +_BOARD_NAMES: dict[str, dict[str, str]] = { + "knulli": {"rk3568": "rk3566"}, } -def derivative_board(platform: str) -> tuple[str, str] | None: - """(derivative, board) when a derivative of the platform names its board.""" - name, where = _DERIVATIVE_BOARDS.get(platform, ("", "")) - if not where: - return None - try: - board = Path(where).read_text(encoding="utf-8", errors="replace").strip().lower() - except OSError: - return None - return (name, board) if board else None - - # The board a card names on a partition a computer reads. Batocera writes # its image's board to boot/batocera.board on the BATOCERA boot partition, # the FAT one beside SHARE (batocera-43.1, board/batocera/scripts/ @@ -1520,7 +1512,11 @@ def detect_board(platform: str) -> str | None: value = next((release[key] for key in where.split("|") if release.get(key)), "") else: return None - return value.strip().lower() or None + board = value.strip().lower() + # A board no TARGET line names is written "unknown" (KNULLI's r16). + if board == "unknown": + return None + return _BOARD_NAMES.get(platform, {}).get(board, board) or None def _parse_json_path(path: Path, *keys: str) -> str | None: @@ -12989,7 +12985,7 @@ def build_parser() -> argparse.ArgumentParser: "--target", metavar="NAME", help="hardware target for core filtering (switch, rpi4, ...). " - "On Batocera, Recalbox, ROCKNIX and Lakka the board is read from the system", + "On Batocera, Recalbox, ROCKNIX, Lakka and KNULLI the board is read from the system", ) parser.add_argument( "--every-board", @@ -15064,12 +15060,6 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None: print(f" {words}") EVENTS.emit("board", platform=plat_name, board=board, applied=target == board, message=words) - else: - derived = derivative_board(plat_name) - if derived: - print(f" {derived[0]} board {derived[1]} detected. {derived[0]} " - "builds its own set of cores and publishes no core list " - "per board: installing the files of every board") if target: targets = target_lists.get(plat_name) if targets is None: diff --git a/install.sh b/install.sh index 42d48c719..61669c012 100755 --- a/install.sh +++ b/install.sh @@ -23,7 +23,7 @@ DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/ins # curl or wget stop on a cut transfer before the hash is read: the site's # build rewrites this line there (scripts/sitebootstrap.py). MISMATCH_ADVICE="The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages. Waiting five minutes settles that." -DEFAULT_INSTALL_SHA256="8760870a1e7e1f1b72379cfec88d595ac8d7729880af905e266b424e3ee306bc" +DEFAULT_INSTALL_SHA256="ec117f8ed92e101b1adc51b1f07bf002840fb8e36c4c492617e1b48c47c6ee92" MAX_INSTALLER_BYTES=2097152 cleanup() { diff --git a/install/launchers/index.json b/install/launchers/index.json index d9e518a9b..4d17f4026 100644 --- a/install/launchers/index.json +++ b/install/launchers/index.json @@ -27,8 +27,8 @@ { "dest": "roms/ports/retrobios/install.py", "repo_path": "install.py", - "size": 1090639, - "sha256": "8760870a1e7e1f1b72379cfec88d595ac8d7729880af905e266b424e3ee306bc", + "size": 1090336, + "sha256": "ec117f8ed92e101b1adc51b1f07bf002840fb8e36c4c492617e1b48c47c6ee92", "executable": false }, { @@ -71,8 +71,8 @@ "muos": { "archive": { "dest": "ARCHIVE/RetroBIOS.muxapp", - "size": 1144266, - "sha256": "1b9ae46c0bbdbb75aaa2120dcce145cea9a15a971f272f44dd556a48e6b7098c" + "size": 1143963, + "sha256": "c99324398d6e3746ef65b3cf491c30defae5556664bb4e7cca1675c3d1c2a444" }, "members": [ { @@ -92,8 +92,8 @@ { "name": "RetroBIOS/install.py", "repo_path": "install.py", - "size": 1090639, - "sha256": "8760870a1e7e1f1b72379cfec88d595ac8d7729880af905e266b424e3ee306bc", + "size": 1090336, + "sha256": "ec117f8ed92e101b1adc51b1f07bf002840fb8e36c4c492617e1b48c47c6ee92", "executable": false }, { @@ -166,8 +166,8 @@ { "dest": ".config/modules/retrobios/install.py", "repo_path": "install.py", - "size": 1090639, - "sha256": "8760870a1e7e1f1b72379cfec88d595ac8d7729880af905e266b424e3ee306bc", + "size": 1090336, + "sha256": "ec117f8ed92e101b1adc51b1f07bf002840fb8e36c4c492617e1b48c47c6ee92", "executable": false }, { diff --git a/tests/test_install.py b/tests/test_install.py index 651d73ea3..a01203c32 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -5400,20 +5400,34 @@ class BatoceraAndKnulliByWhatTheirBuildsWrite(unittest.TestCase): [("emuelec", Path("/storage/roms"))]) self.assertEqual(self._detect({"NAME": "CoreELEC", "ID": "coreelec"}, ()), []) - def test_a_knulli_board_is_named_and_not_narrowed(self): - board = Path(_tempdir(self)) / "knulli.arch" - board.write_text("a133") - with unittest.mock.patch.dict(install._DERIVATIVE_BOARDS, - {"knulli": ("Knulli", str(board))}): - self.assertEqual(install.derivative_board("knulli"), ("Knulli", "a133")) - self.assertIsNone(install.derivative_board("recalbox")) + def test_a_knulli_board_narrows_to_its_target(self): + """KNULLI publishes a core list per board since 2026-11-05, and an + H700 handheld still took every board's files (9.3 GB for 4.5).""" + arch = Path(_tempdir(self)) / "knulli.arch" + targets = json.loads((REPO_ROOT / "install" / "targets" / "knulli.json") + .read_text(encoding="utf-8")) + for written, board in (("h700", "h700"), ("rk3568", "rk3566"), ("unknown", None)): + arch.write_text(written) + with self.subTest(written=written), unittest.mock.patch.dict( + install._BOARD_SOURCES, {"knulli": ("file", str(arch))}): + self.assertEqual(install.detect_board("knulli"), board) + if board: + self.assertIn(board, targets) + + def test_each_board_name_is_a_published_target(self): + for plat, names in install._BOARD_NAMES.items(): + targets = json.loads((REPO_ROOT / "install" / "targets" / f"{plat}.json") + .read_text(encoding="utf-8")) + for written, board in names.items(): + with self.subTest(platform=plat, board=board): + self.assertIn(board, targets) + self.assertNotIn(written, targets) def test_the_run_says_why_every_board_is_installed(self): source = (REPO_ROOT / "install.py").read_text(encoding="utf-8") block = source[source.index("board = detect_board(plat_name)"):] block = block[:block.index("if target:")] self.assertIn("has no", block) - self.assertIn("derivative_board(plat_name)", block) class RomMFirmwareFollowsItsTemplate(unittest.TestCase): diff --git a/wiki/installer.md b/wiki/installer.md index 7cf21b25d..0b59e4444 100644 --- a/wiki/installer.md +++ b/wiki/installer.md @@ -958,7 +958,7 @@ what the options name, everything by default. |--------|--------| | `--platform NAME` | Install for this platform instead of the detected one. A system `--list-platforms` names under its own files (`muos`, `arkos`) stands for the file list it gets, and the run says which. ES-DE, LaunchBox and Android, which keep no BIOS folder, are answered with the run that fills each emulator's folder. Unknown names are refused with the closest ones | | `--dest PATH` | Destination directory, overriding detection. Without `--platform`, the file list is the platform detected here: at a terminal the installer asks, Enter taking the one found, and otherwise it uses the one found, RetroArch's when nothing is found, and refuses when several are. The run prints whose list it is. A folder that does not exist is named with the nearest one that does, then created, except for a system that runs from its SD card, which refuses it. When more than its last level is missing (a mistyped path), a run at a terminal asks first, and the answer defaults to no | -| `--target NAME` | Keep only the files the cores of that hardware target need, and the systems they emulate. An unknown target is refused rather than ignored, since carrying on would install everything. On Batocera, Recalbox, ROCKNIX and Lakka the board is read from the system (`batocera.arch`, `recalbox.arch`, `HW_DEVICE`, `LIBREELEC_DEVICE` or `LIBREELEC_PROJECT`) and applied when no `--target` is given, from a pack alone too when it carries the core list per board. On a computer, the board a Batocera card names on its BATOCERA partition (`boot/batocera.board`) is applied the same way, in line mode and on the full screen | +| `--target NAME` | Keep only the files the cores of that hardware target need, and the systems they emulate. An unknown target is refused rather than ignored, since carrying on would install everything. On Batocera, Recalbox, ROCKNIX, Lakka and KNULLI the board is read from the system (`batocera.arch`, `recalbox.arch`, `HW_DEVICE`, `LIBREELEC_DEVICE` or `LIBREELEC_PROJECT`, `knulli.arch`) and applied when no `--target` is given, from a pack alone too when it carries the core list per board. On a computer, the board a Batocera card names on its BATOCERA partition (`boot/batocera.board`) is applied the same way, in line mode and on the full screen | | `--every-board` | Install the files of every board instead of the one the system names, for a card that moves between devices | | `--installed-cores` | Keep only what the cores already installed read (RetroArch, Lakka, RetroPie). See [Installed cores](#installed-cores) | | `--cores-dir PATH` | The folder RetroArch loads its cores from, for `--installed-cores` when it is not found. Implies `--installed-cores` | @@ -1058,12 +1058,12 @@ AmberELEC, EmuELEC and KNULLI install the BIOS list they publish themselves. The image or source of its Odroid Go builds is published, so neither its marker files nor its folder could be read. -Batocera, Recalbox, ROCKNIX and Lakka also name the board they were built for, and -that board narrows the install unless `--target` or `--every-board` says +Batocera, Recalbox, ROCKNIX, Lakka and KNULLI also name the board they were built +for, and that board narrows the install unless `--target` or `--every-board` says otherwise (see the options above). A board with no core list of its own is -named and gets the files of every board. KNULLI names its board in -`/usr/share/knulli/knulli.arch` but publishes no core list per board, so the -run says so and installs the files of every board. +named and gets the files of every board. KNULLI writes its board to +`/usr/share/knulli/knulli.arch` under the name of the chip its board file +selects: `rk3568` there is the `rk3566` list. Some platforms keep their BIOS in a fixed directory, and that directory is the evidence. They are looked for after the configurations below, for a platform