From 0f982f03b2e2085fab26f27e7310b62a7c924578 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 31 Aug 2026 19:55:53 -0400 Subject: [PATCH] [core] Prefetch tool-scons by PlatformIO's core spec (#18831) --- esphome/platformio/prefetch.py | 18 +++++++++--------- tests/unit_tests/test_platformio_prefetch.py | 16 ++++++++++------ 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/esphome/platformio/prefetch.py b/esphome/platformio/prefetch.py index 1df0a4b328..5097239065 100644 --- a/esphome/platformio/prefetch.py +++ b/esphome/platformio/prefetch.py @@ -832,16 +832,16 @@ def _prefetch(build_dir: Path, env: str) -> None: for name, opts in p.packages.items() if not opts.get("optional") ] - # PIO's build engine installs outside the platform package list; - # skipped when the platform lists it itself - if not any(s.name == "tool-scons" for s in specs): - specs.append( - PackageSpec( - owner="platformio", - name="tool-scons", - requirements=get_core_dependencies()["tool-scons"], - ) + # PIO's build engine installs tool-scons by its own registry spec at build + # start; a platform URL copy has no owner to match it, so prefetch that spec + specs = [s for s in specs if s.name != "tool-scons"] + specs.append( + PackageSpec( + owner="platformio", + name="tool-scons", + requirements=get_core_dependencies()["tool-scons"], ) + ) lib_deps = config.get(f"env:{env}", "lib_deps", []) # pio run's storage dir for this env, with its compatibility # qualifiers: an unqualified library install could land a different diff --git a/tests/unit_tests/test_platformio_prefetch.py b/tests/unit_tests/test_platformio_prefetch.py index d0785d2724..379ef52ebd 100644 --- a/tests/unit_tests/test_platformio_prefetch.py +++ b/tests/unit_tests/test_platformio_prefetch.py @@ -13,6 +13,7 @@ from types import SimpleNamespace from unittest.mock import MagicMock, patch from filelock import Timeout +from platformio.dependencies import get_core_dependencies from platformio.package.manager._install import PackageManagerInstallMixin from platformio.package.manager.base import BasePackageManager from platformio.package.manager.library import LibraryPackageManager @@ -1728,30 +1729,31 @@ def test_preinstall_unlocks_even_when_pool_fails(tmp_path: Path) -> None: m.unlock.assert_called_once_with() -def test_prefetch_skips_duplicate_tool_scons(tmp_path: Path) -> None: - """A platform that lists tool-scons itself does not get it appended.""" +def test_prefetch_replaces_platform_tool_scons_with_core_spec(tmp_path: Path) -> None: + """A platform's own tool-scons spec gives way to the core's registry spec.""" _write_ini(tmp_path, "[env:testenv]\nplatform = fake/p@1\n") fake_platform = MagicMock() fake_platform.packages = {"tool-scons": {"optional": False}} fake_platform.get_package_spec.side_effect = lambda name: _FakeSpec( - uri=None, name=name + uri="https://x/scons.zip", name=name, owner=None ) config = _fake_config(tmp_path, {"platform": "fake/p@1"}) modules = _pio_modules(tmp_path, fake_platform, MagicMock(), config) - batches: list[list[str]] = [] + batches: list[list] = [] with ( patch.dict("sys.modules", modules), patch.object( pf, "_registry_jobs", side_effect=lambda mgr, specs, seen: ( - batches.append([s.name for s in specs]) or ([], 0, []) + batches.append(list(specs)) or ([], 0, []) ), ), patch.object(pf, "_uri_jobs", return_value=([], 0, [])), ): pf._prefetch(tmp_path, "testenv") - assert batches[0] == ["tool-scons"] + (spec,) = batches[0] + assert (spec.name, spec.owner, spec.uri) == ("tool-scons", "platformio", None) def test_platformio_private_api_contract() -> None: @@ -1784,6 +1786,8 @@ def test_platformio_private_api_contract() -> None: assert callable(getattr(BasePackageManager, name)) # The dependency wave mirrors install_dependency's builtin skip assert callable(LibraryPackageManager.is_builtin_lib) + # The prefetch keys tool-scons on this core dependency + assert "tool-scons" in get_core_dependencies() # The pre-install passes these positionally / by keyword assert "compatibility" in inspect.signature(BasePackageManager.__init__).parameters lib_params = inspect.signature(LibraryPackageManager.__init__).parameters