mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-12 22:43:23 -05:00
feat: choose systems, cores and regions to install
This commit is contained in:
1 parent
b0bd20f403
commit
ece2c06aaf
13 files changed
+1426
-34
No files matched your search
+36
-10
@@ -64,6 +64,7 @@ from common import (
|
||||
import packresolve
|
||||
from refresh_data_dirs import cache_lock
|
||||
import region as region_mod
|
||||
from manifestselect import SelectionIndex
|
||||
import slot as slot_mod
|
||||
import slots
|
||||
import split_pack
|
||||
@@ -2952,6 +2953,7 @@ def _manifest_entry(
|
||||
cores: list[str] | None,
|
||||
db: dict,
|
||||
repo_root: str,
|
||||
selection_fields: dict,
|
||||
) -> tuple[dict | None, int]:
|
||||
"""The download record of a resolved file, and the size it occupies.
|
||||
|
||||
@@ -2962,6 +2964,7 @@ def _manifest_entry(
|
||||
scan, a hash computed wrong) has neither a repo path nor a release
|
||||
asset. None then, so the caller records an omission instead of a dead
|
||||
entry that makes the installer refuse the whole manifest.
|
||||
`selection_fields` carries what the installer narrows on.
|
||||
"""
|
||||
sha1 = ""
|
||||
sha256 = ""
|
||||
@@ -2982,6 +2985,7 @@ def _manifest_entry(
|
||||
"size": file_size,
|
||||
"repo_path": repo_path,
|
||||
"cores": cores,
|
||||
**selection_fields,
|
||||
}
|
||||
if is_release_asset:
|
||||
entry["storage"] = "release"
|
||||
@@ -3007,6 +3011,7 @@ def _manifest_core_entries(
|
||||
omitted_by_destination: dict,
|
||||
record_omission,
|
||||
pack_only_sizes: list[int],
|
||||
selection: SelectionIndex,
|
||||
required_only: bool = False,
|
||||
) -> int:
|
||||
"""Add the files a platform's cores need but its list does not name.
|
||||
@@ -3073,6 +3078,7 @@ def _manifest_core_entries(
|
||||
entry, file_size = _manifest_entry(
|
||||
local_path, manifest_dest, [source_emu] if source_emu else [],
|
||||
db, repo_root,
|
||||
selection.fields(dest, fe.get("name", "")),
|
||||
)
|
||||
if entry is None:
|
||||
systems = _extra_system_ids(fe)
|
||||
@@ -3095,7 +3101,7 @@ def _manifest_core_entries(
|
||||
return total_size
|
||||
|
||||
|
||||
def _manifest_region_drops(
|
||||
def _manifest_selection(
|
||||
config: dict,
|
||||
pack_systems: dict,
|
||||
emulators_dir: str,
|
||||
@@ -3106,10 +3112,13 @@ def _manifest_region_drops(
|
||||
source: str,
|
||||
required_only: bool,
|
||||
regions: list[str] | None,
|
||||
) -> set[str]:
|
||||
"""Destinations a region list removes, grouped as the pack groups them."""
|
||||
if not regions:
|
||||
return set()
|
||||
) -> tuple[set[str], SelectionIndex]:
|
||||
"""Destinations a region list removes, and the groups the installer replays.
|
||||
|
||||
Grouped as the pack groups them, so a region the installer applies to
|
||||
the full manifest withdraws what `--region` withdraws from the pack.
|
||||
"""
|
||||
extras: list[dict] = []
|
||||
region_groups, _extra_dests = platform_region_groups(
|
||||
config,
|
||||
pack_systems,
|
||||
@@ -3121,9 +3130,16 @@ def _manifest_region_drops(
|
||||
include_extras=(source != "platform"),
|
||||
include_all=(source == "truth"),
|
||||
required_only=required_only,
|
||||
extras_out=extras,
|
||||
)
|
||||
return region_mod.resolve_region_drops(
|
||||
region_groups, region_mod.build_region_index(emu_profiles), regions
|
||||
region_index = region_mod.build_region_index(emu_profiles)
|
||||
drops = (
|
||||
region_mod.resolve_region_drops(region_groups, region_index, regions)
|
||||
if regions
|
||||
else set()
|
||||
)
|
||||
return drops, SelectionIndex.build(
|
||||
region_groups, region_index, pack_systems, extras, required_only
|
||||
)
|
||||
|
||||
|
||||
@@ -3234,9 +3250,17 @@ def generate_manifest(
|
||||
"required": bool(file_entry.get("required", True)),
|
||||
"reason": reason,
|
||||
"cores": cores,
|
||||
# The builder counts an unavailable regional file in its groups:
|
||||
# the installer must too, or it keeps a fallback the pack drops.
|
||||
**selection.fields(
|
||||
sanitize_pack_path(
|
||||
file_entry.get("destination", file_entry.get("name", ""))
|
||||
),
|
||||
str(file_entry.get("name") or ""),
|
||||
),
|
||||
}
|
||||
|
||||
region_drops = _manifest_region_drops(
|
||||
region_drops, selection = _manifest_selection(
|
||||
config, pack_systems, emulators_dir, db, base_dest, emu_profiles,
|
||||
target_cores, source, required_only, regions,
|
||||
)
|
||||
@@ -3325,7 +3349,8 @@ def generate_manifest(
|
||||
)
|
||||
|
||||
entry, file_size = _manifest_entry(
|
||||
local_path, dest, None, db, repo_root
|
||||
local_path, dest, None, db, repo_root,
|
||||
selection.fields(dest, file_entry["name"]),
|
||||
)
|
||||
if entry is None:
|
||||
record_omission(full_dest, file_entry, sys_id, "not_found", None)
|
||||
@@ -3366,7 +3391,8 @@ def generate_manifest(
|
||||
core_files, config, db, bios_dir, base_dest, repo_root,
|
||||
zip_contents, offline, region_drops, case_insensitive,
|
||||
seen_destinations, seen_lower, seen_parents, manifest_files,
|
||||
omitted_by_destination, record_omission, pack_only_sizes, required_only,
|
||||
omitted_by_destination, record_omission, pack_only_sizes, selection,
|
||||
required_only,
|
||||
)
|
||||
|
||||
# Phase 3: data directories. The installer does not fetch them, so they
|
||||
|
||||
@@ -372,7 +372,7 @@ def generate_readme(db: dict, platforms_dir: str) -> str:
|
||||
"",
|
||||
"## Download BIOS packs",
|
||||
"",
|
||||
"One pack per platform, and it holds everything the platform runs: its own BIOS list plus every file its emulator cores load. Pick your platform, download the ZIP, extract to the BIOS path. The installer above does the same file by file, and `--target` narrows it to one machine; for a region or a bare minimum, build your own pack below.",
|
||||
"One pack per platform, and it holds everything the platform runs: its own BIOS list plus every file its emulator cores load. Pick your platform, download the ZIP, extract to the BIOS path. The installer above does the same file by file. At a terminal it asks whether to install everything or to choose systems, cores and regions, and `--system`, `--core`, `--region` and `--target` make the same choice without questions; for a bare minimum, build your own pack below.",
|
||||
"Files is how many the pack holds once extracted, the figure a file"
|
||||
" manager shows for the folder. It differs from one platform to the"
|
||||
" next because a pack carries only what that platform's emulators"
|
||||
|
||||
@@ -3405,11 +3405,13 @@ guarantee either: source profiles can document files nobody has dumped or
|
||||
that only the user can provide, all of them visible in the
|
||||
[gap analysis](gaps.md).
|
||||
|
||||
Want less than everything? The installer takes `--target switch` to install
|
||||
only what one machine's cores need. From a clone of the repository,
|
||||
`python scripts/generate_pack.py --platform retroarch --region us` keeps one
|
||||
BIOS per region and `--required-only` the bare minimum each core needs to
|
||||
start; `--help` lists every way to build your own.
|
||||
Want less than everything? At a terminal the installer asks whether to install
|
||||
everything or to choose systems, cores and regions, and `--system`, `--core`,
|
||||
`--region` and `--target` make the same choice without questions (see the
|
||||
[installer reference](wiki/installer.md#choosing-what-to-install)). From a
|
||||
clone of the repository, `python scripts/generate_pack.py --platform retroarch
|
||||
--region us` builds the same narrowing as a pack and `--required-only` the bare
|
||||
minimum each core needs to start; `--help` lists every way to build your own.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
"""What an install manifest records about each file so the installer can narrow it.
|
||||
|
||||
The installer runs without the profiles or the database, yet a narrowed
|
||||
install must hold the same files as a pack built with the same narrowing.
|
||||
Each entry therefore carries the platform systems it belongs to and, when
|
||||
the code selects it by territory, its regions and the groups in which it
|
||||
competes. The installer replays region.resolve_region_drops over them.
|
||||
|
||||
The pack's `--system X` keeps the platform's declarations under X and every
|
||||
core extra owned by X, then groups regions over the declarations it kept and
|
||||
over every core extra of the platform. A group membership is therefore either
|
||||
unconditional (a core extra) or present only when the declaring system is
|
||||
kept (a platform declaration), and the manifest records the two apart.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
import region as region_mod
|
||||
from common import _norm_system_id, sanitize_pack_path
|
||||
from packextras import _extra_system_ids, _kept, extra_region_groups
|
||||
|
||||
|
||||
def _dest(entry: dict) -> str:
|
||||
return sanitize_pack_path(entry.get("destination", entry.get("name", "")))
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SelectionIndex:
|
||||
"""System and region facts for one platform, as the pack builder reads them."""
|
||||
|
||||
region_index: dict[str, dict]
|
||||
competing: frozenset[str]
|
||||
systems_by_dest: dict[str, frozenset[str]]
|
||||
declared_by: dict[str, frozenset[str]]
|
||||
extra_groups: dict[str, frozenset[str]]
|
||||
|
||||
@classmethod
|
||||
def build(
|
||||
cls,
|
||||
region_groups: dict[str, list[tuple[str, str]]],
|
||||
region_index: dict[str, dict],
|
||||
pack_systems: dict[str, dict],
|
||||
extras: list[dict],
|
||||
required_only: bool = False,
|
||||
) -> SelectionIndex:
|
||||
by_norm: dict[str, set[str]] = {}
|
||||
for sys_id in pack_systems:
|
||||
by_norm.setdefault(_norm_system_id(sys_id), set()).add(sys_id)
|
||||
|
||||
declared_by: dict[str, set[str]] = {}
|
||||
for sys_id, system in pack_systems.items():
|
||||
for file_entry in _kept(system.get("files", []), required_only):
|
||||
dest = _dest(file_entry)
|
||||
if dest:
|
||||
declared_by.setdefault(dest, set()).add(sys_id)
|
||||
|
||||
owned_by: dict[str, set[str]] = {}
|
||||
extra_groups: dict[str, set[str]] = {}
|
||||
for extra in extras:
|
||||
dest = _dest(extra)
|
||||
if not dest:
|
||||
continue
|
||||
extra_groups.setdefault(dest, set()).update(extra_region_groups(extra))
|
||||
# A profile system the platform does not have is no system the
|
||||
# pack's --system accepts: it would offer what the pack refuses.
|
||||
for sys_id in _extra_system_ids(extra):
|
||||
owned_by.setdefault(dest, set()).update(
|
||||
by_norm.get(_norm_system_id(sys_id), ())
|
||||
)
|
||||
|
||||
systems_by_dest = {
|
||||
dest: frozenset(declared_by.get(dest, set()) | owned_by.get(dest, set()))
|
||||
for dest in set(declared_by) | set(owned_by)
|
||||
}
|
||||
return cls(
|
||||
region_index,
|
||||
frozenset(
|
||||
dest for members in region_groups.values() for dest, _name in members
|
||||
),
|
||||
systems_by_dest,
|
||||
{dest: frozenset(ids) for dest, ids in declared_by.items()},
|
||||
{dest: frozenset(ids) for dest, ids in extra_groups.items()},
|
||||
)
|
||||
|
||||
def fields(self, dest: str, name: str) -> dict:
|
||||
"""What an entry carries for the installer's selection.
|
||||
|
||||
`systems` drives --system. A regional entry adds its regions,
|
||||
`region_groups` it always competes in as a core extra, and
|
||||
`region_system_groups` it competes in only when that platform system
|
||||
is kept. An entry that never competes carries no region field: an
|
||||
untagged file always survives a region filter.
|
||||
"""
|
||||
out: dict = {}
|
||||
systems = self.systems_by_dest.get(dest)
|
||||
if systems:
|
||||
out["systems"] = sorted(systems)
|
||||
if dest not in self.competing:
|
||||
return out
|
||||
regions = region_mod.lookup_regions(self.region_index, dest, name)
|
||||
if not regions:
|
||||
return out
|
||||
out["regions"] = sorted(regions)
|
||||
always = self.extra_groups.get(dest)
|
||||
if always:
|
||||
out["region_groups"] = sorted(always)
|
||||
declared = self.declared_by.get(dest)
|
||||
if declared:
|
||||
out["region_system_groups"] = sorted(declared)
|
||||
return out
|
||||
+18
-11
@@ -685,6 +685,7 @@ def platform_region_groups(
|
||||
include_extras: bool = True,
|
||||
include_all: bool = False,
|
||||
required_only: bool = False,
|
||||
extras_out: list[dict] | None = None,
|
||||
) -> tuple[dict[str, list[tuple[str, str]]], dict[tuple[str, str, str], str]]:
|
||||
"""Group a platform's pack candidates the way region filtering reads them.
|
||||
|
||||
@@ -692,7 +693,8 @@ def platform_region_groups(
|
||||
grouped under, keyed by (emulator, name). verify.py needs that mapping to
|
||||
withdraw from its report exactly what the builder withdraws from the pack:
|
||||
grouping the declared files here and the core extras there would let the
|
||||
two answer differently on the same request.
|
||||
two answer differently on the same request. `extras_out` receives the
|
||||
core extras that were grouped, for the install manifest.
|
||||
"""
|
||||
groups: dict[str, list[tuple[str, str]]] = {}
|
||||
for sys_id, system in systems.items():
|
||||
@@ -733,19 +735,24 @@ def platform_region_groups(
|
||||
extra.get("source_path", ""),
|
||||
)
|
||||
] = dest
|
||||
variant = extra.get("variant_group")
|
||||
if variant:
|
||||
# A variant group is one slot of one core, whatever system each
|
||||
# member is filed under: PicoDrive files its US Mega CD BIOS under
|
||||
# sega-segacd and the EU/JP ones under sega-megacd, and find_bios
|
||||
# picks one of the twelve. Keyed as emulator mode keys it.
|
||||
group_ids = [f"{extra.get('source_emulator', '')}:variant:{variant}"]
|
||||
else:
|
||||
group_ids = _extra_system_ids(extra) or ["_extras"]
|
||||
for group_id in group_ids:
|
||||
for group_id in extra_region_groups(extra):
|
||||
groups.setdefault(group_id, []).append((dest, name))
|
||||
if extras_out is not None:
|
||||
extras_out.append(extra)
|
||||
return groups, extra_dests
|
||||
|
||||
|
||||
def extra_region_groups(extra: dict) -> list[str]:
|
||||
"""The region groups a core extra competes in."""
|
||||
variant = extra.get("variant_group")
|
||||
if variant:
|
||||
# A variant group is one slot of one core, whatever system each
|
||||
# member is filed under: PicoDrive files its US Mega CD BIOS under
|
||||
# sega-segacd and the EU/JP ones under sega-megacd, and find_bios
|
||||
# picks one of the twelve. Keyed as emulator mode keys it.
|
||||
return [f"{extra.get('source_emulator', '')}:variant:{variant}"]
|
||||
return _extra_system_ids(extra) or ["_extras"]
|
||||
|
||||
def emulator_region_drops(
|
||||
selected: list[tuple[str, dict]],
|
||||
standalone: bool,
|
||||
|
||||
Reference in new issue
Block a user