mirror of
https://github.com/esphome/esphome.git
synced 2026-10-02 09:20:22 +00:00
[nrf52] Add nRF Connect SDK 3.4.0 / Zephyr 4.x support (#19863)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
7e32770c5a
commit
5a3e95b161
@@ -20,6 +20,13 @@ __attribute__((weak)) void print_coredump() {}
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
// Zephyr 3.7 renamed z_arch_esf_t to struct arch_esf; the old name was later removed.
|
||||
#if KERNEL_VERSION_NUMBER >= 0x030700
|
||||
using FatalErrorEsf = ::arch_esf;
|
||||
#else
|
||||
using FatalErrorEsf = z_arch_esf_t;
|
||||
#endif
|
||||
|
||||
__attribute__((section(".noinit"))) struct {
|
||||
uint32_t magic;
|
||||
uint32_t reason;
|
||||
@@ -168,7 +175,7 @@ void Logger::dump_crash_() {
|
||||
}
|
||||
}
|
||||
|
||||
void k_sys_fatal_error_handler(unsigned int reason, const z_arch_esf_t *esf) {
|
||||
void k_sys_fatal_error_handler(unsigned int reason, const FatalErrorEsf *esf) {
|
||||
crash_buf.magic = App.get_config_hash();
|
||||
crash_buf.reason = reason;
|
||||
if (esf) {
|
||||
@@ -192,7 +199,7 @@ void k_sys_fatal_error_handler(unsigned int reason, const z_arch_esf_t *esf) {
|
||||
|
||||
extern "C" {
|
||||
|
||||
void k_sys_fatal_error_handler(unsigned int reason, const z_arch_esf_t *esf) {
|
||||
void k_sys_fatal_error_handler(unsigned int reason, const esphome::logger::FatalErrorEsf *esf) {
|
||||
esphome::logger::k_sys_fatal_error_handler(reason, esf);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,7 +438,8 @@ async def to_code(config: ConfigType) -> None:
|
||||
zephyr_add_prj_conf("SERIAL", False, False)
|
||||
|
||||
# disable stuff to make image smaller by default
|
||||
zephyr_add_prj_conf("NCS_BOOT_BANNER", False, False)
|
||||
if framework_ver >= cv.Version(2, 9, 2):
|
||||
zephyr_add_prj_conf("NCS_BOOT_BANNER", False, False)
|
||||
zephyr_add_prj_conf("BOOT_BANNER", False, False)
|
||||
zephyr_add_prj_conf("PRINTK", False, False)
|
||||
zephyr_add_prj_conf("CONSOLE", False, False)
|
||||
@@ -495,8 +496,8 @@ def get_download_types(storage_json: StorageJSON) -> list[dict[str, str]]:
|
||||
types = []
|
||||
UF2_PATH = "zephyr/zephyr.uf2"
|
||||
DFU_PATH = "firmware.zip"
|
||||
HEX_PATH = "zephyr/zephyr.hex" # SDK 2.6.1, only generated when OTA is disabled
|
||||
HEX_MERGED_PATH = "zephyr/merged.hex" # SDK 2.9.2, always generated
|
||||
HEX_PATH = "zephyr/zephyr.hex" # SDK 2.6.1 without OTA, SDK 3.4.0+
|
||||
HEX_MERGED_PATH = "zephyr/merged.hex" # SDK 2.9.2 to 3.3.x, always generated
|
||||
APP_IMAGE_PATH = "zephyr/app_update.bin"
|
||||
build_dir = Path(storage_json.firmware_bin_path).parent
|
||||
if (build_dir / UF2_PATH).is_file():
|
||||
@@ -968,6 +969,11 @@ def run_compile(args, config: ConfigType) -> bool:
|
||||
_LOGGER.info("Build inputs changed, cleaning %s", build_dir)
|
||||
rmtree(build_dir)
|
||||
|
||||
# SDK 3.4.0+ no longer generates merged.hex; drop one left by an older SDK
|
||||
# build so it is never packaged or offered for download.
|
||||
for stale_hex in (build_dir / "merged.hex", build_dir / "zephyr" / "merged.hex"):
|
||||
stale_hex.unlink(missing_ok=True)
|
||||
|
||||
if pch_on:
|
||||
pch.log_pch_in_use()
|
||||
# Zephyr turns ccache on by itself when it is installed
|
||||
@@ -1012,13 +1018,18 @@ def run_compile(args, config: ConfigType) -> bool:
|
||||
west_out = zephyr_dir / "zephyr"
|
||||
_copy_if_exists(west_out / "zephyr.uf2", zephyr_dir / "zephyr.uf2")
|
||||
_copy_if_exists(west_out / "zephyr.signed.bin", zephyr_dir / "app_update.bin")
|
||||
_copy_if_exists(west_out / "zephyr.hex", zephyr_dir / "zephyr.hex")
|
||||
_copy_if_exists(build_dir / "merged.hex", zephyr_dir / "merged.hex")
|
||||
|
||||
# For Adafruit bootloader builds, regenerate the UF2 from merged.hex,
|
||||
# whose records carry the correct flash addresses. The build's own
|
||||
# zephyr.uf2 uses the board's default offset, which is wrong in some cases.
|
||||
merged_hex = zephyr_dir / "merged.hex"
|
||||
if bootloader in _UF2_FAMILY_IDS and merged_hex.is_file():
|
||||
# For Adafruit bootloader builds, regenerate the UF2 from a hex file.
|
||||
# merged.hex carries the correct flash addresses; SDK 3.4.0+ no longer
|
||||
# generates it, so use zephyr.hex there. Chosen by version so a merged.hex
|
||||
# left by an older SDK build is never picked.
|
||||
if framework_ver >= cv.Version(3, 4, 0):
|
||||
hex_file = zephyr_dir / "zephyr.hex"
|
||||
else:
|
||||
hex_file = zephyr_dir / "merged.hex"
|
||||
if bootloader in _UF2_FAMILY_IDS and hex_file.is_file():
|
||||
# Drop the build's own wrong-offset UF2 so it isn't shipped alongside.
|
||||
app_uf2 = west_out / "zephyr.uf2"
|
||||
if app_uf2.is_file():
|
||||
@@ -1035,12 +1046,12 @@ def run_compile(args, config: ConfigType) -> bool:
|
||||
"-c",
|
||||
"-o",
|
||||
str(zephyr_dir / "zephyr.uf2"),
|
||||
str(merged_hex),
|
||||
str(hex_file),
|
||||
],
|
||||
env=env,
|
||||
stream_output=True,
|
||||
):
|
||||
raise EsphomeError("Failed to generate UF2 from merged hex")
|
||||
raise EsphomeError(f"Failed to generate UF2 from {hex_file.name}")
|
||||
|
||||
if bootloader in (
|
||||
BOOTLOADER_ADAFRUIT,
|
||||
@@ -1048,9 +1059,6 @@ def run_compile(args, config: ConfigType) -> bool:
|
||||
BOOTLOADER_ADAFRUIT_NRF52_SD140_V6,
|
||||
BOOTLOADER_ADAFRUIT_NRF52_SD140_V7,
|
||||
):
|
||||
# no fallback is needed for adafruit case. merged merged.hex is always generated.
|
||||
# get_download_types needs fallback for mcuboot (non adafruit)
|
||||
hex_file = zephyr_dir / "merged.hex"
|
||||
dfu_package = build_dir / "firmware.zip"
|
||||
genpkg_cmd = [
|
||||
str(paths["python_executable"]),
|
||||
|
||||
@@ -28,6 +28,26 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
_REQUIREMENTS = Path(__file__).parent / "requirements.txt"
|
||||
TOOLCHAIN_VERSION = "0.17.4"
|
||||
# Zephyr SDK used by nRF Connect SDK 3.4.0 and newer.
|
||||
_TOOLCHAIN_VERSION_NCS_3_4_0 = "1.0.1"
|
||||
_TOOLCHAIN_VERSIONS = (TOOLCHAIN_VERSION, _TOOLCHAIN_VERSION_NCS_3_4_0)
|
||||
|
||||
|
||||
def _uses_sdk_ng_1_toolchain() -> bool:
|
||||
"""True when the framework needs Zephyr SDK 1.0+.
|
||||
|
||||
SDK 1.0 moved the GNU toolchain under gnu/ and renamed its archive
|
||||
to toolchain_gnu_*.
|
||||
"""
|
||||
return CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version(3, 4, 0)
|
||||
|
||||
|
||||
def _get_toolchain_version() -> str:
|
||||
"""Return the Zephyr SDK toolchain version for the current framework."""
|
||||
if _uses_sdk_ng_1_toolchain():
|
||||
return _TOOLCHAIN_VERSION_NCS_3_4_0
|
||||
return TOOLCHAIN_VERSION
|
||||
|
||||
|
||||
# Packages the PlatformIO toolchain's Zephyr build script needs beyond west
|
||||
# (which comes from requirements.txt). Keep the pin in sync with
|
||||
@@ -40,6 +60,20 @@ SDK_NG_TOOLCHAIN_MIRRORS = str_to_lst_of_str(
|
||||
"https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v{VERSION}/toolchain_{sysname}-{machine}_arm-zephyr-eabi.{extension}",
|
||||
)
|
||||
)
|
||||
_SDK_NG_TOOLCHAIN_GNU_MIRRORS = str_to_lst_of_str(
|
||||
os.environ.get(
|
||||
"ESPHOME_SDK_NG_TOOLCHAIN_GNU_MIRRORS",
|
||||
"https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v{VERSION}/toolchain_gnu_{sysname}-{machine}_arm-zephyr-eabi.{extension}",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _get_sdk_ng_toolchain_mirrors() -> list[str]:
|
||||
"""Return toolchain mirror URLs for the current framework version."""
|
||||
if _uses_sdk_ng_1_toolchain():
|
||||
return _SDK_NG_TOOLCHAIN_GNU_MIRRORS
|
||||
return SDK_NG_TOOLCHAIN_MIRRORS
|
||||
|
||||
|
||||
# Minimal SDK provides cmake discovery files (Zephyr-sdkConfig.cmake) and
|
||||
# host tools (dtc etc.) required by the Zephyr cmake build system.
|
||||
@@ -85,15 +119,22 @@ def _get_toolchain_path(version: str) -> Path:
|
||||
return get_sdk_nrf_tools_path() / "toolchains" / version
|
||||
|
||||
|
||||
def toolchain_tool(name: str) -> Path:
|
||||
"""Path to one of the pinned Zephyr SDK's tools (objdump, readelf, ...).
|
||||
def _get_arm_toolchain_path() -> Path:
|
||||
"""The arm-zephyr-eabi directory inside the pinned Zephyr SDK.
|
||||
|
||||
The single owner of the ``arm-zephyr-eabi/bin/arm-zephyr-eabi-<name>``
|
||||
layout and the Windows suffix.
|
||||
The single owner of the SDK 0.x (arm-zephyr-eabi/) and SDK 1.0+
|
||||
(gnu/arm-zephyr-eabi/) layouts.
|
||||
"""
|
||||
toolchain_root = _get_toolchain_path(_get_toolchain_version())
|
||||
if _uses_sdk_ng_1_toolchain():
|
||||
return toolchain_root / "gnu" / "arm-zephyr-eabi"
|
||||
return toolchain_root / "arm-zephyr-eabi"
|
||||
|
||||
|
||||
def toolchain_tool(name: str) -> Path:
|
||||
"""Path to one of the pinned Zephyr SDK's tools (objdump, readelf, ...)."""
|
||||
suffix = ".exe" if os.name == "nt" else ""
|
||||
bin_path = _get_toolchain_path(TOOLCHAIN_VERSION) / "arm-zephyr-eabi" / "bin"
|
||||
return bin_path / f"arm-zephyr-eabi-{name}{suffix}"
|
||||
return _get_arm_toolchain_path() / "bin" / f"arm-zephyr-eabi-{name}{suffix}"
|
||||
|
||||
|
||||
_SITECUSTOMIZE = """\
|
||||
@@ -169,7 +210,7 @@ def get_build_env() -> dict:
|
||||
# "Zephyr-sdk_DIR" environment hint proved unreliable here: containerized
|
||||
# non-root builds failed to locate the SDK with it, while
|
||||
# ZEPHYR_SDK_INSTALL_DIR fixed the same invocation.
|
||||
env["ZEPHYR_SDK_INSTALL_DIR"] = str(_get_toolchain_path(TOOLCHAIN_VERSION))
|
||||
env["ZEPHYR_SDK_INSTALL_DIR"] = str(_get_toolchain_path(_get_toolchain_version()))
|
||||
return env
|
||||
|
||||
|
||||
@@ -561,36 +602,41 @@ def _check_and_install(version: str) -> None:
|
||||
raise EsphomeError(f"Install Zephyr requirements for {version} failure")
|
||||
zephyr_sentinel.touch()
|
||||
|
||||
# Shared by every SDK version; locked only while missing
|
||||
if not (_get_toolchain_path(TOOLCHAIN_VERSION) / ".ready").exists():
|
||||
with _install_lock(f"toolchain-{TOOLCHAIN_VERSION}"):
|
||||
# Shared by every SDK version that uses the same toolchain; locked only
|
||||
# while missing
|
||||
toolchain_version = _get_toolchain_version()
|
||||
if not (_get_toolchain_path(toolchain_version) / ".ready").exists():
|
||||
with _install_lock(f"toolchain-{toolchain_version}"):
|
||||
_install_toolchain()
|
||||
|
||||
|
||||
def _install_toolchain() -> None:
|
||||
toolchains_dir = _get_toolchain_path(TOOLCHAIN_VERSION)
|
||||
toolchain_version = _get_toolchain_version()
|
||||
toolchains_dir = _get_toolchain_path(toolchain_version)
|
||||
sentinel = toolchains_dir / ".ready"
|
||||
if not sentinel.exists():
|
||||
rmdir(toolchains_dir, msg=f"Clean up {TOOLCHAIN_VERSION} toolchain environment")
|
||||
rmdir(toolchains_dir, msg=f"Clean up {toolchain_version} toolchain environment")
|
||||
sysname, machine, extension = _get_toolchain_platform_info()
|
||||
substitutions = {
|
||||
"VERSION": TOOLCHAIN_VERSION,
|
||||
"VERSION": toolchain_version,
|
||||
"sysname": sysname,
|
||||
"machine": machine,
|
||||
"extension": extension,
|
||||
}
|
||||
# Downloaded next to the destination (not a temp file) so an
|
||||
# interrupted download's .part file resumes on the next run.
|
||||
# SDK 1.0+ Zephyr-sdkConfig.cmake looks for the toolchain in
|
||||
# gnu/arm-zephyr-eabi/; extraction strips the archive's single root.
|
||||
for mirrors, extract_dir, what, slug in (
|
||||
(SDK_NG_MINIMAL_MIRRORS, toolchains_dir, "Zephyr SDK minimal", "minimal"),
|
||||
(
|
||||
SDK_NG_TOOLCHAIN_MIRRORS,
|
||||
toolchains_dir / "arm-zephyr-eabi",
|
||||
_get_sdk_ng_toolchain_mirrors(),
|
||||
_get_arm_toolchain_path(),
|
||||
"toolchain",
|
||||
"toolchain",
|
||||
),
|
||||
):
|
||||
_LOGGER.info("Downloading %s %s ...", TOOLCHAIN_VERSION, what)
|
||||
_LOGGER.info("Downloading %s %s ...", toolchain_version, what)
|
||||
download_and_extract(
|
||||
mirrors,
|
||||
substitutions,
|
||||
@@ -598,10 +644,17 @@ def _install_toolchain() -> None:
|
||||
extract_dir,
|
||||
progress_header="Extracting",
|
||||
)
|
||||
# Best-effort prune of resume leftovers, including a previous
|
||||
# TOOLCHAIN_VERSION's orphans; the SDK archives are hundreds of MB.
|
||||
# A locked file must not discard the just-completed install.
|
||||
# Best-effort prune of resume leftovers, including orphans of retired
|
||||
# toolchain versions; the SDK archives are hundreds of MB. The other
|
||||
# toolchain still in use may be downloading under its own lock, so its
|
||||
# leftovers are kept. A locked file must not discard the just-completed
|
||||
# install.
|
||||
other_versions = tuple(
|
||||
f"{v}." for v in _TOOLCHAIN_VERSIONS if v != toolchain_version
|
||||
)
|
||||
for leftover in toolchains_dir.parent.glob("*.archive.part*"):
|
||||
if leftover.name.startswith(other_versions):
|
||||
continue
|
||||
try:
|
||||
leftover.unlink()
|
||||
except OSError as err:
|
||||
|
||||
@@ -7,6 +7,7 @@ import pytest
|
||||
|
||||
from esphome.components import nrf52
|
||||
from esphome.components.zephyr.const import KEY_BOARD
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import KEY_CORE, KEY_FRAMEWORK_VERSION, Toolchain
|
||||
from esphome.core import CORE, EsphomeError
|
||||
|
||||
@@ -110,6 +111,7 @@ def run_cmd(tmp_path: Path) -> Mock:
|
||||
CORE.build_path = tmp_path / "build"
|
||||
CORE.name = "livingroom"
|
||||
CORE.toolchain = Toolchain.SDK_NRF
|
||||
CORE.data[KEY_CORE] = {KEY_FRAMEWORK_VERSION: cv.Version(3, 2, 0)}
|
||||
with (
|
||||
patch.object(nrf52, "check_and_install"),
|
||||
patch.object(nrf52, "_generate_cmake_lists", return_value=False),
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
"""nrf52 sdk-nrf build: the hex file used for the Adafruit UF2 and DFU package."""
|
||||
|
||||
from collections.abc import Iterator
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.components import nrf52
|
||||
from esphome.components.nrf52.const import BOOTLOADER_ADAFRUIT_NRF52_SD140_V7
|
||||
from esphome.components.zephyr.const import KEY_BOARD, KEY_BOOTLOADER
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import KEY_CORE, KEY_FRAMEWORK_VERSION, Toolchain
|
||||
from esphome.core import CORE
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def build_dir(tmp_path: Path) -> Path:
|
||||
CORE.config_path = tmp_path / "test.yaml"
|
||||
CORE.build_path = tmp_path / "build"
|
||||
CORE.name = "livingroom"
|
||||
CORE.toolchain = Toolchain.SDK_NRF
|
||||
return CORE.relative_pioenvs_path(CORE.name)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def run_cmd(tmp_path: Path) -> Iterator[Mock]:
|
||||
with (
|
||||
patch.object(nrf52, "check_and_install"),
|
||||
patch.object(nrf52, "_generate_cmake_lists", return_value=False),
|
||||
patch.object(
|
||||
nrf52,
|
||||
"get_build_paths",
|
||||
return_value={"python_executable": "python3", "framework_path": tmp_path},
|
||||
),
|
||||
patch.object(nrf52, "get_build_env", return_value={}),
|
||||
patch.object(
|
||||
nrf52,
|
||||
"zephyr_data",
|
||||
return_value={
|
||||
KEY_BOARD: "board",
|
||||
KEY_BOOTLOADER: BOOTLOADER_ADAFRUIT_NRF52_SD140_V7,
|
||||
},
|
||||
),
|
||||
patch.object(nrf52.pch, "pch_enabled", return_value=False),
|
||||
patch.object(nrf52, "run_command_ok", return_value=True) as run,
|
||||
):
|
||||
yield run
|
||||
|
||||
|
||||
def _uf2_hex(run_cmd: Mock) -> str:
|
||||
cmd = next(c.args[0] for c in run_cmd.call_args_list if "-f" in c.args[0])
|
||||
return cmd[-1]
|
||||
|
||||
|
||||
def _dfu_hex(run_cmd: Mock) -> str:
|
||||
cmd = next(c.args[0] for c in run_cmd.call_args_list if "genpkg" in c.args[0])
|
||||
return cmd[cmd.index("--application") + 1]
|
||||
|
||||
|
||||
def _build(
|
||||
build_dir: Path,
|
||||
version: cv.Version,
|
||||
outputs: list[str],
|
||||
run_cmd: Mock,
|
||||
leftovers: tuple[str, ...] = (),
|
||||
) -> None:
|
||||
"""Run run_compile; *outputs* appear when west builds, *leftovers* before."""
|
||||
CORE.data[KEY_CORE] = {KEY_FRAMEWORK_VERSION: version}
|
||||
(build_dir / "zephyr" / "zephyr").mkdir(parents=True)
|
||||
# Without a CMake cache run_compile wipes the build dir first
|
||||
for name in ["CMakeCache.txt", *leftovers]:
|
||||
(build_dir / name).write_text("")
|
||||
|
||||
def west_build(cmd: list[str], **kwargs: object) -> bool:
|
||||
if "west" in cmd:
|
||||
for name in outputs:
|
||||
(build_dir / name).write_text("")
|
||||
return True
|
||||
|
||||
run_cmd.side_effect = west_build
|
||||
nrf52.run_compile(None, {})
|
||||
|
||||
|
||||
def test_uf2_and_dfu_use_merged_hex(build_dir: Path, run_cmd: Mock) -> None:
|
||||
_build(
|
||||
build_dir,
|
||||
cv.Version(2, 9, 2),
|
||||
["merged.hex", "zephyr/zephyr/zephyr.hex"],
|
||||
run_cmd,
|
||||
)
|
||||
merged = str(build_dir / "zephyr" / "merged.hex")
|
||||
assert _uf2_hex(run_cmd) == merged
|
||||
assert _dfu_hex(run_cmd) == merged
|
||||
|
||||
|
||||
def test_uf2_and_dfu_fall_back_to_zephyr_hex(build_dir: Path, run_cmd: Mock) -> None:
|
||||
"""The nRF Connect SDK 3.4.0 build produces no merged.hex."""
|
||||
_build(build_dir, cv.Version(3, 4, 0), ["zephyr/zephyr/zephyr.hex"], run_cmd)
|
||||
app_hex = build_dir / "zephyr" / "zephyr.hex"
|
||||
assert app_hex.is_file()
|
||||
assert _uf2_hex(run_cmd) == str(app_hex)
|
||||
assert _dfu_hex(run_cmd) == str(app_hex)
|
||||
|
||||
|
||||
def test_older_sdk_does_not_fall_back_to_zephyr_hex(
|
||||
build_dir: Path, run_cmd: Mock
|
||||
) -> None:
|
||||
"""Before SDK 3.4.0 a missing merged.hex must not be replaced by zephyr.hex."""
|
||||
_build(build_dir, cv.Version(3, 2, 0), ["zephyr/zephyr/zephyr.hex"], run_cmd)
|
||||
assert not any("-f" in c.args[0] for c in run_cmd.call_args_list)
|
||||
assert _dfu_hex(run_cmd) == str(build_dir / "zephyr" / "merged.hex")
|
||||
|
||||
|
||||
def test_stale_merged_hex_is_removed_before_building(
|
||||
build_dir: Path, run_cmd: Mock
|
||||
) -> None:
|
||||
"""A merged.hex left by an older SDK build must not be packaged."""
|
||||
_build(
|
||||
build_dir,
|
||||
cv.Version(3, 4, 0),
|
||||
["zephyr/zephyr/zephyr.hex"],
|
||||
run_cmd,
|
||||
leftovers=("merged.hex", "zephyr/merged.hex"),
|
||||
)
|
||||
assert not (build_dir / "merged.hex").exists()
|
||||
assert not (build_dir / "zephyr" / "merged.hex").exists()
|
||||
app_hex = str(build_dir / "zephyr" / "zephyr.hex")
|
||||
assert _uf2_hex(run_cmd) == app_hex
|
||||
assert _dfu_hex(run_cmd) == app_hex
|
||||
@@ -20,6 +20,7 @@ from esphome.components.nrf52.framework import (
|
||||
_get_penv_site_packages,
|
||||
_get_platformio_penv_path,
|
||||
_get_toolchain_platform_info,
|
||||
_install_toolchain,
|
||||
_needs_venv_rebuild,
|
||||
_wanted_west_projects,
|
||||
check_and_install,
|
||||
@@ -808,6 +809,63 @@ class TestCheckAndInstall:
|
||||
assert substitutions["machine"] == "x86_64"
|
||||
assert substitutions["extension"] == "tar.xz"
|
||||
|
||||
def test_toolchain_download_uses_gnu_url_for_sdk_3_4_0(
|
||||
self,
|
||||
tmp_path: Path,
|
||||
mock_nrf52_ops: SimpleNamespace,
|
||||
) -> None:
|
||||
"""For nRF Connect SDK >= 3.4.0 the toolchain archive name includes 'toolchain_gnu_'."""
|
||||
CORE.data[KEY_CORE] = {KEY_FRAMEWORK_VERSION: Version.parse("3.4.0")}
|
||||
sdk_version = "3.4.0"
|
||||
tools = get_sdk_nrf_tools_path()
|
||||
python_env = tools / "penvs" / f"v{sdk_version}"
|
||||
framework = tools / "frameworks" / f"v{sdk_version}"
|
||||
toolchain_dir = tools / "toolchains" / "1.0.1"
|
||||
for d in (python_env, framework, toolchain_dir):
|
||||
d.mkdir(parents=True, exist_ok=True)
|
||||
(framework / "zephyr" / "scripts").mkdir(parents=True, exist_ok=True)
|
||||
(framework / "zephyr" / "scripts" / "requirements.txt").touch()
|
||||
_mark_venv_ready(python_env)
|
||||
(framework / ".ready").touch()
|
||||
|
||||
check_and_install()
|
||||
|
||||
# Two download calls: minimal SDK first, toolchain second
|
||||
toolchain_call = mock_nrf52_ops.download_from_mirrors.call_args_list[1]
|
||||
mirrors = toolchain_call.args[0]
|
||||
assert all("toolchain_gnu_" in m for m in mirrors)
|
||||
|
||||
def test_toolchain_extracts_under_gnu_for_sdk_3_4_0(
|
||||
self,
|
||||
tmp_path: Path,
|
||||
mock_nrf52_ops: SimpleNamespace,
|
||||
) -> None:
|
||||
"""SDK 1.0+ toolchain archive must land in gnu/arm-zephyr-eabi/.
|
||||
|
||||
Zephyr-sdkConfig.cmake validates the toolchain at gnu/arm-zephyr-eabi/
|
||||
in SDK 1.0+; if the archive is extracted to arm-zephyr-eabi/ instead,
|
||||
cmake reports the package as not found.
|
||||
"""
|
||||
CORE.data[KEY_CORE] = {KEY_FRAMEWORK_VERSION: Version.parse("3.4.0")}
|
||||
sdk_version = "3.4.0"
|
||||
tools = get_sdk_nrf_tools_path()
|
||||
python_env = tools / "penvs" / f"v{sdk_version}"
|
||||
framework = tools / "frameworks" / f"v{sdk_version}"
|
||||
toolchain_dir = tools / "toolchains" / "1.0.1"
|
||||
for d in (python_env, framework, toolchain_dir):
|
||||
d.mkdir(parents=True, exist_ok=True)
|
||||
(framework / "zephyr" / "scripts").mkdir(parents=True, exist_ok=True)
|
||||
(framework / "zephyr" / "scripts" / "requirements.txt").touch()
|
||||
_mark_venv_ready(python_env)
|
||||
(framework / ".ready").touch()
|
||||
|
||||
check_and_install()
|
||||
|
||||
# Two extract calls: minimal SDK first (to toolchain root), toolchain second
|
||||
extract_calls = mock_nrf52_ops.archive_extract_all.call_args_list
|
||||
_, toolchain_extract_dir = extract_calls[1].args[:2]
|
||||
assert toolchain_extract_dir == toolchain_dir / "gnu" / "arm-zephyr-eabi"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# setup_platformio_python_env tests
|
||||
@@ -1040,6 +1098,44 @@ def test_get_build_env(
|
||||
assert env["SOME_PREEXISTING_VAR"] == "kept"
|
||||
|
||||
|
||||
def test_get_build_env_sdk_3_4_0_uses_toolchain_root(
|
||||
setup_core: Path,
|
||||
) -> None:
|
||||
"""For NCS >= 3.4.0, ZEPHYR_SDK_INSTALL_DIR still points at the toolchain root."""
|
||||
CORE.data[KEY_CORE] = {KEY_FRAMEWORK_VERSION: Version.parse("3.4.0")}
|
||||
|
||||
env = get_build_env()
|
||||
|
||||
tools = get_sdk_nrf_tools_path()
|
||||
assert env["ZEPHYR_SDK_INSTALL_DIR"] == str(tools / "toolchains" / "1.0.1")
|
||||
assert "Zephyr-sdk_DIR" not in env
|
||||
|
||||
|
||||
def test_install_toolchain_keeps_other_live_toolchain_leftovers(
|
||||
setup_core: Path,
|
||||
) -> None:
|
||||
"""Pruning must not delete a partial download of the other toolchain in use."""
|
||||
CORE.data[KEY_CORE] = {KEY_FRAMEWORK_VERSION: Version.parse("3.4.0")}
|
||||
toolchains = get_sdk_nrf_tools_path() / "toolchains"
|
||||
toolchains.mkdir(parents=True)
|
||||
own = toolchains / "1.0.1.toolchain.archive.part"
|
||||
other = toolchains / f"{TOOLCHAIN_VERSION}.toolchain.archive.part"
|
||||
retired = toolchains / "0.16.8.toolchain.archive.part"
|
||||
for leftover in (own, other, retired):
|
||||
leftover.write_text("")
|
||||
|
||||
with patch(
|
||||
"esphome.components.nrf52.framework.download_and_extract",
|
||||
side_effect=lambda *args, **kwargs: args[3].mkdir(parents=True),
|
||||
):
|
||||
_install_toolchain()
|
||||
|
||||
assert not own.exists()
|
||||
assert other.exists()
|
||||
assert not retired.exists()
|
||||
assert (toolchains / "1.0.1" / ".ready").exists()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# get_sdk_nrf_tools_path tests
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -24,6 +24,7 @@ def _set_sdk_version(version: cv.Version) -> None:
|
||||
|
||||
|
||||
def test_binutils_come_from_the_pinned_zephyr_sdk() -> None:
|
||||
_set_sdk_version(cv.Version(2, 9, 0))
|
||||
bin_path = (
|
||||
get_sdk_nrf_tools_path()
|
||||
/ "toolchains"
|
||||
@@ -37,6 +38,21 @@ def test_binutils_come_from_the_pinned_zephyr_sdk() -> None:
|
||||
assert toolchain.get_readelf_path() == bin_path / f"arm-zephyr-eabi-readelf{suffix}"
|
||||
|
||||
|
||||
def test_binutils_use_new_toolchain_for_sdk_3_4_0() -> None:
|
||||
_set_sdk_version(cv.Version(3, 4, 0))
|
||||
bin_path = (
|
||||
get_sdk_nrf_tools_path()
|
||||
/ "toolchains"
|
||||
/ "1.0.1"
|
||||
/ "gnu"
|
||||
/ "arm-zephyr-eabi"
|
||||
/ "bin"
|
||||
)
|
||||
suffix = ".exe" if os.name == "nt" else ""
|
||||
assert toolchain.get_objdump_path() == bin_path / f"arm-zephyr-eabi-objdump{suffix}"
|
||||
assert toolchain.get_readelf_path() == bin_path / f"arm-zephyr-eabi-readelf{suffix}"
|
||||
|
||||
|
||||
def test_elf_uses_the_nested_layout_from_sdk_2_9_2(nrf52_build: Path) -> None:
|
||||
_set_sdk_version(cv.Version(2, 9, 2))
|
||||
# A flat image left by an older SDK must not be picked over the current
|
||||
|
||||
Reference in New Issue
Block a user