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,363 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""`tan model zoo` and `tan model add <id>` (tan-cli#1286) -- the runners and
|
|
3
|
+
text renderers, kept out of `model_cmd.py` (module-size budget); that file
|
|
4
|
+
still owns the typer surface, the SDK-root/board.yaml preamble and `finish()`.
|
|
5
|
+
|
|
6
|
+
**`zoo [--sku SKU]`** lists the entries in the bound SDK's
|
|
7
|
+
`metadata/model_zoo/`, optionally only those bench-validated on `SKU` (an empty
|
|
8
|
+
`validated_soms` matches nothing -- `tan.core.model_zoo`). Read-only.
|
|
9
|
+
|
|
10
|
+
**`add <id>`** copies one entry into the project: it fetches the model (a
|
|
11
|
+
bundled starter, or a download verified against the manifest's sha256), writes
|
|
12
|
+
it to `models/<id><ext>` beside `board.yaml`, and appends
|
|
13
|
+
`{name: <id>, source: models/<id><ext>[, compile: ...]}` to `models:` with
|
|
14
|
+
`tan.core.board_yaml_edit` -- a text splice that keeps every other byte of the
|
|
15
|
+
file. Everything is computed before anything is written (so a refused edit
|
|
16
|
+
leaves no stray model file), the model lands first and `board.yaml` last, and
|
|
17
|
+
a failed `board.yaml` write removes the model file it just created. A name
|
|
18
|
+
already in `models:` and an existing destination file are refused, never
|
|
19
|
+
overwritten.
|
|
20
|
+
|
|
21
|
+
Refusals are plain `Issue(...)` returns, not exceptions, so the codes stay
|
|
22
|
+
visible to the issue-code registry gate.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import hashlib
|
|
28
|
+
import os
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
from tan.commands.build_output import ProjectContext
|
|
33
|
+
from tan.core.atomic_write import atomic_write_bytes
|
|
34
|
+
from tan.core.publish import publish_exclusive as _publish
|
|
35
|
+
from tan.core.board_yaml_edit import BoardEditRefused, append_models_entry
|
|
36
|
+
from tan.core.model_zoo import (
|
|
37
|
+
MIN_SDK_COMMIT,
|
|
38
|
+
ZooEntry,
|
|
39
|
+
StagedModel,
|
|
40
|
+
bundled_chunks,
|
|
41
|
+
ZooFetchError,
|
|
42
|
+
ZooUnavailable,
|
|
43
|
+
entry_as_dict,
|
|
44
|
+
fetch_source,
|
|
45
|
+
filter_by_sku,
|
|
46
|
+
load_zoo,
|
|
47
|
+
)
|
|
48
|
+
from tan.envelope import Issue, Project, SdkInfo
|
|
49
|
+
from tan.exit_codes import ExitCode
|
|
50
|
+
|
|
51
|
+
ZOO_DATA_SCHEMA_VERSION = "1"
|
|
52
|
+
ADD_DATA_SCHEMA_VERSION = "1"
|
|
53
|
+
|
|
54
|
+
#: Directory (beside `board.yaml`) `add` puts the fetched model in.
|
|
55
|
+
MODELS_DIR = "models"
|
|
56
|
+
|
|
57
|
+
Result = tuple[Project, SdkInfo | None, dict, list[Issue], ExitCode]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def zoo_empty_data() -> dict[str, Any]:
|
|
61
|
+
return {"schemaVersion": ZOO_DATA_SCHEMA_VERSION, "sku": None, "boardSku": None, "entries": []}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def add_empty_data() -> dict[str, Any]:
|
|
65
|
+
return {"schemaVersion": ADD_DATA_SCHEMA_VERSION, "sku": None, "added": None, "addedEntry": None}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _load(zoo_dir: Path) -> tuple[list[ZooEntry], list[Issue]] | Issue:
|
|
69
|
+
"""The loaded entries plus a warning per skipped manifest, or the
|
|
70
|
+
`model.zoo-unavailable` refusal when the SDK predates the zoo."""
|
|
71
|
+
try:
|
|
72
|
+
entries, problems = load_zoo(zoo_dir)
|
|
73
|
+
except ZooUnavailable:
|
|
74
|
+
return Issue(
|
|
75
|
+
"model.zoo-unavailable",
|
|
76
|
+
"error",
|
|
77
|
+
f"The bound alp-sdk has no metadata/model_zoo/ ({zoo_dir}). The model zoo needs "
|
|
78
|
+
f"alp-sdk commit {MIN_SDK_COMMIT} (alp-sdk#2542) or newer; point --sdk-root at "
|
|
79
|
+
"such a checkout.",
|
|
80
|
+
)
|
|
81
|
+
return entries, [
|
|
82
|
+
Issue("model.zoo-entry-invalid", "warning", f"Skipped zoo entry {name}: {why}.")
|
|
83
|
+
for name, why in problems
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def tolerant_board_sku(board_path: Path) -> str | None:
|
|
88
|
+
"""`som.sku` of `board_path`, or `None` for ANY problem (absent, unreadable,
|
|
89
|
+
not YAML): `zoo` is a browse command and never refuses over the board."""
|
|
90
|
+
import yaml # noqa: PLC0415 (declared dependency)
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
doc = yaml.safe_load(board_path.read_bytes().decode("utf-8"))
|
|
94
|
+
except (OSError, UnicodeDecodeError, yaml.YAMLError, RecursionError):
|
|
95
|
+
return None
|
|
96
|
+
som = doc.get("som") if isinstance(doc, dict) else None
|
|
97
|
+
sku = som.get("sku") if isinstance(som, dict) else None
|
|
98
|
+
return sku if isinstance(sku, str) and sku else None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def run_zoo(*, context: ProjectContext, zoo_dir: Path, sku: str | None) -> Result:
|
|
102
|
+
"""Each row carries `runsHere`: whether the entry is bench-validated on the
|
|
103
|
+
effective SKU (`--sku`, else the project board's `som.sku`); `null` when
|
|
104
|
+
neither names one."""
|
|
105
|
+
project, sdk = context.project(), context.sdk
|
|
106
|
+
board_sku = tolerant_board_sku(Path(context.board_yaml))
|
|
107
|
+
data = zoo_empty_data()
|
|
108
|
+
data["sku"] = sku
|
|
109
|
+
data["boardSku"] = board_sku
|
|
110
|
+
loaded = _load(zoo_dir)
|
|
111
|
+
if isinstance(loaded, Issue):
|
|
112
|
+
return project, sdk, data, [loaded], ExitCode.VALIDATION_FAILURE
|
|
113
|
+
entries, issues = loaded
|
|
114
|
+
if sku:
|
|
115
|
+
entries = filter_by_sku(entries, sku)
|
|
116
|
+
effective = sku or board_sku
|
|
117
|
+
rows = []
|
|
118
|
+
for e in entries:
|
|
119
|
+
row = entry_as_dict(e)
|
|
120
|
+
row["runsHere"] = None if effective is None else effective in e.validated_soms
|
|
121
|
+
rows.append(row)
|
|
122
|
+
data["entries"] = rows
|
|
123
|
+
return project, sdk, data, issues, ExitCode.SUCCESS
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _is_orphan_of(entry: ZooEntry, zoo_dir: Path, dest: Path) -> StagedModel | None:
|
|
127
|
+
"""`dest` as a `StagedModel` when it is a plain file already holding exactly
|
|
128
|
+
this entry's bytes -- what a run interrupted between publishing the model
|
|
129
|
+
and writing `board.yaml` leaves behind -- so a re-run can finish the job
|
|
130
|
+
instead of refusing. A symlink, or any other content, is never adopted."""
|
|
131
|
+
if dest.is_symlink() or not dest.is_file():
|
|
132
|
+
return None
|
|
133
|
+
try:
|
|
134
|
+
got = hashlib.sha256()
|
|
135
|
+
with dest.open("rb") as handle:
|
|
136
|
+
while chunk := handle.read(1 << 20):
|
|
137
|
+
got.update(chunk)
|
|
138
|
+
want = entry.source.get("sha256")
|
|
139
|
+
if want is None:
|
|
140
|
+
bundled = hashlib.sha256()
|
|
141
|
+
for chunk in bundled_chunks(zoo_dir, entry.source["bundled"]):
|
|
142
|
+
bundled.update(chunk)
|
|
143
|
+
want = bundled.hexdigest()
|
|
144
|
+
return StagedModel(dest, got.hexdigest(), dest.stat().st_size) if got.hexdigest() == want else None
|
|
145
|
+
except (OSError, ZooFetchError):
|
|
146
|
+
return None
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _precheck(
|
|
150
|
+
model_id: str,
|
|
151
|
+
entry: ZooEntry | None,
|
|
152
|
+
entries: list[ZooEntry],
|
|
153
|
+
existing_names: set[str],
|
|
154
|
+
dest: Path,
|
|
155
|
+
adopt: bool = False,
|
|
156
|
+
) -> Issue | None:
|
|
157
|
+
"""The refusal `add` must make before fetching anything, or `None`."""
|
|
158
|
+
if entry is None:
|
|
159
|
+
known = ", ".join(e.id for e in entries) or "(none)"
|
|
160
|
+
return Issue(
|
|
161
|
+
"model.zoo-entry-not-found",
|
|
162
|
+
"error",
|
|
163
|
+
f"No model-zoo entry named {model_id}. Available: {known}.",
|
|
164
|
+
)
|
|
165
|
+
if entry.id in existing_names:
|
|
166
|
+
return Issue(
|
|
167
|
+
"model.add-name-exists",
|
|
168
|
+
"error",
|
|
169
|
+
f"board.yaml already declares a model named {entry.id}; nothing was changed.",
|
|
170
|
+
)
|
|
171
|
+
if os.path.lexists(dest) and not adopt:
|
|
172
|
+
return Issue(
|
|
173
|
+
"model.add-destination-exists",
|
|
174
|
+
"error",
|
|
175
|
+
f"{dest} already exists; move it aside first. Nothing was changed.",
|
|
176
|
+
)
|
|
177
|
+
return _unsafe_models_dir(dest.parent)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _unsafe_models_dir(models_dir: Path) -> Issue | None:
|
|
181
|
+
"""Refuse a `models/` that is a symlink or resolves outside the project
|
|
182
|
+
(a write through it would land elsewhere)."""
|
|
183
|
+
if not os.path.lexists(models_dir):
|
|
184
|
+
return None
|
|
185
|
+
board_dir = models_dir.parent.resolve()
|
|
186
|
+
if models_dir.is_symlink() or not models_dir.is_dir() or models_dir.resolve().parent != board_dir:
|
|
187
|
+
return Issue(
|
|
188
|
+
"model.add-destination-unsafe",
|
|
189
|
+
"error",
|
|
190
|
+
f"{models_dir} is not a plain directory inside the project; nothing was changed.",
|
|
191
|
+
)
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _sku_warning(entry: ZooEntry, sku: str | None) -> Issue | None:
|
|
196
|
+
"""The hardware-claim caveat for a `kind: model` entry not validated on `sku`."""
|
|
197
|
+
if entry.kind != "model" or not sku or sku in entry.validated_soms:
|
|
198
|
+
return None
|
|
199
|
+
return Issue(
|
|
200
|
+
"model.add-sku-not-validated",
|
|
201
|
+
"warning",
|
|
202
|
+
f"{entry.id} has not been bench-validated on {sku} "
|
|
203
|
+
f"(validated: {', '.join(entry.validated_soms) or 'none'}).",
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _added_row(entry: ZooEntry, dest: Path, rel_source: str, staged: StagedModel) -> dict[str, Any]:
|
|
208
|
+
return {
|
|
209
|
+
"id": entry.id,
|
|
210
|
+
"name": entry.id,
|
|
211
|
+
"source": rel_source,
|
|
212
|
+
"path": dest.as_posix(),
|
|
213
|
+
"sha256": staged.sha256,
|
|
214
|
+
"bytes": staged.size,
|
|
215
|
+
"kind": entry.kind,
|
|
216
|
+
"license": entry.license,
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _plan_edit(entry: ZooEntry, board_path: Path) -> tuple[str, str] | Issue:
|
|
221
|
+
"""`(new board.yaml text, relative source)` -- computed before any fetch so
|
|
222
|
+
a refused edit costs nothing."""
|
|
223
|
+
rel_source = f"{MODELS_DIR}/{entry.id}{entry.suffix}"
|
|
224
|
+
new_entry: dict[str, Any] = {"name": entry.id, "source": rel_source}
|
|
225
|
+
if entry.compile:
|
|
226
|
+
new_entry["compile"] = entry.compile
|
|
227
|
+
try:
|
|
228
|
+
return append_models_entry(board_path.read_bytes().decode("utf-8"), new_entry), rel_source
|
|
229
|
+
except (OSError, UnicodeDecodeError) as err:
|
|
230
|
+
return Issue("model.board-yaml-edit-failed", "error", f"{board_path}: {err}")
|
|
231
|
+
except BoardEditRefused as err:
|
|
232
|
+
return Issue(
|
|
233
|
+
"model.board-yaml-edit-refused",
|
|
234
|
+
"error",
|
|
235
|
+
f"Not editing {board_path}: {err}. Nothing was changed; add this entry by hand: "
|
|
236
|
+
f"name: {entry.id}, source: {rel_source}.",
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _commit_add(
|
|
241
|
+
entry: ZooEntry,
|
|
242
|
+
zoo_dir: Path,
|
|
243
|
+
dest: Path,
|
|
244
|
+
board_path: Path,
|
|
245
|
+
new_text: str,
|
|
246
|
+
reader: Any,
|
|
247
|
+
adopted: StagedModel | None = None,
|
|
248
|
+
) -> StagedModel | Issue:
|
|
249
|
+
"""Stage (stream + verify) into a temp file beside `dest`, publish it
|
|
250
|
+
without overwriting, then write `board.yaml` last. Any failure removes
|
|
251
|
+
everything this run created, including a `models/` it made."""
|
|
252
|
+
models_dir = dest.parent
|
|
253
|
+
created_dir = not os.path.lexists(models_dir)
|
|
254
|
+
staged: StagedModel | None = None
|
|
255
|
+
published = False
|
|
256
|
+
try:
|
|
257
|
+
if adopted is not None:
|
|
258
|
+
atomic_write_bytes(str(board_path), new_text.encode("utf-8"))
|
|
259
|
+
return adopted
|
|
260
|
+
if created_dir:
|
|
261
|
+
models_dir.mkdir()
|
|
262
|
+
staged = fetch_source(entry, zoo_dir, tmp_dir=models_dir, **({"reader": reader} if reader else {}))
|
|
263
|
+
_publish(staged.path, dest)
|
|
264
|
+
published = True
|
|
265
|
+
try:
|
|
266
|
+
staged.path.unlink(missing_ok=True)
|
|
267
|
+
except OSError:
|
|
268
|
+
pass # the model is published; a leftover temp name is harmless
|
|
269
|
+
atomic_write_bytes(str(board_path), new_text.encode("utf-8"))
|
|
270
|
+
return staged
|
|
271
|
+
except ZooFetchError as err:
|
|
272
|
+
if err.mismatch:
|
|
273
|
+
failure = Issue("model.zoo-integrity-failed", "error", str(err))
|
|
274
|
+
else:
|
|
275
|
+
failure = Issue("model.zoo-fetch-failed", "error", str(err))
|
|
276
|
+
except FileExistsError:
|
|
277
|
+
failure = Issue("model.add-destination-exists", "error", f"{dest} already exists; nothing was changed.")
|
|
278
|
+
except OSError as err:
|
|
279
|
+
if published or adopted is not None:
|
|
280
|
+
failure = Issue("model.board-yaml-edit-failed", "error", f"{board_path}: {err}")
|
|
281
|
+
else:
|
|
282
|
+
failure = Issue("model.add-write-failed", "error", f"{dest}: {err}")
|
|
283
|
+
if published:
|
|
284
|
+
dest.unlink(missing_ok=True)
|
|
285
|
+
if staged is not None:
|
|
286
|
+
staged.path.unlink(missing_ok=True)
|
|
287
|
+
if created_dir:
|
|
288
|
+
try:
|
|
289
|
+
models_dir.rmdir()
|
|
290
|
+
except OSError:
|
|
291
|
+
pass
|
|
292
|
+
return failure
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def run_add(
|
|
296
|
+
*,
|
|
297
|
+
context: ProjectContext,
|
|
298
|
+
zoo_dir: Path,
|
|
299
|
+
model_id: str | None,
|
|
300
|
+
sku: str | None,
|
|
301
|
+
existing_names: set[str],
|
|
302
|
+
reader: Any = None,
|
|
303
|
+
) -> Result:
|
|
304
|
+
project, sdk = context.project(), context.sdk
|
|
305
|
+
data = add_empty_data()
|
|
306
|
+
data["sku"] = sku
|
|
307
|
+
|
|
308
|
+
def refuse(issue: Issue, exit_code: ExitCode, issues: list[Issue]) -> Result:
|
|
309
|
+
return project, sdk, data, [*issues, issue], exit_code
|
|
310
|
+
|
|
311
|
+
if not model_id:
|
|
312
|
+
missing = Issue(
|
|
313
|
+
"model.add-id-missing",
|
|
314
|
+
"error",
|
|
315
|
+
"`tan model add` needs a zoo entry id; list them with `tan model zoo`.",
|
|
316
|
+
)
|
|
317
|
+
return refuse(missing, ExitCode.VALIDATION_FAILURE, [])
|
|
318
|
+
loaded = _load(zoo_dir)
|
|
319
|
+
if isinstance(loaded, Issue):
|
|
320
|
+
return project, sdk, data, [loaded], ExitCode.VALIDATION_FAILURE
|
|
321
|
+
entries, issues = loaded
|
|
322
|
+
entry = next((e for e in entries if e.id == model_id), None)
|
|
323
|
+
board_path = Path(context.board_yaml)
|
|
324
|
+
dest = board_path.parent / MODELS_DIR / f"{model_id}{entry.suffix if entry else ''}"
|
|
325
|
+
adopted = _is_orphan_of(entry, zoo_dir, dest) if entry and entry.id not in existing_names else None
|
|
326
|
+
refusal = _precheck(model_id, entry, entries, existing_names, dest, adopt=adopted is not None)
|
|
327
|
+
if refusal is not None:
|
|
328
|
+
return refuse(refusal, ExitCode.VALIDATION_FAILURE, issues)
|
|
329
|
+
assert entry is not None
|
|
330
|
+
issues.extend(w for w in [_sku_warning(entry, sku)] if w is not None)
|
|
331
|
+
|
|
332
|
+
plan = _plan_edit(entry, board_path)
|
|
333
|
+
if isinstance(plan, Issue):
|
|
334
|
+
refused = plan.code == "model.board-yaml-edit-refused"
|
|
335
|
+
return refuse(plan, ExitCode.VALIDATION_FAILURE if refused else ExitCode.RUNTIME_FAILURE, issues)
|
|
336
|
+
new_text, rel_source = plan
|
|
337
|
+
staged = _commit_add(entry, zoo_dir, dest, board_path, new_text, reader, adopted)
|
|
338
|
+
if isinstance(staged, Issue):
|
|
339
|
+
exit_code = ExitCode.VALIDATION_FAILURE if staged.code == "model.add-destination-exists" else ExitCode.RUNTIME_FAILURE
|
|
340
|
+
return refuse(staged, exit_code, issues)
|
|
341
|
+
data["added"] = entry.id
|
|
342
|
+
data["addedEntry"] = _added_row(entry, dest, rel_source, staged)
|
|
343
|
+
return project, sdk, data, issues, ExitCode.SUCCESS
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def render_zoo_text(data: dict) -> list[str]:
|
|
347
|
+
lines = []
|
|
348
|
+
for e in data.get("entries", []):
|
|
349
|
+
soms = ",".join(e["validatedSoms"]) or "none"
|
|
350
|
+
here = {True: " (runs on this board)", False: "", None: ""}[e.get("runsHere")]
|
|
351
|
+
lines.append(f"{e['id']} [{e['kind']}] {e['task']} {e['license']} validated: {soms}{here}")
|
|
352
|
+
lines.append(f" {e['description']}")
|
|
353
|
+
return lines
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def render_add_text(data: dict) -> list[str]:
|
|
357
|
+
a = data.get("addedEntry")
|
|
358
|
+
if not a:
|
|
359
|
+
return []
|
|
360
|
+
return [
|
|
361
|
+
f"added {a['name']}: {a['source']} ({a['bytes']} bytes, sha256 {a['sha256']})",
|
|
362
|
+
f"board.yaml: appended `{a['name']}` to models:",
|
|
363
|
+
]
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""`tan monitor --capture` input and baud-switch options (tan-cli#1451).
|
|
3
|
+
|
|
4
|
+
Validation of `--send`/`--send-after`/`--send-on`/`--send-gap`/`--reopen-at`/
|
|
5
|
+
`--on` into a `serial_actions.ActionSpec`, and the glue that gives the capture
|
|
6
|
+
loop a `CaptureActions` bound to the open port (reopening through
|
|
7
|
+
`monitor_session.open_port`, so it behaves the same over a local tty and over
|
|
8
|
+
`rfc2217://`). The logic itself is `tan.core.serial_actions`.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
|
|
15
|
+
from tan.commands.monitor_cmd import DATA_SCHEMA_VERSION, MonitorError
|
|
16
|
+
from tan.core import serial_actions, uboot_breakin
|
|
17
|
+
from tan.exit_codes import ExitCode
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _bad(message: str) -> MonitorError:
|
|
21
|
+
return MonitorError(
|
|
22
|
+
"monitor.capture-bad-option",
|
|
23
|
+
message,
|
|
24
|
+
ExitCode.VALIDATION_FAILURE,
|
|
25
|
+
{"schemaVersion": DATA_SCHEMA_VERSION},
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _regex(text: str, flag: str) -> re.Pattern[str]:
|
|
30
|
+
if not text:
|
|
31
|
+
raise ValueError(f"{flag} must not be empty")
|
|
32
|
+
try:
|
|
33
|
+
return re.compile(text)
|
|
34
|
+
except re.error as err:
|
|
35
|
+
raise ValueError(f"{flag} is not a valid regex: {err}") from err
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _check(capture, sends, send_after, send_on, send_gap, reopen_at, on) -> None:
|
|
39
|
+
if not capture:
|
|
40
|
+
raise ValueError("--send/--send-after/--send-on/--send-gap/--reopen-at/--on require --capture")
|
|
41
|
+
if not sends and any(v is not None for v in (send_after, send_on, send_gap)):
|
|
42
|
+
raise ValueError("--send-after/--send-on/--send-gap require --send")
|
|
43
|
+
if send_after is not None and send_on is not None:
|
|
44
|
+
raise ValueError("--send-after and --send-on are mutually exclusive")
|
|
45
|
+
if (reopen_at is None) != (on is None):
|
|
46
|
+
raise ValueError("--reopen-at and --on must be given together")
|
|
47
|
+
if send_after is not None and send_after < 0:
|
|
48
|
+
raise ValueError("--send-after must not be negative")
|
|
49
|
+
if send_gap is not None and send_gap < 0:
|
|
50
|
+
raise ValueError("--send-gap must not be negative")
|
|
51
|
+
if reopen_at is not None and reopen_at <= 0:
|
|
52
|
+
raise ValueError("--reopen-at must be greater than 0")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def action_opts(
|
|
56
|
+
capture: bool,
|
|
57
|
+
send: list[str] | None,
|
|
58
|
+
send_after: float | None,
|
|
59
|
+
send_on: str | None,
|
|
60
|
+
send_gap: float | None,
|
|
61
|
+
reopen_at: int | None,
|
|
62
|
+
on: str | None,
|
|
63
|
+
) -> serial_actions.ActionSpec | None:
|
|
64
|
+
"""Validate the input / baud-switch options. `None` when none was given.
|
|
65
|
+
|
|
66
|
+
All of them need `--capture`; `--send-after`/`--send-on`/`--send-gap` need
|
|
67
|
+
`--send` (and the two triggers exclude each other); `--reopen-at` and
|
|
68
|
+
`--on` need each other. Without a trigger the `--send` items go out at the
|
|
69
|
+
start of the capture."""
|
|
70
|
+
sends = list(send or [])
|
|
71
|
+
values = (send_after, send_on, send_gap, reopen_at, on)
|
|
72
|
+
if not sends and all(v is None for v in values):
|
|
73
|
+
return None
|
|
74
|
+
try:
|
|
75
|
+
_check(capture, sends, send_after, send_on, send_gap, reopen_at, on)
|
|
76
|
+
return serial_actions.ActionSpec(
|
|
77
|
+
sends=tuple(uboot_breakin.parse_escaped(item, "--send") for item in sends),
|
|
78
|
+
send_after_s=send_after,
|
|
79
|
+
send_on=_regex(send_on, "--send-on") if send_on is not None else None,
|
|
80
|
+
send_gap_s=serial_actions.DEFAULT_SEND_GAP_S if send_gap is None else send_gap,
|
|
81
|
+
reopen_baud=reopen_at,
|
|
82
|
+
reopen_on=_regex(on, "--on") if on is not None else None,
|
|
83
|
+
)
|
|
84
|
+
except ValueError as err:
|
|
85
|
+
raise _bad(str(err)) from err
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def build(spec: serial_actions.ActionSpec, ser, port: str):
|
|
89
|
+
"""The `CaptureActions` for the open `ser`: sends honour the rfc2217 write
|
|
90
|
+
guard; a reopen goes through `monitor_session.open_port`."""
|
|
91
|
+
from tan.commands import monitor_session # noqa: PLC0415 (it imports this module's peers)
|
|
92
|
+
|
|
93
|
+
def write(current, data: bytes) -> None:
|
|
94
|
+
target = (
|
|
95
|
+
monitor_session._GuardedWrites(current)
|
|
96
|
+
if getattr(current, "_tan_guard_writes", False)
|
|
97
|
+
else current
|
|
98
|
+
)
|
|
99
|
+
target.write(data)
|
|
100
|
+
|
|
101
|
+
def reopen(baud: int):
|
|
102
|
+
return monitor_session.open_port(port, baud, capture=True)
|
|
103
|
+
|
|
104
|
+
return serial_actions.CaptureActions(spec, ser, reopen, write=write)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def action_error(err: serial_actions.ActionError, port: str, data: dict) -> MonitorError:
|
|
108
|
+
code = "monitor.capture-send-failed" if err.kind == "send" else "monitor.capture-reopen-failed"
|
|
109
|
+
return MonitorError(
|
|
110
|
+
code,
|
|
111
|
+
f"{err} (port '{port}')",
|
|
112
|
+
ExitCode.RUNTIME_FAILURE,
|
|
113
|
+
data,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def report(actions: serial_actions.CaptureActions) -> dict:
|
|
118
|
+
"""`data.capture.actions`: what fired, and what is still pending."""
|
|
119
|
+
spec = actions.spec
|
|
120
|
+
reopened = any(e["action"] == "reopen" for e in actions.events)
|
|
121
|
+
return {
|
|
122
|
+
"events": actions.events,
|
|
123
|
+
"sendsPending": actions.sends_pending,
|
|
124
|
+
"reopenPending": spec.reopen_baud is not None and not reopened,
|
|
125
|
+
}
|