From 4b54b829668c21c07ff3756f6359347c7b9bad88 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Thu, 5 Nov 2026 19:21:23 +0100 Subject: [PATCH] fix: suggest the target when no platform has it verify --all --target swich ended on 'no platform has it' after seventeen INFO lines, two of which held the suggestion. The refusal now names the closest targets across every platform with the platforms that have them, and the per-platform lines print only when some platform keeps the target. --- scripts/common.py | 35 ++++++++++++++++++++++++++++++++--- tests/test_tool_messages.py | 16 ++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/scripts/common.py b/scripts/common.py index 9309e5d99..7bf60f02e 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -3250,26 +3250,55 @@ def build_target_cores_cache( """ cache: dict[str, set[str] | None] = {} skip: list[str] = [] + notes: list[str] = [] for p in platforms: try: cache[p] = load_target_config(p, target, platforms_dir) except FileNotFoundError: if not is_all: raise - print(f"INFO: Skipping {p}: no hardware targets") + notes.append(f"INFO: Skipping {p}: no hardware targets") skip.append(p) except ValueError as e: if is_all: - print(f"INFO: Skipping {p}: {e}") + notes.append(f"INFO: Skipping {p}: {e}") skip.append(p) else: raise kept = [p for p in platforms if p not in skip] if is_all and platforms and not kept: - raise ValueError(f"unknown target '{target}': no platform has it") + # The seventeen per-platform lines buried the one suggestion that + # mattered: the refusal names the closest target across them all. + raise ValueError(_unknown_target_message(target, platforms, platforms_dir)) + for note in notes: + print(note) return cache, kept +def _unknown_target_message(target: str, platforms: list[str], platforms_dir: str) -> str: + """A target no platform has, with the closest names any of them knows.""" + import difflib # noqa: PLC0415 + + targets_dir = os.path.join(platforms_dir, "targets") + owners: dict[str, list[str]] = {} + for platform in platforms: + target_file = os.path.join(targets_dir, f"{platform}.yml") + if not os.path.exists(target_file): + continue + with open(target_file) as f: + targets = (yaml_load(f) or {}).get("targets", {}) + overrides = _target_overrides(platform, targets_dir) + for name in _target_alias_index(targets, overrides): + owners.setdefault(name, []).append(platform) + message = f"unknown target '{target}': no platform has it." + close = difflib.get_close_matches(target, sorted(owners), n=3, cutoff=0.6) + if close: + named = ", ".join(f"{n} ({', '.join(sorted(owners[n]))})" for n in close) + return f"{message} Did you mean: {named}?" + return (f"{message} python scripts/verify.py --platform --list-targets " + "lists the targets of one platform.") + + # Re-exported so the existing call sites keep working while the # modules above become the place to import from. from safeparse import ( # noqa: E402,F401 diff --git a/tests/test_tool_messages.py b/tests/test_tool_messages.py index c478f997a..7f043a22b 100644 --- a/tests/test_tool_messages.py +++ b/tests/test_tool_messages.py @@ -85,6 +85,22 @@ class AMistypedTargetGetsTheClosestName(unittest.TestCase): +class AMistypedTargetAcrossAllPlatforms(unittest.TestCase): + """`verify --all --target swich` ended on "no platform has it" after 17 + INFO lines, two of which held the suggestion.""" + + def test_the_refusal_suggests_and_names_the_platforms(self): + platforms = common.list_registered_platforms(str(REPO_ROOT / "platforms")) + out = io.StringIO() + with contextlib.redirect_stdout(out), self.assertRaises(ValueError) as caught: + common.build_target_cores_cache( + platforms, "swich", str(REPO_ROOT / "platforms"), is_all=True + ) + self.assertIn("Did you mean: switch (", str(caught.exception)) + self.assertIn("retroarch", str(caught.exception)) + self.assertEqual(out.getvalue(), "") + + class AMistypedDataKeyGetsTheClosestName(unittest.TestCase): """`--key dolphin-sis` printed all 27 keys on one line."""