mirror of
https://github.com/esphome/esphome.git
synced 2026-09-11 15:27:33 +00:00
[cli] Accept basenames in prebuilt idedata.json
The dashboard's source-routed runner ships idedata.json with bare basenames in prog_path and extra.flash_images[*].path (the receiver's build-host absolute paths don't resolve on the offloader, and the in-memory Web Serial consumer keys by basename). Without this change, the dashboard would have to write a fresh idedata.json into the staging tmpdir on every install just to flip basenames to absolute paths. Resolve relative paths in the prebuilt idedata against CORE.prebuilt_dir so both the dashboard's wire format and a hand-built directory with absolute paths work. cc_path is left alone because it points at a PlatformIO toolchain binary outside the prebuilt dir; the offloader's own PIO install provides the matching binary. Issue: esphome/device-builder#570 Issue: esphome/device-builder#572
This commit is contained in:
@@ -101,13 +101,43 @@ def _run_idedata(config):
|
||||
raise
|
||||
|
||||
|
||||
def _resolve_prebuilt_idedata_paths(data: dict, prebuilt_dir: Path) -> None:
|
||||
"""Resolve relative paths in a prebuilt idedata.json against prebuilt_dir.
|
||||
|
||||
The dashboard's transparent-install pipeline rewrites ``prog_path`` and
|
||||
``extra.flash_images[*].path`` to bare basenames before shipping the
|
||||
tarball (the receiver's build-host absolute paths don't resolve on the
|
||||
offloader). Accept both shapes: absolute paths pass through unchanged
|
||||
so a hand-built --prebuilt-dir with absolute paths still works, and
|
||||
bare basenames or other relative paths resolve to ``prebuilt_dir / p``.
|
||||
|
||||
Mutates ``data`` in place. ``cc_path`` is left alone because it points
|
||||
at a PlatformIO toolchain binary (~/.platformio/packages/...) that
|
||||
lives outside the prebuilt dir; the offloader's local PIO install
|
||||
provides the matching binary by virtue of running on the same machine
|
||||
as ``esphome upload``.
|
||||
"""
|
||||
prog = data.get("prog_path")
|
||||
if prog is not None and not Path(prog).is_absolute():
|
||||
data["prog_path"] = str(prebuilt_dir / prog)
|
||||
|
||||
extra = data.get("extra")
|
||||
if isinstance(extra, dict):
|
||||
for image in extra.get("flash_images", []) or []:
|
||||
path = image.get("path") if isinstance(image, dict) else None
|
||||
if path is not None and not Path(path).is_absolute():
|
||||
image["path"] = str(prebuilt_dir / path)
|
||||
|
||||
|
||||
def _load_idedata(config):
|
||||
# `esphome upload --prebuilt-dir` ships a pre-rendered idedata.json next
|
||||
# to the artifacts. When present we use it verbatim: ``firmware_bin_path``
|
||||
# (i.e. ``prog_path`` with the ``.bin`` suffix), ``firmware_elf_path``,
|
||||
# ``cc_path`` and ``extra.flash_images[*].path`` are expected to be
|
||||
# absolute paths that resolve under ``CORE.prebuilt_dir``. The dashboard
|
||||
# is responsible for rewriting those paths when it stages the directory.
|
||||
# to the artifacts. When present we use it as-is, with one rewrite pass:
|
||||
# ``prog_path`` and ``extra.flash_images[*].path`` may be either absolute
|
||||
# paths (hand-built directories) or bare basenames (the dashboard's wire
|
||||
# format) -- relative paths are resolved against ``CORE.prebuilt_dir`` so
|
||||
# both shapes work without the dashboard having to write a fresh
|
||||
# idedata.json with absolute paths on every install.
|
||||
#
|
||||
# No schema validation or referenced-path existence check happens here;
|
||||
# a malformed prebuilt idedata.json will surface as a downstream
|
||||
# "file not found" from esptool / picotool. That's an acceptable trade
|
||||
@@ -115,7 +145,9 @@ def _load_idedata(config):
|
||||
if CORE.prebuilt_dir is not None:
|
||||
prebuilt_idedata = CORE.prebuilt_dir / "idedata.json"
|
||||
if prebuilt_idedata.is_file():
|
||||
return json.loads(prebuilt_idedata.read_text(encoding="utf-8"))
|
||||
data = json.loads(prebuilt_idedata.read_text(encoding="utf-8"))
|
||||
_resolve_prebuilt_idedata_paths(data, CORE.prebuilt_dir)
|
||||
return data
|
||||
|
||||
platformio_ini = CORE.relative_build_path("platformio.ini")
|
||||
temp_idedata = CORE.relative_internal_path("idedata", f"{CORE.name}.json")
|
||||
|
||||
@@ -263,6 +263,79 @@ def test_load_idedata_uses_prebuilt_dir_when_set(
|
||||
mock_run_platformio_cli_run.assert_not_called()
|
||||
|
||||
|
||||
def test_load_idedata_resolves_basenames_against_prebuilt_dir(
|
||||
setup_core: Path, mock_run_platformio_cli_run: Mock
|
||||
) -> None:
|
||||
"""The dashboard's wire format ships idedata.json with bare basenames in
|
||||
prog_path and extra.flash_images[*].path (the receiver's build-host
|
||||
absolute paths don't resolve on the offloader). Verify upstream resolves
|
||||
those basenames against CORE.prebuilt_dir so the offloader doesn't have
|
||||
to write a fresh idedata.json on every install. Tracks the request in
|
||||
esphome/device-builder#570."""
|
||||
CORE.build_path = str(setup_core / "build" / "test")
|
||||
CORE.name = "test"
|
||||
|
||||
prebuilt_dir = setup_core / "prebuilt"
|
||||
prebuilt_dir.mkdir()
|
||||
prebuilt_idedata = prebuilt_dir / "idedata.json"
|
||||
prebuilt_idedata.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"prog_path": "firmware.elf",
|
||||
"extra": {
|
||||
"flash_images": [
|
||||
{"path": "bootloader.bin", "offset": "0x1000"},
|
||||
{"path": "partitions.bin", "offset": "0x8000"},
|
||||
]
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
CORE.prebuilt_dir = prebuilt_dir
|
||||
|
||||
result = platformio_api._load_idedata({"name": "test"})
|
||||
|
||||
assert result["prog_path"] == str(prebuilt_dir / "firmware.elf")
|
||||
images = result["extra"]["flash_images"]
|
||||
assert images[0]["path"] == str(prebuilt_dir / "bootloader.bin")
|
||||
assert images[1]["path"] == str(prebuilt_dir / "partitions.bin")
|
||||
mock_run_platformio_cli_run.assert_not_called()
|
||||
|
||||
|
||||
def test_load_idedata_absolute_paths_in_prebuilt_pass_through(
|
||||
setup_core: Path, mock_run_platformio_cli_run: Mock
|
||||
) -> None:
|
||||
"""A hand-built --prebuilt-dir with absolute paths in idedata.json still
|
||||
works: absolute paths pass through unchanged so we don't break the
|
||||
documented-but-rarer shape just to support the dashboard's wire format.
|
||||
"""
|
||||
CORE.build_path = str(setup_core / "build" / "test")
|
||||
CORE.name = "test"
|
||||
|
||||
prebuilt_dir = setup_core / "prebuilt"
|
||||
prebuilt_dir.mkdir()
|
||||
abs_bootloader = "/somewhere/else/bootloader.bin"
|
||||
prebuilt_idedata = prebuilt_dir / "idedata.json"
|
||||
prebuilt_idedata.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"prog_path": str(prebuilt_dir / "firmware.elf"),
|
||||
"extra": {
|
||||
"flash_images": [
|
||||
{"path": abs_bootloader, "offset": "0x1000"},
|
||||
]
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
CORE.prebuilt_dir = prebuilt_dir
|
||||
|
||||
result = platformio_api._load_idedata({"name": "test"})
|
||||
|
||||
assert result["prog_path"] == str(prebuilt_dir / "firmware.elf")
|
||||
assert result["extra"]["flash_images"][0]["path"] == abs_bootloader
|
||||
|
||||
|
||||
def test_load_idedata_falls_back_when_prebuilt_idedata_missing(
|
||||
setup_core: Path, mock_run_platformio_cli_run: Mock
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user