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,237 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Method-aware inputs for `tan doctor`'s `setools` check (tan-cli#1323).
|
|
3
|
+
|
|
4
|
+
The check used to assert, for every host, that "AEN MRAM flashing (`west
|
|
5
|
+
flash`, the alif_flash runner) will fail" without `$SE_UART`. That is Flow A
|
|
6
|
+
only. A planner-emitted AEN manifest dispatches Flow D (`alif_mram_jlink`):
|
|
7
|
+
J-Link over SWD, SETOOLS needed solely to sign the ATOC (`app-gen-toc`), no
|
|
8
|
+
SE-UART at all. So the check must know which method `tan flash` would pick.
|
|
9
|
+
|
|
10
|
+
This module answers that question by calling `tan.core.flash_plan.
|
|
11
|
+
select_flash_method` -- the SAME resolver `tan flash` dispatches with -- on
|
|
12
|
+
each slice of the project's `build/system-manifest.yaml`, never by
|
|
13
|
+
re-deriving the Flow D rule. Pure/IO split: `flash_methods_for_manifest_text`
|
|
14
|
+
is pure; `project_flash_methods` does the one read. Neither raises.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
from tan.core.setools import find_app_gen_toc, resolve_setools_dir
|
|
24
|
+
|
|
25
|
+
from tan.core.flash_plan import FLOW_D_METHOD, FlashTarget, parse_system_manifest, select_flash_method
|
|
26
|
+
|
|
27
|
+
#: The method `select_flash_method` leaves a Flow A entry on: no `FLOW_D_KEYS`,
|
|
28
|
+
#: so `west flash` picks the board's default runner (`alif_flash`, SE-UART).
|
|
29
|
+
FLOW_A_METHOD = "zephyr_west_flash"
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"FLOW_A_METHOD",
|
|
33
|
+
"FLOW_D_METHOD",
|
|
34
|
+
"flash_methods_for_manifest_text",
|
|
35
|
+
"ProjectFlash",
|
|
36
|
+
"project_flash",
|
|
37
|
+
"project_flash_methods",
|
|
38
|
+
"verdict",
|
|
39
|
+
"signing_problems",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def flash_methods_for_manifest_text(text: str) -> frozenset[str] | None:
|
|
44
|
+
"""The set of `flash_method`s `tan flash` would dispatch for the slices of
|
|
45
|
+
this `system-manifest.yaml`, or `None` when the text cannot be parsed.
|
|
46
|
+
|
|
47
|
+
Slices only: a helper MCU's method is never a SETOOLS path."""
|
|
48
|
+
try:
|
|
49
|
+
manifest = parse_system_manifest(text)
|
|
50
|
+
except Exception: # noqa: BLE001 -- a doctor probe must not raise
|
|
51
|
+
return None
|
|
52
|
+
methods = set()
|
|
53
|
+
for s in manifest.slices:
|
|
54
|
+
method = select_flash_method(
|
|
55
|
+
FlashTarget(
|
|
56
|
+
kind="slice", id=s.core_id, flash_method=s.flash_method, flash_args=s.flash_args
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
if method:
|
|
60
|
+
methods.add(method)
|
|
61
|
+
return frozenset(methods)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def project_flash_methods(board_yaml: str | None) -> frozenset[str] | None:
|
|
65
|
+
"""Methods for the project owning `board_yaml`, read from its
|
|
66
|
+
`build/system-manifest.yaml`. `None` when there is no project, no built
|
|
67
|
+
manifest yet, or it is unreadable/invalid -- the caller then phrases the
|
|
68
|
+
verdict per method instead of guessing one."""
|
|
69
|
+
if board_yaml is None:
|
|
70
|
+
return None
|
|
71
|
+
try:
|
|
72
|
+
text = (Path(board_yaml).parent / "build" / "system-manifest.yaml").read_text(
|
|
73
|
+
encoding="utf-8"
|
|
74
|
+
)
|
|
75
|
+
except (OSError, ValueError):
|
|
76
|
+
return None
|
|
77
|
+
return flash_methods_for_manifest_text(text)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def signing_problems(
|
|
81
|
+
setools_dir: str | None,
|
|
82
|
+
executables: tuple[str, ...],
|
|
83
|
+
find_app_gen_toc,
|
|
84
|
+
require_exec: bool = False,
|
|
85
|
+
) -> list[str]:
|
|
86
|
+
"""What stops SETOOLS from signing/writing: `$SETOOLS_DIR` unset, or a
|
|
87
|
+
directory lacking any of `executables`. `app-gen-toc` is located through
|
|
88
|
+
`find_app_gen_toc` (the `.exe`-aware lookup `tan flash` uses); with
|
|
89
|
+
`require_exec` it must also be executable on POSIX (Flow D spawns it)."""
|
|
90
|
+
if not setools_dir:
|
|
91
|
+
return [
|
|
92
|
+
"$SETOOLS_DIR is unset (the Alif Security Toolkit is license-gated and "
|
|
93
|
+
"NOT redistributed by alp-sdk)"
|
|
94
|
+
]
|
|
95
|
+
absent: list[str] = []
|
|
96
|
+
for exe in executables:
|
|
97
|
+
try:
|
|
98
|
+
if exe == "app-gen-toc":
|
|
99
|
+
found = find_app_gen_toc(setools_dir)
|
|
100
|
+
ok = found is not None and (
|
|
101
|
+
not require_exec or os.name == "nt" or os.access(found, os.X_OK)
|
|
102
|
+
)
|
|
103
|
+
else:
|
|
104
|
+
ok = (Path(setools_dir) / exe).is_file()
|
|
105
|
+
except OSError:
|
|
106
|
+
ok = False
|
|
107
|
+
if not ok:
|
|
108
|
+
absent.append(exe)
|
|
109
|
+
if absent:
|
|
110
|
+
return [
|
|
111
|
+
f"$SETOOLS_DIR=`{setools_dir}` does not look like an "
|
|
112
|
+
f"app-release-exec-linux directory (no {', '.join(absent)}{' executable' if require_exec else ''})"
|
|
113
|
+
]
|
|
114
|
+
return []
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
#: Said wherever doctor reports a SETOOLS verdict: doctor cannot see per-run
|
|
118
|
+
#: arguments, so a flag given only to `tan flash` is outside its view.
|
|
119
|
+
FLAG_NOTE = (
|
|
120
|
+
"doctor reads $SETOOLS_DIR and the built manifest's flash_args.setools_dir; "
|
|
121
|
+
"a --setools-dir flag given to `tan flash` is not visible to it"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass(frozen=True)
|
|
126
|
+
class ProjectFlash:
|
|
127
|
+
"""What the project's built manifest says about flashing: the dispatched
|
|
128
|
+
`methods` and the first `flash_args.setools_dir` a slice carries."""
|
|
129
|
+
|
|
130
|
+
methods: frozenset[str]
|
|
131
|
+
setools_dir: str | None = None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def project_flash(board_yaml: str | None) -> ProjectFlash | None:
|
|
135
|
+
"""[`project_flash_methods`] plus the manifest's `setools_dir`, resolved
|
|
136
|
+
with `tan.core.setools.resolve_setools_dir` (empty env, no flag) so it
|
|
137
|
+
reads the key exactly as `tan flash` does.
|
|
138
|
+
|
|
139
|
+
**Assumption:** the manifest lives at `<board.yaml dir>/build/
|
|
140
|
+
system-manifest.yaml`, the default `tan flash`/`tan build` use. A custom
|
|
141
|
+
`--build-root` is invisible here, and doctor then falls back to per-method
|
|
142
|
+
wording. `None` when no project or no readable manifest."""
|
|
143
|
+
methods = project_flash_methods(board_yaml)
|
|
144
|
+
if methods is None or board_yaml is None:
|
|
145
|
+
return None
|
|
146
|
+
setools_dir = None
|
|
147
|
+
try:
|
|
148
|
+
text = (Path(board_yaml).parent / "build" / "system-manifest.yaml").read_text(
|
|
149
|
+
encoding="utf-8"
|
|
150
|
+
)
|
|
151
|
+
for s in parse_system_manifest(text).slices:
|
|
152
|
+
found = resolve_setools_dir(s.flash_args, {}, None)
|
|
153
|
+
if found is not None:
|
|
154
|
+
setools_dir = found.path
|
|
155
|
+
break
|
|
156
|
+
except Exception: # noqa: BLE001 -- a doctor probe must not raise
|
|
157
|
+
setools_dir = None
|
|
158
|
+
return ProjectFlash(methods, setools_dir)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def verdict(
|
|
162
|
+
setools_dir: str | None,
|
|
163
|
+
setools_source: str,
|
|
164
|
+
se_uart: str | None,
|
|
165
|
+
flash_methods: frozenset[str] | None,
|
|
166
|
+
jlink_found: bool | None,
|
|
167
|
+
bundle: str,
|
|
168
|
+
executables: tuple[str, ...],
|
|
169
|
+
is_linux: bool = True,
|
|
170
|
+
) -> tuple[str, str, str | None]:
|
|
171
|
+
"""`(status, detail, fix)` for the `setools` check, per flash method."""
|
|
172
|
+
flow_d = flash_methods is not None and FLOW_D_METHOD in flash_methods
|
|
173
|
+
flow_a = flash_methods is not None and FLOW_A_METHOD in flash_methods
|
|
174
|
+
only_gen_toc = not flow_a
|
|
175
|
+
# Flow A's `alif_flash` runner hard-codes `app-release-exec-linux`; Flow D
|
|
176
|
+
# (J-Link + SETOOLS signing, `app-gen-toc.exe` on Windows) is not Linux-only.
|
|
177
|
+
linux_note = (
|
|
178
|
+
""
|
|
179
|
+
if is_linux or not (flow_a or flash_methods is None)
|
|
180
|
+
else " Flow A (`west flash`, alif_flash) is Linux-only in this tree -- run it "
|
|
181
|
+
"from WSL2/Linux."
|
|
182
|
+
)
|
|
183
|
+
signing = signing_problems(
|
|
184
|
+
setools_dir,
|
|
185
|
+
("app-gen-toc",) if only_gen_toc else executables,
|
|
186
|
+
find_app_gen_toc,
|
|
187
|
+
require_exec=flow_d,
|
|
188
|
+
)
|
|
189
|
+
d_problems = list(signing)
|
|
190
|
+
if flow_d and jlink_found is False:
|
|
191
|
+
d_problems.append("no J-Link tool (JLinkExe/JLink) on PATH or in the workspace venv")
|
|
192
|
+
a_problems = list(signing)
|
|
193
|
+
if flow_a and not se_uart:
|
|
194
|
+
a_problems.append(
|
|
195
|
+
"$SE_UART is unset (the SE-UART device: Linux /dev/ttyUSB*, macOS "
|
|
196
|
+
"/dev/cu.usbserial-*, a passed-through COM under WSL)"
|
|
197
|
+
)
|
|
198
|
+
if flash_methods is None:
|
|
199
|
+
problems = signing
|
|
200
|
+
lead = (
|
|
201
|
+
"AEN MRAM flashing (Flow D `alif_mram_jlink`, the planner default, and "
|
|
202
|
+
"Flow A `west flash`) cannot sign an ATOC: "
|
|
203
|
+
)
|
|
204
|
+
joined = "; ".join(problems)
|
|
205
|
+
else:
|
|
206
|
+
problems = []
|
|
207
|
+
if flow_d and d_problems:
|
|
208
|
+
problems.append("Flow D (`alif_mram_jlink`): " + "; ".join(d_problems))
|
|
209
|
+
if flow_a and a_problems:
|
|
210
|
+
problems.append("Flow A (`west flash`, the alif_flash runner): " + "; ".join(a_problems))
|
|
211
|
+
lead = "AEN MRAM flashing will fail: "
|
|
212
|
+
joined = " | ".join(problems)
|
|
213
|
+
if not problems:
|
|
214
|
+
tools = "app-gen-toc" if only_gen_toc else "/".join(executables)
|
|
215
|
+
ready = f"SETOOLS ready: SETOOLS dir `{setools_dir}` (from {setools_source}) has {tools}"
|
|
216
|
+
if flash_methods is None:
|
|
217
|
+
ready += (
|
|
218
|
+
". Flow D (`alif_mram_jlink`) needs only this plus a J-Link (see the "
|
|
219
|
+
"`jlink` check); Flow A (`west flash`) also needs $SE_UART "
|
|
220
|
+
+ (f"(`{se_uart}`, set)." if se_uart else "(currently unset).")
|
|
221
|
+
)
|
|
222
|
+
elif flow_a:
|
|
223
|
+
ready += f", $SE_UART=`{se_uart}`."
|
|
224
|
+
else:
|
|
225
|
+
ready += " and a J-Link is available (Flow D needs no SE-UART)."
|
|
226
|
+
return "pass", ready + linux_note + " (" + FLAG_NOTE + ".)", None
|
|
227
|
+
fix = (
|
|
228
|
+
f"Download the Alif Security Toolkit (`{bundle}`) from the Alif "
|
|
229
|
+
"developer portal -- it is license-gated and alp-sdk does not "
|
|
230
|
+
"redistribute it -- then `export SETOOLS_DIR=<...>/app-release-exec-linux`. "
|
|
231
|
+
"Flow A (`west flash`) additionally needs `export SE_UART=/dev/ttyUSB0` "
|
|
232
|
+
"(your SE-UART device); Flow D does not. See docs/aen-bench-bringup.md. "
|
|
233
|
+
+ FLAG_NOTE[0].upper()
|
|
234
|
+
+ FLAG_NOTE[1:]
|
|
235
|
+
+ "."
|
|
236
|
+
)
|
|
237
|
+
return "warn", lead + joined + "." + linux_note, fix
|
tan/core/doctor_stale.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""`tan doctor`'s stale-install verdict: is the `tan` that is RUNNING behind
|
|
3
|
+
the source it was installed from?
|
|
4
|
+
|
|
5
|
+
Motivating case: a `pipx install git+https://github.com/alplabai/tan-cli.git@dev#subdirectory=python`
|
|
6
|
+
taken on one day keeps answering `tan 0.7.0` after `dev` has moved on, and
|
|
7
|
+
nothing says so -- agents kept using it unknowingly.
|
|
8
|
+
|
|
9
|
+
The question is answered from the install's own provenance record, PEP 610's
|
|
10
|
+
`direct_url.json` in the `tan-cli` dist-info, in order of cost:
|
|
11
|
+
|
|
12
|
+
* **path / editable install** (`file://` url): offline. Read the source
|
|
13
|
+
tree's `tan/version.py` `TAN_VERSION` and compare with the running one.
|
|
14
|
+
* **VCS install** (`vcs_info`): ONE `git ls-remote <url> <branch>` with a short
|
|
15
|
+
timeout, compared with the installed `commit_id`. Pinned installs (a tag or
|
|
16
|
+
a sha, which `refs/heads/<rev>` does not resolve) are intentional and pass.
|
|
17
|
+
* anything else (wheel/PyPI/frozen binary): no provenance, no verdict.
|
|
18
|
+
|
|
19
|
+
Never raises and never fails doctor: offline/timeout/no-git degrades to
|
|
20
|
+
`None` ("unable to check"), which the caller renders as a pass.
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import re
|
|
26
|
+
import urllib.parse
|
|
27
|
+
from dataclasses import dataclass
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
from tan.core.probe import probe
|
|
31
|
+
|
|
32
|
+
#: Network ceiling for the one `ls-remote`. Doctor must stay snappy offline.
|
|
33
|
+
LS_REMOTE_TIMEOUT_S = 4
|
|
34
|
+
|
|
35
|
+
_VERSION_RE = re.compile(r'^TAN_VERSION\s*=\s*"([^"]+)"', re.MULTILINE)
|
|
36
|
+
_SHA_RE = re.compile(r"^[0-9a-f]{40}$")
|
|
37
|
+
DIST_NAMES = ("tan-cli", "tan_cli", "tan")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class InstallOrigin:
|
|
42
|
+
kind: str # "path" | "vcs"
|
|
43
|
+
url: str
|
|
44
|
+
commit: str | None = None # vcs: installed commit
|
|
45
|
+
revision: str | None = None # vcs: requested branch/tag/sha
|
|
46
|
+
subdirectory: str | None = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True)
|
|
50
|
+
class StaleVerdict:
|
|
51
|
+
detail: str
|
|
52
|
+
fix: str
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def read_install_origin(direct_url_text: str | None) -> InstallOrigin | None:
|
|
56
|
+
"""Parse a PEP 610 `direct_url.json` body; `None` if absent/unusable."""
|
|
57
|
+
if not direct_url_text:
|
|
58
|
+
return None
|
|
59
|
+
try:
|
|
60
|
+
data = json.loads(direct_url_text)
|
|
61
|
+
except ValueError:
|
|
62
|
+
return None
|
|
63
|
+
if not isinstance(data, dict) or not isinstance(data.get("url"), str):
|
|
64
|
+
return None
|
|
65
|
+
url = data["url"]
|
|
66
|
+
sub = data.get("subdirectory")
|
|
67
|
+
sub = sub if isinstance(sub, str) else None
|
|
68
|
+
vcs = data.get("vcs_info")
|
|
69
|
+
if isinstance(vcs, dict) and vcs.get("vcs") == "git":
|
|
70
|
+
commit = vcs.get("commit_id")
|
|
71
|
+
rev = vcs.get("requested_revision")
|
|
72
|
+
return InstallOrigin(
|
|
73
|
+
"vcs",
|
|
74
|
+
url,
|
|
75
|
+
commit if isinstance(commit, str) else None,
|
|
76
|
+
rev if isinstance(rev, str) else None,
|
|
77
|
+
sub,
|
|
78
|
+
)
|
|
79
|
+
if url.startswith("file:"):
|
|
80
|
+
return InstallOrigin("path", url, subdirectory=sub)
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def installed_direct_url_text() -> str | None:
|
|
85
|
+
"""The `direct_url.json` of the running tan's dist, or `None`."""
|
|
86
|
+
from importlib import metadata
|
|
87
|
+
|
|
88
|
+
for name in DIST_NAMES:
|
|
89
|
+
try:
|
|
90
|
+
return metadata.distribution(name).read_text("direct_url.json")
|
|
91
|
+
except metadata.PackageNotFoundError:
|
|
92
|
+
continue
|
|
93
|
+
except OSError:
|
|
94
|
+
return None
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def parse_tan_version(version_py_text: str) -> str | None:
|
|
99
|
+
m = _VERSION_RE.search(version_py_text)
|
|
100
|
+
return m.group(1) if m else None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def source_version(origin: InstallOrigin) -> str | None:
|
|
104
|
+
"""`TAN_VERSION` of a `file://` source tree (offline); `None` if unreadable."""
|
|
105
|
+
from urllib.request import url2pathname
|
|
106
|
+
|
|
107
|
+
root = Path(url2pathname(urllib.parse.urlparse(origin.url).path))
|
|
108
|
+
for rel in (origin.subdirectory or "", "python", ""):
|
|
109
|
+
try:
|
|
110
|
+
text = (root / rel / "tan" / "version.py").read_text(encoding="utf-8")
|
|
111
|
+
except OSError:
|
|
112
|
+
continue
|
|
113
|
+
return parse_tan_version(text)
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def remote_head(origin: InstallOrigin, git_exe: str | None) -> str | None:
|
|
118
|
+
"""The commit `origin.revision` (default `HEAD`) points at on the remote;
|
|
119
|
+
`None` when it cannot be learned or the revision is not a branch (a tag/sha
|
|
120
|
+
pin) -- either way there is no "newer" to report."""
|
|
121
|
+
if git_exe is None or (origin.revision and _SHA_RE.match(origin.revision)):
|
|
122
|
+
return None
|
|
123
|
+
ref = f"refs/heads/{origin.revision}" if origin.revision else "HEAD"
|
|
124
|
+
out = probe([git_exe, "ls-remote", origin.url, ref], timeout=LS_REMOTE_TIMEOUT_S)
|
|
125
|
+
if not out:
|
|
126
|
+
return None
|
|
127
|
+
sha = out.split(None, 1)[0].strip().lower()
|
|
128
|
+
return sha if _SHA_RE.match(sha) else None
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def reinstall_command(origin: InstallOrigin) -> str:
|
|
132
|
+
spec = origin.url if origin.url.startswith("git+") else f"git+{origin.url}"
|
|
133
|
+
if origin.revision:
|
|
134
|
+
spec += f"@{origin.revision}"
|
|
135
|
+
if origin.subdirectory:
|
|
136
|
+
spec += f"#subdirectory={origin.subdirectory}"
|
|
137
|
+
return f'pipx install --force "{spec}"'
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def stale_verdict(
|
|
141
|
+
running_version: str,
|
|
142
|
+
origin: InstallOrigin | None,
|
|
143
|
+
*,
|
|
144
|
+
source_ver: str | None = None,
|
|
145
|
+
remote_commit: str | None = None,
|
|
146
|
+
) -> StaleVerdict | None:
|
|
147
|
+
"""Pure verdict. `None` = not behind, or unable to tell."""
|
|
148
|
+
if origin is None:
|
|
149
|
+
return None
|
|
150
|
+
if origin.kind == "path":
|
|
151
|
+
if source_ver is None or source_ver == running_version:
|
|
152
|
+
return None
|
|
153
|
+
return StaleVerdict(
|
|
154
|
+
f"installed tan is {running_version} but the source it was installed from "
|
|
155
|
+
f"({origin.url}) is {source_ver} -- reinstall to pick it up",
|
|
156
|
+
"reinstall from that source (pipx install --force <source>/python)",
|
|
157
|
+
)
|
|
158
|
+
if origin.commit and remote_commit and origin.commit != remote_commit:
|
|
159
|
+
rev = origin.revision or "HEAD"
|
|
160
|
+
return StaleVerdict(
|
|
161
|
+
f"installed tan {running_version} was built from {origin.commit[:8]} but "
|
|
162
|
+
f"{rev} is now {remote_commit[:8]} -- this install is behind",
|
|
163
|
+
reinstall_command(origin),
|
|
164
|
+
)
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def running_tan_verdict(version: str, git_exe: str | None, offline: bool) -> StaleVerdict | None:
|
|
169
|
+
"""IO half: read the running tan's provenance and judge it. `offline`
|
|
170
|
+
skips the one `git ls-remote`. Cannot raise."""
|
|
171
|
+
try:
|
|
172
|
+
origin = read_install_origin(installed_direct_url_text())
|
|
173
|
+
if origin is None:
|
|
174
|
+
return None
|
|
175
|
+
if origin.kind == "path":
|
|
176
|
+
return stale_verdict(version, origin, source_ver=source_version(origin))
|
|
177
|
+
remote = None if offline else remote_head(origin, git_exe)
|
|
178
|
+
return stale_verdict(version, origin, remote_commit=remote)
|
|
179
|
+
except Exception: # noqa: BLE001 -- a doctor probe must never traceback
|
|
180
|
+
return None
|