fix: name the boards a downloaded core reaches

A board's core list names Batocera's standalone amiberry and the
buildbot's amiberry core alike, so install.py --target kept the
amiberry 8.5.0 libretro files on bcm2711, whose RetroArch downloads
nothing. resolve_platform_cores decides that reach, and the install
list now carries its answer as downloaded_cores, read by the
installer's narrowing for a target.

The builder tested only the first installable name of a profile:
Batocera lists higan_sfc_balanced and nside_sfc_balanced, the x86_64
downloader offers the second, and the core left the x86_64 pack with
its Super GameBoy files.
This commit is contained in:
Abdessamad Derraz committed 2026-11-10 02:07:54 +01:00
1 parent 7f20702b48
commit 2a6b8b5943
10 files changed
+183 -30

No files matched your search

+34 -13
View File
@@ -6,7 +6,7 @@ and file resolution - eliminates DRY violations across scripts.
from __future__ import annotations
from collections.abc import Callable, Iterable, Mapping
from collections.abc import Callable, Collection, Iterable, Mapping
from collections import Counter
import argparse
@@ -3230,9 +3230,30 @@ def resolve_platform_cores(
Alias profiles are always excluded (they point to another profile).
If target_cores is provided, result is intersected with it.
"""
result, downloaded_only = _platform_profiles(config, profiles)
if target_cores is not None:
result = result & designated_profiles(profiles, target_cores)
result -= {
profile for profile, names in downloaded_only.items()
if not downloadable_on(target_cores, names)
}
return result
def downloaded_only_profiles(
config: dict, profiles: dict[str, dict]
) -> dict[str, frozenset[str]]:
"""The profiles a platform reaches only through its RetroArch core
downloader, each with the installable names that reach it."""
return _platform_profiles(config, profiles)[1]
def _platform_profiles(
config: dict, profiles: dict[str, dict]
) -> tuple[set[str], dict[str, frozenset[str]]]:
cores_config = config.get("cores")
platform_key = str(config.get("_key") or "")
downloaded_only: dict[str, str] = {}
downloaded_only: dict[str, set[str]] = {}
# The builds the platform compiles itself, by the names they answer to.
# Such a name means that build there, and another revision of the same
# project is not what the platform runs: ROCKNIX and RetroPie build only
@@ -3323,9 +3344,11 @@ def resolve_platform_cores(
# A core reached only through the RetroArch downloader runs on the
# boards whose downloader offers it: Batocera's x86_64 RetroArch
# installs the amiberry core, its armv7 and aarch64 boards cannot.
# Every installable name counts: Batocera's x86_64 downloader offers
# nside_sfc_balanced, not the higan_sfc_balanced it also lists.
for c in config.get("installable_cores") or []:
for profile in libretro_builds(c) - dual - result:
downloaded_only.setdefault(profile, str(c))
downloaded_only.setdefault(profile, set()).add(str(c))
result |= dual | set(downloaded_only)
# Support "all_libretro" as a list element: combines all libretro
# profiles with explicitly listed standalone cores (e.g. RetroDECK
@@ -3343,13 +3366,7 @@ def resolve_platform_cores(
and not p.get("built_by")
}
if target_cores is not None:
result = result & designated_profiles(profiles, target_cores)
result -= {
profile for profile, c in downloaded_only.items()
if not downloadable_on(target_cores, c)
}
return result
return result, {p: frozenset(names) for p, names in downloaded_only.items()}
@functools.lru_cache(maxsize=1)
@@ -3362,14 +3379,18 @@ def _retroarch_target_sets() -> tuple[frozenset[str], ...]:
)
def downloadable_on(target_cores: set[str], name: str) -> bool:
"""Whether a board's core set holds a RetroArch download list offering `name`.
def downloadable_on(target_cores: set[str], names: Collection[str]) -> bool:
"""Whether a board's core set holds a RetroArch download list offering
one of `names`.
A board with a downloader carries a whole RetroArch targets entry in its
set (load_target_config, `downloads`), a board without one only what it
builds.
"""
return any(name in found and found <= target_cores for found in _retroarch_target_sets())
return any(
found <= target_cores and not found.isdisjoint(names)
for found in _retroarch_target_sets()
)
def unresolved_platform_cores(config: dict, profiles: dict[str, dict]) -> set[str]:
+39
View File
@@ -86,6 +86,9 @@ from common import (
ProfileSelectionError,
core_system_dirs,
declared_root,
downloadable_on,
downloaded_only_profiles,
list_available_targets,
entry_serves_systems,
entry_system_ids,
platform_layouts,
@@ -5291,6 +5294,9 @@ def generate_manifest(
omitted_by_destination.values(), key=lambda entry: entry["dest"]
),
"core_names": core_names,
**({"downloaded_cores": downloaded} if (downloaded := _downloaded_cores(
config, emu_profiles, core_names, platforms_dir,
)) else {}),
"system_cores": system_cores,
# The names the platform itself gives its systems (Batocera's psx),
# which --system accepts beside the ids.
@@ -5445,6 +5451,39 @@ def _manifest_data_directories(
return trees
def _downloaded_cores(
config: dict,
profiles: dict[str, dict],
core_names: dict[str, list[str]],
platforms_dir: str,
) -> dict[str, list[str]]:
"""The cores only the platform's RetroArch downloader brings, each with
the boards, aliases included, whose downloader offers it.
A target list names the board's standalone amiberry and the buildbot's
amiberry core alike, so the installer cannot tell from the name whether
the board reaches the core. `resolve_platform_cores` decides it, and
`install.py --target` reads its answer here.
"""
reached = {
profile: names
for profile, names in downloaded_only_profiles(config, profiles).items()
if profile in core_names
}
if not reached:
return {}
platform = str(config.get("_key") or "")
boards: dict[str, list[str]] = {profile: [] for profile in reached}
for target in list_available_targets(platform, platforms_dir):
cores = load_target_config(platform, target["name"], platforms_dir)
if cores is None:
continue
for profile, names in reached.items():
if downloadable_on(cores, names):
boards[profile] += [target["name"], *target.get("aliases", [])]
return {profile: sorted(names) for profile, names in sorted(boards.items())}
def _manifest_core_index(
config: dict,
profiles: dict[str, dict],