docs: refresh installer and release pages

The installer output example showed 5865 files and no Not installable
line, while the RetroArch manifest gives 8069 and omits 11. Exit code 2
also comes from every option argparse refuses. RETROBIOS_REF holds a
file list only until the next release deletes its tag, and large files
always come from large-files. The release commit now carries the
manifests the pipeline regenerates. A RetroPie 4.8 image ships Python
3.7.3, below the bootstrap's floor, and the pages say what to use.
This commit is contained in:
Abdessamad Derraz committed 2026-10-21 05:22:28 +02:00
1 parent 32bda05be3
commit 9edf785513
5 files changed
+139 -24

No files matched your search

+103
View File
@@ -78,6 +78,79 @@ class TestSkeletonReadsTheResultShape(unittest.TestCase):
self.assertIn(key.value, result)
class InstallerOutputExample(unittest.TestCase):
"""The page showed a run with no `Not installable:` line while the
RetroArch manifest left eleven entries out, and a count that the menu
example below it no longer shared."""
def test_run_and_menu_show_one_dated_run_of_the_manifest(self):
import install
text = _page("installer.md")
intro, run = re.search(r"(?s)\n([^\n]*)\n\n```\n(Fetching file index.*?)```", text).groups()
self.assertRegex(intro, r"\d{4}-\d{2}-\d{2}", "an output example says when it was taken")
manifest = json.loads((ROOT / "install" / "retroarch.json").read_text(encoding="utf-8"))
omitted = re.search(r"Not installable: \d+ files? \(\d+ required, (.*)\)\.", run)
if manifest.get("omitted_files"):
self.assertIsNotNone(omitted, "the manifest omits files and the run shows no line for it")
if omitted:
for part in omitted.group(1).split(", "):
self.assertIn(part.rsplit(":", 1)[0], install.OMISSION_REASONS.values())
count, size = re.search(r"\n (\d+) files \(([\d.]+ [KMG]B)\)", run).groups()
self.assertIn(f"0/{count} present", run)
menu = re.search(r"Retroarch: (\d+) files, ([\d.]+ [KMG]B)\.", text)
self.assertEqual(menu.groups(), (count, size))
class ReleaseRefPromise(unittest.TestCase):
"""RETROBIOS_REF was said to pin a reproducible file set, while every
release deletes the previous tags and large files come from one fixed
release whatever the ref."""
def test_the_page_says_what_a_tag_does_not_pin(self):
import install
self.assertIn("/large-files/", install.RELEASE_URL)
self.assertIn("--cleanup-tag", _page("release-process.md"))
row = next(
line for line in _page("installer.md").splitlines()
if line.startswith("| `RETROBIOS_REF`")
)
self.assertIn("`large-files`", row)
self.assertIn("next release", row)
for page in PUBLISHED:
for paragraph in page.read_text(encoding="utf-8").split("\n\n"):
if "RETROBIOS_REF" in paragraph:
self.assertNotIn("reproducible", paragraph, page.name)
class ReleaseCommitCarriesTheManifests(unittest.TestCase):
"""The documented release commit left out install/*.json, which the
release pipeline regenerates: v2026.10.10 added three by hand."""
def test_the_commit_step_adds_what_the_pipeline_rewrites(self):
pipeline = (ROOT / "scripts" / "pipeline.py").read_text(encoding="utf-8")
self.assertIn('"--manifest"', pipeline)
block = _section(_page("release-process.md"), "Cutting a release")
add = next(line for line in block.splitlines() if line.startswith("git add "))
self.assertIn("install/*.json", add.split())
class PageOpenings(unittest.TestCase):
"""Four pages opened by announcing what they cover."""
META = re.compile(r"(?i)\bthis (page|guide) (covers|documents|explains|describes|picks)\b|^Diagnosis guide")
def test_no_page_announces_itself(self):
offenders = [
f"{page.name}: {line[:80]}"
for page in PUBLISHED
for line in FENCED.sub("", page.read_text(encoding="utf-8")).splitlines()
if self.META.search(line)
]
self.assertEqual(offenders, [], "\n".join(offenders))
class NoHedgedRules(unittest.TestCase):
"""The profiling guide and the verification discipline turned rules into
wishes ("it's worth", "Ideally", "something to look into")."""
@@ -97,6 +170,22 @@ class NoHedgedRules(unittest.TestCase):
self.assertEqual(offenders, [], "\n".join(offenders))
class ExitStatusTable(unittest.TestCase):
"""Code 2 was documented as 'every file failed' only, while argparse
exits 2 on every refused option, and the 141 example named an option
that does not exist."""
def test_codes_and_options_are_the_installers(self):
source = (ROOT / "install.py").read_text(encoding="utf-8")
section = _section(_page("installer.md"), "Exit status")
documented = {int(row[0].strip("`")) for row in _table_rows(section)}
raised = {int(code) for code in re.findall(r"sys\.exit\((\d+)\)", source)}
self.assertEqual(documented, raised | {2})
self.assertIn("install.py: error:", section)
for option in set(re.findall(r"(--[a-z][a-z-]+)", section)):
self.assertIn(f'"{option}"', source, option)
class ProfileFieldsRequired(unittest.TestCase):
"""The table marked source, profiled_date, core_version and systems as
required and called the schema authoritative, which requires none."""
@@ -115,5 +204,19 @@ class ProfileFieldsRequired(unittest.TestCase):
self.assertEqual(marked, required)
class PythonFloorOnRetroPie(unittest.TestCase):
"""The page offered `apt install python3` to a RetroPie 4.8 image, where
python3 is 3.7.3 and apt has nothing newer."""
def test_both_pages_name_the_case(self):
install_sh = (ROOT / "install.sh").read_text(encoding="utf-8")
self.assertIn("(3, 8)", install_sh)
first = _section(_page("first-install.md"), "Error: Python 3.8 or newer is required.")
self.assertIn("RetroPie 4.8", first)
self.assertIn("3.7.3", first)
changing = _section(_page("installer.md"), "Changing the installer")
self.assertIn("3.7.3", changing)
if __name__ == "__main__":
unittest.main()
+9 -4
View File
@@ -1,8 +1,7 @@
# First install
One path, from an empty BIOS folder to files the emulator can read. Every step
shows what the screen looks like when it worked, so there is never a moment of
wondering.
shows what the screen looks like when it worked.
If something on this page does not match what happens, the last section covers
the cases that come up.
@@ -154,11 +153,17 @@ its BIOS folder, see [Getting started](getting-started.md).
### Error: Python 3.8 or newer is required.
Python is missing from the machine. The message names the command that installs
it, `sudo apt install python3` on Debian and Ubuntu. On a handheld running Batocera or Recalbox it is already there, so
No Python 3.8 or newer was found: Python is missing, or older. The message
names the command that installs it, `sudo apt install python3` on Debian and
Ubuntu. On a handheld running Batocera or Recalbox it is already there, so
this message usually means the command ran on the computer rather than on the
console.
A stock RetroPie 4.8 image is Debian Buster, where `python3` is 3.7.3 and apt
has nothing newer, so the same command changes nothing. The RetroPie pack from
the [releases page](https://github.com/Abdess/retrobios/releases/latest),
extracted into `~/RetroPie/BIOS/`, installs the same files.
### Error: the downloaded installer does not match its expected fingerprint.
Nothing was run and nothing was written. The download did not match the
+15 -10
View File
@@ -100,14 +100,15 @@ macOS.
rest.
7. Copies into standalone emulator directories, only with `--standalone-copies`.
A run looks like this:
A RetroArch run into an empty folder printed this on 2026-10-21:
```
Fetching file index for retroarch...
5865 files (5.7 GB)
8069 files (6.5 GB)
Not installable: 11 files (1 required, not found: 11).
Checking existing files...
0/5865 present (0 verified, 0 wrong hash)
0/8069 present (0 verified, 0 wrong hash)
```
When the manifest leaves entries out (a file the collection cannot supply, or
@@ -122,9 +123,9 @@ By default the installer fetches everything the platform runs. At a terminal,
after detection, it asks:
```
Retroarch: 5865 files, 5.7 GB.
Retroarch: 8069 files, 6.5 GB.
Enter) install everything
i) only what the installed cores read: 102 files, 44.5 MB
i) only what the installed cores read: 100 files, 44.2 MB
c) choose systems, cores and regions
q) quit
```
@@ -246,7 +247,7 @@ Files the list does not name are never read, moved or deleted.
| Variable | Effect |
|----------|--------|
| `RETROBIOS_REF` | Branch or tag the file list and the payloads are read from. Defaults to `main`, and a release tag pins a reproducible file set |
| `RETROBIOS_REF` | Branch or tag the file list and the files committed to the repository are read from. Defaults to `main`. Files the manifest marks `release_asset` always come from the `large-files` release, whatever the ref, and that release replaces an asset in place when it is rebuilt. Each release deletes the previous release tags ([release process](release-process.md#cutting-a-release)), so a tag holds a file list until the next release |
| `RETROBIOS_BASE_URL` | Base URL for the manifest and the files it declares. HTTPS only, plain HTTP allowed to loopback for end-to-end tests. Whoever controls this base controls the expected hashes, hence the check |
| `RETROBIOS_OS` | Names the host outright (`linux`, `wsl`, `windows`, `darwin`, `android`) instead of detecting it |
| `EXTERNAL_STORAGE` | Android only, the shared storage root RetroArch builds its default directories from. Defaults to `/storage/emulated/0` |
@@ -467,8 +468,12 @@ followed.
## Changing the installer
`install.py` runs on the user's machine, often an older handheld or a stock
RetroPie image, so it stays on Python 3.8 and the standard library alone.
`install.py` runs on the user's machine, often an older handheld, so it stays
on Python 3.8 and the standard library alone. The RetroPie 4.8 images
(`retropie-buster-4.8-*.img.gz`) are Debian Buster, whose `python3` is 3.7.3:
there the bootstrap stops before running anything, and the RetroPie pack from
the releases page is the way to install
([first install](first-install.md#error-python-38-or-newer-is-required)).
Tests refuse any 3.9 syntax or API, and one runs the installer end to end under
a real 3.8 interpreter, which CI installs beside 3.12 so the check cannot skip.
The build scripts need 3.12 and are not bound by this.
@@ -487,9 +492,9 @@ fields, the installer refuses to narrow rather than install everything.
|------|---------|
| `0` | everything asked for is installed or already up to date |
| `1` | a failure: the one-liner could not download or verify `install.py`, or found no Python 3.8 (nothing was run), an unusable base URL, an unknown platform or target, `--installed-cores` with no core found, a manifest that fails validation, a fetch that never succeeded, no platform to install for in a non-interactive run, some files failed while others installed, or `--check` found files to fetch |
| `2` | nothing was installed: every file the run tried failed (a BIOS folder that cannot be written, for one) |
| `2` | nothing was installed: every file the run tried failed (a BIOS folder that cannot be written, for one). Also an option the installer refused before any download: an unknown option, `--jobs` outside 1 to 32, `--target` with `--every-board`. The message then starts with `install.py: error:` |
| `130` | stopped with Ctrl+C. Run the same command again to carry on |
| `141` | the output was piped to a reader that closed early (`--list \| head`) |
| `141` | the output was piped to a reader that closed early (`--list-platforms \| head`) |
`install.ps1` passes the installer's code on unchanged: run as a file it exits
with it, run through `irm \| iex` it sets `$LASTEXITCODE` (an `exit` there
+9 -5
View File
@@ -1,11 +1,11 @@
# Release process
This page documents the CI/CD pipeline: what each workflow does, how releases
are built, and how to run the process manually.
Two GitHub workflows check pull requests and publish the site. Releases are
built, checked and uploaded from the maintainer's machine.
## CI workflows overview
The project uses 2 GitHub Actions workflows. All use only official GitHub
Both workflows use only official GitHub
actions (`actions/checkout`, `actions/setup-python`, `actions/upload-pages-artifact`,
`actions/deploy-pages`). No third-party actions.
@@ -237,8 +237,12 @@ python scripts/check_release.py --tag "v${DATE}" --sums dist/SHA256SUMS.txt \
&& gh release edit "v${DATE}" --draft=false --latest
# The record and the pages it feeds go to main with the release, so the
# table never describes a pack other than the one the link serves.
git add release.json README.md mkdocs.yml
# table never describes a pack other than the one the link serves. The
# install manifests go too: step 1 regenerated them, and their pack_files
# and pack_size describe the packs just built. git status names anything
# else the run rewrote.
git status --short
git add release.json README.md mkdocs.yml install/*.json install/targets/*.json
git commit -m "chore: record release v${DATE}" && git push
# 6. Keep only the new release plus large-files: an older pack carries hashes
+3 -5
View File
@@ -1,7 +1,5 @@
# Troubleshooting
Diagnosis guide organized by symptom. Each section describes what to check and how to fix it.
## Game won't start / black screen
Most launch failures are caused by a missing or incorrect BIOS file.
@@ -280,9 +278,9 @@ The one-line bootstrap verifies the downloaded installer before running it, and
the installer reads its manifests from the same revision. A platform name is
checked before anything is downloaded, so a "No file list" message comes from
the revision: `RETROBIOS_REF` naming a tag that does not exist, or one older
than the platform. Unset it, or pick a tag from the releases page.
`RETROBIOS_REF` pins an installation to a published tag when a reproducible
file set matters more than the newest one.
than the platform. Unset it, or pick a tag from the releases page. What a tag
holds and for how long is under
[environment overrides](installer.md#environment-overrides).
### Manual pack not found in release