tan-cli 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tan/__init__.py +0 -0
- tan/__main__.py +120 -0
- tan/cli.py +939 -0
- tan/command_table.py +265 -0
- tan/commands/__init__.py +8 -0
- tan/commands/bootstrap_cmd.py +4856 -0
- tan/commands/bootstrap_patches.py +164 -0
- tan/commands/bootstrap_toolchain_fetch.py +155 -0
- tan/commands/bootstrap_toolchain_pin.py +36 -0
- tan/commands/build/__init__.py +1 -0
- tan/commands/build/configure_inputs.py +277 -0
- tan/commands/build/execute.py +1852 -0
- tan/commands/build/host_python.py +59 -0
- tan/commands/build/link_stale.py +78 -0
- tan/commands/build/manifest.py +428 -0
- tan/commands/build/materialise.py +81 -0
- tan/commands/build/token_substitution.py +381 -0
- tan/commands/build/toolchain.py +482 -0
- tan/commands/build/user_defines_wipe.py +115 -0
- tan/commands/build/workspace_patches.py +65 -0
- tan/commands/build_cmd.py +2236 -0
- tan/commands/build_output.py +331 -0
- tan/commands/clean_cmd.py +1183 -0
- tan/commands/completion_cmd.py +786 -0
- tan/commands/debug_config_cmd.py +2425 -0
- tan/commands/diff_cmd.py +940 -0
- tan/commands/doctor_cmd.py +4767 -0
- tan/commands/examples_cmd.py +644 -0
- tan/commands/explain_cmd.py +1319 -0
- tan/commands/faultdecode_cmd.py +931 -0
- tan/commands/flash_atoc_guard.py +240 -0
- tan/commands/flash_cmd.py +5731 -0
- tan/commands/flash_mram_guard.py +150 -0
- tan/commands/flash_ram.py +578 -0
- tan/commands/flash_raw.py +583 -0
- tan/commands/generate_cmd.py +1456 -0
- tan/commands/image_cmd.py +753 -0
- tan/commands/init_cmd.py +1740 -0
- tan/commands/inspect_cmd.py +377 -0
- tan/commands/kconfig_cmd.py +734 -0
- tan/commands/model_cmd.py +1514 -0
- tan/commands/model_device_cmd.py +351 -0
- tan/commands/model_device_live.py +234 -0
- tan/commands/model_host_cmd.py +430 -0
- tan/commands/model_zoo_cmd.py +363 -0
- tan/commands/monitor_actions.py +125 -0
- tan/commands/monitor_cmd.py +626 -0
- tan/commands/monitor_session.py +505 -0
- tan/commands/new_som_cmd.py +1522 -0
- tan/commands/pinmux_cmd.py +568 -0
- tan/commands/presets_cmd.py +1123 -0
- tan/commands/probe_cmd.py +421 -0
- tan/commands/reset_cmd.py +354 -0
- tan/commands/reset_confirm.py +168 -0
- tan/commands/run_cmd.py +637 -0
- tan/commands/scaffold_cmd.py +490 -0
- tan/commands/sdk_cmd.py +1723 -0
- tan/commands/size_cmd.py +1020 -0
- tan/commands/support_bundle_cmd.py +1073 -0
- tan/commands/trace_cmd.py +341 -0
- tan/commands/validate_cmd.py +2009 -0
- tan/commands/west_forward_cmd.py +736 -0
- tan/commands/workspace_patch_check.py +296 -0
- tan/core/__init__.py +1 -0
- tan/core/alp_conf_pregen.py +100 -0
- tan/core/artifact_provenance.py +240 -0
- tan/core/atoc_guard.py +276 -0
- tan/core/atoc_guard_messages.py +232 -0
- tan/core/atoc_replacement.py +118 -0
- tan/core/atomic_write.py +178 -0
- tan/core/board_context.py +351 -0
- tan/core/board_diagnostic.py +111 -0
- tan/core/board_files.py +221 -0
- tan/core/board_validator.py +542 -0
- tan/core/board_validator_compat.py +196 -0
- tan/core/board_validator_run.py +129 -0
- tan/core/board_validator_schema.py +186 -0
- tan/core/board_validator_skew.py +46 -0
- tan/core/board_yaml_edit.py +295 -0
- tan/core/board_yaml_pos.py +118 -0
- tan/core/bootstrap.py +2572 -0
- tan/core/build_plan.py +625 -0
- tan/core/consent.py +104 -0
- tan/core/console_filter.py +245 -0
- tan/core/debug_launch.py +1803 -0
- tan/core/devicetree_lint.py +168 -0
- tan/core/dir_removal.py +229 -0
- tan/core/doctor_git.py +172 -0
- tan/core/doctor_libraries.py +253 -0
- tan/core/doctor_render.py +185 -0
- tan/core/doctor_scope.py +66 -0
- tan/core/doctor_setools.py +237 -0
- tan/core/doctor_stale.py +180 -0
- tan/core/document_guards.py +723 -0
- tan/core/dp_id.py +97 -0
- tan/core/elf_load.py +63 -0
- tan/core/error_catalog.py +171 -0
- tan/core/example_catalog.py +499 -0
- tan/core/example_facets.py +151 -0
- tan/core/faultdecode.py +521 -0
- tan/core/flash_plan.py +2501 -0
- tan/core/flow_d_report.py +381 -0
- tan/core/flow_d_uptime.py +164 -0
- tan/core/fs_confine.py +130 -0
- tan/core/global_flags.py +230 -0
- tan/core/host_python.py +210 -0
- tan/core/image_bundle.py +247 -0
- tan/core/inert.py +147 -0
- tan/core/jlink_binary.py +128 -0
- tan/core/jlink_probe.py +481 -0
- tan/core/jsonc_splice.py +404 -0
- tan/core/launch_provenance.py +257 -0
- tan/core/link_refusal.py +76 -0
- tan/core/metadata_schema.py +376 -0
- tan/core/model_check.py +227 -0
- tan/core/model_device.py +449 -0
- tan/core/model_doctor.py +198 -0
- tan/core/model_host.py +48 -0
- tan/core/model_list.py +112 -0
- tan/core/model_zoo.py +353 -0
- tan/core/module_template.py +340 -0
- tan/core/mram_link.py +87 -0
- tan/core/new_som_ospi.py +94 -0
- tan/core/os_class.py +159 -0
- tan/core/patch_paths.py +87 -0
- tan/core/pending.py +40 -0
- tan/core/plain_zephyr_plan.py +138 -0
- tan/core/plan_exec.py +373 -0
- tan/core/plan_tokens.py +600 -0
- tan/core/probe.py +155 -0
- tan/core/probe_plan.py +237 -0
- tan/core/proxy.py +168 -0
- tan/core/publish.py +31 -0
- tan/core/python_floor.py +178 -0
- tan/core/ram_run.py +694 -0
- tan/core/ram_watch.py +231 -0
- tan/core/raw_write.py +160 -0
- tan/core/reset_plan.py +56 -0
- tan/core/run.py +161 -0
- tan/core/scaffold.py +2203 -0
- tan/core/scaffold_selftest_identity.py +225 -0
- tan/core/sdk_default_registry.py +565 -0
- tan/core/sdk_discovery.py +996 -0
- tan/core/sdk_removal.py +465 -0
- tan/core/serial_actions.py +175 -0
- tan/core/serial_capture.py +185 -0
- tan/core/serial_url.py +45 -0
- tan/core/setools.py +608 -0
- tan/core/setools_scratch.py +243 -0
- tan/core/shapes.py +194 -0
- tan/core/size.py +610 -0
- tan/core/som_buildability.py +204 -0
- tan/core/som_schema_version.py +62 -0
- tan/core/subprocess_env.py +201 -0
- tan/core/sysbuild_image.py +94 -0
- tan/core/system_manifest.py +827 -0
- tan/core/text_layout.py +73 -0
- tan/core/timestamp.py +182 -0
- tan/core/tool_lookup.py +383 -0
- tan/core/toolchain_pin.py +117 -0
- tan/core/toolchain_provision.py +1224 -0
- tan/core/trusted_path.py +134 -0
- tan/core/uboot_breakin.py +154 -0
- tan/core/uri_reference.py +415 -0
- tan/core/user_defines.py +152 -0
- tan/core/venv.py +332 -0
- tan/core/west_patches.py +143 -0
- tan/core/west_workspace_refusal.py +159 -0
- tan/core/zephyr_env.py +124 -0
- tan/env.py +185 -0
- tan/envelope.py +647 -0
- tan/exit_codes.py +12 -0
- tan/lazy_typer.py +185 -0
- tan/model/__init__.py +2 -0
- tan/model/_gen_fixture.py +132 -0
- tan/model/adapters/__init__.py +116 -0
- tan/model/adapters/cpu.py +26 -0
- tan/model/adapters/deepx.py +94 -0
- tan/model/adapters/drpai.py +242 -0
- tan/model/adapters/ethos_u.py +951 -0
- tan/model/adapters/executorch.py +55 -0
- tan/model/analyze.py +694 -0
- tan/model/build.py +304 -0
- tan/model/check.py +682 -0
- tan/model/manifest.py +625 -0
- tan/model/measure.py +216 -0
- tan/model/npu_ops_validate.py +130 -0
- tan/model/package.py +125 -0
- tan/model/perf.py +635 -0
- tan/model/perf_apply.py +721 -0
- tan/model/prep.py +193 -0
- tan/model/sram_fit.py +502 -0
- tan/model/targets.py +374 -0
- tan/model/tensorio.py +287 -0
- tan/net.py +73 -0
- tan/output_format.py +213 -0
- tan/planner/__init__.py +219 -0
- tan/planner/aperture.py +202 -0
- tan/planner/buildplan.py +1092 -0
- tan/planner/camera_owner.py +192 -0
- tan/planner/cameras.py +132 -0
- tan/planner/carveout.py +755 -0
- tan/planner/cli.py +274 -0
- tan/planner/headers.py +315 -0
- tan/planner/kconfig.py +2435 -0
- tan/planner/kconfig_symbols.py +473 -0
- tan/planner/libraries.py +677 -0
- tan/planner/link_target.py +237 -0
- tan/planner/linux_ownership.py +216 -0
- tan/planner/loader.py +1525 -0
- tan/planner/manifest.py +116 -0
- tan/planner/memory.py +161 -0
- tan/planner/memregion.py +56 -0
- tan/planner/models.py +533 -0
- tan/planner/orchestrator.py +874 -0
- tan/planner/ownership.py +203 -0
- tan/planner/partition.py +1160 -0
- tan/planner/paths.py +32 -0
- tan/planner/plain_slice.py +139 -0
- tan/planner/project_emit/__init__.py +60 -0
- tan/planner/project_emit/bom_netlist.py +375 -0
- tan/planner/project_emit/dts.py +580 -0
- tan/planner/project_emit/hw_info.py +221 -0
- tan/planner/project_emit/native_sim.py +136 -0
- tan/planner/project_emit/west_libs.py +213 -0
- tan/planner/project_loader.py +264 -0
- tan/planner/sdk_capability.py +203 -0
- tan/planner/sdk_compat.py +395 -0
- tan/planner/secure.py +350 -0
- tan/planner/slugs.py +394 -0
- tan/planner/som_metadata.py +325 -0
- tan/planner/strict_loaders.py +131 -0
- tan/planner/template.py +1037 -0
- tan/planner/template_pins.py +535 -0
- tan/planner/template_rewrite.py +714 -0
- tan/planner/topology.py +208 -0
- tan/planner/validate.py +365 -0
- tan/planner/whole_device_alias.py +67 -0
- tan/planner/zephyr_board.py +3510 -0
- tan/planner_cli.py +97 -0
- tan/planner_emit.py +617 -0
- tan/planner_root.py +119 -0
- tan/soc_ref.py +58 -0
- tan/templates/__init__.py +34 -0
- tan/templates/vendored/MANIFEST.md +1862 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/README.md +112 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/board.yaml +38 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.conf +11 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.overlay +29 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/prj.conf +5 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/src/main.c +266 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/testcase.yaml +53 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/README.md +112 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/board.yaml +38 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.conf +11 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.overlay +29 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/prj.conf +5 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/src/main.c +266 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/testcase.yaml +53 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/CMakeLists.txt +10 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/README.md +75 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/board.yaml +38 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/prj.conf +8 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.c +371 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.h +178 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/main.c +373 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/testcase.yaml +62 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/CMakeLists.txt +10 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/README.md +75 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/board.yaml +37 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/prj.conf +8 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.c +371 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.h +178 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/main.c +373 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/testcase.yaml +62 -0
- tan/templates/vendored/iot/E1M-AEN801/CMakeLists.txt +14 -0
- tan/templates/vendored/iot/E1M-AEN801/README.md +103 -0
- tan/templates/vendored/iot/E1M-AEN801/board.yaml +69 -0
- tan/templates/vendored/iot/E1M-AEN801/prj.conf +15 -0
- tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.c +196 -0
- tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.h +173 -0
- tan/templates/vendored/iot/E1M-AEN801/src/main.c +262 -0
- tan/templates/vendored/iot/E1M-AEN801/testcase.yaml +68 -0
- tan/templates/vendored/minimal/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/minimal/E1M-AEN801/README.md +108 -0
- tan/templates/vendored/minimal/E1M-AEN801/board.yaml +32 -0
- tan/templates/vendored/minimal/E1M-AEN801/prj.conf +8 -0
- tan/templates/vendored/minimal/E1M-AEN801/src/main.c +111 -0
- tan/templates/vendored/minimal/E1M-AEN801/testcase.yaml +49 -0
- tan/templates/vendored/minimal/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/minimal/E1M-V2N101/README.md +108 -0
- tan/templates/vendored/minimal/E1M-V2N101/board.yaml +32 -0
- tan/templates/vendored/minimal/E1M-V2N101/prj.conf +8 -0
- tan/templates/vendored/minimal/E1M-V2N101/src/main.c +111 -0
- tan/templates/vendored/minimal/E1M-V2N101/testcase.yaml +49 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/README.md +120 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/board.yaml +57 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/boards/native_sim_native_64.overlay +40 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/CMakeLists.txt +13 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/main.c +171 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/prj.conf +17 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/testcase.yaml +23 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/prj.conf +22 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/src/main.c +185 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/testcase.yaml +26 -0
- tan/templates/vendored/sensor/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/sensor/E1M-AEN801/README.md +112 -0
- tan/templates/vendored/sensor/E1M-AEN801/board.yaml +57 -0
- tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.conf +10 -0
- tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.overlay +28 -0
- tan/templates/vendored/sensor/E1M-AEN801/prj.conf +5 -0
- tan/templates/vendored/sensor/E1M-AEN801/src/main.c +186 -0
- tan/templates/vendored/sensor/E1M-AEN801/testcase.yaml +52 -0
- tan/templates/vendored/sensor/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/sensor/E1M-V2N101/README.md +112 -0
- tan/templates/vendored/sensor/E1M-V2N101/board.yaml +57 -0
- tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.conf +10 -0
- tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.overlay +28 -0
- tan/templates/vendored/sensor/E1M-V2N101/prj.conf +5 -0
- tan/templates/vendored/sensor/E1M-V2N101/src/main.c +186 -0
- tan/templates/vendored/sensor/E1M-V2N101/testcase.yaml +52 -0
- tan/version.py +102 -0
- tan_cli-0.7.0.dist-info/METADATA +125 -0
- tan_cli-0.7.0.dist-info/RECORD +330 -0
- tan_cli-0.7.0.dist-info/WHEEL +5 -0
- tan_cli-0.7.0.dist-info/entry_points.txt +2 -0
- tan_cli-0.7.0.dist-info/top_level.txt +1 -0
tan/core/plan_exec.py
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Pure build-plan execution decisions (ADR-0020): turn the plan's
|
|
3
|
+
envAppendPath and executionPolicy into concrete env values and skip/fail
|
|
4
|
+
dispositions. No IO, no spawning -- the executor calls these and owns the IO."""
|
|
5
|
+
import ntpath
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
from collections.abc import Callable, Sequence
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from enum import StrEnum
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def normalize_path(path: Path) -> Path:
|
|
15
|
+
"""Lexical `.`/`..` collapse, no filesystem access -- port of
|
|
16
|
+
`tan_core::path_guard::normalize` (crates/tan-core/src/path_guard.rs).
|
|
17
|
+
Used (joined onto a resolved workspace root first) so a relative and an
|
|
18
|
+
absolute spelling of the SAME `--sdk-root` normalize to the identical
|
|
19
|
+
string before [`sdk_stamp_key`] compares them -- otherwise a relative
|
|
20
|
+
`--sdk-root ../alp-sdk` byte-mismatches the absolute pointer `tan sdk
|
|
21
|
+
switch` already pinned for the SAME checkout, thrashing a full wipe on
|
|
22
|
+
every alternating invocation between the two forms (tan-cli#163).
|
|
23
|
+
|
|
24
|
+
A `..` past the anchor (the drive/root, or nothing left on a relative
|
|
25
|
+
path) is dropped rather than kept literal, matching `PathBuf::pop`'s own
|
|
26
|
+
"false, does nothing" behaviour at the top."""
|
|
27
|
+
anchor = path.anchor
|
|
28
|
+
parts: list[str] = []
|
|
29
|
+
for part in (path.parts[1:] if anchor else path.parts):
|
|
30
|
+
if part == ".":
|
|
31
|
+
continue
|
|
32
|
+
if part == "..":
|
|
33
|
+
if parts:
|
|
34
|
+
parts.pop()
|
|
35
|
+
continue
|
|
36
|
+
parts.append(part)
|
|
37
|
+
if anchor:
|
|
38
|
+
return Path(anchor).joinpath(*parts)
|
|
39
|
+
return Path(*parts) if parts else Path(".")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class PolicyAction(StrEnum):
|
|
43
|
+
SKIP = "skip"
|
|
44
|
+
FAIL = "fail"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass(frozen=True)
|
|
48
|
+
class ExecutionPolicy:
|
|
49
|
+
unknown_backend: PolicyAction | None = None
|
|
50
|
+
missing_tool: PolicyAction | None = None
|
|
51
|
+
null_command: PolicyAction | None = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def sep_for_key(var: str) -> str:
|
|
55
|
+
"""The join separator for ONE envAppendPath var -- per-key, not uniformly
|
|
56
|
+
os.pathsep. EXTRA_ZEPHYR_MODULES is a CMake list that Zephyr's
|
|
57
|
+
zephyr_module.py splits on ';' on EVERY platform (never an OS path list);
|
|
58
|
+
joining it with ':' on Linux/WSL fails `west build` configure with
|
|
59
|
+
"is not a valid zephyr module"."""
|
|
60
|
+
return ";" if var == "EXTRA_ZEPHYR_MODULES" else os.pathsep
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def apply_env_append(base: list[tuple[str, str]], append: dict[str, list[str]]) -> None:
|
|
64
|
+
"""Append each value to its var using that var's separator, skipping a value
|
|
65
|
+
already present segment-wise. A var absent from `base` is seeded from the
|
|
66
|
+
appended values (the plan owns it). Mutates `base` in place."""
|
|
67
|
+
for var, values in append.items():
|
|
68
|
+
sep = sep_for_key(var)
|
|
69
|
+
current = next((v for k, v in base if k == var), None)
|
|
70
|
+
segments = current.split(sep) if current else []
|
|
71
|
+
for val in values:
|
|
72
|
+
if val not in segments:
|
|
73
|
+
segments.append(val)
|
|
74
|
+
joined = sep.join(segments)
|
|
75
|
+
for i, (k, _) in enumerate(base):
|
|
76
|
+
if k == var:
|
|
77
|
+
base[i] = (var, joined)
|
|
78
|
+
break
|
|
79
|
+
else:
|
|
80
|
+
base.append((var, joined))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def assemble_slice_env(
|
|
84
|
+
slice_env: dict[str, str],
|
|
85
|
+
env_append_path: dict[str, list[str]],
|
|
86
|
+
inherited: Callable[[str], str | None],
|
|
87
|
+
gap_fillers: Sequence[tuple[str, str]],
|
|
88
|
+
) -> list[tuple[str, str]]:
|
|
89
|
+
"""Start from the slice's verbatim env, seed any envAppendPath var the slice
|
|
90
|
+
doesn't pin from `inherited` (so an append EXTENDS rather than replaces an
|
|
91
|
+
inherited PYTHONPATH), apply the appends, then merge the CLI's
|
|
92
|
+
consumer-mechanism gap fillers -- "plan wins / CLI fills gaps"."""
|
|
93
|
+
env: list[tuple[str, str]] = list(slice_env.items())
|
|
94
|
+
for key in env_append_path:
|
|
95
|
+
if not any(k == key for k, _ in env):
|
|
96
|
+
value = inherited(key)
|
|
97
|
+
if value is not None:
|
|
98
|
+
env.append((key, value))
|
|
99
|
+
apply_env_append(env, env_append_path)
|
|
100
|
+
for key, value in gap_fillers:
|
|
101
|
+
for i, (k, _) in enumerate(env):
|
|
102
|
+
if k == key:
|
|
103
|
+
env[i] = (key, value)
|
|
104
|
+
break
|
|
105
|
+
else:
|
|
106
|
+
env.append((key, value))
|
|
107
|
+
return env
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def resolve_action(
|
|
111
|
+
policy: ExecutionPolicy | None, key: str, default: PolicyAction
|
|
112
|
+
) -> PolicyAction:
|
|
113
|
+
"""Honour executionPolicy's entry when present, else the CLI's built-in
|
|
114
|
+
behaviour for an older plan that omits it."""
|
|
115
|
+
if policy is None:
|
|
116
|
+
return default
|
|
117
|
+
picked = getattr(policy, key, None)
|
|
118
|
+
return picked if picked is not None else default
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class SdkStampAction(StrEnum):
|
|
122
|
+
KEEP = "keep"
|
|
123
|
+
PRISTINE = "pristine"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def sdk_stamp_action(
|
|
127
|
+
cached: str | None,
|
|
128
|
+
current: str | None,
|
|
129
|
+
cache_configured: bool,
|
|
130
|
+
build_dir_overridden: bool,
|
|
131
|
+
cwd_under_build_root: bool,
|
|
132
|
+
) -> SdkStampAction:
|
|
133
|
+
"""Whether to wipe a slice's build dir because it was configured against a
|
|
134
|
+
different SDK root. West refuses to reconfigure a build dir whose CMake cache
|
|
135
|
+
is bound to another source tree ("FATAL ERROR: refusing to proceed without
|
|
136
|
+
--force"), which reaches the user as a bare "terminated with exit code: 1".
|
|
137
|
+
|
|
138
|
+
A missing stamp on an already-configured dir reads as stale DELIBERATELY --
|
|
139
|
+
it is the only way a build dir predating this feature ever self-heals."""
|
|
140
|
+
if build_dir_overridden or not cwd_under_build_root or not cache_configured:
|
|
141
|
+
return SdkStampAction.KEEP
|
|
142
|
+
if current is None:
|
|
143
|
+
return SdkStampAction.KEEP
|
|
144
|
+
return SdkStampAction.KEEP if cached == current else SdkStampAction.PRISTINE
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class PristineSkipped(StrEnum):
|
|
148
|
+
"""Why an explicit `tan build --pristine` did NOT wipe a slice's build
|
|
149
|
+
dir -- port of the Rust oracle's `PristineSkipped`
|
|
150
|
+
(`crates/tan-core/src/plan_exec.rs`, pre-tan-cli#269 deletion). Every
|
|
151
|
+
member has a fixed `.reason()` sentence below rather than a message
|
|
152
|
+
assembled at each call site, so `build.pristine-skipped` always names the
|
|
153
|
+
SAME fact for the SAME cause.
|
|
154
|
+
|
|
155
|
+
`NEVER_CONFIGURED` is not one of the two structural safety guards
|
|
156
|
+
(`BUILD_DIR_OVERRIDDEN`/`CWD_OUTSIDE_BUILD_ROOT`) -- it is the ordinary
|
|
157
|
+
"nothing to wipe" case, and (per the oracle's own comment) the only one
|
|
158
|
+
of the three a stock plan reaches today: `write_sdk_stamp` itself
|
|
159
|
+
creates `<cwd>/build`, so after any prior slice the directory exists
|
|
160
|
+
holding nothing but tan's own stamp file."""
|
|
161
|
+
|
|
162
|
+
BUILD_DIR_OVERRIDDEN = "build-dir-overridden"
|
|
163
|
+
CWD_OUTSIDE_BUILD_ROOT = "cwd-outside-build-root"
|
|
164
|
+
NEVER_CONFIGURED = "never-configured"
|
|
165
|
+
|
|
166
|
+
def reason(self) -> str:
|
|
167
|
+
"""One clause naming what stopped the wipe -- phrased as the reason,
|
|
168
|
+
not as an apology, verbatim from the oracle's own `reason()`."""
|
|
169
|
+
return {
|
|
170
|
+
PristineSkipped.BUILD_DIR_OVERRIDDEN: (
|
|
171
|
+
"the slice redirects west's build dir (`-d`/`--build-dir`), so tan "
|
|
172
|
+
"cannot know which directory to wipe"
|
|
173
|
+
),
|
|
174
|
+
PristineSkipped.CWD_OUTSIDE_BUILD_ROOT: (
|
|
175
|
+
"the plan cwd is not under `build/`, so the wipe target could hold "
|
|
176
|
+
"files the build never created"
|
|
177
|
+
),
|
|
178
|
+
PristineSkipped.NEVER_CONFIGURED: (
|
|
179
|
+
"that build dir has no CMakeCache.txt — it was never configured, "
|
|
180
|
+
"so there was nothing to wipe"
|
|
181
|
+
),
|
|
182
|
+
}[self]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def pristine_suppression(
|
|
186
|
+
force_pristine: bool,
|
|
187
|
+
build_dir_overridden: bool,
|
|
188
|
+
cwd_under_build_root: bool,
|
|
189
|
+
cache_configured: bool,
|
|
190
|
+
) -> PristineSkipped | None:
|
|
191
|
+
"""Whether an explicit `--pristine` was suppressed, and why -- `None` when
|
|
192
|
+
the wipe actually happened or none was asked for (tan-cli#183, tan-cli#427).
|
|
193
|
+
|
|
194
|
+
ONE decision for all THREE suppression paths, rather than a message
|
|
195
|
+
bolted onto whichever branch the caller happens to be in. Order is
|
|
196
|
+
most-specific-first: an overridden build dir is reported even when the
|
|
197
|
+
cwd would also have failed, because that is the fact the user must
|
|
198
|
+
change first."""
|
|
199
|
+
if not force_pristine:
|
|
200
|
+
return None
|
|
201
|
+
if build_dir_overridden:
|
|
202
|
+
return PristineSkipped.BUILD_DIR_OVERRIDDEN
|
|
203
|
+
if not cwd_under_build_root:
|
|
204
|
+
return PristineSkipped.CWD_OUTSIDE_BUILD_ROOT
|
|
205
|
+
if not cache_configured:
|
|
206
|
+
return PristineSkipped.NEVER_CONFIGURED
|
|
207
|
+
return None
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def sdk_stamp_key(sdk_root: str | None, sdk_commit: str | None) -> str | None:
|
|
211
|
+
"""The identity [`sdk_stamp_action`] actually compares, and the executor
|
|
212
|
+
writes to `.tan-sdk-root`: the resolved SDK root path alone is not enough,
|
|
213
|
+
because a standalone checkout that changes CONTENT at the SAME path (a
|
|
214
|
+
`git pull`/`git checkout <tag>` in a `--sdk-root ../alp-sdk` checkout)
|
|
215
|
+
leaves a path-only stamp matching while the build dir is actually stale.
|
|
216
|
+
Folding the plan's own `sdkCommit` in closes it: a freshly emitted plan
|
|
217
|
+
always reflects the SDK checkout's CURRENT git HEAD, so a content change
|
|
218
|
+
at the same root changes the key.
|
|
219
|
+
|
|
220
|
+
`sdk_commit` blank or absent (an older plan, or an SDK checkout with no
|
|
221
|
+
resolvable git HEAD) degrades the key to the root alone -- the historical,
|
|
222
|
+
path-only stamp. `None` when `sdk_root` itself is unresolved -- nothing to
|
|
223
|
+
key on at all."""
|
|
224
|
+
if sdk_root is None:
|
|
225
|
+
return None
|
|
226
|
+
commit = sdk_commit.strip() if sdk_commit is not None else ""
|
|
227
|
+
return f"{sdk_root}@{commit}" if commit else sdk_root
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def west_build_source_dir(args: Sequence[str]) -> str | None:
|
|
231
|
+
"""The SOURCE_DIR positional `west build`'s own `_sanity_check_source_dir`
|
|
232
|
+
resolves -- `None` when `args` carries no `-b`/`--board` flag (a shape
|
|
233
|
+
`orchestrator.py` never emits for a `west build` slice).
|
|
234
|
+
|
|
235
|
+
`orchestrator.py` always emits the source dir as the token immediately
|
|
236
|
+
following `-b`'s own value: `cmd = ["west", "build", "-b", slice_.board,
|
|
237
|
+
app_dir]` (`tan.planner.orchestrator`, the `zephyr` backend's command
|
|
238
|
+
builder). That is NOT the same as `args[-1]`, the guess this replaces --
|
|
239
|
+
a real zephyr slice's command tail is never just that four-element
|
|
240
|
+
prefix. `cmd += ["--", *defines]` always appends at least one CMake
|
|
241
|
+
define (`defines` starts life as `["-DPython3_EXECUTABLE=${PYTHON}"]`
|
|
242
|
+
and is never emptied), and a sysbuild slice additionally inserts
|
|
243
|
+
`--sysbuild` between the source dir and that `--` separator -- so
|
|
244
|
+
neither "the last token" nor "the last token before `--`" is safe.
|
|
245
|
+
Anchoring on `-b`'s own position instead matches exactly how the
|
|
246
|
+
emitter places the source dir, regardless of what is appended after
|
|
247
|
+
it."""
|
|
248
|
+
for i, token in enumerate(args):
|
|
249
|
+
if token in ("-b", "--board") and i + 2 < len(args):
|
|
250
|
+
return args[i + 2]
|
|
251
|
+
return None
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _build_dir_overridden(args: Sequence[str]) -> bool:
|
|
255
|
+
"""Verbatim mirror of `tan.commands.build.manifest.build_dir_overridden`
|
|
256
|
+
-- duplicated, not imported: `tan/core` is pure/dependency-free and must
|
|
257
|
+
not import from `tan/commands` (the reverse of the one real dependency
|
|
258
|
+
direction in this codebase). Keep the two definitions in sync by hand if
|
|
259
|
+
`west build`'s own build-dir flag set ever changes."""
|
|
260
|
+
return any(a == "-d" or a == "--build-dir" or a.startswith("--build-dir=") for a in args)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
#: tan-cli#697. `_pin_west_workspace` (`tan.commands.build.execute`) redirects
|
|
264
|
+
#: `west build`'s own spawned cwd to the resolved Zephyr workspace so west's
|
|
265
|
+
#: ancestor-`.west` walk lands on the right one -- but `west build`'s own
|
|
266
|
+
#: source-directory sanity check (`_sanity_check_source_dir`, upstream
|
|
267
|
+
#: `scripts/west_commands/build.py:534`, frozen third-party code this repo
|
|
268
|
+
#: does not own) then calls `os.path.relpath(self.source_dir)` with NO
|
|
269
|
+
#: explicit `start`, which defaults to `os.getcwd()` -- the pinned
|
|
270
|
+
#: WORKSPACE, not the slice's own project. On Windows, `os.path.relpath`
|
|
271
|
+
#: RAISES rather than returning a path when its two arguments live on
|
|
272
|
+
#: different drive letters (`ValueError: path is on mount 'E:', start on
|
|
273
|
+
#: mount 'C:'`, reproduced verbatim in the issue). There is no fix reachable
|
|
274
|
+
#: from tan's own side of the seam -- `west` is an upstream pip dependency,
|
|
275
|
+
#: not `crates/`/`tan/planner` -- so the only correct move is to REFUSE,
|
|
276
|
+
#: with a coded reason naming both mounts, before spawning a process
|
|
277
|
+
#: already known to crash.
|
|
278
|
+
CROSS_DRIVE_MSG = "west build cannot span two Windows drives"
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
#: tan-cli#801: the structural shape both of `tan.commands.build.execute`'s
|
|
282
|
+
#: missing-tool refusals emit -- the slice's own `command` precheck
|
|
283
|
+
#: (`` tool `{tool}` not found -- searched ... ``, at the START of the
|
|
284
|
+
#: message) and `_missing_post_tool`'s post-build-step refusal (`` slice
|
|
285
|
+
#: `{core_id}` post-build step N of M (`{label}`) cannot run: tool `{tool}`
|
|
286
|
+
#: not found -- ... ``, where the phrase sits mid-string, AFTER "cannot
|
|
287
|
+
#: run: "). `build_cmd._missing_tool_issues` used to key on
|
|
288
|
+
#: `message.startswith("tool `")`, which only ever matched the first shape
|
|
289
|
+
#: -- the #550 post-build-step skip's reason never promoted into
|
|
290
|
+
#: `issues[]`, silently, because the phrase is not at position 0 there.
|
|
291
|
+
#: Matched with `.search`, never `.match`/`startswith`, for exactly that
|
|
292
|
+
#: reason: the marker's POSITION in the message is not part of the
|
|
293
|
+
#: contract, only its shape is. Anchored on the backtick-delimited tool
|
|
294
|
+
#: name (not two independent "tool `" / "` not found" substring checks
|
|
295
|
+
#: with no adjacency requirement between them) so an unrelated message that
|
|
296
|
+
#: happens to contain both fragments separately cannot false-positive.
|
|
297
|
+
_MISSING_TOOL_PREFIX = "tool `"
|
|
298
|
+
_MISSING_TOOL_SUFFIX = "` not found"
|
|
299
|
+
MISSING_TOOL_RE = re.compile(
|
|
300
|
+
re.escape(_MISSING_TOOL_PREFIX) + r"[^`]*" + re.escape(_MISSING_TOOL_SUFFIX)
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def missing_tool_message(tool: str) -> str:
|
|
305
|
+
"""The one place `` tool `{tool}` not found `` is spelled. Every producer
|
|
306
|
+
in `tan.commands.build.execute` (the slice's own `command` precheck AND
|
|
307
|
+
`_missing_post_tool`'s post-build-step refusal) MUST build its message
|
|
308
|
+
through this function rather than hand-writing the literal -- MISSING_TOOL_RE
|
|
309
|
+
above is built from the exact same `_MISSING_TOOL_PREFIX`/`_MISSING_TOOL_SUFFIX`
|
|
310
|
+
pair, so a wording edit made only at a call site (not here) silently stops
|
|
311
|
+
`build_cmd._missing_tool_issues` from promoting that producer's refusals
|
|
312
|
+
into `issues[]` again -- the exact #801 regression this coupling exists to
|
|
313
|
+
prevent."""
|
|
314
|
+
return f"{_MISSING_TOOL_PREFIX}{tool}{_MISSING_TOOL_SUFFIX}"
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@dataclass(frozen=True)
|
|
318
|
+
class CrossDriveRefusal:
|
|
319
|
+
#: Full explanation, naming both mounts and both paths -- this run's
|
|
320
|
+
#: stdout and the envelope's `data.slices[].reason`.
|
|
321
|
+
message: str
|
|
322
|
+
#: Short form for `system-manifest.yaml` `slices[].reason` -- names both
|
|
323
|
+
#: mounts, drops the absolute paths and the upstream-mechanism
|
|
324
|
+
#: explanation. Same split, for the same reason, as the missing-tool
|
|
325
|
+
#: refusal's own `manifest_message` (`tan.commands.build.execute`): a
|
|
326
|
+
#: build ARTEFACT that outlives the run and gets forwarded should not
|
|
327
|
+
#: carry a customer's private directory layout.
|
|
328
|
+
manifest_message: str
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def cross_drive_source_refusal(
|
|
332
|
+
workspace_dir: Path | None, args: Sequence[str]
|
|
333
|
+
) -> CrossDriveRefusal | None:
|
|
334
|
+
"""`None` when `_pin_west_workspace` is safe to redirect `west build`'s
|
|
335
|
+
cwd to `workspace_dir`; else the tan-cli#697 refusal, naming both
|
|
336
|
+
mounts, for a slice that pin would otherwise send straight into the
|
|
337
|
+
`ValueError` [`CROSS_DRIVE_MSG`] documents.
|
|
338
|
+
|
|
339
|
+
The no-op conditions mirror `_pin_west_workspace`'s own guard verbatim:
|
|
340
|
+
a slice that pin never rewrites can never hit the crash the pin
|
|
341
|
+
introduces, so it must never be refused here either.
|
|
342
|
+
|
|
343
|
+
`ntpath.splitdrive`, never `os.path.splitdrive`: the two-drive-letter
|
|
344
|
+
shape this exists to catch is a WINDOWS filesystem fact, independent of
|
|
345
|
+
whatever platform is running this check right now. `ntpath` parses
|
|
346
|
+
`C:\\...`/`C:/...` identically on every host -- which is what makes
|
|
347
|
+
this function exercisable (and unit-tested) on a POSIX box with no
|
|
348
|
+
two-drive Windows filesystem to reproduce against, unlike the crash it
|
|
349
|
+
heads off, which only ever fires on Windows itself. Either side missing
|
|
350
|
+
a drive letter (a POSIX path, or a Windows UNC share) returns `None`,
|
|
351
|
+
not a refusal -- that combination is not the shape the issue reproduced,
|
|
352
|
+
and a false "no refusal" there only leaves west to run exactly as it
|
|
353
|
+
does today, never a NEW failure this function introduces. The
|
|
354
|
+
comparison itself is case-insensitive (`C:` and `c:` name the same
|
|
355
|
+
mount)."""
|
|
356
|
+
if workspace_dir is None or not args or args[0] != "build" or _build_dir_overridden(args):
|
|
357
|
+
return None
|
|
358
|
+
source = west_build_source_dir(args)
|
|
359
|
+
if source is None:
|
|
360
|
+
return None
|
|
361
|
+
ws_drive, _ = ntpath.splitdrive(str(workspace_dir))
|
|
362
|
+
src_drive, _ = ntpath.splitdrive(source)
|
|
363
|
+
if not ws_drive or not src_drive or ws_drive.lower() == src_drive.lower():
|
|
364
|
+
return None
|
|
365
|
+
message = (
|
|
366
|
+
f"{CROSS_DRIVE_MSG} -- the resolved west workspace `{workspace_dir}` is on "
|
|
367
|
+
f"mount `{ws_drive}`, but this slice's own project source directory `{source}` "
|
|
368
|
+
f"is on mount `{src_drive}`. west build's own source-directory check "
|
|
369
|
+
f"(`os.path.relpath`, upstream `scripts/west_commands/build.py`) raises rather "
|
|
370
|
+
f"than resolving a path across two Windows drives -- move the project or the "
|
|
371
|
+
f"`tan bootstrap`-created workspace so both live on the same drive."
|
|
372
|
+
)
|
|
373
|
+
return CrossDriveRefusal(message, f"{CROSS_DRIVE_MSG} ({ws_drive} vs {src_drive})")
|