mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-03 08:33:46 -06:00
chore: merge true platform list versions
# Conflicts: # install.ps1 # install.py # install.sh # install/launchers/index.json
This commit is contained in:
commit
7ec4d00375
45 files changed
+559
-64
No files matched your search
+150
-1
@@ -641,6 +641,12 @@ EVENT_KEYS: dict[str, tuple[str, ...]] = {
|
||||
# up to MAX_SCANNED_FILES, truncated past them), and the folders below
|
||||
# it on another drive, not entered.
|
||||
"scanned": ("folder", "files", "truncated", "other_drives"),
|
||||
# Before choices, once a list is read: the system here runs another
|
||||
# version than the one the list was built for (list_version, from the
|
||||
# manifest, installed_version, from the system's own version file).
|
||||
# message says it and what it means. Not written when either version
|
||||
# is unknown, and never a refusal.
|
||||
"version": ("platform", "list_version", "installed_version", "message"),
|
||||
# Before the file events of a check: the systems the run's list lets
|
||||
# one choose among, --system aside, in id order (other for the files
|
||||
# no system names). What Choose systems lists, and what the plan counts
|
||||
@@ -1661,6 +1667,127 @@ _CARD_SYSTEMS = {"MinUI": "/.system/res"}
|
||||
_DETECTED_OS: dict[tuple[str, Path], str] = {}
|
||||
|
||||
|
||||
# A version as a list or a system writes it: v1.22.2, 43.1, 8.3.1.1.
|
||||
_VERSION_TEXT = re.compile(r"[0-9A-Za-z][0-9A-Za-z._+-]{0,39}")
|
||||
# What generate_pack.py wrote as every list's version until it recorded the
|
||||
# platform's own: not a version, so a list holding it names none.
|
||||
_NO_LIST_VERSION = "1.0"
|
||||
_MD5_CHECK = "{name}'s own BIOS check may report some files as wrong though they work."
|
||||
|
||||
|
||||
def _version_line(path: Path) -> str:
|
||||
"""The first line of a small version file, "" when it cannot be read."""
|
||||
if not _is_file(path):
|
||||
return ""
|
||||
try:
|
||||
with path.open("rb") as fh:
|
||||
head = fh.read(4096)
|
||||
except OSError:
|
||||
return ""
|
||||
lines = head.decode("utf-8", errors="replace").strip().splitlines()
|
||||
return lines[0].strip() if lines else ""
|
||||
|
||||
|
||||
def _batocera_version(_bios_path: Path) -> str:
|
||||
# batocera-system.mk:9,100-102 (batocera-43.1): the version, then the
|
||||
# date and time of the build.
|
||||
words = _version_line(_on_device("/usr/share/batocera/batocera.version")).split()
|
||||
return words[0] if words else ""
|
||||
|
||||
|
||||
def _recalbox_version(_bios_path: Path) -> str:
|
||||
# Dockerfile:31 (recalbox 10.1.1) writes RECALBOX_VERSION, which
|
||||
# .gitlab-ci.yml:723 sets to the release tag, or for a build of a branch
|
||||
# to its date and name. A build from a working tree says "development"
|
||||
# (projects/configgen/configgen/challenge/RuleScript.py:64). Only a
|
||||
# release is a version a list is built for.
|
||||
text = _version_line(_on_device("/recalbox/recalbox.version"))
|
||||
return text if re.fullmatch(r"\d+(?:\.\d+)+", text) else ""
|
||||
|
||||
|
||||
def _retrobat_version(bios_path: Path) -> str:
|
||||
# <RetroBat>/system/version.info, which es-update writes and es-checkversion
|
||||
# reads (emulatorlauncher 48cdc616, es-update/RetrobatVersion.cs:66-92).
|
||||
# The 8.3.1 installer holds "8.3.1.1-stable-win64".
|
||||
text = _version_line(bios_path.parent / "system" / "version.info")
|
||||
head = text.split("-", 1)[0]
|
||||
return head if re.fullmatch(r"\d+(?:\.\d+)+", head) else ""
|
||||
|
||||
|
||||
def _os_release_version(key: str, value: str) -> Callable[[Path], str]:
|
||||
"""VERSION_ID from os-release, on the system that names itself so."""
|
||||
def read(_bios_path: Path) -> str:
|
||||
release = _parse_os_release()
|
||||
if release.get(key, "").lower() != value:
|
||||
return ""
|
||||
return release.get("VERSION_ID", "")
|
||||
return read
|
||||
|
||||
|
||||
def _same_release(installed: str, listed: str) -> bool:
|
||||
return installed == listed
|
||||
|
||||
|
||||
def _retrobat_release(installed: str, listed: str) -> bool:
|
||||
# The release tagged 8.3.1 ships 8.3.1.1: a fourth number is a build of
|
||||
# the tagged version.
|
||||
return installed == listed or installed.startswith(listed + ".")
|
||||
|
||||
|
||||
class _VersionSource(NamedTuple):
|
||||
"""Where a system states the version it runs, and what a different one means."""
|
||||
|
||||
read: Callable[[Path], str]
|
||||
meaning: str
|
||||
same: Callable[[str, str], bool] = _same_release
|
||||
# Written by the list (v6.1) and not by the system (6.1).
|
||||
prefix: str = ""
|
||||
|
||||
|
||||
# The systems whose version a file states, read without running anything.
|
||||
# RetroArch writes its version in no file: its lists come from core info
|
||||
# files the online updater replaces, so its version is not compared. KNULLI
|
||||
# writes its codename (knulli-system.mk:9,100-102, "scarab") and ROCKNIX the
|
||||
# date of the build (scripts/image:82), not the release their lists name.
|
||||
# AmberELEC's list names a prerelease tag, and how that tag maps to the
|
||||
# VERSION its image writes is not established.
|
||||
_VERSION_SOURCES: dict[str, _VersionSource] = {
|
||||
"batocera": _VersionSource(_batocera_version, _MD5_CHECK),
|
||||
"recalbox": _VersionSource(_recalbox_version, _MD5_CHECK),
|
||||
"retrobat": _VersionSource(_retrobat_version, _MD5_CHECK, _retrobat_release),
|
||||
# scripts/image:159-164 (Lakka-LibreELEC v6.1) writes VERSION_ID from
|
||||
# OS_VERSION, "6.1" in distributions/Lakka/version, and ID=lakka.
|
||||
"lakka": _VersionSource(
|
||||
_os_release_version("ID", "lakka"),
|
||||
"Lakka checks only that a file is there, so this matters only where a core "
|
||||
"of the version it runs reads a file this list does not name.", prefix="v"),
|
||||
# scripts/image:172-177 (EmuELEC v4.8): VERSION_ID from OS_VERSION, 4.8 in
|
||||
# distributions/EmuELEC/version, and NAME=EmuELEC.
|
||||
"emuelec": _VersionSource(_os_release_version("NAME", "emuelec"), _MD5_CHECK),
|
||||
}
|
||||
|
||||
|
||||
def version_note(plat: str, bios_path: Path, manifest: dict) -> tuple[str, str, str] | None:
|
||||
"""(list version, installed version, sentence) when the system here runs
|
||||
another version than the one the list was built for, else None: an
|
||||
unknown version on either side says nothing."""
|
||||
source = _VERSION_SOURCES.get(plat)
|
||||
listed = manifest.get("version")
|
||||
if (source is None or not isinstance(listed, str) or listed == _NO_LIST_VERSION
|
||||
or not _VERSION_TEXT.fullmatch(listed)):
|
||||
return None
|
||||
installed = source.read(bios_path)
|
||||
if not _VERSION_TEXT.fullmatch(installed):
|
||||
return None
|
||||
compared = listed[len(source.prefix):] if listed.startswith(source.prefix) else listed
|
||||
if source.same(installed, compared):
|
||||
return None
|
||||
name = display_name(plat, manifest)
|
||||
return listed, installed, (
|
||||
f"This list was built for {name} {listed}, this machine runs {installed}. "
|
||||
+ source.meaning.format(name=name))
|
||||
|
||||
|
||||
def _rule_matches(rule: _OsRule, release: dict[str, str]) -> bool:
|
||||
if rule.method == "os_release":
|
||||
# Any of the KEY=value pairs, the value compared without case.
|
||||
@@ -5778,10 +5905,12 @@ def _report_outcome(
|
||||
kinds: tuple[Tally, int] | None = None,
|
||||
same_choice: str = "",
|
||||
term: Terminal | None = None,
|
||||
notes: list[str] | None = None,
|
||||
) -> None:
|
||||
"""Close the run on what happened, and leave with a status that says so.
|
||||
kinds is the count of the files installed (tally) and of the data
|
||||
tree files, given for a list marking its game data, said apart.
|
||||
tree files, given for a list marking its game data, said apart. notes
|
||||
are lines every ending repeats (a list built for another version).
|
||||
|
||||
A run that installed nothing used to end on the word Done and a zero exit,
|
||||
which reads as success to a person and to a script alike. offline counts
|
||||
@@ -5819,6 +5948,8 @@ def _report_outcome(
|
||||
if declined and not downloaded and not errors and not interrupted and not offline:
|
||||
say(f"\nNothing installed: {_plural(declined, 'file')} not downloaded, as answered.")
|
||||
say(f"{_plural(up_to_date, 'file')} already up to date at {_locations(paths)}.")
|
||||
for note in notes or []:
|
||||
say(note)
|
||||
return
|
||||
# Left out for an unknown save directory, said with that reason in
|
||||
# every ending: "the collection does not hold it" blamed the collection.
|
||||
@@ -5828,6 +5959,7 @@ def _report_outcome(
|
||||
f"{_plural(len(unplaced), 'file')} read from "
|
||||
f"{unplaced[0].get('frontend') or 'the frontend'}'s save directory left out: "
|
||||
"its place is not known. Name it with --save-dir next time."] if unplaced else [])
|
||||
kept += list(notes or [])
|
||||
if interrupted:
|
||||
say(f"\nStopped. {_plural(downloaded, 'file')} installed.")
|
||||
for line in kept:
|
||||
@@ -8183,6 +8315,9 @@ class PackSource:
|
||||
# regions), None for a pack that does not say: the remedy for an
|
||||
# option the pack cannot apply names that list only when it can.
|
||||
self.list_records: frozenset | None = None
|
||||
# The version of the platform's list the pack was built from, "" for
|
||||
# a pack that does not record it.
|
||||
self.list_version = ""
|
||||
try:
|
||||
for path in self.files:
|
||||
archive = zipfile.ZipFile(path)
|
||||
@@ -8273,6 +8408,9 @@ class PackSource:
|
||||
records = data.get("list_records")
|
||||
if isinstance(records, list) and all(isinstance(axis, str) for axis in records):
|
||||
self.list_records = frozenset(records) & {"systems", "cores", "regions"}
|
||||
version = data.get("list_version")
|
||||
if isinstance(version, str) and _VERSION_TEXT.fullmatch(version):
|
||||
self.list_version = version
|
||||
for key in ("system_titles", "system_names"):
|
||||
held = data.get(key)
|
||||
if isinstance(held, dict):
|
||||
@@ -8315,6 +8453,7 @@ class PackSource:
|
||||
return {
|
||||
"manifest_version": 2,
|
||||
"platform": plat,
|
||||
**({"version": self.list_version} if self.list_version else {}),
|
||||
**{key: value for key, value in self.names.items() if value},
|
||||
"files": [
|
||||
{"dest": f["path"], "sha1": f["sha1"], "size": f["size"],
|
||||
@@ -11930,6 +12069,7 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
|
||||
total_omitted = 0
|
||||
omitted_seen: list[dict] = []
|
||||
left_out_seen: list[dict] = []
|
||||
version_notes: list[str] = []
|
||||
total_interrupted = False
|
||||
total_bytes = 0
|
||||
total_seconds = 0.0
|
||||
@@ -12137,6 +12277,14 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
|
||||
args.emulator, manifest, plat_name, files, omitted_files
|
||||
)
|
||||
|
||||
# Said before the install and again with the result.
|
||||
note = None if listing else version_note(plat_name, bios_path, manifest)
|
||||
if note is not None:
|
||||
print(f" {note[2]}")
|
||||
version_notes.append(note[2])
|
||||
EVENTS.emit("version", platform=plat_name, list_version=note[0],
|
||||
installed_version=note[1], message=note[2])
|
||||
|
||||
# Before the menu: it offered 23 files, then 22 were checked, the
|
||||
# one read from an unknown save directory left out. Said once the
|
||||
# choice is made, over the files it keeps (say_saves).
|
||||
@@ -12620,6 +12768,7 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
|
||||
total_errors + total_offline),
|
||||
offline=total_offline,
|
||||
term=term,
|
||||
notes=version_notes,
|
||||
)
|
||||
finish_app(args, platforms, app_cards, interactive and not total_declined)
|
||||
|
||||
|
||||
Reference in new issue
Block a user