mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 11:07:33 +00:00
[nrf52] add support for native builds (#16898)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
This commit is contained in:
@@ -136,6 +136,54 @@ def test_get_project_cmakelists_full_emits_builtin_components_property(
|
||||
assert "JPEGDEC APPEND" not in content
|
||||
|
||||
|
||||
def test_get_component_cmakelists_no_link_flags() -> None:
|
||||
"""With no -Wl, flags the target_link_options block is emitted with an empty body."""
|
||||
CORE.build_flags = set()
|
||||
from esphome.build_gen.espidf import get_component_cmakelists
|
||||
|
||||
content = get_component_cmakelists()
|
||||
assert "target_link_options(${COMPONENT_LIB} PUBLIC\n \n)" in content
|
||||
|
||||
|
||||
def test_get_component_cmakelists_single_link_flag() -> None:
|
||||
"""A single -Wl, flag appears indented inside target_link_options."""
|
||||
CORE.build_flags = {"-Wl,--gc-sections"}
|
||||
from esphome.build_gen.espidf import get_component_cmakelists
|
||||
|
||||
content = get_component_cmakelists()
|
||||
assert (
|
||||
"target_link_options(${COMPONENT_LIB} PUBLIC\n -Wl,--gc-sections\n)"
|
||||
in content
|
||||
)
|
||||
|
||||
|
||||
def test_get_component_cmakelists_multiple_link_flags_sorted() -> None:
|
||||
"""Multiple -Wl, flags are sorted and joined with the four-space indent."""
|
||||
CORE.build_flags = {"-Wl,-z,noexecstack", "-Wl,--gc-sections", "-Wl,-Map=out.map"}
|
||||
from esphome.build_gen.espidf import get_component_cmakelists
|
||||
|
||||
content = get_component_cmakelists()
|
||||
expected = (
|
||||
"target_link_options(${COMPONENT_LIB} PUBLIC\n"
|
||||
" -Wl,--gc-sections\n"
|
||||
" -Wl,-Map=out.map\n"
|
||||
" -Wl,-z,noexecstack\n"
|
||||
")"
|
||||
)
|
||||
assert expected in content
|
||||
|
||||
|
||||
def test_get_component_cmakelists_compile_flags_excluded_from_link_opts() -> None:
|
||||
"""-D and -W (non-linker) flags must not appear in target_link_options."""
|
||||
CORE.build_flags = {"-DFOO", "-Wall", "-Wl,--gc-sections"}
|
||||
from esphome.build_gen.espidf import get_component_cmakelists
|
||||
|
||||
content = get_component_cmakelists()
|
||||
assert "-DFOO" not in content.split("target_link_options")[1]
|
||||
assert "-Wall" not in content.split("target_link_options")[1]
|
||||
assert "-Wl,--gc-sections" in content
|
||||
|
||||
|
||||
def test_get_project_cmakelists_emits_managed_components_property(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
|
||||
@@ -25,6 +25,8 @@ from esphome.framework_helpers import (
|
||||
archive_extract_all,
|
||||
create_venv,
|
||||
download_from_mirrors,
|
||||
get_project_compile_flags,
|
||||
get_project_link_flags,
|
||||
get_python_env_executable_path,
|
||||
get_system_python_path,
|
||||
rmdir,
|
||||
@@ -952,3 +954,84 @@ class TestSevenZipExtractAll:
|
||||
out.mkdir()
|
||||
archive_extract_all(archive, out)
|
||||
assert (out / "hello.txt").exists()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# get_project_compile_flags / get_project_link_flags
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _make_core(flags: set[str]):
|
||||
core = MagicMock()
|
||||
core.build_flags = flags
|
||||
return core
|
||||
|
||||
|
||||
class TestGetProjectCompileFlags:
|
||||
def test_returns_define_flags(self) -> None:
|
||||
with patch("esphome.core.CORE", _make_core({"-DFOO", "-DBAR=1"})):
|
||||
assert get_project_compile_flags() == ["-DBAR=1", "-DFOO"]
|
||||
|
||||
def test_returns_warning_flags(self) -> None:
|
||||
with patch(
|
||||
"esphome.core.CORE",
|
||||
_make_core({"-Wno-error", "-Wall"}),
|
||||
):
|
||||
assert get_project_compile_flags() == ["-Wall", "-Wno-error"]
|
||||
|
||||
def test_excludes_linker_flags(self) -> None:
|
||||
with patch(
|
||||
"esphome.core.CORE",
|
||||
_make_core({"-DFOO", "-Wl,--gc-sections", "-Wl,-Map=output.map"}),
|
||||
):
|
||||
assert get_project_compile_flags() == ["-DFOO"]
|
||||
|
||||
def test_excludes_other_flags(self) -> None:
|
||||
with patch(
|
||||
"esphome.core.CORE",
|
||||
_make_core({"-O2", "-std=gnu++20", "-DFOO"}),
|
||||
):
|
||||
assert get_project_compile_flags() == ["-DFOO"]
|
||||
|
||||
def test_empty_build_flags(self) -> None:
|
||||
with patch("esphome.core.CORE", _make_core(set())):
|
||||
assert get_project_compile_flags() == []
|
||||
|
||||
def test_result_is_sorted(self) -> None:
|
||||
with patch(
|
||||
"esphome.core.CORE",
|
||||
_make_core({"-DZFLAG", "-DAFLAG", "-Wno-unused"}),
|
||||
):
|
||||
result = get_project_compile_flags()
|
||||
assert result == sorted(result)
|
||||
|
||||
|
||||
class TestGetProjectLinkFlags:
|
||||
def test_returns_linker_flags(self) -> None:
|
||||
with patch(
|
||||
"esphome.core.CORE",
|
||||
_make_core({"-Wl,--gc-sections", "-Wl,-Map=output.map"}),
|
||||
):
|
||||
assert get_project_link_flags() == [
|
||||
"-Wl,--gc-sections",
|
||||
"-Wl,-Map=output.map",
|
||||
]
|
||||
|
||||
def test_excludes_compile_flags(self) -> None:
|
||||
with patch(
|
||||
"esphome.core.CORE",
|
||||
_make_core({"-DFOO", "-Wall", "-Wl,--gc-sections"}),
|
||||
):
|
||||
assert get_project_link_flags() == ["-Wl,--gc-sections"]
|
||||
|
||||
def test_empty_build_flags(self) -> None:
|
||||
with patch("esphome.core.CORE", _make_core(set())):
|
||||
assert get_project_link_flags() == []
|
||||
|
||||
def test_result_is_sorted(self) -> None:
|
||||
with patch(
|
||||
"esphome.core.CORE",
|
||||
_make_core({"-Wl,-z", "-Wl,-a", "-Wl,-m"}),
|
||||
):
|
||||
result = get_project_link_flags()
|
||||
assert result == sorted(result)
|
||||
|
||||
@@ -58,6 +58,9 @@ def nrf52_dirs(setup_core: Path) -> SimpleNamespace:
|
||||
toolchain_dir = tools / "toolchains" / _TOOLCHAIN_VERSION
|
||||
for d in (python_env, framework, toolchain_dir):
|
||||
d.mkdir(parents=True, exist_ok=True)
|
||||
zephyr_scripts = framework / "zephyr" / "scripts"
|
||||
zephyr_scripts.mkdir(parents=True, exist_ok=True)
|
||||
(zephyr_scripts / "requirements.txt").touch()
|
||||
return SimpleNamespace(
|
||||
python_env=python_env,
|
||||
framework=framework,
|
||||
@@ -102,6 +105,7 @@ class TestCheckAndInstall:
|
||||
) -> None:
|
||||
"""All three sentinels present → nothing downloaded or compiled."""
|
||||
(nrf52_dirs.python_env / ".ready").touch()
|
||||
(nrf52_dirs.python_env / ".zephyr_reqs_ready").touch()
|
||||
(nrf52_dirs.framework / ".ready").touch()
|
||||
(nrf52_dirs.toolchain / ".ready").touch()
|
||||
|
||||
@@ -121,11 +125,13 @@ class TestCheckAndInstall:
|
||||
check_and_install()
|
||||
|
||||
mock_nrf52_ops.create_venv.assert_called_once()
|
||||
# pip install west, west init, west update
|
||||
assert mock_nrf52_ops.run_command_ok.call_count == 3
|
||||
mock_nrf52_ops.download_from_mirrors.assert_called_once()
|
||||
mock_nrf52_ops.archive_extract_all.assert_called_once()
|
||||
# pip install requirements, west init, west update, pip install zephyr reqs
|
||||
assert mock_nrf52_ops.run_command_ok.call_count == 4
|
||||
# minimal SDK + per-arch toolchain
|
||||
assert mock_nrf52_ops.download_from_mirrors.call_count == 2
|
||||
assert mock_nrf52_ops.archive_extract_all.call_count == 2
|
||||
assert (nrf52_dirs.python_env / ".ready").exists()
|
||||
assert (nrf52_dirs.python_env / ".zephyr_reqs_ready").exists()
|
||||
assert (nrf52_dirs.framework / ".ready").exists()
|
||||
assert (nrf52_dirs.toolchain / ".ready").exists()
|
||||
|
||||
@@ -140,9 +146,10 @@ class TestCheckAndInstall:
|
||||
check_and_install()
|
||||
|
||||
mock_nrf52_ops.create_venv.assert_not_called()
|
||||
# west init + west update only (no pip install)
|
||||
assert mock_nrf52_ops.run_command_ok.call_count == 2
|
||||
mock_nrf52_ops.download_from_mirrors.assert_called_once()
|
||||
# west init, west update, pip install zephyr reqs
|
||||
assert mock_nrf52_ops.run_command_ok.call_count == 3
|
||||
# minimal SDK + per-arch toolchain
|
||||
assert mock_nrf52_ops.download_from_mirrors.call_count == 2
|
||||
|
||||
def test_toolchain_only_missing(
|
||||
self,
|
||||
@@ -151,24 +158,26 @@ class TestCheckAndInstall:
|
||||
) -> None:
|
||||
"""Venv and framework ready → only toolchain downloaded and extracted."""
|
||||
(nrf52_dirs.python_env / ".ready").touch()
|
||||
(nrf52_dirs.python_env / ".zephyr_reqs_ready").touch()
|
||||
(nrf52_dirs.framework / ".ready").touch()
|
||||
|
||||
check_and_install()
|
||||
|
||||
mock_nrf52_ops.create_venv.assert_not_called()
|
||||
mock_nrf52_ops.run_command_ok.assert_not_called()
|
||||
mock_nrf52_ops.download_from_mirrors.assert_called_once()
|
||||
mock_nrf52_ops.archive_extract_all.assert_called_once()
|
||||
# minimal SDK + per-arch toolchain
|
||||
assert mock_nrf52_ops.download_from_mirrors.call_count == 2
|
||||
assert mock_nrf52_ops.archive_extract_all.call_count == 2
|
||||
|
||||
def test_west_install_failure_raises(
|
||||
def test_requirements_install_failure_raises(
|
||||
self,
|
||||
nrf52_dirs: SimpleNamespace,
|
||||
mock_nrf52_ops: SimpleNamespace,
|
||||
) -> None:
|
||||
"""Failing pip install west raises EsphomeError."""
|
||||
"""Failing pip install -r requirements.txt raises EsphomeError."""
|
||||
mock_nrf52_ops.run_command_ok.return_value = False
|
||||
|
||||
with pytest.raises(EsphomeError, match="Install west"):
|
||||
with pytest.raises(EsphomeError, match="Install requirements"):
|
||||
check_and_install()
|
||||
|
||||
def test_framework_init_failure_raises(
|
||||
|
||||
Reference in New Issue
Block a user