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,734 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""`tan kconfig` -- the board-scoped Kconfig symbol menu for one core (the
|
|
3
|
+
vscode `prj.conf` LSP's live feed).
|
|
4
|
+
|
|
5
|
+
Port of `crates/tan-cli/src/commands/kconfig.rs`. **Not a second Kconfig
|
|
6
|
+
path**: the actual solve is `tan.planner.kconfig_symbols.emit_kconfig`, the
|
|
7
|
+
SAME in-process renderer `tan.planner_root.emit("kconfig", ...)` already
|
|
8
|
+
serves for `west alp-emit kconfig` parity (`tan.planner.cli.emit_artefact`'s
|
|
9
|
+
`mode == "kconfig"` branch) -- this module is composition only: resolve the
|
|
10
|
+
project/SDK/core, call that ONE renderer, and fold its JSON into the standard
|
|
11
|
+
envelope. Reimplementing the Kconfig solve here would be exactly the drift
|
|
12
|
+
the oracle's own module doc warns against (two copies that can disagree).
|
|
13
|
+
|
|
14
|
+
**`--core` defaulting is CLI-side**, mirroring `tan_core::kconfig::
|
|
15
|
+
resolve_default_kconfig_core`: when omitted, this reads board.yaml's own
|
|
16
|
+
`cores:` block DIRECTLY (a lenient, un-validated read -- `preset:`
|
|
17
|
+
cross-file cores are not expanded, matching the oracle's `parse_board_model`,
|
|
18
|
+
which does the same single-file read) and picks the one `os: zephyr` core;
|
|
19
|
+
zero or more than one is a named-candidates error, never a guess.
|
|
20
|
+
`emit_kconfig` itself always requires an explicit core.
|
|
21
|
+
|
|
22
|
+
**ZEPHYR_BASE resolution** (tan-cli#453) delegates to `tan.core.venv.
|
|
23
|
+
west_workspace_dir` -- the SAME shared resolver `build`/`flash`/
|
|
24
|
+
`west_forward_cmd` already consult, not a second, private copy. It used to be
|
|
25
|
+
one: a hand-rolled 3-tier ladder (upward `.west` walk, an already-exported
|
|
26
|
+
`ZEPHYR_BASE`, the SDK-derived layouts) that skipped the MANIFEST
|
|
27
|
+
verification (tan-cli#307) the shared resolver applies to tiers 1 and 2 --
|
|
28
|
+
so on a host with an unrelated ancestor `.west` checkout, or an ambient
|
|
29
|
+
`$ZEPHYR_BASE` naming a DIFFERENT Zephyr tree, this command picked that
|
|
30
|
+
wrong workspace instead of the one an ordinary `tan bootstrap` had already
|
|
31
|
+
built for THIS `--sdk-root`, or fell through to nothing. "Dead by default on
|
|
32
|
+
any host that has another Zephyr checkout around" -- verbatim the bug
|
|
33
|
+
report. `west_workspace_dir` returns the workspace TOPDIR; `zephyr_base` is
|
|
34
|
+
then `<topdir>/zephyr`, filtered to a real directory, mirroring
|
|
35
|
+
`build/execute.py`'s own tan-cli#308 derivation exactly (`workspace_dir /
|
|
36
|
+
"zephyr"`, `None` if it is not a directory yet) so the two can no longer
|
|
37
|
+
disagree about what counts as "bootstrapped".
|
|
38
|
+
|
|
39
|
+
The resolved base is INJECTED into `os.environ["ZEPHYR_BASE"]` around the
|
|
40
|
+
`_plan_emit` call (temporarily, restored after) rather than relied on
|
|
41
|
+
ambiently -- `tan.planner.kconfig_symbols` reads `os.environ["ZEPHYR_BASE"]`
|
|
42
|
+
directly (it renders in-process, so there is no child env to set it on the
|
|
43
|
+
way the Rust oracle sets it on a spawned command), so a tree- or
|
|
44
|
+
SDK-derived workspace would otherwise never reach the planner at all.
|
|
45
|
+
|
|
46
|
+
**The workspace `west` no longer depends on `PATH` at all** (tan-cli#453,
|
|
47
|
+
other half; tan-cli#459 review). The in-process renderer's own
|
|
48
|
+
workspace-dependent step (`kconfig_symbols._load_board_symbols`) used to
|
|
49
|
+
shell a BARE `west build` with no explicit `env=` -- unlike every spawn in
|
|
50
|
+
`build`/`flash`, it never resolved an absolute, venv-scoped `west` binary
|
|
51
|
+
before invoking it, so it depended entirely on whatever `west` PATH already
|
|
52
|
+
had, which is nothing: `tan bootstrap` installs `west` ONLY inside the
|
|
53
|
+
workspace-local venv, never system- or user-wide. An earlier version of this
|
|
54
|
+
fix prepended that venv's `bin`/`Scripts` dir onto `os.environ["PATH"]`
|
|
55
|
+
around the call here, in this module -- a mitigation scoped to `tan kconfig`
|
|
56
|
+
alone that left the same bare `"west"` in place one layer down. The real fix
|
|
57
|
+
is IN `kconfig_symbols._load_board_symbols`: it now resolves
|
|
58
|
+
`tan.core.venv.west_program` itself and spawns that absolute path, so
|
|
59
|
+
neither this module nor any other future caller needs to touch `PATH` for
|
|
60
|
+
it to work.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
from __future__ import annotations
|
|
64
|
+
|
|
65
|
+
import json as _json
|
|
66
|
+
import os
|
|
67
|
+
import sys
|
|
68
|
+
from pathlib import Path
|
|
69
|
+
|
|
70
|
+
import typer
|
|
71
|
+
|
|
72
|
+
from tan.commands.presets_cmd import resolve_project_paths, resolve_sdk
|
|
73
|
+
from tan.commands.sdk_cmd import NO_SDK_NEXT_STEPS
|
|
74
|
+
from tan.core.sdk_discovery import with_sdk_search
|
|
75
|
+
from tan.core.global_flags import accept_global_flags
|
|
76
|
+
from tan.core.link_refusal import refusal_code
|
|
77
|
+
from tan.core.sdk_discovery import sdk_resolution_issues
|
|
78
|
+
from tan.core.venv import west_workspace_dir
|
|
79
|
+
from tan.envelope import Envelope, Issue, Project, SdkDisclosure, SdkInfo, emit
|
|
80
|
+
from tan.exit_codes import ExitCode
|
|
81
|
+
from tan.output_format import FORMAT_HELP, OutputFormat
|
|
82
|
+
|
|
83
|
+
#: `tan.planner.kconfig_symbols.SCHEMA_VERSION`, verbatim -- not imported
|
|
84
|
+
#: directly: every `tan.planner.*` submodule import runs the package's own
|
|
85
|
+
#: `__init__.py`, which needs `tan.planner_root.bind_sdk_root` to have
|
|
86
|
+
#: already happened (its `paths.py` binds `REPO = sdk_root()` at module
|
|
87
|
+
#: scope) -- pulling the constant in at THIS module's import time, before
|
|
88
|
+
#: any SDK is resolved, would be exactly that ordering trap.
|
|
89
|
+
KCONFIG_SCHEMA_VERSION = 1
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class _CoreResolutionError(Exception):
|
|
93
|
+
"""`--core` could not be resolved from board.yaml. Carries the envelope
|
|
94
|
+
issue code and message the caller reports verbatim."""
|
|
95
|
+
|
|
96
|
+
def __init__(self, code: str, message: str) -> None:
|
|
97
|
+
super().__init__(message)
|
|
98
|
+
self.code = code
|
|
99
|
+
self.message = message
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _yaml_type_name(value: object) -> str:
|
|
103
|
+
"""serde_yaml-flavoured type name for a board-yaml-invalid message
|
|
104
|
+
(`invalid type: sequence, expected struct BoardModel`, verified against
|
|
105
|
+
`target/debug/tan.exe`)."""
|
|
106
|
+
if isinstance(value, list):
|
|
107
|
+
return "sequence"
|
|
108
|
+
if isinstance(value, bool):
|
|
109
|
+
return "boolean"
|
|
110
|
+
if isinstance(value, (int, float)):
|
|
111
|
+
return "number"
|
|
112
|
+
if isinstance(value, str):
|
|
113
|
+
return "string"
|
|
114
|
+
return type(value).__name__
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _resolve_core(core_arg: str | None, board_yaml: str) -> str:
|
|
118
|
+
"""The explicit `--core`, or the board's one declared `os: zephyr` core.
|
|
119
|
+
|
|
120
|
+
Mirrors `tan_core::kconfig::resolve_core` (Rust `kconfig.rs`): a raw,
|
|
121
|
+
single-file read of board.yaml's `cores:` block -- no schema validation,
|
|
122
|
+
no preset expansion -- so this works even for a board.yaml the full
|
|
123
|
+
planner would refuse for an unrelated reason, exactly like the oracle.
|
|
124
|
+
"""
|
|
125
|
+
if core_arg:
|
|
126
|
+
return core_arg
|
|
127
|
+
try:
|
|
128
|
+
text = Path(board_yaml).read_text(encoding="utf-8")
|
|
129
|
+
except (OSError, UnicodeDecodeError) as err:
|
|
130
|
+
# tan-cli#396: `UnicodeDecodeError` is a `ValueError`, NOT an `OSError`,
|
|
131
|
+
# so an `except OSError` alone could not fire for a board.yaml saved in
|
|
132
|
+
# cp1252/latin-1 (or with one stray byte pasted into a comment) -- it
|
|
133
|
+
# escaped as an unhandled traceback, rc 1 with ZERO bytes on stdout.
|
|
134
|
+
# That is the worst shape a machine interface has: the extension's two
|
|
135
|
+
# string matches against the envelope both fail open, so the `prj.conf`
|
|
136
|
+
# symbol menu renders empty or stale and never says why.
|
|
137
|
+
#
|
|
138
|
+
# Deliberately the SAME code and message an unreadable file gets,
|
|
139
|
+
# because the oracle reaches this one arm for both: Rust's
|
|
140
|
+
# `std::fs::read_to_string` returns an `io::Error` (kind `InvalidData`,
|
|
141
|
+
# "stream did not contain valid UTF-8") for non-UTF-8 bytes, and
|
|
142
|
+
# `crates/tan-cli/src/commands/kconfig.rs:156-161` maps every
|
|
143
|
+
# `read_to_string` error to `kconfig.board-yaml-missing`. Not
|
|
144
|
+
# `errors="replace"`: that would silently mangle the user's bytes and
|
|
145
|
+
# then resolve a core out of the mangled result.
|
|
146
|
+
#
|
|
147
|
+
# tan-cli#440: a SECOND `except UnicodeDecodeError:` used to follow
|
|
148
|
+
# this clause, raising `kconfig.board-yaml-invalid` for the same
|
|
149
|
+
# bytes. It was unreachable -- the tuple above already consumes every
|
|
150
|
+
# `UnicodeDecodeError` -- and it contradicted this one about which
|
|
151
|
+
# code the contract owes. Deleted rather than reordered: reordering
|
|
152
|
+
# would have made the WRONG code live. Re-measured against the frozen
|
|
153
|
+
# oracle before deleting, on a board.yaml carrying one 0xE9 byte:
|
|
154
|
+
#
|
|
155
|
+
# $ target/debug/tan kconfig --sdk-root <sdk> --format json # tan 0.4.1
|
|
156
|
+
# ... "issues":[{"code":"kconfig.board-yaml-missing","severity":"error",
|
|
157
|
+
# "message":"failed to read board.yaml at `<...>`: stream did not
|
|
158
|
+
# contain valid UTF-8"}] rc=2
|
|
159
|
+
#
|
|
160
|
+
# so `board-yaml-missing` is the contract and the deleted arm was the
|
|
161
|
+
# pre-#421 explanation left behind by that parity correction.
|
|
162
|
+
raise _CoreResolutionError(
|
|
163
|
+
"kconfig.board-yaml-missing",
|
|
164
|
+
f"failed to read board.yaml at `{board_yaml}`: {err}",
|
|
165
|
+
) from err
|
|
166
|
+
import yaml # noqa: PLC0415 -- deferred (tan-cli#810); see `new_som_cmd`'s
|
|
167
|
+
# `_yaml_scalar` for why this file must not
|
|
168
|
+
# import PyYAML at module scope.
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
doc = yaml.safe_load(text)
|
|
172
|
+
except yaml.YAMLError as err:
|
|
173
|
+
raise _CoreResolutionError(
|
|
174
|
+
"kconfig.board-yaml-invalid",
|
|
175
|
+
f"failed to parse board.yaml at `{board_yaml}`: {err}",
|
|
176
|
+
) from err
|
|
177
|
+
# `doc` not a mapping (a list-shaped board.yaml, e.g.) is a structural
|
|
178
|
+
# parse failure, not "no cores declared" -- the oracle's `parse_board_model`
|
|
179
|
+
# deserializes the WHOLE document into a typed struct, so a non-mapping
|
|
180
|
+
# top level (or a `cores:` block that isn't itself a mapping) fails there,
|
|
181
|
+
# never reaches core resolution at all. Falling through to the ambiguous
|
|
182
|
+
# branch here would tell the customer to pass `--core` for a file that
|
|
183
|
+
# will not parse, full stop -- same exit code, wrong issue code and wrong
|
|
184
|
+
# remedy. `doc is None` (an empty/`null` file) is legitimate -- the oracle's
|
|
185
|
+
# `Option<BoardModel>` parses that to the default model -- and keeps
|
|
186
|
+
# falling through to "no cores declared" below.
|
|
187
|
+
if doc is not None and not isinstance(doc, dict):
|
|
188
|
+
raise _CoreResolutionError(
|
|
189
|
+
"kconfig.board-yaml-invalid",
|
|
190
|
+
f"failed to parse board.yaml at `{board_yaml}`: board.yaml is not valid "
|
|
191
|
+
f"YAML: invalid type: {_yaml_type_name(doc)}, expected a mapping",
|
|
192
|
+
)
|
|
193
|
+
cores = doc.get("cores") if isinstance(doc, dict) else None
|
|
194
|
+
if cores is not None and not isinstance(cores, dict):
|
|
195
|
+
raise _CoreResolutionError(
|
|
196
|
+
"kconfig.board-yaml-invalid",
|
|
197
|
+
f"failed to parse board.yaml at `{board_yaml}`: board.yaml is not valid "
|
|
198
|
+
f"YAML: cores: invalid type: {_yaml_type_name(cores)}, expected a map",
|
|
199
|
+
)
|
|
200
|
+
cores = cores or {}
|
|
201
|
+
zephyr_ids = sorted(
|
|
202
|
+
cid
|
|
203
|
+
for cid, entry in cores.items()
|
|
204
|
+
if isinstance(entry, dict) and entry.get("os") == "zephyr"
|
|
205
|
+
)
|
|
206
|
+
if len(zephyr_ids) == 1:
|
|
207
|
+
return zephyr_ids[0]
|
|
208
|
+
hint = (
|
|
209
|
+
"board.yaml declares no cores"
|
|
210
|
+
if not cores
|
|
211
|
+
else f"declared cores: {', '.join(sorted(str(c) for c in cores))}"
|
|
212
|
+
)
|
|
213
|
+
raise _CoreResolutionError(
|
|
214
|
+
"kconfig.core-ambiguous",
|
|
215
|
+
f"--core <id> is required (board.yaml doesn't declare exactly one Zephyr "
|
|
216
|
+
f"core); {hint}",
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _resolve_zephyr_base(root: str, sdk_root: str | None) -> str | None:
|
|
221
|
+
"""The bootstrapped `ZEPHYR_BASE`, or `None`. tan-cli#453: delegates to
|
|
222
|
+
`tan.core.venv.west_workspace_dir` -- the SAME manifest-verified resolver
|
|
223
|
+
`build`/`flash`/`west_forward_cmd` already share (see the module
|
|
224
|
+
docstring for why the private ladder this replaced was a real bug, not
|
|
225
|
+
just a duplicate) -- rather than a second, private tier ladder that could
|
|
226
|
+
drift from it again. `<topdir>/zephyr`, filtered to a real directory,
|
|
227
|
+
mirrors `build/execute.py`'s own tan-cli#308 derivation byte for byte.
|
|
228
|
+
"""
|
|
229
|
+
workspace = west_workspace_dir(root, Path(sdk_root) if sdk_root else None)
|
|
230
|
+
if workspace is None:
|
|
231
|
+
return None
|
|
232
|
+
zephyr = workspace / "zephyr"
|
|
233
|
+
return str(zephyr) if zephyr.is_dir() else None
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _empty_data(core: str | None) -> dict:
|
|
237
|
+
return {
|
|
238
|
+
"schemaVersion": KCONFIG_SCHEMA_VERSION,
|
|
239
|
+
"board": "",
|
|
240
|
+
"core": core or "",
|
|
241
|
+
"symbols": [],
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _fail(
|
|
246
|
+
*,
|
|
247
|
+
root: str,
|
|
248
|
+
board_path: str,
|
|
249
|
+
exit_code: ExitCode,
|
|
250
|
+
code: str,
|
|
251
|
+
message: str,
|
|
252
|
+
core: str | None,
|
|
253
|
+
json_mode: bool,
|
|
254
|
+
sdk: SdkInfo | None = None,
|
|
255
|
+
sdk_issues: list[Issue] | None = None,
|
|
256
|
+
) -> None:
|
|
257
|
+
"""tan-cli#497 defect 2: this used to hardcode `[Issue(code, "error",
|
|
258
|
+
message)]`, so EVERY refusal dropped whatever `sdk.project-pin-unresolved`
|
|
259
|
+
/ `sdk.global-default-foreign-project` the resolution had already
|
|
260
|
+
computed -- while `sdk current`, `presets`, `size` and `image` all
|
|
261
|
+
surfaced them from the identical workspace. `sdk_issues` is the pair from
|
|
262
|
+
the one shared `sdk_resolution_issues`, prepended (warnings first, the
|
|
263
|
+
order `flash`/`size`/`image` use), so no refusal path here can drop them
|
|
264
|
+
again. Printed in text mode too, `{severity}: {message}` -- the shape
|
|
265
|
+
`build_cmd`/`run` use -- because text is the DEFAULT mode and this
|
|
266
|
+
command's whole job is answering "which symbols does my board have",
|
|
267
|
+
solved out of a checkout the pin does not name."""
|
|
268
|
+
warnings = sdk_issues or []
|
|
269
|
+
if json_mode:
|
|
270
|
+
emit(
|
|
271
|
+
Envelope(
|
|
272
|
+
"kconfig",
|
|
273
|
+
Project.resolved(root, board_path),
|
|
274
|
+
_empty_data(core),
|
|
275
|
+
[*warnings, Issue(code, "error", message)],
|
|
276
|
+
exit_code,
|
|
277
|
+
sdk=sdk,
|
|
278
|
+
)
|
|
279
|
+
)
|
|
280
|
+
else:
|
|
281
|
+
for issue in warnings:
|
|
282
|
+
print(f"{issue.severity}: {issue.message}", file=sys.stderr)
|
|
283
|
+
print(f"kconfig: {message}", file=sys.stderr)
|
|
284
|
+
raise typer.Exit(int(exit_code))
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class _KconfigShapeError(Exception):
|
|
288
|
+
"""A `--emit kconfig` document decoded as JSON but does not match
|
|
289
|
+
`tan_core::KconfigData`/`KconfigSymbol` (`crates/tan-core/src/kconfig.rs`).
|
|
290
|
+
|
|
291
|
+
The oracle's `parse_kconfig` deserializes straight into those typed
|
|
292
|
+
structs, so a MISSING `board`/`core`/`symbols` key -- or a symbol missing
|
|
293
|
+
`name`/`type`/`prompt`/`depends`/`default`/`help` -- is a `serde_json`
|
|
294
|
+
deserialize error wrapped in the SAME `KconfigError::Json` variant a raw
|
|
295
|
+
JSON syntax error gets (`kconfig.rs`'s `deserialize_present_option` doc
|
|
296
|
+
comment: "a missing key is never legitimate input -- it means the SDK
|
|
297
|
+
renamed the field"). This carries only the `serde`-shaped detail text;
|
|
298
|
+
the caller wraps it in the identical `kconfig.parse-failed` code and
|
|
299
|
+
`"kconfig emit is not valid JSON: {0}"` message prefix a syntax error
|
|
300
|
+
gets, rather than inventing a second message shape the oracle has none
|
|
301
|
+
of.
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _validate_kconfig_data(data: dict) -> dict:
|
|
306
|
+
"""Structurally validate an already-JSON-decoded emit body and rebuild
|
|
307
|
+
`data` from exactly the modelled fields (dropping any unmodelled key --
|
|
308
|
+
the oracle's typed deserialize does the same), so `data` is exactly the
|
|
309
|
+
shape `Envelope<KconfigData>` serializes. Raises `_KconfigShapeError` on
|
|
310
|
+
the first missing/mistyped key, mirroring the oracle's required (never
|
|
311
|
+
`#[serde(default)]`) `board`/`core`/`symbols`/per-symbol fields --
|
|
312
|
+
`default` is the one field that is REQUIRED-but-nullable.
|
|
313
|
+
"""
|
|
314
|
+
|
|
315
|
+
def require(mapping: dict, key: str) -> object:
|
|
316
|
+
if key not in mapping:
|
|
317
|
+
raise _KconfigShapeError(f"missing field `{key}`")
|
|
318
|
+
return mapping[key]
|
|
319
|
+
|
|
320
|
+
def require_str(mapping: dict, key: str) -> str:
|
|
321
|
+
value = require(mapping, key)
|
|
322
|
+
if not isinstance(value, str):
|
|
323
|
+
raise _KconfigShapeError(
|
|
324
|
+
f"invalid type: {_yaml_type_name(value)} for `{key}`, expected a string"
|
|
325
|
+
)
|
|
326
|
+
return value
|
|
327
|
+
|
|
328
|
+
board = require_str(data, "board")
|
|
329
|
+
core = require_str(data, "core")
|
|
330
|
+
symbols = require(data, "symbols")
|
|
331
|
+
if not isinstance(symbols, list):
|
|
332
|
+
raise _KconfigShapeError(
|
|
333
|
+
f"invalid type: {_yaml_type_name(symbols)} for `symbols`, expected a list"
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
rebuilt_symbols = []
|
|
337
|
+
for index, symbol in enumerate(symbols):
|
|
338
|
+
if not isinstance(symbol, dict):
|
|
339
|
+
raise _KconfigShapeError(
|
|
340
|
+
f"invalid type: {_yaml_type_name(symbol)} for `symbols[{index}]`, "
|
|
341
|
+
f"expected an object"
|
|
342
|
+
)
|
|
343
|
+
name = require_str(symbol, "name")
|
|
344
|
+
sym_type = require_str(symbol, "type")
|
|
345
|
+
prompt = require_str(symbol, "prompt")
|
|
346
|
+
depends = require_str(symbol, "depends")
|
|
347
|
+
default = require(symbol, "default") # present, but nullable
|
|
348
|
+
if default is not None and not isinstance(default, str):
|
|
349
|
+
raise _KconfigShapeError(
|
|
350
|
+
f"invalid type: {_yaml_type_name(default)} for `symbols[{index}].default`, "
|
|
351
|
+
f"expected a string or null"
|
|
352
|
+
)
|
|
353
|
+
help_text = require_str(symbol, "help")
|
|
354
|
+
rebuilt_symbols.append(
|
|
355
|
+
{
|
|
356
|
+
"name": name,
|
|
357
|
+
"type": sym_type,
|
|
358
|
+
"prompt": prompt,
|
|
359
|
+
"depends": depends,
|
|
360
|
+
"default": default,
|
|
361
|
+
"help": help_text,
|
|
362
|
+
}
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
"schemaVersion": data["schemaVersion"],
|
|
367
|
+
"board": board,
|
|
368
|
+
"core": core,
|
|
369
|
+
"symbols": rebuilt_symbols,
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _text_lines(data: dict, verbose: bool) -> list[str]:
|
|
374
|
+
symbols = data.get("symbols") or []
|
|
375
|
+
lines = [
|
|
376
|
+
f"kconfig: {len(symbols)} symbol(s) for core '{data.get('core', '')}' "
|
|
377
|
+
f"({data.get('board', '')})"
|
|
378
|
+
]
|
|
379
|
+
if verbose:
|
|
380
|
+
for sym in symbols:
|
|
381
|
+
lines.append(f" {sym.get('name')} [{sym.get('type')}] — {sym.get('prompt')}")
|
|
382
|
+
return lines
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _run_kconfig(
|
|
386
|
+
*,
|
|
387
|
+
root: str,
|
|
388
|
+
board_path: str,
|
|
389
|
+
core: str | None,
|
|
390
|
+
sdk_root: str | None,
|
|
391
|
+
verbose: bool,
|
|
392
|
+
json_mode: bool,
|
|
393
|
+
disclosure: SdkDisclosure,
|
|
394
|
+
) -> None:
|
|
395
|
+
"""The whole setup-class ladder plus the emit, split out of `kconfig`
|
|
396
|
+
below so that command can wrap it in ONE catch-all (tan-cli#396) without
|
|
397
|
+
indenting 130 lines under a `try:`.
|
|
398
|
+
|
|
399
|
+
Every failure exits through `_fail`, which raises `typer.Exit` after
|
|
400
|
+
writing the envelope -- so this function's only control-flow exception is
|
|
401
|
+
`typer.Exit`, which is exactly what the caller re-raises untouched.
|
|
402
|
+
|
|
403
|
+
`disclosure` is the caller's, by reference: the resolution facts are
|
|
404
|
+
computed HERE and the caller's `kconfig.internal-failure` handler is the
|
|
405
|
+
tenth `_fail` site, so it needs a name to read them from after this
|
|
406
|
+
function has already raised. See `SdkDisclosure`.
|
|
407
|
+
"""
|
|
408
|
+
# Setup-class check #1: no SDK checkout resolved -- checked before core
|
|
409
|
+
# resolution so every setup-class failure here is uniformly one shape,
|
|
410
|
+
# never a spawn attempt with half-resolved inputs (mirrors kconfig.rs).
|
|
411
|
+
sdk = resolve_sdk(sdk_root, root)
|
|
412
|
+
if sdk.path is None:
|
|
413
|
+
# tan-cli#497 defect 2, the branch #578 explicitly left open (its own
|
|
414
|
+
# comment here used to read "there is nothing resolved to report").
|
|
415
|
+
# tan-cli#468 closed the gap this branch used to work around:
|
|
416
|
+
# `presets_cmd.resolve_sdk` now always returns an `ActiveSdk` (never a
|
|
417
|
+
# bare `None`) and carries `broken_project_pin`/
|
|
418
|
+
# `foreign_global_default_for` through even when `.path` stays
|
|
419
|
+
# unresolved -- so a workspace whose own `.alp/sdk-path` pin is
|
|
420
|
+
# broken, with no OTHER tier resolving anything either, no longer has
|
|
421
|
+
# to re-run the ladder a second time to recover them; `sdk` already
|
|
422
|
+
# has them.
|
|
423
|
+
#
|
|
424
|
+
# Still no `sdk=` -- there is genuinely no root to report, matching
|
|
425
|
+
# the oracle -- but `sdk_issues` carries whatever the ladder found
|
|
426
|
+
# before giving up, in both JSON and text (`_fail` prepends and
|
|
427
|
+
# prints them), so the workspace's own diagnosis reaches the user
|
|
428
|
+
# instead of being computed and silently discarded.
|
|
429
|
+
sdk_issues = sdk_resolution_issues(
|
|
430
|
+
sdk.broken_project_pin, sdk.tier, sdk.foreign_global_default_for
|
|
431
|
+
)
|
|
432
|
+
_fail(
|
|
433
|
+
root=root,
|
|
434
|
+
board_path=board_path,
|
|
435
|
+
exit_code=ExitCode.VALIDATION_FAILURE,
|
|
436
|
+
code="kconfig.no-sdk-root",
|
|
437
|
+
# `tan sdk switch` refuses in this build (tan-cli#305). Dropped
|
|
438
|
+
# "run `tan bootstrap` first" too -- bootstrap resolves an SDK
|
|
439
|
+
# through this exact same ladder, so with none resolved it
|
|
440
|
+
# refuses right back with tan-cli#305's own fix text; it is not a
|
|
441
|
+
# remedy for THIS failure, just a second site that needs one.
|
|
442
|
+
message=with_sdk_search(
|
|
443
|
+
f"no alp-sdk checkout found — pass `--sdk-root <PATH>`, or "
|
|
444
|
+
f"{NO_SDK_NEXT_STEPS}.",
|
|
445
|
+
root,
|
|
446
|
+
sdk_root,
|
|
447
|
+
),
|
|
448
|
+
core=None,
|
|
449
|
+
json_mode=json_mode,
|
|
450
|
+
sdk_issues=sdk_issues,
|
|
451
|
+
)
|
|
452
|
+
return
|
|
453
|
+
# tan-cli#504's blessed constructor: it carries `foreign_global_default_for`
|
|
454
|
+
# and `broken_project_pin` off the resolution onto the `SdkInfo`, which is
|
|
455
|
+
# what `Envelope._with_sdk_resolution_advisories` reads. A raw
|
|
456
|
+
# `SdkInfo(sdk.path, sdk.tier)` drops both and
|
|
457
|
+
# `tests/gates/test_sdk_info_is_built_from_a_resolution.py` refuses it.
|
|
458
|
+
sdk_info = SdkInfo.from_resolution(sdk.path, sdk)
|
|
459
|
+
# tan-cli#497 defect 2: `resolve_sdk` deliberately CARRIES both facts onto
|
|
460
|
+
# the `ActiveSdk` it returns, and this module read neither -- it imported
|
|
461
|
+
# `resolve_sdk` and none of the issue helpers, so a workspace whose
|
|
462
|
+
# `.alp/sdk-path` pin misses answered `ok: true, issues: []` with a full
|
|
463
|
+
# symbol menu solved out of a checkout the pin does not name. Computed
|
|
464
|
+
# ONCE here, threaded into all seven `_fail` sites below and into the
|
|
465
|
+
# success emit -- and, via `disclosure` just below, into the eighth site
|
|
466
|
+
# this function cannot reach: `kconfig`'s own `kconfig.internal-failure`
|
|
467
|
+
# catch-all. Ten `_fail` calls exist; the tenth is the `sdk.path is None`
|
|
468
|
+
# branch above, which reads the same two facts straight off `sdk` now
|
|
469
|
+
# (tan-cli#468 -- `resolve_sdk` carries them even when unresolved, so no
|
|
470
|
+
# second `resolve_sdk_tiered` call is needed any more).
|
|
471
|
+
#
|
|
472
|
+
# Kept alongside #504's envelope-seam advisory rather than deleted in
|
|
473
|
+
# favour of it: the seam appends its pair at the END and dedupes BY CODE
|
|
474
|
+
# ("a command that already emitted the pair keeps its own copy and
|
|
475
|
+
# position"), so this hand-call is what keeps the two issues in this
|
|
476
|
+
# command's own order, ahead of the command-specific issues below.
|
|
477
|
+
sdk_issues = sdk_resolution_issues(
|
|
478
|
+
sdk.broken_project_pin, sdk.tier, sdk.foreign_global_default_for
|
|
479
|
+
)
|
|
480
|
+
# Handed to the caller's `kconfig.internal-failure` handler the instant
|
|
481
|
+
# both facts exist -- that handler is the one `_fail` site this function
|
|
482
|
+
# cannot reach, and every line below it can raise something unenumerated.
|
|
483
|
+
disclosure.record(sdk_info, sdk_issues)
|
|
484
|
+
|
|
485
|
+
try:
|
|
486
|
+
resolved_core = _resolve_core(core, board_path)
|
|
487
|
+
except _CoreResolutionError as err:
|
|
488
|
+
_fail(
|
|
489
|
+
root=root,
|
|
490
|
+
board_path=board_path,
|
|
491
|
+
exit_code=ExitCode.VALIDATION_FAILURE,
|
|
492
|
+
code=err.code,
|
|
493
|
+
message=err.message,
|
|
494
|
+
core=None,
|
|
495
|
+
json_mode=json_mode,
|
|
496
|
+
sdk=sdk_info,
|
|
497
|
+
sdk_issues=sdk_issues,
|
|
498
|
+
)
|
|
499
|
+
return
|
|
500
|
+
|
|
501
|
+
# Setup-class check #2: no bootstrapped Zephyr workspace (needs
|
|
502
|
+
# ZEPHYR_BASE for the real Kconfig solver).
|
|
503
|
+
zephyr_base = _resolve_zephyr_base(root, sdk.path)
|
|
504
|
+
if zephyr_base is None:
|
|
505
|
+
_fail(
|
|
506
|
+
root=root,
|
|
507
|
+
board_path=board_path,
|
|
508
|
+
exit_code=ExitCode.VALIDATION_FAILURE,
|
|
509
|
+
code="kconfig.no-workspace",
|
|
510
|
+
message="no bootstrapped Zephyr workspace found for `--emit kconfig` "
|
|
511
|
+
"(needs ZEPHYR_BASE) — run `tan bootstrap` first.",
|
|
512
|
+
core=resolved_core,
|
|
513
|
+
json_mode=json_mode,
|
|
514
|
+
sdk=sdk_info,
|
|
515
|
+
sdk_issues=sdk_issues,
|
|
516
|
+
)
|
|
517
|
+
return
|
|
518
|
+
|
|
519
|
+
from tan.planner_root import emit as _plan_emit
|
|
520
|
+
|
|
521
|
+
# Inject the RESOLVED base, not the ambient one -- `kconfig_symbols`
|
|
522
|
+
# renders in-process and reads `os.environ["ZEPHYR_BASE"]` directly, so a
|
|
523
|
+
# tree- or SDK-derived workspace (tiers 1/3 above) would never reach the
|
|
524
|
+
# planner without this. Restored afterwards so this call has no lasting
|
|
525
|
+
# effect on the process env (other commands, and other tests in the same
|
|
526
|
+
# process, must not see it).
|
|
527
|
+
prev_zephyr_base = os.environ.get("ZEPHYR_BASE")
|
|
528
|
+
os.environ["ZEPHYR_BASE"] = zephyr_base
|
|
529
|
+
try:
|
|
530
|
+
try:
|
|
531
|
+
stdout_text = _plan_emit(
|
|
532
|
+
"kconfig", root=sdk.path, board_yaml=Path(board_path), core=resolved_core
|
|
533
|
+
)
|
|
534
|
+
except SystemExit as err:
|
|
535
|
+
_fail(
|
|
536
|
+
root=root,
|
|
537
|
+
board_path=board_path,
|
|
538
|
+
exit_code=ExitCode.RUNTIME_FAILURE,
|
|
539
|
+
code="kconfig.emit-failed",
|
|
540
|
+
message=f"the kconfig emit exited early (code {err.code}).",
|
|
541
|
+
core=resolved_core,
|
|
542
|
+
json_mode=json_mode,
|
|
543
|
+
sdk=sdk_info,
|
|
544
|
+
sdk_issues=sdk_issues,
|
|
545
|
+
)
|
|
546
|
+
return
|
|
547
|
+
except Exception as err: # noqa: BLE001 -- every planner failure is an
|
|
548
|
+
# envelope, never a traceback (mirrors build_cmd._emit_plan's own
|
|
549
|
+
# backstop); includes `OrchestratorError` for an unknown/non-Zephyr
|
|
550
|
+
# `--core`.
|
|
551
|
+
# tan-cli#1350: a `diagnostics.link: itcm` refusal keeps its own
|
|
552
|
+
# code and the validation exit, as in `tan build`.
|
|
553
|
+
link_code = refusal_code(err)
|
|
554
|
+
_fail(
|
|
555
|
+
root=root,
|
|
556
|
+
board_path=board_path,
|
|
557
|
+
exit_code=(ExitCode.VALIDATION_FAILURE if link_code
|
|
558
|
+
else ExitCode.RUNTIME_FAILURE),
|
|
559
|
+
code=link_code or "kconfig.emit-failed",
|
|
560
|
+
message=f"the kconfig emit failed: {type(err).__name__}: {err}",
|
|
561
|
+
core=resolved_core,
|
|
562
|
+
json_mode=json_mode,
|
|
563
|
+
sdk=sdk_info,
|
|
564
|
+
sdk_issues=sdk_issues,
|
|
565
|
+
)
|
|
566
|
+
return
|
|
567
|
+
finally:
|
|
568
|
+
if prev_zephyr_base is None:
|
|
569
|
+
os.environ.pop("ZEPHYR_BASE", None)
|
|
570
|
+
else:
|
|
571
|
+
os.environ["ZEPHYR_BASE"] = prev_zephyr_base
|
|
572
|
+
|
|
573
|
+
try:
|
|
574
|
+
data = _json.loads(stdout_text)
|
|
575
|
+
except _json.JSONDecodeError as err:
|
|
576
|
+
_fail(
|
|
577
|
+
root=root,
|
|
578
|
+
board_path=board_path,
|
|
579
|
+
exit_code=ExitCode.RUNTIME_FAILURE,
|
|
580
|
+
code="kconfig.parse-failed",
|
|
581
|
+
message=f"kconfig emit is not valid JSON: {err}",
|
|
582
|
+
core=resolved_core,
|
|
583
|
+
json_mode=json_mode,
|
|
584
|
+
sdk=sdk_info,
|
|
585
|
+
sdk_issues=sdk_issues,
|
|
586
|
+
)
|
|
587
|
+
return
|
|
588
|
+
if not isinstance(data, dict) or data.get("schemaVersion") != KCONFIG_SCHEMA_VERSION:
|
|
589
|
+
found = data.get("schemaVersion") if isinstance(data, dict) else None
|
|
590
|
+
_fail(
|
|
591
|
+
root=root,
|
|
592
|
+
board_path=board_path,
|
|
593
|
+
exit_code=ExitCode.RUNTIME_FAILURE,
|
|
594
|
+
code="kconfig.parse-failed",
|
|
595
|
+
message=f"unsupported kconfig schemaVersion {found!r} (this CLI "
|
|
596
|
+
f"consumes v{KCONFIG_SCHEMA_VERSION}); upgrade the CLI or the SDK so "
|
|
597
|
+
f"the versions match",
|
|
598
|
+
core=resolved_core,
|
|
599
|
+
json_mode=json_mode,
|
|
600
|
+
sdk=sdk_info,
|
|
601
|
+
sdk_issues=sdk_issues,
|
|
602
|
+
)
|
|
603
|
+
return
|
|
604
|
+
|
|
605
|
+
try:
|
|
606
|
+
data = _validate_kconfig_data(data)
|
|
607
|
+
except _KconfigShapeError as err:
|
|
608
|
+
_fail(
|
|
609
|
+
root=root,
|
|
610
|
+
board_path=board_path,
|
|
611
|
+
exit_code=ExitCode.RUNTIME_FAILURE,
|
|
612
|
+
code="kconfig.parse-failed",
|
|
613
|
+
message=f"kconfig emit is not valid JSON: {err}",
|
|
614
|
+
core=resolved_core,
|
|
615
|
+
json_mode=json_mode,
|
|
616
|
+
sdk=sdk_info,
|
|
617
|
+
sdk_issues=sdk_issues,
|
|
618
|
+
)
|
|
619
|
+
return
|
|
620
|
+
|
|
621
|
+
if json_mode:
|
|
622
|
+
emit(
|
|
623
|
+
Envelope(
|
|
624
|
+
"kconfig",
|
|
625
|
+
Project.resolved(root, board_path),
|
|
626
|
+
data,
|
|
627
|
+
# tan-cli#497 defect 2: a literal `[]` here was the WORST of
|
|
628
|
+
# the drops -- a full symbol menu, `ok: true`, solved out of a
|
|
629
|
+
# checkout the workspace's own pin does not name, with nothing
|
|
630
|
+
# in the envelope the extension could notice.
|
|
631
|
+
list(sdk_issues),
|
|
632
|
+
ExitCode.SUCCESS,
|
|
633
|
+
sdk=sdk_info,
|
|
634
|
+
)
|
|
635
|
+
)
|
|
636
|
+
else:
|
|
637
|
+
for issue in sdk_issues:
|
|
638
|
+
print(f"{issue.severity}: {issue.message}", file=sys.stderr)
|
|
639
|
+
for line in _text_lines(data, verbose):
|
|
640
|
+
print(line, file=sys.stderr)
|
|
641
|
+
raise typer.Exit(int(ExitCode.SUCCESS))
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def kconfig(
|
|
645
|
+
core: str = typer.Option(
|
|
646
|
+
None,
|
|
647
|
+
"--core",
|
|
648
|
+
metavar="CORE_ID",
|
|
649
|
+
help="Core id to scope the Kconfig symbol menu to (default: the board's "
|
|
650
|
+
"one declared Zephyr core, when unambiguous).",
|
|
651
|
+
),
|
|
652
|
+
project: str = typer.Option(
|
|
653
|
+
None, "--project", metavar="PATH", help="Project root (defaults to current directory)."
|
|
654
|
+
),
|
|
655
|
+
board_yaml: str = typer.Option(
|
|
656
|
+
None,
|
|
657
|
+
"--board-yaml",
|
|
658
|
+
metavar="PATH",
|
|
659
|
+
help="Explicit board.yaml path (overrides project resolution).",
|
|
660
|
+
),
|
|
661
|
+
sdk_root: str = typer.Option(None, "--sdk-root", metavar="PATH", help="alp-sdk checkout root."),
|
|
662
|
+
verbose: bool = typer.Option(False, "--verbose", help="Emit additional diagnostic detail."),
|
|
663
|
+
output_format: OutputFormat = typer.Option(OutputFormat.TEXT, "--format", help=FORMAT_HELP),
|
|
664
|
+
) -> None:
|
|
665
|
+
"""Show the board-scoped Kconfig symbol menu for one core (the vscode
|
|
666
|
+
`prj.conf` LSP's live feed). Needs a bootstrapped Zephyr workspace."""
|
|
667
|
+
json_mode = output_format == "json"
|
|
668
|
+
|
|
669
|
+
# The same backstop `presets_cmd.py` has had all along and this command
|
|
670
|
+
# did not (tan-cli#396). Nothing enumerated reaches it -- every IO/parse
|
|
671
|
+
# failure below is already coded -- and that is precisely the point: the
|
|
672
|
+
# ONE nobody thought of must arrive as an envelope rather than as a
|
|
673
|
+
# traceback on stderr with zero bytes on stdout, which the vscode
|
|
674
|
+
# extension renders as an empty or stale `prj.conf` symbol menu with no
|
|
675
|
+
# error of any kind (`contract/README.md:33-37`).
|
|
676
|
+
#
|
|
677
|
+
# `typer.Exit` is re-raised untouched: it is a `RuntimeError` subclass, so
|
|
678
|
+
# a bare `except Exception` swallows it -- and every SUCCESSFUL run, plus
|
|
679
|
+
# every already-coded refusal, leaves through exactly that exception.
|
|
680
|
+
# Catching it here would have turned `ok: true` into an internal failure.
|
|
681
|
+
#
|
|
682
|
+
# The pre-resolution defaults mirror `presets_cmd`'s: `resolve_project_
|
|
683
|
+
# paths` is itself inside the guard, so the failing envelope needs a
|
|
684
|
+
# `project` block even when resolution is what blew up.
|
|
685
|
+
root, board_path = ".", "./board.yaml"
|
|
686
|
+
# tan-cli#497 defect 2, the tenth `_fail` site. The first pass threaded the
|
|
687
|
+
# resolution pair into the seven sites inside `_run_kconfig` and left THIS
|
|
688
|
+
# one -- the catch-all, which runs strictly AFTER `resolve_sdk` has already
|
|
689
|
+
# produced both facts -- hardcoding a single-element issue list. Measured
|
|
690
|
+
# against a broken-`.alp/sdk-path` workspace with a discoverable sibling and
|
|
691
|
+
# `_resolve_zephyr_base` (outside every `try` in `_run_kconfig`) raising
|
|
692
|
+
# `OSError: [Errno 24] Too many open files`: exit 5,
|
|
693
|
+
# `issues: [kconfig.internal-failure]`, `sdk.project-pin-unresolved`
|
|
694
|
+
# dropped -- the same drop the issue is about, on a path the fix was
|
|
695
|
+
# believed to cover.
|
|
696
|
+
#
|
|
697
|
+
# The facts cannot simply be recomputed here: `resolve_sdk` is itself one
|
|
698
|
+
# of the things that can raise, and re-running the ladder in an exception
|
|
699
|
+
# handler risks a second raise out of the one place that must not throw.
|
|
700
|
+
# They are RECORDED by `_run_kconfig` instead, into a carrier this function
|
|
701
|
+
# owns, so the handler only ever reads two fields.
|
|
702
|
+
disclosure = SdkDisclosure()
|
|
703
|
+
try:
|
|
704
|
+
root, board_path = resolve_project_paths(project, board_yaml)
|
|
705
|
+
_run_kconfig(
|
|
706
|
+
root=root,
|
|
707
|
+
board_path=board_path,
|
|
708
|
+
core=core,
|
|
709
|
+
sdk_root=sdk_root,
|
|
710
|
+
verbose=verbose,
|
|
711
|
+
json_mode=json_mode,
|
|
712
|
+
disclosure=disclosure,
|
|
713
|
+
)
|
|
714
|
+
except typer.Exit:
|
|
715
|
+
raise
|
|
716
|
+
except Exception as err: # noqa: BLE001 -- the envelope IS the error contract
|
|
717
|
+
_fail(
|
|
718
|
+
root=root,
|
|
719
|
+
board_path=board_path,
|
|
720
|
+
exit_code=ExitCode.INTERNAL_FAILURE,
|
|
721
|
+
code="kconfig.internal-failure",
|
|
722
|
+
message=f"kconfig failed unexpectedly: {type(err).__name__}: {err}",
|
|
723
|
+
core=core,
|
|
724
|
+
json_mode=json_mode,
|
|
725
|
+
sdk=disclosure.sdk,
|
|
726
|
+
sdk_issues=disclosure.issues,
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
# tan-cli#261: adds the six oracle `GlobalArgs` flags this command was still
|
|
731
|
+
# missing (`--all`/`--ci`/`--no-color`/`--non-interactive`/`--quiet`/
|
|
732
|
+
# `--target`) on top of `--verbose`, already declared and read above; see
|
|
733
|
+
# `tan.core.global_flags`.
|
|
734
|
+
kconfig = accept_global_flags(kconfig)
|