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
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Per-run scratch overlay of an Alif SETOOLS install, the DEVICE-entry source
|
|
3
|
+
and the ATOC report parser -- the side-effect-free half of the Flow D sign step
|
|
4
|
+
(tan-cli#1325, tan-cli#1322, tan-cli#1318).
|
|
5
|
+
|
|
6
|
+
**Why a scratch tree (tan-cli#1325).** `app-gen-toc` writes its inputs and
|
|
7
|
+
outputs into the install it runs from: `build/images/`, `build/config/`,
|
|
8
|
+
`build/AppTocPackage.bin`, an APPEND-mode `build/app-package-map.txt` and
|
|
9
|
+
`build/logs/SBContent.log`. Running it inside the customer's shared install
|
|
10
|
+
overwrote a package another user of that install (a raw bench recipe, another
|
|
11
|
+
tan run, another slice) had left there. So every sign now runs in a private
|
|
12
|
+
overlay and the shared install is only ever READ.
|
|
13
|
+
|
|
14
|
+
**What the overlay is, measured against SETOOLS SE_FW_1.110.00.** The tool
|
|
15
|
+
`chdir`s into one of its own subdirectories and then addresses the output tree
|
|
16
|
+
as `../build/...` (its log line reads `Logging to ../build/logs/SBContent.log`),
|
|
17
|
+
so a symlinked SUBDIRECTORY would resolve `..` back into the shared install and
|
|
18
|
+
write there. Therefore:
|
|
19
|
+
|
|
20
|
+
* every top-level FILE (the `app-gen-*`/`app-write-mram` executables,
|
|
21
|
+
`version.txt`, ...) is symlinked -- read-only and cwd-independent;
|
|
22
|
+
* every top-level DIRECTORY is COPIED (`bin/`, `cert/`, `utils/`: ~2.5 MiB
|
|
23
|
+
measured -- the tool writes there, e.g. its logs), so its `..` is the scratch
|
|
24
|
+
root. The ONLY exceptions are an explicit read-only allowlist,
|
|
25
|
+
[`_LINK_DIRS`] (`alif/`: ~74 MiB of System Package images the signing step
|
|
26
|
+
does not read, so copying it per run would cost more than it isolates), and
|
|
27
|
+
`utils/key`, the signing keys, which are symlinked INTO the copied `utils/`
|
|
28
|
+
rather than duplicated into the temp directory (measured: `app-gen-toc` signs
|
|
29
|
+
correctly through the link and the shared install stays byte-identical);
|
|
30
|
+
* `build/` is a FRESH real directory holding only what this one sign needs
|
|
31
|
+
(`config/`, `images/`, `logs/`), so `app-package-map.txt` is never shared
|
|
32
|
+
history and the post-sign report belongs to exactly this run.
|
|
33
|
+
|
|
34
|
+
Where a symlink cannot be created (Windows without the privilege) the file is
|
|
35
|
+
copied instead.
|
|
36
|
+
"""
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
import json
|
|
40
|
+
import os
|
|
41
|
+
import re
|
|
42
|
+
import shutil
|
|
43
|
+
import tempfile
|
|
44
|
+
from dataclasses import dataclass
|
|
45
|
+
|
|
46
|
+
from tan.core.flash_plan import FlashPlanError
|
|
47
|
+
|
|
48
|
+
#: Top-level directories that are symlinked, not copied: read-only to the sign
|
|
49
|
+
#: step (see the module docstring). Everything else is copied.
|
|
50
|
+
_LINK_DIRS = frozenset({"alif"})
|
|
51
|
+
|
|
52
|
+
#: Directory, relative to the install root, whose contents are key material:
|
|
53
|
+
#: linked into the copy instead of duplicated.
|
|
54
|
+
_KEY_DIR = os.path.join("utils", "key")
|
|
55
|
+
|
|
56
|
+
#: The stock device configuration SETOOLS ships, relative to the install root.
|
|
57
|
+
STOCK_DEVICE_CONFIG_REL = os.path.join("build", "config", "app-device-config.json")
|
|
58
|
+
|
|
59
|
+
#: The DEVICE entry's `version` in the bench recipe
|
|
60
|
+
#: (alp-sdk `scripts/bench/aen/flash-run.sh`).
|
|
61
|
+
DEVICE_ENTRY_VERSION = "0.5.00"
|
|
62
|
+
|
|
63
|
+
#: Registered issue code (`contract/issue-codes.json`) for the refusal below.
|
|
64
|
+
DEVICE_CONFIG_MISSING_CODE = "flash.device-config-missing"
|
|
65
|
+
|
|
66
|
+
_DEVICE_BASENAME_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class DeviceConfigMissingError(FlashPlanError):
|
|
70
|
+
"""No device configuration could be found for the DEVICE ATOC entry.
|
|
71
|
+
`code` is the registered issue code `tan flash` reports for it."""
|
|
72
|
+
|
|
73
|
+
code = DEVICE_CONFIG_MISSING_CODE
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass(frozen=True)
|
|
77
|
+
class DeviceConfig:
|
|
78
|
+
"""The resolved DEVICE-entry source: an absolute `path` (read-only), a human
|
|
79
|
+
`source` naming where it came from, and the `metadata.device` the file itself
|
|
80
|
+
declares (`None` when it does not parse)."""
|
|
81
|
+
|
|
82
|
+
path: str
|
|
83
|
+
source: str
|
|
84
|
+
metadata_device: str | None = None
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def name(self) -> str:
|
|
88
|
+
return os.path.basename(self.path)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def read_metadata_device(path: str) -> str | None:
|
|
92
|
+
"""`metadata.device` out of a device-config JSON, or `None`."""
|
|
93
|
+
try:
|
|
94
|
+
with open(path, encoding="utf-8") as fh:
|
|
95
|
+
doc = json.load(fh)
|
|
96
|
+
device = doc["metadata"]["device"]
|
|
97
|
+
except (OSError, ValueError, KeyError, TypeError):
|
|
98
|
+
return None
|
|
99
|
+
return device if isinstance(device, str) else None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def family_mismatch(metadata_device: str | None, flash_device: str | None) -> str | None:
|
|
103
|
+
"""A warning when the device configuration was written for a different Alif
|
|
104
|
+
family than `flash_device` (the J-Link part profile): the part numbers share
|
|
105
|
+
their first three characters within a family (`AE8...` Ensemble E8). `None`
|
|
106
|
+
when they agree or either is unknown."""
|
|
107
|
+
if not metadata_device or not flash_device:
|
|
108
|
+
return None
|
|
109
|
+
if metadata_device[:3].upper() == flash_device[:3].upper():
|
|
110
|
+
return None
|
|
111
|
+
return (
|
|
112
|
+
f"the device configuration declares device '{metadata_device}' but this slice's J-Link "
|
|
113
|
+
f"part profile is '{flash_device}' -- a different Alif family. The DEVICE entry carries "
|
|
114
|
+
"firewall, clock and boot settings. This is a warning, not a refusal: the metadata may be "
|
|
115
|
+
"stale (the stock SETOOLS file declares an E7 part, yet its compiled blob is byte-identical "
|
|
116
|
+
"to the E8 board's original DEVICE entry). Confirm it is the right file (it is the stock "
|
|
117
|
+
"SETOOLS config unless flash_args.setools_device_config names another)."
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def resolve_device_config(
|
|
122
|
+
explicit: str | None, setools_dir: str, *, entry_id: str
|
|
123
|
+
) -> DeviceConfig:
|
|
124
|
+
"""`flash_args.setools_device_config` (already resolved to an absolute path
|
|
125
|
+
by the caller) when set -- refusing, never falling through to the stock
|
|
126
|
+
file, if it does not exist -- else SETOOLS' own stock
|
|
127
|
+
`build/config/app-device-config.json`. Refuses with
|
|
128
|
+
[`DeviceConfigMissingError`] when neither exists: the DEVICE entry carries the
|
|
129
|
+
firewall/clock/boot configuration a table-replacing ATOC write would
|
|
130
|
+
otherwise delete (tan-cli#1322), so dropping it silently is not an option.
|
|
131
|
+
`--no-device-config` never reaches here."""
|
|
132
|
+
if explicit:
|
|
133
|
+
if not os.path.isfile(explicit):
|
|
134
|
+
raise DeviceConfigMissingError(
|
|
135
|
+
f"alif_mram_jlink[{entry_id}]: flash_args.setools_device_config is "
|
|
136
|
+
f"'{explicit}' but no such file exists -- the DEVICE entry's source "
|
|
137
|
+
"is never silently swapped for the stock config. Fix the path, or "
|
|
138
|
+
"pass --no-device-config to sign an app-only ATOC (a table-replacing "
|
|
139
|
+
"write then deletes the resident DEVICE entry)."
|
|
140
|
+
)
|
|
141
|
+
config = DeviceConfig(explicit, "flash_args.setools_device_config")
|
|
142
|
+
else:
|
|
143
|
+
stock = os.path.join(setools_dir, STOCK_DEVICE_CONFIG_REL)
|
|
144
|
+
if not os.path.isfile(stock):
|
|
145
|
+
raise DeviceConfigMissingError(
|
|
146
|
+
f"alif_mram_jlink[{entry_id}]: no device configuration for the DEVICE "
|
|
147
|
+
f"ATOC entry -- flash_args.setools_device_config is unset and SETOOLS "
|
|
148
|
+
f"ships no '{stock}'. The ATOC write REPLACES the whole table, so a "
|
|
149
|
+
"missing DEVICE entry deletes the resident one (firewall regions, HFXO "
|
|
150
|
+
"trims, SE_BOOT_INFO). Point flash_args.setools_device_config at an "
|
|
151
|
+
"Alif device-config JSON, or pass --no-device-config to knowingly sign "
|
|
152
|
+
"an app-only ATOC."
|
|
153
|
+
)
|
|
154
|
+
config = DeviceConfig(stock, "the stock SETOOLS build/config/app-device-config.json")
|
|
155
|
+
config = DeviceConfig(config.path, config.source, read_metadata_device(config.path))
|
|
156
|
+
if _DEVICE_BASENAME_RE.match(config.name) is None:
|
|
157
|
+
raise DeviceConfigMissingError(
|
|
158
|
+
f"alif_mram_jlink[{entry_id}]: the device configuration file name "
|
|
159
|
+
f"'{config.name}' is not a plain file name (letters, digits, '.', '_', '-')"
|
|
160
|
+
)
|
|
161
|
+
return config
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _link_or_copy(src: str, dst: str) -> None:
|
|
165
|
+
try:
|
|
166
|
+
os.symlink(src, dst, target_is_directory=os.path.isdir(src))
|
|
167
|
+
except (OSError, NotImplementedError):
|
|
168
|
+
if os.path.isdir(src):
|
|
169
|
+
shutil.copytree(src, dst, symlinks=True)
|
|
170
|
+
else:
|
|
171
|
+
shutil.copy2(src, dst)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def make_scratch(setools_dir: str, parent: str | None = None) -> str:
|
|
175
|
+
"""Create a private overlay of `setools_dir` (see the module docstring) and
|
|
176
|
+
return its root. The shared install is only read. Raises `OSError` on a
|
|
177
|
+
filesystem failure; the half-built directory is removed first."""
|
|
178
|
+
root = tempfile.mkdtemp(prefix="tan-setools-", dir=parent)
|
|
179
|
+
try:
|
|
180
|
+
shared_key = os.path.join(setools_dir, _KEY_DIR)
|
|
181
|
+
key_abs = os.path.abspath(shared_key)
|
|
182
|
+
|
|
183
|
+
def _skip_keys(directory: str, names: list[str]) -> list[str]:
|
|
184
|
+
# The signing keys are never copied: a kill between a copy and its removal would
|
|
185
|
+
# leave them in the temp directory (tan-cli#1485). They are linked in below.
|
|
186
|
+
return [n for n in names if os.path.abspath(os.path.join(directory, n)) == key_abs]
|
|
187
|
+
|
|
188
|
+
for entry in os.scandir(setools_dir):
|
|
189
|
+
if entry.name == "build":
|
|
190
|
+
continue
|
|
191
|
+
dst = os.path.join(root, entry.name)
|
|
192
|
+
if entry.is_dir(follow_symlinks=False) and entry.name not in _LINK_DIRS:
|
|
193
|
+
shutil.copytree(entry.path, dst, symlinks=True, ignore=_skip_keys)
|
|
194
|
+
else:
|
|
195
|
+
_link_or_copy(entry.path, dst)
|
|
196
|
+
scratch_utils = os.path.dirname(os.path.join(root, _KEY_DIR))
|
|
197
|
+
if os.path.isdir(shared_key) and os.path.isdir(scratch_utils):
|
|
198
|
+
_link_or_copy(shared_key, os.path.join(root, _KEY_DIR))
|
|
199
|
+
for sub in ("config", "images", "logs"):
|
|
200
|
+
os.makedirs(os.path.join(root, "build", sub))
|
|
201
|
+
except BaseException:
|
|
202
|
+
shutil.rmtree(root, ignore_errors=True)
|
|
203
|
+
raise
|
|
204
|
+
return root
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def cleanup_scratch(path: str | None) -> bool:
|
|
208
|
+
"""Remove a scratch overlay made by [`make_scratch`] -- ONLY the overlay:
|
|
209
|
+
symlinks are unlinked, never followed. `True` when nothing is left."""
|
|
210
|
+
if not path:
|
|
211
|
+
return True
|
|
212
|
+
shutil.rmtree(path, ignore_errors=True)
|
|
213
|
+
return not os.path.exists(path)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
# ── the ATOC report ─────────────────────────────────────────────────────────
|
|
217
|
+
|
|
218
|
+
_TOC_ENTRY_RE = re.compile(
|
|
219
|
+
r"APP TOC entry for\s+(\S+)\s+obj_address\s+(0x[0-9A-Fa-f]+)"
|
|
220
|
+
)
|
|
221
|
+
_PACKAGE_SIZE_RE = re.compile(r"APP Package total size:\s*(\d+)\s*bytes")
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@dataclass(frozen=True)
|
|
225
|
+
class AtocReport:
|
|
226
|
+
"""What `app-package-map.txt` says about the package this run built:
|
|
227
|
+
`entries` are `(name, obj_address)` per `APP TOC entry for` line (empty for
|
|
228
|
+
a report that carries none), `package_size` the reported total in bytes."""
|
|
229
|
+
|
|
230
|
+
entries: tuple[tuple[str, str], ...] = ()
|
|
231
|
+
package_size: int | None = None
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def parse_atoc_report(text: str) -> AtocReport:
|
|
235
|
+
"""Parse the entry list and total size out of an `app-package-map.txt`. The
|
|
236
|
+
scratch report is fresh (one block), so every match belongs to this run."""
|
|
237
|
+
# `app-gen-toc` pads the name field with NULs ("DEVICE\x00\x00"); they are not
|
|
238
|
+
# part of the name (bench round 6).
|
|
239
|
+
entries = tuple(
|
|
240
|
+
(m.group(1).replace("\x00", ""), m.group(2)) for m in _TOC_ENTRY_RE.finditer(text)
|
|
241
|
+
)
|
|
242
|
+
sizes = _PACKAGE_SIZE_RE.findall(text)
|
|
243
|
+
return AtocReport(entries=entries, package_size=int(sizes[-1]) if sizes else None)
|
tan/core/shapes.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Two questions four command modules each answered with their own private
|
|
3
|
+
copy: "is this directory an alp-sdk checkout?" and "what shape is this YAML
|
|
4
|
+
value?" (tan-cli#408).
|
|
5
|
+
|
|
6
|
+
Neither is domain logic worth four implementations. `_is_sdk_root` had two
|
|
7
|
+
(`build_cmd.py`, `flash_cmd.py`) and `_yaml_kind` two
|
|
8
|
+
(`diff_cmd.py`, `pinmux_cmd.py`), and the copies had already drifted in TYPE
|
|
9
|
+
-- one took `str`, one took `Path` -- which is exactly how a "same" helper
|
|
10
|
+
stops being the same one.
|
|
11
|
+
|
|
12
|
+
Lives under `tan.core` rather than beside any one caller because
|
|
13
|
+
`tan/commands/*` import each other freely and `build_cmd` already imports
|
|
14
|
+
`sdk_cmd` at module level; a shared helper hosted in either would be a new
|
|
15
|
+
edge in that graph. `tan.core` imports no command module, so this direction
|
|
16
|
+
cannot cycle.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
#: THE marker for an alp-sdk checkout (I-31), as path segments so callers can
|
|
25
|
+
#: join it either way. THE single spelling: `build_cmd` and, since
|
|
26
|
+
#: tan-cli#815, `sdk_cmd` both re-export this one rather than holding a
|
|
27
|
+
#: literal of their own. Relocating `scripts/alp_project.py` is a one-line
|
|
28
|
+
#: change here, which is the whole point -- a second literal meant missing it
|
|
29
|
+
#: silently broke SDK resolution in five commands.
|
|
30
|
+
SDK_MARKER = ("scripts", "alp_project.py")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def is_sdk_root(path: Path | str) -> bool:
|
|
34
|
+
"""Whether `path` is an alp-sdk checkout -- port of `util.rs::
|
|
35
|
+
has_loader_script`, and INCAPABLE OF RAISING.
|
|
36
|
+
|
|
37
|
+
Accepts `Path` or `str` because the copies this replaces disagreed:
|
|
38
|
+
`build_cmd`'s took a `Path`, `flash_cmd`'s took a `str`. Callers keep
|
|
39
|
+
whichever they already hold rather than converting at
|
|
40
|
+
every site.
|
|
41
|
+
|
|
42
|
+
tan-cli#408 asks for a deliberate decision on the `except (OSError,
|
|
43
|
+
ValueError)` guard the string-based copy carried, so: **it stays**,
|
|
44
|
+
and the reason is that this is a PRE-FLIGHT guard. Every caller is asking
|
|
45
|
+
"may I use this?" in a command whose whole job is to answer with an
|
|
46
|
+
envelope; a path with an embedded NUL or an unreadable parent must read
|
|
47
|
+
as "not an SDK root", never as a traceback where a refusal belongs.
|
|
48
|
+
|
|
49
|
+
Measured on this tree's interpreter (CPython 3.14.6), both
|
|
50
|
+
`Path.is_file()` and `os.path.isfile()` already return `False` rather
|
|
51
|
+
than raising on an embedded NUL, so the guard catches nothing there
|
|
52
|
+
today. It is kept anyway: `requires-python = ">=3.12"`, the floor was not
|
|
53
|
+
measured here, and a guard that costs one `try` is not worth trading for
|
|
54
|
+
an assumption about an interpreter nobody ran.
|
|
55
|
+
"""
|
|
56
|
+
try:
|
|
57
|
+
return os.path.isfile(os.path.join(str(path), *SDK_MARKER))
|
|
58
|
+
except (OSError, ValueError):
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def is_file(path: Path | str) -> bool:
|
|
63
|
+
"""`os.path.isfile`, INCAPABLE OF RAISING -- the same pre-flight contract
|
|
64
|
+
`is_sdk_root` above documents at length.
|
|
65
|
+
|
|
66
|
+
Four private copies preceded this one (tan-cli#815): `size_cmd`,
|
|
67
|
+
`image_cmd` and `flash_cmd` each held a byte-identical `str` version, and
|
|
68
|
+
`bootstrap_cmd`'s took a `Path` -- the TYPE drift this module's docstring
|
|
69
|
+
was written about, so the signature is `Path | str` for the same reason
|
|
70
|
+
`is_sdk_root`'s is: callers keep whichever they already hold.
|
|
71
|
+
|
|
72
|
+
Its narrower `except OSError` (the others caught `(OSError, ValueError)`)
|
|
73
|
+
was INERT: `pathlib` catches `ValueError` inside `Path.is_file()`, so
|
|
74
|
+
there was nothing to miss. Measured, not assumed -- an earlier draft of
|
|
75
|
+
tan-cli#815 called that half a behaviour change and it was not. The type
|
|
76
|
+
change does move one input: `Path('')` normalises to `Path('.')`, so the
|
|
77
|
+
old `_is_dir("")` answered `True` where this answers `False`. Unreachable
|
|
78
|
+
from its call sites, every one of which passes a join.
|
|
79
|
+
|
|
80
|
+
All three `str` callers read `build/system-manifest.yaml`, whose values
|
|
81
|
+
are manifest-supplied strings that may carry an embedded NUL or an
|
|
82
|
+
overlong component. Such a path is "not a file", never an exception
|
|
83
|
+
escaping the envelope.
|
|
84
|
+
"""
|
|
85
|
+
try:
|
|
86
|
+
return os.path.isfile(path)
|
|
87
|
+
except (OSError, ValueError):
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def is_dir(path: Path | str) -> bool:
|
|
92
|
+
"""`os.path.isdir`, INCAPABLE OF RAISING -- `is_file`'s sibling, same
|
|
93
|
+
contract and same two prior copies (`image_cmd` took `str` and caught
|
|
94
|
+
`(OSError, ValueError)`, `bootstrap_cmd` took `Path` and caught `OSError`).
|
|
95
|
+
|
|
96
|
+
NOT the same question as `presets_cmd._is_dir` or
|
|
97
|
+
`examples_cmd._is_dir_no_follow`, which take an `os.DirEntry` and differ
|
|
98
|
+
on whether symlinks are followed. Those are a different predicate with a
|
|
99
|
+
deliberate divergence recorded at each site; they are not copies of this
|
|
100
|
+
and must not be folded into it.
|
|
101
|
+
"""
|
|
102
|
+
try:
|
|
103
|
+
return os.path.isdir(path)
|
|
104
|
+
except (OSError, ValueError):
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def matches_glob_suffix(name: str, *suffixes: str) -> bool:
|
|
109
|
+
"""True if @name ends in any of @suffixes -- case-SENSITIVELY on POSIX,
|
|
110
|
+
case-INSENSITIVELY on Windows, matching `Path.glob`'s own
|
|
111
|
+
`case_sensitive=None` default (platform casing rules) exactly.
|
|
112
|
+
|
|
113
|
+
THE one spelling of the casing rule every `Path.glob("*.<ext>")` ->
|
|
114
|
+
`os.listdir`/`os.scandir` swap in this tree needs (tan-cli#1127 review
|
|
115
|
+
round 2, then tan-cli#1132). Swapping a glob for a listing plus a plain
|
|
116
|
+
`name.endswith(suffix)` silently NARROWS the match on Windows: a
|
|
117
|
+
`Foo.YAML` board file, or a `prj.CONF` Kconfig fragment, that `glob`
|
|
118
|
+
used to enumerate there becomes invisible, and nothing at the call site
|
|
119
|
+
announces the change. Three call sites share this one rule rather than
|
|
120
|
+
re-deriving it per swap -- `new_som_cmd._is_yaml_board_file`,
|
|
121
|
+
`commands/build/configure_inputs`'s fragment/overlay walk, and
|
|
122
|
+
`model/analyze._resolve_table`'s support-table listing.
|
|
123
|
+
|
|
124
|
+
@suffixes are case-folded here, so a caller may spell them either way;
|
|
125
|
+
what varies by platform is only whether @name is folded to match.
|
|
126
|
+
"""
|
|
127
|
+
if os.name == "nt":
|
|
128
|
+
return name.lower().endswith(tuple(s.lower() for s in suffixes))
|
|
129
|
+
return name.endswith(suffixes)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def rejected_sdk_root_message(sdk_root: str, consequence: str) -> str:
|
|
133
|
+
"""The `<command>.sdk-root-unresolved` message for a `--sdk-root` the
|
|
134
|
+
loader-marker check REJECTED, naming the value the caller typed.
|
|
135
|
+
|
|
136
|
+
`--sdk-root` is TERMINAL (I-31), so a path without `scripts/alp_project.py`
|
|
137
|
+
resolves to nothing rather than falling through to a lower tier. Five
|
|
138
|
+
commands (`generate`, `model build`, `new-som`, `pinmux`, `validate`) still
|
|
139
|
+
dropped the rejected path on the floor, and four of those (all but
|
|
140
|
+
`pinmux`) answered with the SAME string they use when no flag was given at
|
|
141
|
+
all -- "Use --sdk-root, place the project near an alp-sdk checkout, ...",
|
|
142
|
+
i.e. recommending the flag the caller had just passed -- with the failing
|
|
143
|
+
value nowhere in the envelope and nowhere in the stderr text either.
|
|
144
|
+
`pinmux` used a message of its own with the same gap (tan-cli#497 defect
|
|
145
|
+
7). A user who typos `--sdk-root ~/alp-sdk-typo` was told to pass
|
|
146
|
+
`--sdk-root`, and could not see WHICH path had been rejected or why, on the
|
|
147
|
+
one surface that knew both.
|
|
148
|
+
|
|
149
|
+
`consequence` is the caller's own "and so this is what you got instead"
|
|
150
|
+
clause, because it differs per command (no pinmux table read, nothing
|
|
151
|
+
generated, nothing validated) and is the half a reader acts on. The
|
|
152
|
+
remediation clause is deliberately NOT carried into it: naming the rejected
|
|
153
|
+
path IS the remediation here.
|
|
154
|
+
|
|
155
|
+
The no-flag branch is deliberately left alone at every call site. There is
|
|
156
|
+
no typed value to name on it, and for `presets` its exact string is
|
|
157
|
+
byte-pinned by the `presets-no-sdk` golden envelope.
|
|
158
|
+
|
|
159
|
+
tan-cli#620 landed a same-named helper in `tan/commands/sdk_cmd.py` with
|
|
160
|
+
a byte-identical body, and the note here asking for the two to be
|
|
161
|
+
collapsed sat unexecuted for four releases (tan-cli#815). They are one
|
|
162
|
+
function now: `sdk_cmd` imports this one. The direction is the only one
|
|
163
|
+
that cannot cycle -- `tan.core` imports no command module (see the module
|
|
164
|
+
docstring).
|
|
165
|
+
"""
|
|
166
|
+
marker = "/".join(SDK_MARKER)
|
|
167
|
+
return (
|
|
168
|
+
f'alp-sdk root is unresolved: --sdk-root "{sdk_root}" is not an alp-sdk '
|
|
169
|
+
f"checkout ({marker} not found under it). {consequence}"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def yaml_kind(value: Any) -> str:
|
|
174
|
+
"""A short YAML-ish type name for an error message -- not a claim of
|
|
175
|
+
matching serde's exact wording (see `diff_cmd`'s module docstring for the
|
|
176
|
+
scope note this inherits).
|
|
177
|
+
|
|
178
|
+
`bool` is tested BEFORE `int`, and that order is load-bearing rather than
|
|
179
|
+
stylistic: `bool` is a subclass of `int` in Python, so the reverse order
|
|
180
|
+
reports every `true`/`false` in a board.yaml as "a number".
|
|
181
|
+
"""
|
|
182
|
+
if value is None:
|
|
183
|
+
return "null"
|
|
184
|
+
if isinstance(value, bool):
|
|
185
|
+
return "a boolean"
|
|
186
|
+
if isinstance(value, (int, float)):
|
|
187
|
+
return "a number"
|
|
188
|
+
if isinstance(value, str):
|
|
189
|
+
return "a string"
|
|
190
|
+
if isinstance(value, list):
|
|
191
|
+
return "a sequence"
|
|
192
|
+
if isinstance(value, dict):
|
|
193
|
+
return "a mapping"
|
|
194
|
+
return type(value).__name__
|