diff --git a/esphome/arduino8266/framework.py b/esphome/arduino8266/framework.py index cded1438c4..b27744cb20 100644 --- a/esphome/arduino8266/framework.py +++ b/esphome/arduino8266/framework.py @@ -75,6 +75,14 @@ def framework_package_version(ver: Version) -> str: f"Arduino core {ver} is not supported yet; " "the newest known core series is 3.x" ) + if ver < Version(2, 6, 3): + # Older cores use the 1.x/2.x package-major encodings, which the + # PlatformIO path handles before delegating here; never encode them + # wrongly for a caller that skipped that guard + raise EsphomeError( + f"Arduino core {ver} predates the package encoding this helper " + "implements (2.6.3 and newer)" + ) return f"3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" @@ -133,7 +141,13 @@ def check_and_install(framework_version: Version) -> InstalledPaths: def get_build_env(toolchain_path: Path) -> dict[str, str]: env = os.environ.copy() - env["PATH"] = str(toolchain_path / "bin") + os.pathsep + env.get("PATH", "") + # Drop empty entries: a trailing separator from an absent PATH would + # make the shell search the current directory for tools + parts = [ + str(toolchain_path / "bin"), + *filter(None, env.get("PATH", "").split(os.pathsep)), + ] + env["PATH"] = os.pathsep.join(parts) env.update(ccache_env()) return env diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 47b122afb8..dafb9b3cc6 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -194,7 +194,7 @@ def _format_framework_arduino_version(ver: cv.Version) -> str: except EsphomeError as err: # Anchor the 4.x rejection to the framework version line instead of # aborting with a bare traceback-level error - raise cv.Invalid(str(err)) from err + raise cv.Invalid(str(err), path=[CONF_VERSION]) from err # NOTE: Keep this in mind when updating the recommended version: diff --git a/tests/unit_tests/test_arduino8266_framework.py b/tests/unit_tests/test_arduino8266_framework.py index 56d89cc37d..cdd1ae0417 100644 --- a/tests/unit_tests/test_arduino8266_framework.py +++ b/tests/unit_tests/test_arduino8266_framework.py @@ -27,6 +27,10 @@ def test_framework_package_version() -> None: # A future major bump needs its own encoding, not a doomed registry lookup with pytest.raises(EsphomeError, match="not supported yet"): framework.framework_package_version(cv.Version(4, 0, 0)) + # The pre-2.6.3 eras use other encodings; the helper is total, not wrong + with pytest.raises(EsphomeError, match="predates the package encoding"): + framework.framework_package_version(cv.Version(2, 6, 2)) + assert framework.framework_package_version(cv.Version(2, 6, 3)) == "3.20603.0" def test_format_framework_arduino_version_pins_all_series() -> None: @@ -39,8 +43,9 @@ def test_format_framework_arduino_version_pins_all_series() -> None: assert fmt(cv.Version(2, 7, 4)) == "~3.20704.0" assert fmt(cv.Version(3, 1, 2)) == "~3.30102.0" # Anchored to the framework version line, not a bare EsphomeError - with pytest.raises(cv.Invalid, match="not supported yet"): + with pytest.raises(cv.Invalid, match="not supported yet") as excinfo: fmt(cv.Version(4, 0, 0)) + assert excinfo.value.path == ["version"] def test_tools_path_default_and_prefix(tmp_path: Path) -> None: @@ -125,3 +130,22 @@ def test_check_and_install_rejects_old_core(tmp_path: Path) -> None: """Calling the installer below the floor fails before any download.""" with pytest.raises(EsphomeError, match=">= 3.1.1"): framework.check_and_install(cv.Version(3, 0, 2)) + + +def test_get_build_env_without_path_has_no_empty_entry(tmp_path: Path) -> None: + """An absent PATH must not leave a trailing separator (an empty entry + means the current directory to the shell).""" + with ( + patch.dict(os.environ, {}, clear=True), + patch.object(framework, "ccache_env", return_value={}), + ): + env = framework.get_build_env(tmp_path) + assert env["PATH"] == str(tmp_path / "bin") + with ( + patch.dict( + os.environ, {"PATH": f"/usr/bin{os.pathsep}{os.pathsep}/bin"}, clear=True + ), + patch.object(framework, "ccache_env", return_value={}), + ): + env = framework.get_build_env(tmp_path) + assert env["PATH"].split(os.pathsep) == [str(tmp_path / "bin"), "/usr/bin", "/bin"]