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,568 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""`tan pinmux` -- the E1M pinmux capability table (E1M pad -> silicon
|
|
3
|
+
function) for a SoM family (tan-cli#257).
|
|
4
|
+
|
|
5
|
+
Mirrors `crates/tan-cli/src/commands/pinmux.rs` plus the `tan-core` helpers it
|
|
6
|
+
composes (`pinmux::{parse_pinmux_table_checked, pinmux_family_for_sku}`).
|
|
7
|
+
Resolves a `metadata/pinmux/<family>.yaml` family stem from an explicit
|
|
8
|
+
`--family` or a `--sku` prefix, reads that table out of the resolved SDK root,
|
|
9
|
+
and echoes it in the envelope -- the single source the extension/LSP consume
|
|
10
|
+
instead of parsing `metadata/pinmux/<family>.yaml` themselves.
|
|
11
|
+
|
|
12
|
+
**Fail-soft, deliberately, with two exceptions** -- this paragraph's, and the
|
|
13
|
+
refused `--family` tan-cli#359 added below. An unresolved SDK root, an
|
|
14
|
+
unknown SKU, no `--sku`/`--family` at all, or a family with no generated table
|
|
15
|
+
on disk are each a `warning`-severity issue at exit 0 -- `pinmux` answers "I
|
|
16
|
+
don't know" the same way for all of them, never a hard failure. A table that
|
|
17
|
+
DOES exist on disk but fails to parse (schema-version skew) or parses to ZERO
|
|
18
|
+
pads (`pinmux-capability-v1.schema.json` requires `minItems: 1`, so an empty
|
|
19
|
+
table is never a legitimate v1 document -- and, measured against the real
|
|
20
|
+
`metadata/pinmux/v2n.yaml` in this checkout, an all-`"TBD"` family genuinely
|
|
21
|
+
reaches this today) is NOT fail-soft: `error` severity,
|
|
22
|
+
[`tan.exit_codes.ExitCode.VALIDATION_FAILURE`].
|
|
23
|
+
|
|
24
|
+
**No SDK-resolution warning for a broken project pin.** Unlike `presets`/
|
|
25
|
+
`sdk current`, this command never emits `sdk.project-pin-unresolved` --
|
|
26
|
+
measured against the oracle with a `.alp/sdk-path` pointing at a nonexistent
|
|
27
|
+
checkout: `pinmux` silently falls through to `pinmux.sdk-root-unresolved`
|
|
28
|
+
exactly as it would with no pointer at all. `resolve_project_paths`/
|
|
29
|
+
`resolve_sdk` (from `presets_cmd`, reused here rather than re-derived) already
|
|
30
|
+
carry the pin-rejection detail; this module simply never reads it.
|
|
31
|
+
|
|
32
|
+
**Row-level fail-soft mirrors `parse_pinmux_table_checked`, not the
|
|
33
|
+
un-checked `parse_pinmux_table`**: a pad row missing `e1m_pad`/`e1m_function`
|
|
34
|
+
is DROPPED (never an error), and a row's `e1m_pad == "TBD"` sentinel is
|
|
35
|
+
dropped too (the source TSV carries no E1M edge pad for that silicon pad --
|
|
36
|
+
`metadata/pinmux/v2n.yaml`'s ENTIRE table is TBD-only at the time of writing,
|
|
37
|
+
which is exactly what makes `pinmux.table-empty` a live path, not a
|
|
38
|
+
hypothetical one).
|
|
39
|
+
|
|
40
|
+
**`--family` is validated, and the resolved table path re-checked -- a
|
|
41
|
+
DELIBERATE divergence from the oracle (tan-cli#359).** The oracle builds
|
|
42
|
+
`sdk_root.join("metadata").join("pinmux").join(format!("{fam}.yaml"))` with no
|
|
43
|
+
check on `fam` at all, and `Path::join`/`pathlib` both DISCARD the accumulated
|
|
44
|
+
prefix when the joined component is absolute -- so `--family <other-sdk>/
|
|
45
|
+
metadata/pinmux/aen` read a table out of a completely different checkout while
|
|
46
|
+
the envelope still reported `sdkRoot` as the one it never touched (`..`
|
|
47
|
+
components walked out the same way). [`_is_safe_family_stem`] and the
|
|
48
|
+
[`resolve_confined`] re-check below close that; see `_resolve`'s own comment for
|
|
49
|
+
why BOTH are needed rather than either alone.
|
|
50
|
+
|
|
51
|
+
**A pad field is a `String` in the oracle, not a strict `serde_yaml`
|
|
52
|
+
struct-typed deserialize -- refuted by running it.** Every `PinmuxPad` field
|
|
53
|
+
is `String`, and (measured against the oracle) `String` fields coerce ANY
|
|
54
|
+
scalar to its own YAML-spelled text rather than rejecting a wrong-looking
|
|
55
|
+
one: `owner: 7` reads back `"7"`, `silicon_pad: true` reads back `"true"`,
|
|
56
|
+
both at exit 0 with no issue -- an earlier version of this module treated any
|
|
57
|
+
non-`str` scalar there as a hard parse failure, which was simply wrong, not a
|
|
58
|
+
documented divergence. Only a genuine compound value (`owner: [a, b]`,
|
|
59
|
+
`e1m_pad: {a: b}`) is a real type mismatch no `String` field can absorb, and
|
|
60
|
+
that half stayed a document-level `PinmuxParseError` -- one malformed
|
|
61
|
+
sequence/mapping field still fails the whole table, not just that row.
|
|
62
|
+
|
|
63
|
+
**Text mode renders every issue, not just the summary line -- a DELIBERATE
|
|
64
|
+
divergence from the oracle (tan-cli#458).** `crates/tan-cli/src/commands/
|
|
65
|
+
pinmux.rs`'s own `text` build (its lines 151-159) is exactly
|
|
66
|
+
`vec![format!("pinmux: family={} pads={}", ...)]` and nothing else -- an
|
|
67
|
+
exit-2 `pinmux.table-empty`/`pinmux.family-invalid`/
|
|
68
|
+
`pinmux.schema-version-unsupported` reaches an oracle terminal as a bare
|
|
69
|
+
`pads=0`, with the reason visible only in `--format json`. This port instead
|
|
70
|
+
follows the summary line with one `f"pinmux: {issue.message}"` line per issue
|
|
71
|
+
on stderr, matching the text-mode convention `generate_cmd`/`monitor_cmd`/
|
|
72
|
+
`build_cmd`/`doctor_cmd`/`examples_cmd` already use for THEIR issues (not
|
|
73
|
+
every sibling command's: `size_cmd`/`image_cmd` build a separate text list
|
|
74
|
+
that drops issues from it entirely, which is their own gap, not a pattern to
|
|
75
|
+
match).
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
from __future__ import annotations
|
|
79
|
+
|
|
80
|
+
from dataclasses import dataclass
|
|
81
|
+
from pathlib import Path
|
|
82
|
+
from typing import Any
|
|
83
|
+
|
|
84
|
+
import typer
|
|
85
|
+
|
|
86
|
+
from tan.commands.presets_cmd import resolve_project_paths, resolve_sdk
|
|
87
|
+
from tan.core.fs_confine import PathEscapeError, resolve_confined
|
|
88
|
+
from tan.core.sdk_discovery import ActiveSdk, with_sdk_search
|
|
89
|
+
from tan.envelope import Envelope, Issue, Project, SdkInfo, emit
|
|
90
|
+
from tan.exit_codes import ExitCode
|
|
91
|
+
from tan.output_format import FORMAT_HELP, OutputFormat, resolve_format
|
|
92
|
+
from tan.core.shapes import rejected_sdk_root_message, yaml_kind
|
|
93
|
+
|
|
94
|
+
#: `data.schemaVersion` for this command's payload.
|
|
95
|
+
DATA_SCHEMA_VERSION = "1"
|
|
96
|
+
|
|
97
|
+
#: The only `schemaVersion` a `metadata/pinmux/<family>.yaml` may declare
|
|
98
|
+
#: (`pinmux-capability-v1.schema.json`).
|
|
99
|
+
SCHEMA_VERSION = "pinmux-capability-v1"
|
|
100
|
+
|
|
101
|
+
#: `sku` prefix -> pinmux family stem (`metadata/pinmux/<stem>.yaml`), checked
|
|
102
|
+
#: in order -- verbatim from `tan_core::pinmux::pinmux_family_for_sku`. E1M-V2M
|
|
103
|
+
#: reuses the base V2N pinout in full (`metadata/e1m_modules/v2n-m1/README.md`)
|
|
104
|
+
#: -- there is no separate `v2n-m1.yaml` table, so it maps to `"v2n"` too.
|
|
105
|
+
_FAMILY_PREFIX_TABLE = (
|
|
106
|
+
("E1M-AEN", "aen"),
|
|
107
|
+
("E1M-V2N", "v2n"),
|
|
108
|
+
("E1M-V2M", "v2n"),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def pinmux_family_for_sku(sku: str) -> str | None:
|
|
113
|
+
"""The pinmux family stem for `sku`'s prefix, or `None` for an
|
|
114
|
+
unrecognized SKU."""
|
|
115
|
+
for prefix, stem in _FAMILY_PREFIX_TABLE:
|
|
116
|
+
if sku.startswith(prefix):
|
|
117
|
+
return stem
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _is_safe_family_stem(family: str) -> bool:
|
|
122
|
+
"""True when `family` is a plain `metadata/pinmux/<stem>.yaml` stem: ASCII
|
|
123
|
+
letters/digits/`-`/`_`, non-empty (tan-cli#359).
|
|
124
|
+
|
|
125
|
+
A CHARSET allowlist rather than a shape blocklist, the same trade
|
|
126
|
+
`tan.core.flash_plan.validate_identifier` already makes in this tree for
|
|
127
|
+
the same reason: every shape a hand-written blocklist would have to
|
|
128
|
+
enumerate carries a character this charset already rejects -- a separator
|
|
129
|
+
(`/` AND `\\`, checked in the raw string on EVERY host, because pathlib on
|
|
130
|
+
POSIX does not treat `\\` as one, so an `os.sep`-based check would protect
|
|
131
|
+
Linux and leave Windows open), a Windows drive or UNC prefix (`:`), a `.`
|
|
132
|
+
or `..` component (`.`), a NUL or a newline. Deliberately tighter than
|
|
133
|
+
`tan_core::path_guard::is_plain_relative`, which is a check for a plain
|
|
134
|
+
RELATIVE PATH: `a/b` passes that and is still not a family stem, and on
|
|
135
|
+
POSIX it also accepts `C:\\x` and `..\\..\\x` as ordinary filenames.
|
|
136
|
+
|
|
137
|
+
No dot is admitted because no `metadata/pinmux/*.yaml` stem has ever
|
|
138
|
+
contained one (`aen`, `v2n`); admitting one to be liberal would
|
|
139
|
+
buy nothing and hand back the `.`/`..` component this rejects outright.
|
|
140
|
+
"""
|
|
141
|
+
return bool(family) and all(c.isascii() and (c.isalnum() or c in "-_") for c in family)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@dataclass(frozen=True)
|
|
145
|
+
class PinmuxPad:
|
|
146
|
+
e1m_pad: str
|
|
147
|
+
e1m_function: str
|
|
148
|
+
owner: str
|
|
149
|
+
silicon_peripheral: str
|
|
150
|
+
silicon_pad: str
|
|
151
|
+
|
|
152
|
+
def as_dict(self) -> dict[str, str]:
|
|
153
|
+
return {
|
|
154
|
+
"e1mPad": self.e1m_pad,
|
|
155
|
+
"e1mFunction": self.e1m_function,
|
|
156
|
+
"owner": self.owner,
|
|
157
|
+
"siliconPeripheral": self.silicon_peripheral,
|
|
158
|
+
"siliconPad": self.silicon_pad,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@dataclass(frozen=True)
|
|
163
|
+
class PinmuxTable:
|
|
164
|
+
family: str
|
|
165
|
+
display_name: str | None
|
|
166
|
+
pads: list[PinmuxPad]
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class PinmuxParseError(Exception):
|
|
170
|
+
"""The pinmux capability document itself did not parse, or its
|
|
171
|
+
`schemaVersion` is not `pinmux-capability-v1` -- the two `Err` cases
|
|
172
|
+
`parse_pinmux_table_checked` distinguishes from an ordinary fail-soft
|
|
173
|
+
dropped row (see the module docstring)."""
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
#: tan-cli#408: one implementation, in `tan.core.shapes` -- `diff_cmd.py`
|
|
177
|
+
#: carried the same body with a docstring this copy had lost.
|
|
178
|
+
_yaml_kind = yaml_kind
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _pad_field(row: dict, field: str) -> str | None:
|
|
182
|
+
"""`row[field]` coerced to its YAML-spelled string, or `None` when the
|
|
183
|
+
key is absent/null. Raises `PinmuxParseError` for a sequence/mapping
|
|
184
|
+
value -- the one shape a `String` pad field can never absorb; every
|
|
185
|
+
OTHER scalar (bool/int/float, in addition to an actual string) takes its
|
|
186
|
+
YAML-spelled text instead, matching the oracle's own `String`-field
|
|
187
|
+
coercion (measured: `owner: 7` -> `"7"`, `silicon_pad: true` -> `"true"`,
|
|
188
|
+
both at exit 0 -- see the module docstring)."""
|
|
189
|
+
value = row.get(field)
|
|
190
|
+
if value is None:
|
|
191
|
+
return None
|
|
192
|
+
if isinstance(value, (list, dict)):
|
|
193
|
+
raise PinmuxParseError(f"pads[].{field}: expected a string, got {_yaml_kind(value)}")
|
|
194
|
+
if isinstance(value, bool):
|
|
195
|
+
return "true" if value else "false"
|
|
196
|
+
if isinstance(value, str):
|
|
197
|
+
return value
|
|
198
|
+
return str(value)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def parse_pinmux_table_checked(text: str) -> PinmuxTable:
|
|
202
|
+
"""Parse a `pinmux-capability-v1` YAML document. Raises `PinmuxParseError`
|
|
203
|
+
for a document that does not parse, is not a mapping, or does not declare
|
|
204
|
+
the exact `schemaVersion` this parser accepts. Individual pad rows fail
|
|
205
|
+
soft per the module docstring."""
|
|
206
|
+
try:
|
|
207
|
+
import yaml # noqa: PLC0415 (optional at runtime, by design)
|
|
208
|
+
except ImportError as err:
|
|
209
|
+
raise PinmuxParseError(
|
|
210
|
+
"this build of tan has no YAML support installed, so the pinmux table cannot "
|
|
211
|
+
"be read."
|
|
212
|
+
) from err
|
|
213
|
+
try:
|
|
214
|
+
raw = yaml.safe_load(text)
|
|
215
|
+
except Exception as err: # noqa: BLE001 -- yaml.YAMLError and anything a loader raises
|
|
216
|
+
raise PinmuxParseError(f"could not be parsed: {err}") from err
|
|
217
|
+
|
|
218
|
+
if not isinstance(raw, dict):
|
|
219
|
+
raw = {}
|
|
220
|
+
schema_version = raw.get("schemaVersion")
|
|
221
|
+
if schema_version != SCHEMA_VERSION:
|
|
222
|
+
raise PinmuxParseError(
|
|
223
|
+
f"unsupported pinmux capability schemaVersion {schema_version!r} "
|
|
224
|
+
f"(expected {SCHEMA_VERSION!r})"
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
raw_pads = raw.get("pads")
|
|
228
|
+
if raw_pads is not None and not isinstance(raw_pads, list):
|
|
229
|
+
raise PinmuxParseError(f"pads: expected a sequence, got {_yaml_kind(raw_pads)}")
|
|
230
|
+
|
|
231
|
+
pads: list[PinmuxPad] = []
|
|
232
|
+
for row in raw_pads or []:
|
|
233
|
+
if not isinstance(row, dict):
|
|
234
|
+
raise PinmuxParseError(f"pads[]: expected a mapping, got {_yaml_kind(row)}")
|
|
235
|
+
e1m_pad = _pad_field(row, "e1m_pad")
|
|
236
|
+
e1m_function = _pad_field(row, "e1m_function")
|
|
237
|
+
if e1m_pad is None or e1m_function is None:
|
|
238
|
+
continue # `p.e1m_pad?`/`p.e1m_function?` -- missing key, drop the row
|
|
239
|
+
if e1m_pad == "TBD":
|
|
240
|
+
continue # sentinel: no E1M edge ball for this silicon pad
|
|
241
|
+
pads.append(
|
|
242
|
+
PinmuxPad(
|
|
243
|
+
e1m_pad=e1m_pad,
|
|
244
|
+
e1m_function=e1m_function,
|
|
245
|
+
owner=_pad_field(row, "owner") or "",
|
|
246
|
+
silicon_peripheral=_pad_field(row, "silicon_peripheral") or "",
|
|
247
|
+
silicon_pad=_pad_field(row, "silicon_pad") or "",
|
|
248
|
+
)
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
family = raw.get("family")
|
|
252
|
+
display_name = raw.get("display_name")
|
|
253
|
+
return PinmuxTable(
|
|
254
|
+
family=family if isinstance(family, str) else "",
|
|
255
|
+
display_name=display_name if isinstance(display_name, str) else None,
|
|
256
|
+
pads=pads,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
_ResolveResult = tuple[
|
|
261
|
+
ActiveSdk | None, str | None, str | None, list[PinmuxPad], list[Issue], ExitCode
|
|
262
|
+
]
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _resolve(
|
|
266
|
+
sku: str | None, family: str | None, sdk_root: str | None, root: str
|
|
267
|
+
) -> _ResolveResult:
|
|
268
|
+
"""`(sdk, resolved_family, display_name, pads, issues, exit_code)` -- the
|
|
269
|
+
whole family/table resolution, isolated from `pinmux()` so the command's
|
|
270
|
+
own top-level `try`/`except` can wrap it once (matching `presets_cmd`'s
|
|
271
|
+
own catch-all convention: an exception nobody enumerated must still reach
|
|
272
|
+
the caller as one coded issue, never a bare traceback with an empty
|
|
273
|
+
stdout).
|
|
274
|
+
"""
|
|
275
|
+
issues: list[Issue] = []
|
|
276
|
+
|
|
277
|
+
# Family resolution: explicit `--family` wins; else map `--sku` by prefix.
|
|
278
|
+
# `--family` never even evaluates whether `--sku` maps -- no unknown-sku
|
|
279
|
+
# warning fires when `--family` is also given (measured against the
|
|
280
|
+
# oracle: `--sku E1M-BOGUS --family v2n` reports family "v2n", no
|
|
281
|
+
# `pinmux.unknown-sku` issue).
|
|
282
|
+
resolved_family: str | None
|
|
283
|
+
if family is not None:
|
|
284
|
+
resolved_family = family
|
|
285
|
+
elif sku is not None:
|
|
286
|
+
resolved_family = pinmux_family_for_sku(sku)
|
|
287
|
+
if resolved_family is None:
|
|
288
|
+
issues.append(
|
|
289
|
+
Issue(
|
|
290
|
+
"pinmux.unknown-sku",
|
|
291
|
+
"warning",
|
|
292
|
+
f"SKU '{sku}' maps to no known pinmux family.",
|
|
293
|
+
)
|
|
294
|
+
)
|
|
295
|
+
else:
|
|
296
|
+
resolved_family = None
|
|
297
|
+
issues.append(
|
|
298
|
+
Issue("pinmux.no-target", "warning", "Provide --sku <sku> or --family <family>.")
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
exit_code = ExitCode.SUCCESS
|
|
302
|
+
sdk = resolve_sdk(sdk_root, root)
|
|
303
|
+
display_name: str | None = None
|
|
304
|
+
pads: list[PinmuxPad] = []
|
|
305
|
+
|
|
306
|
+
# tan-cli#359. `--family` is caller-controlled and reaches this join
|
|
307
|
+
# unvalidated on the oracle: an ABSOLUTE value discards the SDK prefix
|
|
308
|
+
# entirely (`Path("/sdk-a") / "/sdk-b/metadata/pinmux/aen"` IS
|
|
309
|
+
# `/sdk-b/...`, same as Rust's `Path::join`) and `..` walks out of it, so
|
|
310
|
+
# the envelope reported `sdkRoot` = the checkout it never read. Checked
|
|
311
|
+
# here, at the ONE place a family becomes a path, so the `--sku` route is
|
|
312
|
+
# covered by construction too -- and BEFORE `sdk`/`resolved_family` are
|
|
313
|
+
# even paired, so a rejected family never reaches the read.
|
|
314
|
+
#
|
|
315
|
+
# TWO INDEPENDENT checks, because each has a hole the other covers: the
|
|
316
|
+
# stem charset cannot see a SYMLINK planted inside `metadata/pinmux/`
|
|
317
|
+
# (`aen.yaml` -> elsewhere is a perfectly plain stem), and a containment
|
|
318
|
+
# re-check alone would wave through a `../pinmux/aen`-shaped family that
|
|
319
|
+
# merely happens to land back inside. Exactly one coded issue either way;
|
|
320
|
+
# nothing else can have been appended yet at the first check (`--family`
|
|
321
|
+
# short-circuits the `--sku` lookup, and `pinmux.no-target` only fires
|
|
322
|
+
# when `resolved_family is None`).
|
|
323
|
+
if resolved_family is not None and not _is_safe_family_stem(resolved_family):
|
|
324
|
+
issues.append(
|
|
325
|
+
Issue(
|
|
326
|
+
"pinmux.family-invalid",
|
|
327
|
+
"error",
|
|
328
|
+
f"Pinmux family '{resolved_family}' is not a plain family stem "
|
|
329
|
+
"(ASCII letters, digits, '-' and '_' only) -- refusing to read a "
|
|
330
|
+
"table from outside <sdkRoot>/metadata/pinmux.",
|
|
331
|
+
)
|
|
332
|
+
)
|
|
333
|
+
return sdk, resolved_family, None, [], issues, ExitCode.VALIDATION_FAILURE
|
|
334
|
+
|
|
335
|
+
# tan-cli#468: `resolve_sdk` now always returns an `ActiveSdk`, never a
|
|
336
|
+
# bare `None` -- `sdk.path` is what says whether a usable checkout
|
|
337
|
+
# resolved, so both branches below guard on that instead of `sdk`'s own
|
|
338
|
+
# (now-unconditional) truthiness. `Path(sdk.path)` would otherwise raise
|
|
339
|
+
# on a `None` the moment `resolved_family` alone gated this branch.
|
|
340
|
+
if sdk.path is not None and resolved_family is not None:
|
|
341
|
+
table_dir = Path(sdk.path) / "metadata" / "pinmux"
|
|
342
|
+
try:
|
|
343
|
+
# `resolve_confined` resolves BOTH sides and compares components,
|
|
344
|
+
# so it is correct where a `str.startswith` prefix test is not
|
|
345
|
+
# (Windows case folding, `C:\proj` vs `C:\project2`) -- the same
|
|
346
|
+
# shared guard `init`/`generate`/`scaffold` use, not a fourth
|
|
347
|
+
# hand-rolled copy of the predicate. `OSError`/`ValueError` are
|
|
348
|
+
# caught per its own docstring: a path shape the host rejects
|
|
349
|
+
# outright (a Windows device-namespace path, one embedding a NUL)
|
|
350
|
+
# raises there and is a refusal just the same.
|
|
351
|
+
table_path = resolve_confined(table_dir, table_dir / f"{resolved_family}.yaml")
|
|
352
|
+
except (PathEscapeError, OSError, ValueError):
|
|
353
|
+
issues.append(
|
|
354
|
+
Issue(
|
|
355
|
+
"pinmux.family-invalid",
|
|
356
|
+
"error",
|
|
357
|
+
f"Pinmux capability table for family '{resolved_family}' resolves "
|
|
358
|
+
"outside <sdkRoot>/metadata/pinmux -- refusing to read it.",
|
|
359
|
+
)
|
|
360
|
+
)
|
|
361
|
+
return sdk, resolved_family, None, [], issues, ExitCode.VALIDATION_FAILURE
|
|
362
|
+
try:
|
|
363
|
+
text = table_path.read_text(encoding="utf-8")
|
|
364
|
+
except (OSError, UnicodeDecodeError):
|
|
365
|
+
# tan-cli#415: `UnicodeDecodeError` is a `ValueError`, not an
|
|
366
|
+
# `OSError`, so `except OSError` alone let a non-UTF-8 pinmux
|
|
367
|
+
# table escape `_resolve` uncaught -- caught upstream only by
|
|
368
|
+
# `pinmux()`'s generic backstop, which reported it as an `error`-
|
|
369
|
+
# severity `pinmux.internal-failure` at a non-zero exit instead of
|
|
370
|
+
# this SAME `warning`-severity "I don't know" every other
|
|
371
|
+
# unreadable table already gets at exit 0.
|
|
372
|
+
issues.append(
|
|
373
|
+
Issue(
|
|
374
|
+
"pinmux.table-not-found",
|
|
375
|
+
"warning",
|
|
376
|
+
f"No pinmux capability table for family '{resolved_family}' "
|
|
377
|
+
f"(metadata/pinmux/{resolved_family}.yaml).",
|
|
378
|
+
)
|
|
379
|
+
)
|
|
380
|
+
else:
|
|
381
|
+
try:
|
|
382
|
+
table = parse_pinmux_table_checked(text)
|
|
383
|
+
except PinmuxParseError as err:
|
|
384
|
+
issues.append(
|
|
385
|
+
Issue(
|
|
386
|
+
"pinmux.schema-version-unsupported",
|
|
387
|
+
"error",
|
|
388
|
+
f"Pinmux capability table for family '{resolved_family}' failed to "
|
|
389
|
+
f"parse (metadata/pinmux/{resolved_family}.yaml): {err}",
|
|
390
|
+
)
|
|
391
|
+
)
|
|
392
|
+
exit_code = ExitCode.VALIDATION_FAILURE
|
|
393
|
+
else:
|
|
394
|
+
display_name = table.display_name
|
|
395
|
+
pads = table.pads
|
|
396
|
+
if not pads:
|
|
397
|
+
# `pinmux-capability-v1.schema.json` requires `minItems: 1`:
|
|
398
|
+
# a successful parse of a real v1 table is never
|
|
399
|
+
# legitimately empty.
|
|
400
|
+
issues.append(
|
|
401
|
+
Issue(
|
|
402
|
+
"pinmux.table-empty",
|
|
403
|
+
"error",
|
|
404
|
+
f"Pinmux capability table for family '{resolved_family}' parsed "
|
|
405
|
+
f"with zero pads (metadata/pinmux/{resolved_family}.yaml).",
|
|
406
|
+
)
|
|
407
|
+
)
|
|
408
|
+
exit_code = ExitCode.VALIDATION_FAILURE
|
|
409
|
+
elif sdk.path is None:
|
|
410
|
+
# tan-cli#497 defect 7: when `--sdk-root` WAS given and the loader-marker
|
|
411
|
+
# check rejected it, name the value. The bare string below is the answer
|
|
412
|
+
# for BOTH cases today, so a typo'd flag read as "alp-sdk root is
|
|
413
|
+
# unresolved" with the path the user had just typed nowhere in the
|
|
414
|
+
# envelope (`data.sdkRoot` is `null` on this branch) and nowhere in the
|
|
415
|
+
# stderr text either.
|
|
416
|
+
issues.append(
|
|
417
|
+
Issue(
|
|
418
|
+
"pinmux.sdk-root-unresolved",
|
|
419
|
+
"warning",
|
|
420
|
+
rejected_sdk_root_message(sdk_root, "Cannot read the pinmux table.")
|
|
421
|
+
if sdk_root
|
|
422
|
+
else with_sdk_search(
|
|
423
|
+
"alp-sdk root is unresolved; cannot read the pinmux table.", root
|
|
424
|
+
),
|
|
425
|
+
)
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
return sdk, resolved_family, display_name, pads, issues, exit_code
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def pinmux(
|
|
432
|
+
ctx: typer.Context,
|
|
433
|
+
project: str = typer.Option(
|
|
434
|
+
None, "--project", metavar="PATH", help="Project root (defaults to current directory)."
|
|
435
|
+
),
|
|
436
|
+
sku: str = typer.Option(
|
|
437
|
+
None,
|
|
438
|
+
"--sku",
|
|
439
|
+
# tan-cli#720: `--som` accepted as an alias -- `tan init` spells this
|
|
440
|
+
# same value `--som`, and being rejected here for the flag `init` just
|
|
441
|
+
# taught is the trap this closes.
|
|
442
|
+
"--som",
|
|
443
|
+
metavar="SKU",
|
|
444
|
+
help="SoM SKU to resolve the pinmux family from (e.g. `E1M-AEN701`) (alias: --som).",
|
|
445
|
+
),
|
|
446
|
+
board_yaml: str = typer.Option(
|
|
447
|
+
None,
|
|
448
|
+
"--board-yaml",
|
|
449
|
+
metavar="PATH",
|
|
450
|
+
help="Explicit board.yaml path (overrides project resolution).",
|
|
451
|
+
),
|
|
452
|
+
family: str = typer.Option(
|
|
453
|
+
None,
|
|
454
|
+
"--family",
|
|
455
|
+
metavar="FAMILY",
|
|
456
|
+
help="Pinmux family stem directly (e.g. `aen`, `v2n`); overrides `--sku`.",
|
|
457
|
+
),
|
|
458
|
+
sdk_root: str = typer.Option(
|
|
459
|
+
None, "--sdk-root", metavar="PATH", help="alp-sdk checkout root."
|
|
460
|
+
),
|
|
461
|
+
target: str = typer.Option( # accepted, not read
|
|
462
|
+
None,
|
|
463
|
+
"--target",
|
|
464
|
+
metavar="EMIT",
|
|
465
|
+
help="Generation target (e.g. zephyr-conf, dts-overlay, cmake-args, yocto-conf).",
|
|
466
|
+
),
|
|
467
|
+
all_targets: bool = typer.Option( # accepted, not read
|
|
468
|
+
False, "--all", help="Run command against all relevant targets."
|
|
469
|
+
),
|
|
470
|
+
output_format: OutputFormat = typer.Option(None, "--format", help=FORMAT_HELP),
|
|
471
|
+
verbose: bool = typer.Option( # accepted, not read
|
|
472
|
+
False, "--verbose", help="Emit additional diagnostic detail."
|
|
473
|
+
),
|
|
474
|
+
quiet: bool = typer.Option( # accepted, not read; pinmux's text line is unconditional
|
|
475
|
+
False, "--quiet", help="Suppress non-essential output."
|
|
476
|
+
),
|
|
477
|
+
no_color: bool = typer.Option( # accepted, not read; pinmux emits no ANSI color
|
|
478
|
+
False, "--no-color", help="Disable ANSI color in text output."
|
|
479
|
+
),
|
|
480
|
+
non_interactive: bool = typer.Option( # accepted, not read; pinmux never prompts
|
|
481
|
+
False, "--non-interactive", help="Never prompt."
|
|
482
|
+
),
|
|
483
|
+
ci: bool = typer.Option( # accepted, not read
|
|
484
|
+
False, "--ci", help="CI mode: implies non-interactive and disables color."
|
|
485
|
+
),
|
|
486
|
+
) -> None:
|
|
487
|
+
"""Show the E1M pinmux capability table (E1M pad -> silicon function) for
|
|
488
|
+
a SoM family.
|
|
489
|
+
|
|
490
|
+
`--target`/`--all`/`--verbose`/`--quiet`/`--no-color`/`--non-interactive`/
|
|
491
|
+
`--ci` are declared, not consumed: `pinmux` reads only `--sku`/`--family`
|
|
492
|
+
plus the resolved SDK root (`crates/tan-cli/src/commands/pinmux.rs` never
|
|
493
|
+
touches `GlobalArgs::target`/`all`/`verbose`/`quiet`), but the oracle's
|
|
494
|
+
clap `GlobalArgs` are `global = true`, so every verb accepts all of them.
|
|
495
|
+
"""
|
|
496
|
+
del target, all_targets, verbose, quiet, no_color, non_interactive, ci
|
|
497
|
+
resolved_format = resolve_format(output_format, ctx.obj, choices=OutputFormat)
|
|
498
|
+
json_mode = resolved_format == "json"
|
|
499
|
+
|
|
500
|
+
root, board_path = resolve_project_paths(project, board_yaml)
|
|
501
|
+
try:
|
|
502
|
+
sdk, resolved_family, display_name, pads, issues, exit_code = _resolve(
|
|
503
|
+
sku, family, sdk_root, root
|
|
504
|
+
)
|
|
505
|
+
except Exception as err: # noqa: BLE001 -- the envelope IS the error contract
|
|
506
|
+
sdk, resolved_family, display_name, pads = None, family, None, []
|
|
507
|
+
issues = [
|
|
508
|
+
Issue(
|
|
509
|
+
"pinmux.internal-failure",
|
|
510
|
+
"error",
|
|
511
|
+
f"pinmux failed unexpectedly: {err.__class__.__name__}: {err}",
|
|
512
|
+
)
|
|
513
|
+
]
|
|
514
|
+
exit_code = ExitCode.INTERNAL_FAILURE
|
|
515
|
+
|
|
516
|
+
data: dict[str, Any] = {
|
|
517
|
+
"schemaVersion": DATA_SCHEMA_VERSION,
|
|
518
|
+
"sdkRoot": sdk.path if sdk is not None else None,
|
|
519
|
+
}
|
|
520
|
+
if sku is not None:
|
|
521
|
+
data["sku"] = sku
|
|
522
|
+
data["family"] = resolved_family
|
|
523
|
+
if display_name is not None:
|
|
524
|
+
data["displayName"] = display_name
|
|
525
|
+
data["pads"] = [p.as_dict() for p in pads]
|
|
526
|
+
|
|
527
|
+
# Built ONCE, for both formats: `Envelope.__init__` appends the
|
|
528
|
+
# tan-cli#407 `sdk.discovery-divergent` warning to a NEW issues list
|
|
529
|
+
# (`_with_sdk_divergence`) rather than mutating the one passed in, so a
|
|
530
|
+
# text branch that kept iterating the pre-envelope `issues` local (as
|
|
531
|
+
# this one used to) would build its summary+issues lines correctly for
|
|
532
|
+
# every issue `_resolve` raised, but silently never print a divergence
|
|
533
|
+
# warning appended at the envelope seam -- reproduced live: two alp-sdk
|
|
534
|
+
# checkouts resolving from one directory with no `--sku`/`--family` at
|
|
535
|
+
# all printed only `pinmux: family=- pads=0` in text mode while the JSON
|
|
536
|
+
# envelope carried `sdk.discovery-divergent` alongside `pinmux.no-target`.
|
|
537
|
+
envelope = Envelope(
|
|
538
|
+
"pinmux",
|
|
539
|
+
Project.resolved(root, board_path),
|
|
540
|
+
data,
|
|
541
|
+
issues,
|
|
542
|
+
exit_code,
|
|
543
|
+
# tan-cli#468: `sdk` (from `_resolve`) is now a real `ActiveSdk` even
|
|
544
|
+
# when nothing usable resolved -- `.path` is what says whether there
|
|
545
|
+
# is a checkout to report, `sdk is not None` alone no longer does.
|
|
546
|
+
sdk=SdkInfo.from_resolution(sdk.path, sdk)
|
|
547
|
+
if sdk is not None and sdk.path is not None
|
|
548
|
+
else None,
|
|
549
|
+
)
|
|
550
|
+
if json_mode:
|
|
551
|
+
emit(envelope)
|
|
552
|
+
else:
|
|
553
|
+
stream = typer.get_text_stream("stderr")
|
|
554
|
+
stream.write(f"pinmux: family={resolved_family or '-'} pads={len(pads)}\n")
|
|
555
|
+
# tan-cli#458: the summary line above answers "what happened" but never
|
|
556
|
+
# "why" -- an exit-2 `pinmux.table-empty`/`pinmux.family-invalid`/
|
|
557
|
+
# `pinmux.schema-version-unsupported` used to print a plausible
|
|
558
|
+
# `pads=0` and nothing else, with the envelope's own issue never
|
|
559
|
+
# reaching a terminal. `generate_cmd`/`monitor_cmd`/`build_cmd`/
|
|
560
|
+
# `doctor_cmd`/`examples_cmd` already render issues in text mode this
|
|
561
|
+
# same way, one `f"{command}: {issue.message}"` line per issue on
|
|
562
|
+
# stderr -- pinmux was an outlier among THOSE that built a summary
|
|
563
|
+
# line and stopped (`size_cmd`/`image_cmd` are a separate, still-open
|
|
564
|
+
# gap: they build a distinct text list that drops issues from it
|
|
565
|
+
# entirely, out of scope here).
|
|
566
|
+
for issue in envelope.issues:
|
|
567
|
+
stream.write(f"pinmux: {issue.message}\n")
|
|
568
|
+
raise typer.Exit(int(exit_code))
|