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,395 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""The hw_rev <-> SDK-version compatibility gate `metadata/sdk_version.yaml`
|
|
3
|
+
advertises.
|
|
4
|
+
|
|
5
|
+
That file has stated since it was written that `scripts/alp_project.py`
|
|
6
|
+
"refuses to emit when the requested hw_rev is outside
|
|
7
|
+
[min_sdk_version, max_sdk_version]", and that
|
|
8
|
+
`scripts/validate_board_yaml.py` runs "the same check, exit code 3 on
|
|
9
|
+
mismatch". Neither existed: a grep for `min_sdk_version` across
|
|
10
|
+
`scripts/` returned nothing, so a customer whose upgraded SDK no longer
|
|
11
|
+
supported their board revision got a normal, successful emit and firmware
|
|
12
|
+
built against the wrong hardware assumptions (alp-sdk issue #1019).
|
|
13
|
+
|
|
14
|
+
This module is that check, relocated verbatim from alp-sdk's
|
|
15
|
+
`scripts/alp_orchestrate/sdk_compat.py`. It is deliberately pure and
|
|
16
|
+
data-only so the comparison can be tested without a board.yaml, a
|
|
17
|
+
metadata tree, or a loader.
|
|
18
|
+
|
|
19
|
+
Also here (alp-sdk issue #1025's SAFE half only): `revision_known()`, the
|
|
20
|
+
existence predicate -- given a resolved `hw_revisions:` table, is the
|
|
21
|
+
requested hw_rev even a declared key. A revision absent from the table used
|
|
22
|
+
to silently resolve to base-revision routing with a clean exit code; that is
|
|
23
|
+
a wrong-hardware emit, not a version mismatch, so it is a distinct check
|
|
24
|
+
with a distinct error (`SdkRevisionUnknown`, not `SdkRevisionUnsupported`)
|
|
25
|
+
run BEFORE the range comparison below -- an unknown revision has no bounds
|
|
26
|
+
to compare against.
|
|
27
|
+
|
|
28
|
+
Also here: `revision_buildable()`, the maintainer's broad-reading decision
|
|
29
|
+
on alp-sdk #1025's other half -- a revision that EXISTS but is
|
|
30
|
+
`status: reserved`, `status: tbd`, or carries no `status` key at all is
|
|
31
|
+
refused, distinctly from not existing at all (`SdkRevisionNotBuildable`,
|
|
32
|
+
not `SdkRevisionUnknown`). `revision_known()` itself stays deliberately
|
|
33
|
+
blind to `status:` -- existence and buildability are two different
|
|
34
|
+
questions, answered by two different predicates, so a later status change
|
|
35
|
+
can't slip into the wrong one silently.
|
|
36
|
+
|
|
37
|
+
All three of those gates read ONE file per SoM family,
|
|
38
|
+
`metadata/e1m_modules/<family>/hw-revisions.yaml`, through
|
|
39
|
+
`load_family_table()` below. That reader used to swallow `OSError` and
|
|
40
|
+
`yaml.YAMLError` and answer `{}`, which every predicate here reads as
|
|
41
|
+
"nothing to judge" -- so a single tab-indented line in that file disabled
|
|
42
|
+
the unknown-revision gate, the not-buildable gate AND the SDK-range gate
|
|
43
|
+
at once and emitted a wrong-hardware artefact at exit 0. It now refuses
|
|
44
|
+
instead; see `load_family_table`'s own docstring for why an ABSENT table
|
|
45
|
+
is still benign while a present-but-unusable one is not.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
from __future__ import annotations
|
|
49
|
+
|
|
50
|
+
import re
|
|
51
|
+
from pathlib import Path
|
|
52
|
+
from typing import Any, Optional
|
|
53
|
+
|
|
54
|
+
import yaml
|
|
55
|
+
|
|
56
|
+
from .models import OrchestratorError
|
|
57
|
+
from .strict_loaders import fast_safe_load
|
|
58
|
+
|
|
59
|
+
# A revision may bound one side, both, or neither. `max_sdk_version: ~`
|
|
60
|
+
# (YAML null) is the common case in tree today and means "no upper bound
|
|
61
|
+
# declared yet" -- every one of the 27 ranges currently shipped is
|
|
62
|
+
# open-ended on the high side. An ABSENT bound is unbounded, never zero:
|
|
63
|
+
# reading it as a bound would refuse every `status: reserved` revision,
|
|
64
|
+
# which declares no range at all.
|
|
65
|
+
_VERSION_RE = re.compile(r"^\s*v?(\d+)\.(\d+)\.(\d+)")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def parse_version(value: Optional[str]) -> Optional[tuple[int, int, int]]:
|
|
69
|
+
"""Parse `X.Y.Z` (or `vX.Y.Z`) into a comparable tuple.
|
|
70
|
+
|
|
71
|
+
Returns None for anything unparseable, including None itself. A
|
|
72
|
+
malformed bound is treated as absent rather than as a failure: this
|
|
73
|
+
gate exists to catch a real hw_rev/SDK mismatch, and turning a typo in
|
|
74
|
+
metadata into a refused build would be a worse failure than the one it
|
|
75
|
+
prevents. `check_metadata` in the validator is where malformed
|
|
76
|
+
metadata belongs.
|
|
77
|
+
"""
|
|
78
|
+
if not isinstance(value, str):
|
|
79
|
+
return None
|
|
80
|
+
m = _VERSION_RE.match(value)
|
|
81
|
+
if m is None:
|
|
82
|
+
return None
|
|
83
|
+
return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def incompatibility(
|
|
87
|
+
sdk_version: Optional[str],
|
|
88
|
+
minimum: Optional[str],
|
|
89
|
+
maximum: Optional[str],
|
|
90
|
+
) -> Optional[str]:
|
|
91
|
+
"""Return a human reason the SDK is outside the range, or None if it fits.
|
|
92
|
+
|
|
93
|
+
Both bounds are INCLUSIVE: a revision declaring `min_sdk_version: 0.3.0`
|
|
94
|
+
is supported *by* 0.3.0, not merely after it.
|
|
95
|
+
"""
|
|
96
|
+
running = parse_version(sdk_version)
|
|
97
|
+
if running is None:
|
|
98
|
+
# No readable version (a wheel with no adjacent metadata/ tree, the
|
|
99
|
+
# same case `buildplan._sdk_version` guards). Provenance is
|
|
100
|
+
# best-effort there and a missing version is not evidence of a
|
|
101
|
+
# mismatch, so this stays quiet rather than refusing.
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
lo = parse_version(minimum)
|
|
105
|
+
hi = parse_version(maximum)
|
|
106
|
+
|
|
107
|
+
if lo is not None and running < lo:
|
|
108
|
+
return (f"needs SDK >= {minimum}, but this SDK is "
|
|
109
|
+
f"{sdk_version}")
|
|
110
|
+
if hi is not None and running > hi:
|
|
111
|
+
return (f"is supported up to SDK {maximum}, but this SDK is "
|
|
112
|
+
f"{sdk_version}")
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def read_sdk_version(metadata_root: Path) -> Optional[str]:
|
|
117
|
+
"""The `version:` field out of `metadata/sdk_version.yaml`.
|
|
118
|
+
|
|
119
|
+
Same read-and-strip idiom as `buildplan._sdk_version`, taking the
|
|
120
|
+
metadata root as an argument so a test (and `--metadata-root`) can
|
|
121
|
+
point it somewhere else.
|
|
122
|
+
"""
|
|
123
|
+
try:
|
|
124
|
+
text = (metadata_root / "sdk_version.yaml").read_text(encoding="utf-8")
|
|
125
|
+
except OSError:
|
|
126
|
+
return None
|
|
127
|
+
for line in text.splitlines():
|
|
128
|
+
stripped = line.strip()
|
|
129
|
+
if stripped.startswith("version:"):
|
|
130
|
+
return stripped.split(":", 1)[1].split("#", 1)[0].strip()
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _revision(table: Any, hw_rev: Optional[str]) -> dict[str, Any]:
|
|
135
|
+
"""The entry for `hw_rev` out of a `hw_revisions:` mapping, or {}."""
|
|
136
|
+
if not hw_rev or not isinstance(table, dict):
|
|
137
|
+
return {}
|
|
138
|
+
entry = (table.get("hw_revisions") or {}).get(hw_rev)
|
|
139
|
+
return entry if isinstance(entry, dict) else {}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _hw_revisions(table: Any) -> Optional[dict[str, Any]]:
|
|
143
|
+
"""The raw `hw_revisions:` mapping out of `table`, or None if absent.
|
|
144
|
+
|
|
145
|
+
None -- not {} -- distinguishes "this table declares no `hw_revisions:`
|
|
146
|
+
section at all" (an inline board has no per-board table; there is
|
|
147
|
+
nothing to assert existence against) from "a table exists and the key
|
|
148
|
+
simply isn't in it" (a genuine unknown-revision refusal).
|
|
149
|
+
"""
|
|
150
|
+
if not isinstance(table, dict):
|
|
151
|
+
return None
|
|
152
|
+
revisions = table.get("hw_revisions")
|
|
153
|
+
return revisions if isinstance(revisions, dict) else None
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def revision_known(table: Any, hw_rev: Optional[str]) -> Optional[bool]:
|
|
157
|
+
"""Whether `hw_rev` is a declared key in `table`'s `hw_revisions:` map.
|
|
158
|
+
|
|
159
|
+
Pure existence check (alp-sdk #1025's safe half) -- deliberately blind
|
|
160
|
+
to `status:`. A `status: reserved` revision, or one carrying no
|
|
161
|
+
`status` key at all, is still "known" here; whether it is buildable is
|
|
162
|
+
a different question this predicate does not answer.
|
|
163
|
+
|
|
164
|
+
Returns None -- not False -- when `table` has no `hw_revisions:`
|
|
165
|
+
mapping at all, so a caller can tell "nothing to check existence
|
|
166
|
+
against" (e.g. an inline board with no per-board table -- skip) apart
|
|
167
|
+
from "checked, and it's missing" (True/False -- refuse on False).
|
|
168
|
+
"""
|
|
169
|
+
revisions = _hw_revisions(table)
|
|
170
|
+
if revisions is None:
|
|
171
|
+
return None
|
|
172
|
+
return bool(hw_rev) and hw_rev in revisions
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# Statuses a build is permitted to proceed against. Everything else --
|
|
176
|
+
# `reserved`, `tbd`, and (the broad reading) a revision that carries no
|
|
177
|
+
# `status` key at all -- is refused. alp-sdk's schema requires `status` on
|
|
178
|
+
# every entry (issue #1025), so a missing key means a table hand-edited or
|
|
179
|
+
# written before that requirement, not a deliberate declaration.
|
|
180
|
+
_NOT_BUILDABLE_STATUSES = frozenset({"reserved", "tbd"})
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def revision_buildable(table: Any, hw_rev: Optional[str]) -> Optional[bool]:
|
|
184
|
+
"""Whether `hw_rev`'s declared `status:` permits a build.
|
|
185
|
+
|
|
186
|
+
Tri-state like `revision_known()`, and answers a DIFFERENT question:
|
|
187
|
+
None when there is nothing to judge -- no `hw_revisions:` table at all,
|
|
188
|
+
or `hw_rev` isn't a key in it (that is `revision_known()`'s failure to
|
|
189
|
+
report, not this one's -- an unknown revision has no status to have
|
|
190
|
+
read). Otherwise False for `status: reserved`, `status: tbd`, or an
|
|
191
|
+
entry with no `status` key; True for every other declared status.
|
|
192
|
+
"""
|
|
193
|
+
revisions = _hw_revisions(table)
|
|
194
|
+
if revisions is None:
|
|
195
|
+
return None
|
|
196
|
+
entry = revisions.get(hw_rev) if hw_rev else None
|
|
197
|
+
if not isinstance(entry, dict):
|
|
198
|
+
# A key present but not a dict (None, a bare string, ...) is a
|
|
199
|
+
# malformed entry, not an absent one -- it has no status, so it
|
|
200
|
+
# is not buildable. Only an absent key stays None (unknown).
|
|
201
|
+
return False if hw_rev in revisions else None
|
|
202
|
+
status = entry.get("status")
|
|
203
|
+
if not isinstance(status, str) or status in _NOT_BUILDABLE_STATUSES:
|
|
204
|
+
return False
|
|
205
|
+
return True
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _table_unreadable(path: Path, why: str) -> OrchestratorError:
|
|
209
|
+
"""The refusal every present-but-unusable family table raises.
|
|
210
|
+
|
|
211
|
+
One message shape so the three gates that read the table cannot
|
|
212
|
+
disagree about what an unreadable one means.
|
|
213
|
+
"""
|
|
214
|
+
return OrchestratorError(
|
|
215
|
+
f"{path}: the SoM-family hardware-revision table is present but "
|
|
216
|
+
f"unusable ({why}). This table is the ONLY thing the hw_rev "
|
|
217
|
+
"existence, buildable-status and SDK-version-range gates check "
|
|
218
|
+
"against, so an unusable one is refused rather than read as "
|
|
219
|
+
"\"this family declares no revisions\" -- treating it as empty "
|
|
220
|
+
"would let every one of those gates pass and emit firmware for "
|
|
221
|
+
"an unverified hardware revision. Restore the file (a partial "
|
|
222
|
+
"checkout, a truncated write or a hand-edit that broke the YAML "
|
|
223
|
+
"are the usual causes) and re-run.")
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def load_family_table(metadata_root: Path, family_dir: str) -> Any:
|
|
227
|
+
"""The raw parsed `hw-revisions.yaml` for a SoM family, or {} when the
|
|
228
|
+
family ships no table at all.
|
|
229
|
+
|
|
230
|
+
Public, not `_`-private, because it has a SECOND consumer outside this
|
|
231
|
+
module: `alp_project_loader._hwrev_pad_route_overrides()` carries its
|
|
232
|
+
own copies of the #1025 existence and buildable gates for the
|
|
233
|
+
`--emit composed-route-table` / `--emit carrier-netlist` path, which
|
|
234
|
+
resolves its SoM data independently of `alp_orchestrate.loader`. That
|
|
235
|
+
reader had its own tolerant `yaml.safe_load(...) or {}` and fell open
|
|
236
|
+
on exactly the shapes guarded below; both readers now share this one,
|
|
237
|
+
so they cannot disagree about whether a damaged table is fatal or
|
|
238
|
+
about what the refusal says.
|
|
239
|
+
|
|
240
|
+
ABSENT and UNUSABLE are two different situations and only one of them
|
|
241
|
+
is benign (#563). A family directory with no `hw-revisions.yaml`
|
|
242
|
+
legitimately has nothing to check against -- the tri-state predicates
|
|
243
|
+
below answer None ("nothing to judge") and the gates skip, which is
|
|
244
|
+
what an in-development family and the test-isolation fixture in
|
|
245
|
+
`tests/scripts/_orchestrate_support.py` both rely on. A table that
|
|
246
|
+
EXISTS but cannot be read or parsed is the opposite: there IS
|
|
247
|
+
something to check against and we failed to read it, so answering
|
|
248
|
+
None would silently disable all three gates on one typo and produce
|
|
249
|
+
a wrong-hardware artefact at exit 0. That is a refusal.
|
|
250
|
+
|
|
251
|
+
Raises `OrchestratorError` (not one of the `SdkRevision*` subclasses):
|
|
252
|
+
those name a defect in the customer's `board.yaml`, and
|
|
253
|
+
`scripts/validate_board_yaml.py` maps each to its own exit code. An
|
|
254
|
+
unreadable table is a defect in the SDK's own metadata tree, so it
|
|
255
|
+
takes the generic refusal + exit 1 instead.
|
|
256
|
+
"""
|
|
257
|
+
path = metadata_root / "e1m_modules" / family_dir / "hw-revisions.yaml"
|
|
258
|
+
if not path.is_file():
|
|
259
|
+
return {}
|
|
260
|
+
try:
|
|
261
|
+
text = path.read_text(encoding="utf-8")
|
|
262
|
+
except OSError as exc:
|
|
263
|
+
raise _table_unreadable(path, f"cannot be read: {exc}") from exc
|
|
264
|
+
try:
|
|
265
|
+
# libyaml-backed yaml.safe_load; this runs once per hw-rev check
|
|
266
|
+
# and was the third-hottest YAML read in tests/scripts (#2328).
|
|
267
|
+
table = fast_safe_load(text)
|
|
268
|
+
except yaml.YAMLError as exc:
|
|
269
|
+
detail = str(exc)
|
|
270
|
+
first = detail.splitlines()[0] if detail else type(exc).__name__
|
|
271
|
+
raise _table_unreadable(path, f"is not valid YAML: {first}") from exc
|
|
272
|
+
if not isinstance(table, dict):
|
|
273
|
+
# An empty file parses to None and a stray scalar to a str/int;
|
|
274
|
+
# both are "present but not a table", the same fail-open hazard
|
|
275
|
+
# as a parse error. `metadata/schemas/hw-revisions-v1.schema.json`
|
|
276
|
+
# requires a mapping with `family`, `display_name` and
|
|
277
|
+
# `hw_revisions`, so no legitimate in-tree file lands here.
|
|
278
|
+
kind = "an empty file" if table is None else type(table).__name__
|
|
279
|
+
raise _table_unreadable(
|
|
280
|
+
path, f"does not parse to a mapping ({kind})")
|
|
281
|
+
if not isinstance(table.get("hw_revisions"), dict):
|
|
282
|
+
# A file truncated above its `hw_revisions:` block still parses
|
|
283
|
+
# as valid YAML, so the parse guards above do not catch it --
|
|
284
|
+
# and a family table with no `hw_revisions:` mapping disables
|
|
285
|
+
# the same three gates just as completely. The schema requires
|
|
286
|
+
# the key, so this is only ever a damaged file.
|
|
287
|
+
raise _table_unreadable(
|
|
288
|
+
path, "declares no `hw_revisions:` mapping")
|
|
289
|
+
return table
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def family_revision(metadata_root: Path,
|
|
293
|
+
family_dir: Optional[str],
|
|
294
|
+
hw_rev: Optional[str]) -> dict[str, Any]:
|
|
295
|
+
"""The SoM-family `hw_revisions:` entry for `hw_rev`, or {}.
|
|
296
|
+
|
|
297
|
+
`family_dir` is the directory name (`aen`, `v2n`, `v2n-m1`),
|
|
298
|
+
not the SoM preset's `family:` string (`alif-ensemble`, ...) -- the two
|
|
299
|
+
differ and only the former names a path.
|
|
300
|
+
"""
|
|
301
|
+
if not family_dir:
|
|
302
|
+
return {}
|
|
303
|
+
return _revision(load_family_table(metadata_root, family_dir), hw_rev)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def family_revision_known(metadata_root: Path,
|
|
307
|
+
family_dir: Optional[str],
|
|
308
|
+
hw_rev: Optional[str]) -> Optional[bool]:
|
|
309
|
+
"""`revision_known()` against the SoM-family hw-revisions.yaml table."""
|
|
310
|
+
if not family_dir:
|
|
311
|
+
return None
|
|
312
|
+
return revision_known(load_family_table(metadata_root, family_dir), hw_rev)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def family_revision_buildable(metadata_root: Path,
|
|
316
|
+
family_dir: Optional[str],
|
|
317
|
+
hw_rev: Optional[str]) -> Optional[bool]:
|
|
318
|
+
"""`revision_buildable()` against the SoM-family hw-revisions.yaml table."""
|
|
319
|
+
if not family_dir:
|
|
320
|
+
return None
|
|
321
|
+
return revision_buildable(load_family_table(metadata_root, family_dir), hw_rev)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def family_available_revisions(metadata_root: Path,
|
|
325
|
+
family_dir: Optional[str]) -> list[str]:
|
|
326
|
+
"""Sorted `hw_revisions:` keys for a SoM family, or [] if none/missing.
|
|
327
|
+
|
|
328
|
+
For an error message naming what a customer's SDK actually knows about
|
|
329
|
+
(`family_revision_known` returning False needs somewhere to point).
|
|
330
|
+
"""
|
|
331
|
+
if not family_dir:
|
|
332
|
+
return []
|
|
333
|
+
revisions = _hw_revisions(load_family_table(metadata_root, family_dir))
|
|
334
|
+
return sorted(revisions.keys()) if revisions else []
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def board_revision(board_preset: Any, hw_rev: Optional[str]) -> dict[str, Any]:
|
|
338
|
+
"""The board `hw_revisions:` entry for `hw_rev`, or {}."""
|
|
339
|
+
return _revision(board_preset, hw_rev)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def check(
|
|
343
|
+
sdk_version: Optional[str],
|
|
344
|
+
*,
|
|
345
|
+
som_revision: dict[str, Any],
|
|
346
|
+
som_label: str,
|
|
347
|
+
board_revision_entry: dict[str, Any],
|
|
348
|
+
board_label: str,
|
|
349
|
+
) -> Optional[str]:
|
|
350
|
+
"""Both halves of the claim -- "the family / board hw_revisions tables".
|
|
351
|
+
|
|
352
|
+
Returns a single assembled message naming every side that refuses, or
|
|
353
|
+
None when the SDK sits inside every declared range. The SoM is checked
|
|
354
|
+
first because its range is the one a customer changes by swapping
|
|
355
|
+
modules.
|
|
356
|
+
"""
|
|
357
|
+
reasons: list[str] = []
|
|
358
|
+
for entry, label in ((som_revision, som_label),
|
|
359
|
+
(board_revision_entry, board_label)):
|
|
360
|
+
if not entry:
|
|
361
|
+
continue
|
|
362
|
+
why = incompatibility(sdk_version,
|
|
363
|
+
entry.get("min_sdk_version"),
|
|
364
|
+
entry.get("max_sdk_version"))
|
|
365
|
+
if why:
|
|
366
|
+
reasons.append(f"{label} {why}")
|
|
367
|
+
if not reasons:
|
|
368
|
+
return None
|
|
369
|
+
return "; ".join(reasons)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def board_designator(table: Any, hw_rev: Optional[str]) -> Optional[str]:
|
|
373
|
+
"""Compose the full board designator for a revision, e.g. `2626-r2`.
|
|
374
|
+
|
|
375
|
+
The physical board is `E1M-AEN-2626-R2`: `2626` is a YYWW datecode carried by
|
|
376
|
+
the Altium board number and declared once, per family, as `board_datecode:`
|
|
377
|
+
in `hw-revisions.yaml`. A module whose identity says only `r2` cannot be tied
|
|
378
|
+
back to its board number, so the identity written into the EEPROM -- and the
|
|
379
|
+
build-side value the boot banner compares it against -- both carry the
|
|
380
|
+
composed form.
|
|
381
|
+
|
|
382
|
+
IMPORTANT: this is NOT a replacement for the bare revision key. `hw_rev`
|
|
383
|
+
stays `r2` everywhere it is used as a LOOKUP KEY (board.yaml, the loader's
|
|
384
|
+
`family_revision_known()` check, `pad_route_overrides`). Composing there
|
|
385
|
+
would break the lookup. Only the identity/compare surfaces use this.
|
|
386
|
+
|
|
387
|
+
Returns the bare `hw_rev` unchanged when the family declares no datecode, so
|
|
388
|
+
families that have not adopted one are untouched.
|
|
389
|
+
"""
|
|
390
|
+
if not hw_rev:
|
|
391
|
+
return hw_rev
|
|
392
|
+
if not isinstance(table, dict):
|
|
393
|
+
return hw_rev
|
|
394
|
+
datecode = table.get("board_datecode")
|
|
395
|
+
return f"{datecode}-{hw_rev}" if datecode else hw_rev
|