mirror of
https://github.com/esphome/esphome.git
synced 2026-08-22 22:26:21 +00:00
Strip the python path for cmd.exe, name rules by kind, take the resolved install
The build.ninja python variable goes through strip_win_long_path_prefix like every other emitted binary path (the NSIS launcher's extended prefix cannot be spawned by cmd.exe). Compile rules are named by the shared source kinds directly, retiring the identity translation table and its drift test. write_project takes the caller's resolved ccache, get_flash_ld_path reads the InstalledPaths the ninja file linked against instead of re-resolving the version, the board backstop checks the one table it indexes with an honest comment, and cc/cxx/ar paths go through the shared toolchain_tool accessor (gaining the Windows suffix).
This commit is contained in:
@@ -42,9 +42,11 @@ from esphome.components.esp8266.const import (
|
||||
KEY_FLASH_SIZE,
|
||||
KEY_SCANF_FLOAT,
|
||||
)
|
||||
from esphome.const import KEY_CORE, KEY_FRAMEWORK_VERSION
|
||||
from esphome.core import CORE, EsphomeError
|
||||
from esphome.framework_helpers import get_project_cxx_compile_flags
|
||||
from esphome.framework_helpers import (
|
||||
get_project_cxx_compile_flags,
|
||||
strip_win_long_path_prefix,
|
||||
)
|
||||
from esphome.helpers import mkdir_p, write_file_if_changed
|
||||
from esphome.platformio.library import SOURCE_KIND_FOR_SUFFIX, lex_build_flags
|
||||
|
||||
@@ -55,10 +57,8 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Compile rule per source suffix, derived from the shared suffix -> kind map
|
||||
# so every source extension a library manifest can select has a rule.
|
||||
_RULE_FOR_KIND = {"c": "cc", "cxx": "cxx", "asm": "asm"}
|
||||
_RULE_FOR_SUFFIX = {
|
||||
suffix: _RULE_FOR_KIND[kind] for suffix, kind in SOURCE_KIND_FOR_SUFFIX.items()
|
||||
}
|
||||
# Compile rule names are exactly the shared suffix -> kind values (c, cxx,
|
||||
# asm), so every source extension a library manifest can select has a rule.
|
||||
|
||||
# Always excluded from the core build: ESPHome uses its own native OTA
|
||||
# backend, so the Arduino Updater (and its 228-byte global) never links.
|
||||
@@ -476,7 +476,7 @@ def _collect_sources(root: Path, exclude: set[str] = frozenset()) -> list[Path]:
|
||||
return sorted(
|
||||
p
|
||||
for p in root.rglob("*")
|
||||
if p.suffix in _RULE_FOR_SUFFIX and p.name not in exclude
|
||||
if p.suffix in SOURCE_KIND_FOR_SUFFIX and p.name not in exclude
|
||||
)
|
||||
|
||||
|
||||
@@ -645,7 +645,9 @@ def _ninja_compile_edges(
|
||||
rel = src.relative_to(root).as_posix()
|
||||
obj = f"obj/{group}/{rel}.o"
|
||||
escaped_obj = _e(obj)
|
||||
lines.append(f"build {escaped_obj}: {_RULE_FOR_SUFFIX[src.suffix]} {_e(src)}")
|
||||
lines.append(
|
||||
f"build {escaped_obj}: {SOURCE_KIND_FOR_SUFFIX[src.suffix]} {_e(src)}"
|
||||
)
|
||||
if flags:
|
||||
lines.append(f" flags = {flags}")
|
||||
# Escaped once here: the returned paths only ever appear in build
|
||||
@@ -658,14 +660,15 @@ def _common_parent(paths: list[Path]) -> Path:
|
||||
return Path(os.path.commonpath([str(p.parent) for p in paths]))
|
||||
|
||||
|
||||
def write_project(paths: InstalledPaths) -> bool:
|
||||
def write_project(paths: InstalledPaths, ccache: str | None) -> bool:
|
||||
"""Write the ninja build for the current configuration.
|
||||
|
||||
Returns True when ``build.ninja`` changed, so the caller can skip work
|
||||
derived purely from it (the compile database) on unchanged builds.
|
||||
``ccache`` is the caller's already-resolved binary (None when disabled)
|
||||
so one build never pays the runnability probe per consumer. Returns
|
||||
True when ``build.ninja`` changed, so the caller can skip work derived
|
||||
purely from it (the compile database) on unchanged builds.
|
||||
"""
|
||||
from esphome.arduino.library import resolve_libraries
|
||||
from esphome.arduino8266.framework import ccache_path
|
||||
|
||||
framework = paths.framework
|
||||
toolchain_bin = paths.toolchain / "bin"
|
||||
@@ -679,9 +682,10 @@ def write_project(paths: InstalledPaths) -> bool:
|
||||
config = _resolve_build_config(flag_defines)
|
||||
esp8266_data = CORE.data[KEY_ESP8266]
|
||||
board = esp8266_data[KEY_BOARD]
|
||||
# Config validation accepts the board as a bare string, so this is the
|
||||
# first place an unknown board can fail by name instead of a KeyError
|
||||
if board not in BOARDS or board not in ESP8266_BOARD_BUILD:
|
||||
# _validate_native_toolchain rejects unknown boards at config time and a
|
||||
# test pins the two board tables equal; this backstop covers callers
|
||||
# that bypassed validation
|
||||
if board not in ESP8266_BOARD_BUILD:
|
||||
raise EsphomeError(f"Board '{board}' is not supported by the native toolchain")
|
||||
board_build = ESP8266_BOARD_BUILD[board]
|
||||
flash_ld_name = _flash_ld_name(board)
|
||||
@@ -801,7 +805,6 @@ def write_project(paths: InstalledPaths) -> bool:
|
||||
)
|
||||
|
||||
build_tool = Path(__file__).parent / "build_tool.py"
|
||||
ccache = ccache_path()
|
||||
|
||||
# $in/$out stay unquoted in the rule commands: ninja shell-escapes its
|
||||
# built-in path variables itself when expanding a command (POSIX and
|
||||
@@ -810,13 +813,16 @@ def write_project(paths: InstalledPaths) -> bool:
|
||||
lines = [
|
||||
"# Auto-generated by ESPHome",
|
||||
"ninja_required_version = 1.5",
|
||||
f"cc = {_q(toolchain_bin / 'xtensa-lx106-elf-gcc')}",
|
||||
f"cxx = {_q(toolchain_bin / 'xtensa-lx106-elf-g++')}",
|
||||
f"python = {_q(sys.executable)}",
|
||||
f"cc = {_q(toolchain_tool(paths.toolchain, 'gcc'))}",
|
||||
f"cxx = {_q(toolchain_tool(paths.toolchain, 'g++'))}",
|
||||
# The NSIS launcher starts Python with a \\?\ extended-length path
|
||||
# that cmd.exe cannot spawn; same strip every other emitted binary
|
||||
# path gets
|
||||
f"python = {_q(strip_win_long_path_prefix(sys.executable))}",
|
||||
f"buildtool = {_q(build_tool)}",
|
||||
f"ccache = {_q(ccache) if ccache else ''}",
|
||||
"",
|
||||
"rule cc",
|
||||
"rule c",
|
||||
" command = $ccache $cc -MMD -MF $out.d $cflags $flags -c $in -o $out",
|
||||
" depfile = $out.d",
|
||||
" deps = gcc",
|
||||
@@ -832,7 +838,7 @@ def write_project(paths: InstalledPaths) -> bool:
|
||||
" deps = gcc",
|
||||
" description = AS $out",
|
||||
"rule ar",
|
||||
f" command = $python $buildtool ar {_q(toolchain_bin / 'xtensa-lx106-elf-ar')} $out $out.rsp",
|
||||
f" command = $python $buildtool ar {_q(toolchain_tool(paths.toolchain, 'ar'))} $out $out.rsp",
|
||||
" rspfile = $out.rsp",
|
||||
" rspfile_content = $in_newline",
|
||||
" description = AR $out",
|
||||
@@ -938,18 +944,16 @@ def write_project(paths: InstalledPaths) -> bool:
|
||||
return write_file_if_changed(build_dir / "build.ninja", "\n".join(lines))
|
||||
|
||||
|
||||
def get_flash_ld_path(build_dir: Path) -> Path:
|
||||
"""The flash linker script the link actually uses (for size reporting)."""
|
||||
from esphome.arduino8266.framework import (
|
||||
framework_package_version,
|
||||
get_framework_path,
|
||||
)
|
||||
def get_flash_ld_path(build_dir: Path, paths: InstalledPaths) -> Path:
|
||||
"""The flash linker script the link actually uses (for size reporting).
|
||||
|
||||
Reads the same install the ninja file linked against instead of
|
||||
re-resolving the framework version.
|
||||
"""
|
||||
name = _active_flash_ld_name(_flash_ld_name(CORE.data[KEY_ESP8266][KEY_BOARD]))
|
||||
if CORE.testing_mode:
|
||||
return build_dir / "ld" / name
|
||||
version = framework_package_version(CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION])
|
||||
return get_framework_path(version) / "tools" / "sdk" / "ld" / name
|
||||
return paths.framework / "tools" / "sdk" / "ld" / name
|
||||
|
||||
|
||||
def _flash_size_str(flash_size: int) -> str:
|
||||
|
||||
@@ -69,13 +69,6 @@ def _shq(tok: str) -> str:
|
||||
return f'"{tok}"' if os.name == "nt" else f"'{tok}'"
|
||||
|
||||
|
||||
def test_rule_map_covers_all_source_suffixes() -> None:
|
||||
"""Every suffix a library manifest can select must map to a ninja rule."""
|
||||
from esphome.platformio.library import SRC_FILE_EXTENSIONS
|
||||
|
||||
assert set(arduino8266._RULE_FOR_SUFFIX) == set(SRC_FILE_EXTENSIONS)
|
||||
|
||||
|
||||
def _resolve(*flags: str):
|
||||
"""Set the build flags and resolve the knob config in one step."""
|
||||
_set_flags(*flags)
|
||||
@@ -221,9 +214,8 @@ def _write_ninja(
|
||||
"esphome.arduino.library.resolve_libraries",
|
||||
return_value=libraries or [],
|
||||
),
|
||||
patch("esphome.arduino8266.framework.ccache_path", return_value=ccache),
|
||||
):
|
||||
arduino8266.write_project(paths)
|
||||
arduino8266.write_project(paths, ccache)
|
||||
return (CORE.relative_pioenvs_path(CORE.name) / "build.ninja").read_text()
|
||||
|
||||
|
||||
@@ -299,7 +291,7 @@ def test_write_project_link_line_and_exclusions(tmp_path: Path) -> None:
|
||||
assert "core_esp8266_main.cpp.o" in content
|
||||
# Assembly and C sources compile through their own rules
|
||||
assert "cont.S.o: asm" in content
|
||||
assert "abi.c.o: cc" in content
|
||||
assert "abi.c.o: c" in content
|
||||
# throw_stubs is force-included for ESPHome sources only, via one shared
|
||||
# srcflags variable rather than a copy of the flags line per edge
|
||||
src_lines = [line for line in content.splitlines() if "obj/src/" in line]
|
||||
@@ -547,23 +539,19 @@ def test_write_project_libraries_and_variant(
|
||||
|
||||
def test_get_flash_ld_path(tmp_path: Path) -> None:
|
||||
|
||||
paths = InstalledPaths(
|
||||
framework=tmp_path / "framework",
|
||||
toolchain=tmp_path / "toolchain",
|
||||
ninja=Path("ninja"),
|
||||
)
|
||||
CORE.testing_mode = True
|
||||
assert get_flash_ld_path(tmp_path) == (
|
||||
assert get_flash_ld_path(tmp_path, paths) == (
|
||||
tmp_path / "ld" / "testing_eagle.flash.4m.ld"
|
||||
)
|
||||
|
||||
CORE.testing_mode = False
|
||||
with (
|
||||
patch(
|
||||
"esphome.arduino8266.framework.get_framework_path",
|
||||
return_value=tmp_path / "framework",
|
||||
),
|
||||
patch(
|
||||
"esphome.arduino8266.framework.framework_package_version",
|
||||
return_value="3.30102.0",
|
||||
),
|
||||
):
|
||||
assert get_flash_ld_path(tmp_path) == (
|
||||
# Reads the same install the ninja file linked against; no re-resolve
|
||||
assert get_flash_ld_path(tmp_path, paths) == (
|
||||
tmp_path / "framework" / "tools" / "sdk" / "ld" / "eagle.flash.4m.ld"
|
||||
)
|
||||
|
||||
@@ -756,8 +744,8 @@ def test_write_project_returns_changed(tmp_path: Path) -> None:
|
||||
patch("esphome.arduino.library.resolve_libraries", return_value=[]),
|
||||
patch("esphome.arduino8266.framework.ccache_path", return_value=None),
|
||||
):
|
||||
assert arduino8266.write_project(paths) is True
|
||||
assert arduino8266.write_project(paths) is False
|
||||
assert arduino8266.write_project(paths, None) is True
|
||||
assert arduino8266.write_project(paths, None) is False
|
||||
|
||||
|
||||
def test_write_project_missing_elf2bin_raises(tmp_path: Path) -> None:
|
||||
@@ -774,7 +762,7 @@ def test_write_project_missing_elf2bin_raises(tmp_path: Path) -> None:
|
||||
patch("esphome.arduino.library.resolve_libraries", return_value=[]),
|
||||
pytest.raises(EsphomeError, match="elf2bin"),
|
||||
):
|
||||
arduino8266.write_project(paths)
|
||||
arduino8266.write_project(paths, None)
|
||||
|
||||
|
||||
def test_write_project_missing_src_dir_raises(tmp_path: Path) -> None:
|
||||
@@ -789,7 +777,7 @@ def test_write_project_missing_src_dir_raises(tmp_path: Path) -> None:
|
||||
),
|
||||
pytest.raises(EsphomeError, match="source directory"),
|
||||
):
|
||||
arduino8266.write_project(paths)
|
||||
arduino8266.write_project(paths, None)
|
||||
|
||||
|
||||
def test_build_config_custom_mmu_without_knob_raises() -> None:
|
||||
|
||||
Reference in New Issue
Block a user