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,264 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""SKU / board-preset / pad-route resolution for the `alp_project.py` emit modes.
|
|
3
|
+
|
|
4
|
+
RELOCATED (was alp-sdk `scripts/alp_project_loader.py`). Only the part the six
|
|
5
|
+
relocated `alp_project.py`-owned emit modes actually reach came across --
|
|
6
|
+
`dts-overlay`, `native-sim-overlay`, `hw-info-h`, `west-libraries`,
|
|
7
|
+
`carrier-netlist` and `zephyr-board`. Everything else that file held was already
|
|
8
|
+
here: `_sku_family`, `silicon_to_kconfig`, `resolve_soc_path`,
|
|
9
|
+
`_resolve_silicon_variant`, `resolve_memory_map`, `resolve_capabilities` and
|
|
10
|
+
`som_unpopulated_capabilities` live in `som_metadata.py`, and `_load_yaml` in
|
|
11
|
+
`loader.py`. Copying them again would be a second source of truth for facts the
|
|
12
|
+
planner already reads once.
|
|
13
|
+
|
|
14
|
+
Three mechanical changes, no behavioural one on the success path -- which is the
|
|
15
|
+
path byte-identity is measured on
|
|
16
|
+
(`tests/parity/test_planner_emit_parity.py`):
|
|
17
|
+
|
|
18
|
+
* **`sys.exit` became `raise OrchestratorError`.** alp-sdk's copy ran inside a
|
|
19
|
+
spawned interpreter, where exiting was a non-zero rc the caller reported. In
|
|
20
|
+
`tan`'s own process a `sys.exit` is a `SystemExit` that tears down the CLI past
|
|
21
|
+
every envelope guarantee -- an unwritten stdout is rendered as nothing at all
|
|
22
|
+
by the extension. Same message text, minus the `alp_project: ` prefix the
|
|
23
|
+
spawned CLI added.
|
|
24
|
+
* **`REPO` is the BOUND SDK checkout** (`paths.REPO`), not a `__file__` walk. The
|
|
25
|
+
walk landed on the SDK because the file lived there; inside `tan` it would land
|
|
26
|
+
on the `tan` package and every `metadata/**` path would be wrong.
|
|
27
|
+
* **`_validate_and_load` did NOT come across.** It ran alp-sdk's rich diagnostic
|
|
28
|
+
validator (`alp_cli.validator`) before handing the emitters a plain dict --
|
|
29
|
+
importing `scripts/alp_cli` is exactly the edge this relocation removes. What
|
|
30
|
+
the emitters consume is the plain dict, and alp-sdk's own code already has a
|
|
31
|
+
branch for producing it without `alp_cli` (that function's `ImportError`
|
|
32
|
+
fallback is `_load_yaml`). `load_board_yaml` -- which every caller here runs
|
|
33
|
+
first -- validates the same file against `metadata/schemas/board.schema.json`,
|
|
34
|
+
so nothing goes unvalidated; what is lost is only the rendered-diagnostic
|
|
35
|
+
presentation, which `tan validate` owns on this side.
|
|
36
|
+
|
|
37
|
+
One accidental improvement rides along: alp-sdk's `_validate_and_load` re-read
|
|
38
|
+
the file with a bare `path.read_text()`, i.e. in the host's locale encoding, so
|
|
39
|
+
a non-ASCII board.yaml decoded as cp1252 on Windows. `loader._load_yaml` reads
|
|
40
|
+
UTF-8 explicitly.
|
|
41
|
+
|
|
42
|
+
A fourth change landed later and IS behavioural on a refusal path: alp-sdk
|
|
43
|
+
#1025 fixed `_hwrev_pad_route_overrides` to raise on an `hw_rev` absent from
|
|
44
|
+
the resolved `hw_revisions:` table -- ported here in the same shape (see that
|
|
45
|
+
function's own docstring). Before it, an unrecognised `hw_rev` silently
|
|
46
|
+
resolved to base-revision pad routing with a clean exit code on this file's
|
|
47
|
+
two emit modes (`carrier-netlist` / `composed-route-table`), which never run
|
|
48
|
+
`loader.load_board_yaml` and so never saw that fix's other half.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
from __future__ import annotations
|
|
52
|
+
|
|
53
|
+
from pathlib import Path
|
|
54
|
+
from typing import Any
|
|
55
|
+
|
|
56
|
+
from . import sdk_compat
|
|
57
|
+
from .loader import _load_yaml
|
|
58
|
+
from .models import OrchestratorError, SdkRevisionNotBuildable, SdkRevisionUnknown
|
|
59
|
+
from .paths import METADATA_ROOT, REPO
|
|
60
|
+
from .som_metadata import _sku_family
|
|
61
|
+
|
|
62
|
+
__all__ = [
|
|
63
|
+
"_compose_route",
|
|
64
|
+
"_hwrev_pad_route_overrides",
|
|
65
|
+
"_resolve_board",
|
|
66
|
+
"_resolve_inline_or_preset_board",
|
|
67
|
+
"_resolve_pad_routes",
|
|
68
|
+
"_resolve_sku",
|
|
69
|
+
"_sku_form_factor",
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _sku_form_factor(sku: str) -> str:
|
|
74
|
+
"""Return the E1M connector family for a SKU string."""
|
|
75
|
+
return "e1m-x" if _sku_family(sku) in {"v2n", "v2n-m1"} else "e1m"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _resolve_sku(sku: str, metadata_root: Path = METADATA_ROOT) -> dict[str, Any]:
|
|
79
|
+
# Per-SKU preset lives at metadata/e1m_modules/<SKU>.yaml.
|
|
80
|
+
preset_path = metadata_root / "e1m_modules" / f"{sku}.yaml"
|
|
81
|
+
if not preset_path.is_file():
|
|
82
|
+
raise OrchestratorError(
|
|
83
|
+
f"no preset for SKU {sku} at "
|
|
84
|
+
f"{preset_path.relative_to(REPO) if preset_path.is_relative_to(REPO) else preset_path} "
|
|
85
|
+
f"(remaining SKUs land alongside the user-supplied HW config writeup)"
|
|
86
|
+
)
|
|
87
|
+
return _load_yaml(preset_path)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _resolve_board(preset: str, metadata_root: Path = METADATA_ROOT) -> dict[str, Any]:
|
|
91
|
+
# Shared board definition lives at metadata/boards/<preset>.yaml.
|
|
92
|
+
preset_path = metadata_root / "boards" / f"{preset}.yaml"
|
|
93
|
+
if not preset_path.is_file():
|
|
94
|
+
raise OrchestratorError(
|
|
95
|
+
f"`preset: {preset}` does not resolve at "
|
|
96
|
+
f"{preset_path.relative_to(REPO) if preset_path.is_relative_to(REPO) else preset_path}")
|
|
97
|
+
return _load_yaml(preset_path)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _resolve_inline_or_preset_board(
|
|
101
|
+
project: dict[str, Any], metadata_root: Path = METADATA_ROOT,
|
|
102
|
+
) -> dict[str, Any]:
|
|
103
|
+
"""Return the resolved board dict for a project.
|
|
104
|
+
|
|
105
|
+
Mirrors the orchestrator's resolution: `preset: <name>` loads
|
|
106
|
+
metadata/boards/<name>.yaml; otherwise the project's own
|
|
107
|
+
top-level `name`/`populated`/`e1m_routes` are wrapped into the
|
|
108
|
+
same shape the downstream emitters consume.
|
|
109
|
+
"""
|
|
110
|
+
if "preset" in project:
|
|
111
|
+
return _resolve_board(project["preset"], metadata_root)
|
|
112
|
+
return {
|
|
113
|
+
"name": project.get("name"),
|
|
114
|
+
"populated": dict(project.get("populated") or {}),
|
|
115
|
+
"e1m_routes": dict(project.get("e1m_routes") or {}),
|
|
116
|
+
"default_hw_rev": project.get("hw_rev"),
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _resolve_pad_routes(
|
|
121
|
+
sku_preset: dict[str, Any],
|
|
122
|
+
) -> dict[str, dict[str, Any]]:
|
|
123
|
+
"""Index a SoM preset's `pad_routes:` block by E1M pad/instance.
|
|
124
|
+
|
|
125
|
+
Returns a dict keyed by `E1M_*` identifier (e.g., `E1M_GPIO_IO15`,
|
|
126
|
+
`E1M_SPI1`) with the full route entry as value:
|
|
127
|
+
|
|
128
|
+
{ "dispatch": "...", "dispatch_pin": 14, "doc": "..." }
|
|
129
|
+
|
|
130
|
+
Pads NOT present in the preset's `pad_routes:` (or absent block
|
|
131
|
+
entirely) are implicitly `direct` -- they route to the main
|
|
132
|
+
silicon's GPIO / peripheral with no mediator.
|
|
133
|
+
|
|
134
|
+
The SDK's codegen layer composes this with the board preset's
|
|
135
|
+
`e1m_routes:` block: when an E1M pad appears in both, the board
|
|
136
|
+
supplies the role and the SoM supplies the dispatch path. The two
|
|
137
|
+
blocks together let a customer swap SoMs without touching the board
|
|
138
|
+
YAML or any app source -- the som-swappable-without-board-changes
|
|
139
|
+
promise.
|
|
140
|
+
"""
|
|
141
|
+
routes = sku_preset.get("pad_routes") or []
|
|
142
|
+
if not isinstance(routes, list):
|
|
143
|
+
return {}
|
|
144
|
+
indexed: dict[str, dict[str, Any]] = {}
|
|
145
|
+
for entry in routes:
|
|
146
|
+
if not isinstance(entry, dict):
|
|
147
|
+
continue
|
|
148
|
+
e1m = entry.get("e1m")
|
|
149
|
+
if not isinstance(e1m, str):
|
|
150
|
+
continue
|
|
151
|
+
# Last-write-wins on duplicates -- the schema doesn't forbid
|
|
152
|
+
# them but real SoMs shouldn't have any; if the YAML carries
|
|
153
|
+
# duplicates, surface the later entry (most-recent author wins).
|
|
154
|
+
indexed[e1m] = entry
|
|
155
|
+
return indexed
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _hwrev_pad_route_overrides(
|
|
159
|
+
sku: str,
|
|
160
|
+
hw_rev: str | None,
|
|
161
|
+
metadata_root: Path = METADATA_ROOT,
|
|
162
|
+
) -> list[dict[str, Any]]:
|
|
163
|
+
"""Per-rev pad-route overrides for the selected ``hw_rev``.
|
|
164
|
+
|
|
165
|
+
The base SoM ``pad_routes:`` tracks the *production* revision. A board
|
|
166
|
+
revision whose routing differs declares the deviating pads as data in
|
|
167
|
+
the family ``hw-revisions.yaml`` ``pad_route_overrides:`` block; this
|
|
168
|
+
returns that rev's list (empty when it declares none, so the base
|
|
169
|
+
``pad_routes:`` then applies verbatim). Applying these is what makes
|
|
170
|
+
the composed route table differ between revisions of one SKU.
|
|
171
|
+
|
|
172
|
+
Raises `SdkRevisionUnknown` when `hw_rev` isn't a key in the family
|
|
173
|
+
table, and `SdkRevisionNotBuildable` when it IS a key but its `status:`
|
|
174
|
+
refuses a build (`reserved`/`tbd`/status-less -- alp-sdk #1025's
|
|
175
|
+
existence + status halves; the status half by `1a91a232`, ported here
|
|
176
|
+
tan-cli#485). This emit path resolves its own SoM/board data
|
|
177
|
+
independently of `loader.load_board_yaml`, so both gates need their own
|
|
178
|
+
copy here -- before tan-cli#485, `--target carrier-netlist` /
|
|
179
|
+
`composed-route-table` emitted a full route table for a `reserved`
|
|
180
|
+
hw_rev while `tan build` refused the same input. Same predicates
|
|
181
|
+
(`sdk_compat.revision_known`/`revision_buildable`), same error TYPE and
|
|
182
|
+
MESSAGE SHAPE as `loader.load_board_yaml`'s SoM-side refusals -- the
|
|
183
|
+
`status_repr` construction below is `loader._status_repr` INLINED, not
|
|
184
|
+
imported, so a caller does not see two different shapes of the same
|
|
185
|
+
problem depending on which path resolved it; keep the two in sync by
|
|
186
|
+
hand if either one's message wording changes.
|
|
187
|
+
|
|
188
|
+
Reads the table through ``sdk_compat.load_family_table()`` rather than
|
|
189
|
+
its own ``yaml.safe_load``, so both independent readers of this file
|
|
190
|
+
agree about what a damaged one means (#563). This site used to do
|
|
191
|
+
``yaml.safe_load(...) or {}``, and that ``or {}`` FAILED OPEN on the
|
|
192
|
+
two shapes that parse without raising: an EMPTY file and a file
|
|
193
|
+
TRUNCATED above its ``hw_revisions:`` block both yielded ``{}``, both
|
|
194
|
+
gates above then read "nothing to judge", and ``--emit
|
|
195
|
+
composed-route-table`` shipped a wrong-hardware artefact at exit 0 for
|
|
196
|
+
an hw_rev that does not exist -- the exact outcome the two gates below
|
|
197
|
+
exist to prevent. An unparseable table escaped as a raw
|
|
198
|
+
``yaml.ScannerError`` traceback: a refusal, but not a diagnosable one.
|
|
199
|
+
The shared reader turns all of those into one coded
|
|
200
|
+
``OrchestratorError`` naming the file. An ABSENT table still returns
|
|
201
|
+
``{}`` there and stays benign here.
|
|
202
|
+
"""
|
|
203
|
+
if not hw_rev:
|
|
204
|
+
return []
|
|
205
|
+
try:
|
|
206
|
+
family = _sku_family(sku)
|
|
207
|
+
except ValueError:
|
|
208
|
+
return []
|
|
209
|
+
data = sdk_compat.load_family_table(metadata_root, family)
|
|
210
|
+
if not data:
|
|
211
|
+
# Absent table only: `load_family_table` raises on every
|
|
212
|
+
# present-but-unusable shape, so reaching here with a falsy
|
|
213
|
+
# `data` means the family genuinely ships no table.
|
|
214
|
+
return []
|
|
215
|
+
if sdk_compat.revision_known(data, hw_rev) is False:
|
|
216
|
+
available = sorted((data.get("hw_revisions") or {}).keys())
|
|
217
|
+
raise SdkRevisionUnknown(
|
|
218
|
+
f"SoM {sku} hw_rev {hw_rev!r} is not a known hardware "
|
|
219
|
+
f"revision. Available hw_rev(s) for {sku}: {available}.")
|
|
220
|
+
if sdk_compat.revision_buildable(data, hw_rev) is False:
|
|
221
|
+
status = (data.get("hw_revisions") or {}).get(hw_rev, {}).get("status")
|
|
222
|
+
status_repr = f"status: {status!r}" if status is not None else \
|
|
223
|
+
"carries no `status:` key"
|
|
224
|
+
raise SdkRevisionNotBuildable(
|
|
225
|
+
f"SoM {sku} hw_rev {hw_rev!r} exists but is not buildable "
|
|
226
|
+
f"({status_repr}).")
|
|
227
|
+
rev = (data.get("hw_revisions") or {}).get(hw_rev) or {}
|
|
228
|
+
overrides = rev.get("pad_route_overrides") or []
|
|
229
|
+
return [e for e in overrides
|
|
230
|
+
if isinstance(e, dict) and isinstance(e.get("e1m"), str)]
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _compose_route(
|
|
234
|
+
e1m_pad: str,
|
|
235
|
+
board_route: dict[str, Any] | None,
|
|
236
|
+
pad_routes: dict[str, dict[str, Any]],
|
|
237
|
+
) -> dict[str, Any]:
|
|
238
|
+
"""Compose a board `e1m_routes:` entry (role on the board
|
|
239
|
+
side) with the SoM's `pad_routes:` entry (dispatch path on the
|
|
240
|
+
SoM side) for a single E1M pad / instance.
|
|
241
|
+
|
|
242
|
+
Returns a dict with: `board_role`, `board_macro`, `dispatch`
|
|
243
|
+
(defaults to `direct` when the SoM declares no proxy),
|
|
244
|
+
`dispatch_pin`, plus any docs.
|
|
245
|
+
|
|
246
|
+
Callers use the composed dict to drive codegen (Zephyr DTS
|
|
247
|
+
overlays, dispatch shims, capability validation).
|
|
248
|
+
"""
|
|
249
|
+
out: dict[str, Any] = {"e1m": e1m_pad}
|
|
250
|
+
if board_route:
|
|
251
|
+
out["board_role"] = board_route.get("role")
|
|
252
|
+
out["board_macro"] = board_route.get("macro")
|
|
253
|
+
if board_route.get("doc"):
|
|
254
|
+
out["board_doc"] = board_route["doc"]
|
|
255
|
+
som_route = pad_routes.get(e1m_pad)
|
|
256
|
+
if som_route:
|
|
257
|
+
out["dispatch"] = som_route.get("dispatch", "direct")
|
|
258
|
+
if som_route.get("dispatch_pin") is not None:
|
|
259
|
+
out["dispatch_pin"] = som_route["dispatch_pin"]
|
|
260
|
+
if som_route.get("doc"):
|
|
261
|
+
out["som_doc"] = som_route["doc"]
|
|
262
|
+
else:
|
|
263
|
+
out["dispatch"] = "direct"
|
|
264
|
+
return out
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Copyright (c) 2026 Alp Lab AB
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""
|
|
4
|
+
A general "does the BOUND alp-sdk checkout carry capability X" floor
|
|
5
|
+
(tan-cli#591).
|
|
6
|
+
|
|
7
|
+
`tan/planner/zephyr_board.py` hand-ports alp-sdk's `scripts/gen_zephyr_board.py`
|
|
8
|
+
generator, which grows a REQUIRED fact from time to time (the SE-owned `atoc`
|
|
9
|
+
memory-map region, alp-sdk#1289; the SoC JSON's `zephyr_peripherals_dtsi` key,
|
|
10
|
+
alp-sdk#1352). When such a fact is absent, there are two entirely different
|
|
11
|
+
causes that used to produce ONE message blaming the customer's own metadata:
|
|
12
|
+
|
|
13
|
+
1. the bound alp-sdk checkout PREDATES the requirement -- no released
|
|
14
|
+
alp-sdk has it yet, and no SoM/SoC authoring fixes it;
|
|
15
|
+
2. the checkout is new enough, but THIS SoM/SoC/board's own metadata is
|
|
16
|
+
genuinely incomplete.
|
|
17
|
+
|
|
18
|
+
This module answers (1) directly, from the checkout's own facts, rather than
|
|
19
|
+
inferring it from the shape of one board's metadata (the imprecise heuristic
|
|
20
|
+
`_aen_missing_region_message` used to apply to `atoc` alone, before this
|
|
21
|
+
module existed -- see its removal in the same change that added this file).
|
|
22
|
+
A caller still owns its OWN case-(2) message; `require_capability` only rules
|
|
23
|
+
case (1) out or in.
|
|
24
|
+
|
|
25
|
+
Deliberately keyed on a CAPABILITY, never a bare alp-sdk version number: the
|
|
26
|
+
planner mirror (`tan/planner/`) moves per-capability as alp-sdk gains ported
|
|
27
|
+
requirements one at a time, not on a release cadence, so a version-number
|
|
28
|
+
floor would need renegotiating on every unrelated alp-sdk release and would
|
|
29
|
+
say nothing true about a checkout built from an arbitrary commit. A
|
|
30
|
+
capability's *probe* instead reads a fact that ships with the checkout itself
|
|
31
|
+
-- a JSON-Schema property, or an entry in alp-sdk's own
|
|
32
|
+
`metadata/quality-tasks-v1.json` gate registry (the same registry
|
|
33
|
+
`adding-a-ci-gate`'s four-site lockstep keeps in sync with `scripts/check_*.py`
|
|
34
|
+
on the alp-sdk side) -- so the answer is correct for ANY checkout, release or
|
|
35
|
+
`dev`, without tan knowing which alp-sdk version introduced it.
|
|
36
|
+
|
|
37
|
+
Every probe reads ONLY under `metadata_root` (never `<sdk_root>/scripts/`):
|
|
38
|
+
`metadata/` is the one directory `tan/planner/` already has a resolved root
|
|
39
|
+
for (ADR-0017 -- the generators relocated into tan, the facts did not), and
|
|
40
|
+
restricting probes to it means a probe can be exercised against a plain copy
|
|
41
|
+
of `metadata/`, the same fixture shape
|
|
42
|
+
`tests/planner/test_zephyr_board_metadata_facts.py`'s `_MutatedMetadata`
|
|
43
|
+
already builds for every other refusal in this generator.
|
|
44
|
+
|
|
45
|
+
Adding the NEXT ported requirement's floor is: one `SdkCapability` entry
|
|
46
|
+
below (an id, the alp-sdk issue, a description, and a probe), plus one
|
|
47
|
+
`require_capability(metadata_root, THE_CAPABILITY)` call at the site that
|
|
48
|
+
already raises the authoring-gap message for that fact -- not a second
|
|
49
|
+
bespoke heuristic.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
from __future__ import annotations
|
|
53
|
+
|
|
54
|
+
import json
|
|
55
|
+
from collections.abc import Callable
|
|
56
|
+
from dataclasses import dataclass
|
|
57
|
+
from pathlib import Path
|
|
58
|
+
from typing import Any
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class SdkTooOldError(Exception):
|
|
62
|
+
"""The BOUND alp-sdk checkout itself predates a required capability --
|
|
63
|
+
distinct from a `ZephyrBoardEmitError` authoring gap, which means the
|
|
64
|
+
checkout knows the fact but THIS SoM/SoC/board's own metadata omits it.
|
|
65
|
+
|
|
66
|
+
Not a `ZephyrBoardEmitError` subclass on purpose: `zephyr_board.py`'s own
|
|
67
|
+
exception funnels (both `tan generate`'s `_emit_one_in_process` and every
|
|
68
|
+
`pytest.raises` in its test module) catch broadly enough that the
|
|
69
|
+
distinction survives in the message via `type(err).__name__` -- exactly
|
|
70
|
+
how `tan generate`'s in-process engine already reports every planner
|
|
71
|
+
exception (`generate_cmd.py::_emit_one_in_process`) -- without requiring
|
|
72
|
+
a new top-level issue code in `generate_cmd.py`, which is out of scope
|
|
73
|
+
for this fix (tan-cli#591 stays inside `tan/planner/`).
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(self, capability: "SdkCapability") -> None:
|
|
77
|
+
self.capability = capability
|
|
78
|
+
super().__init__(
|
|
79
|
+
f"this alp-sdk predates {capability.description} "
|
|
80
|
+
f"({capability.issue}); {capability.emit_description} needs a "
|
|
81
|
+
"checkout containing it -- upgrade alp-sdk, or pin tan to a "
|
|
82
|
+
"release that predates the requirement"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass(frozen=True)
|
|
87
|
+
class SdkCapability:
|
|
88
|
+
"""One ported requirement's checkable floor.
|
|
89
|
+
|
|
90
|
+
*probe* answers True iff *metadata_root* shows the bound alp-sdk
|
|
91
|
+
checkout knows the concept AT ALL -- independent of whether any
|
|
92
|
+
particular SoM/SoC/board actually used it. `id` and `issue` are for
|
|
93
|
+
diagnostics and tests, not compared against anything; `description` and
|
|
94
|
+
`emit_description` compose directly into `SdkTooOldError`'s message.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
id: str
|
|
98
|
+
issue: str
|
|
99
|
+
description: str
|
|
100
|
+
probe: Callable[[Path], bool]
|
|
101
|
+
emit_description: str = "the AEN board emit"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _read_json(path: Path) -> Any | None:
|
|
105
|
+
"""`None` on anything short of a parsed JSON value -- absent, unreadable,
|
|
106
|
+
or malformed are all "this probe can't confirm the capability", not a
|
|
107
|
+
crash. A probe answering `False` when it can't tell is NOT neutral:
|
|
108
|
+
`require_capability` treats every `False` as the SDK-floor refusal, so
|
|
109
|
+
an unreadable or malformed probe file surfaces as `SdkTooOldError`
|
|
110
|
+
(the "upgrade alp-sdk" message), not as the caller's authoring-gap
|
|
111
|
+
message."""
|
|
112
|
+
if not path.is_file():
|
|
113
|
+
return None
|
|
114
|
+
try:
|
|
115
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
116
|
+
except (OSError, ValueError):
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def schema_declares_property(schema_relpath: str, prop: str) -> Callable[[Path], bool]:
|
|
121
|
+
"""Probe factory: True iff `metadata/schemas/<schema_relpath>` declares
|
|
122
|
+
*prop* as a top-level JSON-Schema property.
|
|
123
|
+
|
|
124
|
+
A property that exists only from some alp-sdk commit onward is a
|
|
125
|
+
reliable, checkout-only marker for "this checkout is new enough to know
|
|
126
|
+
the concept": schemas ship with every checkout, release or `dev` alike,
|
|
127
|
+
so the probe needs nothing beyond the already-resolved `metadata_root`.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
def _probe(metadata_root: Path) -> bool:
|
|
131
|
+
spec = _read_json(metadata_root / "schemas" / schema_relpath)
|
|
132
|
+
if not isinstance(spec, dict):
|
|
133
|
+
return False
|
|
134
|
+
return prop in (spec.get("properties") or {})
|
|
135
|
+
|
|
136
|
+
return _probe
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def quality_task_declared(task_id: str) -> Callable[[Path], bool]:
|
|
140
|
+
"""Probe factory: True iff `metadata/quality-tasks-v1.json` lists a task
|
|
141
|
+
with id *task_id* -- alp-sdk's own registry of which `check_*.py` gates
|
|
142
|
+
exist (the same registry `adding-a-ci-gate`'s four-site lockstep keeps
|
|
143
|
+
current on the alp-sdk side). A requirement that shipped its own gate is
|
|
144
|
+
checkable through the registry it already updated, rather than inventing
|
|
145
|
+
a second marker file for tan to track.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
def _probe(metadata_root: Path) -> bool:
|
|
149
|
+
doc = _read_json(metadata_root / "quality-tasks-v1.json")
|
|
150
|
+
if not isinstance(doc, dict):
|
|
151
|
+
return False
|
|
152
|
+
tasks = doc.get("tasks")
|
|
153
|
+
if not isinstance(tasks, list):
|
|
154
|
+
return False
|
|
155
|
+
return any(isinstance(t, dict) and t.get("id") == task_id for t in tasks)
|
|
156
|
+
|
|
157
|
+
return _probe
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# ---------------------------------------------------------------------
|
|
161
|
+
# Registry -- one entry per ported requirement.
|
|
162
|
+
# ---------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
AEN_ATOC_RESERVATION = SdkCapability(
|
|
165
|
+
id="aen.atoc_reservation",
|
|
166
|
+
issue="alp-sdk#1289",
|
|
167
|
+
description="the SE-owned ATOC reservation",
|
|
168
|
+
probe=quality_task_declared("atoc-reservation"),
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
AEN_ZEPHYR_PERIPHERALS_DTSI = SdkCapability(
|
|
172
|
+
id="aen.zephyr_peripherals_dtsi",
|
|
173
|
+
issue="alp-sdk#1352",
|
|
174
|
+
description="the per-SoC `zephyr_peripherals_dtsi` overlay reference",
|
|
175
|
+
probe=schema_declares_property(
|
|
176
|
+
"soc-spec-v1.schema.json", "zephyr_peripherals_dtsi"
|
|
177
|
+
),
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
AEN_SOM_POWER_DOMAINS = SdkCapability(
|
|
181
|
+
id="aen.som_power_domains",
|
|
182
|
+
issue="alp-sdk#2784",
|
|
183
|
+
description="the AEN SoM `power_domains:` block (on-module-links-v2)",
|
|
184
|
+
probe=schema_declares_property(
|
|
185
|
+
"on-module-links-v2.schema.json", "power_domains"
|
|
186
|
+
),
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def require_capability(metadata_root: Path, capability: SdkCapability) -> None:
|
|
191
|
+
"""No-op when the bound checkout carries *capability*; raises
|
|
192
|
+
`SdkTooOldError` when it doesn't.
|
|
193
|
+
|
|
194
|
+
Answers ONLY "does the checkout know the concept" -- a caller still owns
|
|
195
|
+
its own authoring-gap message for "the checkout knows it, but this
|
|
196
|
+
SoM/SoC/board's metadata doesn't declare it". A probe that cannot read
|
|
197
|
+
its metadata file (absent, unreadable, malformed -- e.g. a partial
|
|
198
|
+
`--metadata-root` copy) answers False and so also raises
|
|
199
|
+
`SdkTooOldError`; the message cannot tell that apart from a genuinely
|
|
200
|
+
old checkout.
|
|
201
|
+
"""
|
|
202
|
+
if not capability.probe(metadata_root):
|
|
203
|
+
raise SdkTooOldError(capability)
|