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,253 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""`tan doctor`'s ADR-0018 curated-library verdict, ported out of alp-sdk.
|
|
3
|
+
|
|
4
|
+
This is the port of `scripts/alp_cli/doctor.py::_check_libraries` (alp-sdk
|
|
5
|
+
ADR-0020 end-state B: the user command surface is `tan`, so a user-facing
|
|
6
|
+
check does not stay behind in alp-sdk as a second CLI). It answers one
|
|
7
|
+
question about the SELECTED PROJECT -- for every curated library the
|
|
8
|
+
project's `board.yaml` asks for, what TIER and LICENCE is it, and can it
|
|
9
|
+
actually be wired on this target?
|
|
10
|
+
|
|
11
|
+
## Why it resolves rather than merely lists
|
|
12
|
+
|
|
13
|
+
Tier and licence could be read straight out of `metadata/libraries/<name>.yaml`
|
|
14
|
+
with no planner at all. The compatibility half cannot: `requires:` constraints
|
|
15
|
+
are checked against the project's SoC cores and per-core OSes, and a library
|
|
16
|
+
with no `integration:` section for any OS this project runs is a build-time
|
|
17
|
+
refusal. Reporting the first without the second is the failure mode a doctor
|
|
18
|
+
exists to prevent -- a green "lvgl (tier A, MIT)" line on a project where the
|
|
19
|
+
emitter will refuse it. So this routes through
|
|
20
|
+
`tan.planner.libraries.resolve_selection`, the SAME function
|
|
21
|
+
`tan.planner.buildplan` calls when it renders the plan; the report and the
|
|
22
|
+
build can therefore never disagree about a selection.
|
|
23
|
+
|
|
24
|
+
## WARN, never FAIL
|
|
25
|
+
|
|
26
|
+
An incompatible selection is a WARN here because the hard gate is the emit:
|
|
27
|
+
`tan build` refuses outright. `tan doctor` is a preflight report and a `fail`
|
|
28
|
+
row from it costs exit 4, which would make an unbuildable-library selection
|
|
29
|
+
indistinguishable from a missing toolchain. Same posture alp-sdk's original
|
|
30
|
+
took, kept deliberately.
|
|
31
|
+
|
|
32
|
+
alp-sdk's `alp doctor` had a second lever this port does not carry: `--strict`,
|
|
33
|
+
which folded a WARN row into a nonzero exit (`n_warn` counted the libraries
|
|
34
|
+
row same as any other). `tan doctor` has no `--strict` flag AT ALL -- it is
|
|
35
|
+
not a libraries-specific gap, every WARN-worthy check in this module is
|
|
36
|
+
already exit-0-only (`exit_code_for` above only inspects `fail`). Adding
|
|
37
|
+
`--strict` scoped to just this one row would make it behave differently from
|
|
38
|
+
every other check `tan doctor` reports; that is a real, pre-existing capability
|
|
39
|
+
difference from `alp doctor`, not something this port introduces or reverses,
|
|
40
|
+
and it is accepted deliberately: `tan build`'s own refusal is the actual gate
|
|
41
|
+
a CI pipeline should script against, not a doctor exit code.
|
|
42
|
+
|
|
43
|
+
## The defect fixed in the port, not carried across
|
|
44
|
+
|
|
45
|
+
alp-sdk's version read the RAW `libraries:` list out of the YAML and passed
|
|
46
|
+
each entry straight into `load_manifest()` and an f-string. board.yaml's
|
|
47
|
+
`libraries:` entries are `{name, cores?}` OBJECTS as well as bare strings
|
|
48
|
+
(`alp_orchestrate.loader._normalize_libraries`, and `tan.planner.loader`'s
|
|
49
|
+
relocated twin), so a core-scoped entry reached `load_manifest` as a dict:
|
|
50
|
+
|
|
51
|
+
$ cd examples/peripheral-io/fmt-formatting
|
|
52
|
+
$ PYTHONPATH=<alp-sdk>/scripts python3 -m alp_cli doctor
|
|
53
|
+
...
|
|
54
|
+
alp_orchestrate.models.OrchestratorError: unknown library
|
|
55
|
+
`{'name': 'fmt', 'cores': ['m55_hp']}` in `libraries:` -- no manifest at
|
|
56
|
+
metadata/libraries/{'name': 'fmt', 'cores': ['m55_hp']}.yaml.
|
|
57
|
+
|
|
58
|
+
Measured, not reasoned: exit 1, the whole command down, on the SUCCESS path
|
|
59
|
+
(`resolve_selection` had already passed, using the loader's normalised names;
|
|
60
|
+
the raw re-read for the labels is what raised). All 42 in-tree alp-sdk
|
|
61
|
+
examples that select libraries use that shape, so there was no shipped
|
|
62
|
+
project the check worked on. alp-sdk's `docs/getting-started.md` carried it
|
|
63
|
+
as a known open bug; the port is where it goes away.
|
|
64
|
+
|
|
65
|
+
Names therefore come from `scoped_names(project)` -- the loader's own
|
|
66
|
+
normalised, deduplicated, two-channel view -- not from the raw document. The
|
|
67
|
+
dedup matters on its own: `libraries: [fmt, {name: fmt, cores: [m55_hp]}]` is
|
|
68
|
+
schema-valid (`uniqueItems` compares whole entries) and the raw read reports
|
|
69
|
+
`fmt` twice.
|
|
70
|
+
|
|
71
|
+
The raw read survives only as the cheap "is anything selected at all" peek (a
|
|
72
|
+
project selecting nothing must cost nothing and stay silent) and as the label
|
|
73
|
+
fallback when the project could not be LOADED, which is the one path where no
|
|
74
|
+
normalised view exists to take names from.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
from __future__ import annotations
|
|
78
|
+
|
|
79
|
+
from dataclasses import dataclass
|
|
80
|
+
from pathlib import Path
|
|
81
|
+
from typing import Any
|
|
82
|
+
|
|
83
|
+
__all__ = ["LibraryReport", "inspect_selection"]
|
|
84
|
+
|
|
85
|
+
#: `outcome` values, and what each means for the caller's Check status.
|
|
86
|
+
#:
|
|
87
|
+
#: `ok` every selection resolved and is wireable -> pass
|
|
88
|
+
#: `unresolved` a name, a `requires:` or an `integration:` failed -> warn
|
|
89
|
+
#: `unreadable` `board.yaml` itself could not be parsed -> warn
|
|
90
|
+
#: `unverifiable` libraries ARE selected but nothing could check -> warn
|
|
91
|
+
#: them (no SDK checkout resolved, or the planner is
|
|
92
|
+
#: unavailable/already bound elsewhere in this process)
|
|
93
|
+
OUTCOMES = ("ok", "unresolved", "unreadable", "unverifiable")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass(frozen=True)
|
|
97
|
+
class LibraryReport:
|
|
98
|
+
"""One project's curated-library verdict.
|
|
99
|
+
|
|
100
|
+
`labels` are the rendered `name (tier X, LICENCE)` strings, in declaration
|
|
101
|
+
order -- built even on the `unresolved` path, where the offending entry
|
|
102
|
+
reads `name (unknown)`, because "which of my five libraries is the broken
|
|
103
|
+
one" is the question that report has to answer.
|
|
104
|
+
|
|
105
|
+
`detail` carries the underlying refusal verbatim on every non-`ok`
|
|
106
|
+
outcome: it is the emitter's OWN message (`OrchestratorError`), so the
|
|
107
|
+
warning a customer reads here is word-for-word the error `tan build`
|
|
108
|
+
will print, not a paraphrase of it.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
outcome: str
|
|
112
|
+
names: tuple[str, ...]
|
|
113
|
+
labels: tuple[str, ...] = ()
|
|
114
|
+
detail: str = ""
|
|
115
|
+
|
|
116
|
+
def __post_init__(self) -> None:
|
|
117
|
+
if self.outcome not in OUTCOMES:
|
|
118
|
+
raise ValueError(f"unknown library-report outcome {self.outcome!r}; expected one of {OUTCOMES}")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _raw_names(document: Any) -> list[str]:
|
|
122
|
+
"""The `libraries:` names as WRITTEN, for the two paths that have no
|
|
123
|
+
loaded project to ask (the emptiness peek, and the label fallback when
|
|
124
|
+
loading failed). Both entry shapes are accepted -- see the module
|
|
125
|
+
docstring for why reading only the string shape was a defect. NOT
|
|
126
|
+
deduplicated and NOT canonicalised: no loader has run on either path, so
|
|
127
|
+
what the customer wrote is all there is to report."""
|
|
128
|
+
if not isinstance(document, dict):
|
|
129
|
+
return []
|
|
130
|
+
entries = document.get("libraries") or []
|
|
131
|
+
if not isinstance(entries, list):
|
|
132
|
+
return []
|
|
133
|
+
names: list[str] = []
|
|
134
|
+
for entry in entries:
|
|
135
|
+
if isinstance(entry, str):
|
|
136
|
+
names.append(entry)
|
|
137
|
+
elif isinstance(entry, dict) and entry.get("name"):
|
|
138
|
+
names.append(str(entry["name"]))
|
|
139
|
+
return names
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _labels(names, load_manifest, metadata_root: Path) -> tuple[str, ...]:
|
|
143
|
+
"""`name (tier X, LICENCE)` per selection, in declaration order.
|
|
144
|
+
|
|
145
|
+
Per-name `try`, not one around the loop: on the `unresolved` path exactly
|
|
146
|
+
one entry is usually the broken one, and "which of my five" is the whole
|
|
147
|
+
question that report answers. A single guard would collapse all five to
|
|
148
|
+
`(unknown)` the moment any one of them failed to load."""
|
|
149
|
+
out: list[str] = []
|
|
150
|
+
for name in names:
|
|
151
|
+
try:
|
|
152
|
+
manifest = load_manifest(name, metadata_root)
|
|
153
|
+
except Exception:
|
|
154
|
+
out.append(f"{name} (unknown)")
|
|
155
|
+
continue
|
|
156
|
+
out.append(f"{name} (tier {manifest.get('tier', '?')}, {manifest.get('license', '?')})")
|
|
157
|
+
return tuple(out)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _unverifiable(names: list[str], detail: str) -> LibraryReport:
|
|
161
|
+
return LibraryReport(
|
|
162
|
+
"unverifiable", tuple(names), tuple(f"{n} (unverified)" for n in names), detail
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _planner(sdk_root: Path):
|
|
167
|
+
"""`(load_board_yaml, load_manifest, resolve_selection, scoped_names)`.
|
|
168
|
+
|
|
169
|
+
Binds BEFORE importing `tan.planner`: `tan/planner/paths.py` evaluates
|
|
170
|
+
`REPO = sdk_root()` at module scope and half the package freezes
|
|
171
|
+
`metadata_root` as a default argument, so an unbound import cannot be
|
|
172
|
+
repaired afterwards (`tan.planner_root`). A rebind to a DIFFERENT root in
|
|
173
|
+
an already-planner-bound process raises `PlannerRootError` by design --
|
|
174
|
+
doctor is a REPORT and must not be the thing that decides which SDK a
|
|
175
|
+
process is pointed at, so the caller turns that into `unverifiable`
|
|
176
|
+
rather than forcing it."""
|
|
177
|
+
from tan.planner_root import bind_sdk_root
|
|
178
|
+
|
|
179
|
+
bind_sdk_root(sdk_root)
|
|
180
|
+
from tan.planner import load_board_yaml
|
|
181
|
+
from tan.planner.libraries import load_manifest, resolve_selection, scoped_names
|
|
182
|
+
|
|
183
|
+
return load_board_yaml, load_manifest, resolve_selection, scoped_names
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def inspect_selection(
|
|
187
|
+
board_yaml: Path | None, sdk_root: Path | None
|
|
188
|
+
) -> LibraryReport | None:
|
|
189
|
+
"""Resolve the project's `libraries:` selection, or `None` for nothing to say.
|
|
190
|
+
|
|
191
|
+
`None` -- not a status, an ABSENCE -- for no `board.yaml` in scope and for
|
|
192
|
+
a `board.yaml` that selects no libraries; see `doctor_cmd.libraries_check`
|
|
193
|
+
for why a "0 selected" row is worse than no row. A project that DOES
|
|
194
|
+
select libraries always gets one, including when nothing could be
|
|
195
|
+
verified.
|
|
196
|
+
|
|
197
|
+
Never raises: every failure becomes an outcome, because this runs inside
|
|
198
|
+
`_collect`, whose stated invariant is that no probe may raise.
|
|
199
|
+
"""
|
|
200
|
+
if board_yaml is None or not board_yaml.is_file():
|
|
201
|
+
return None
|
|
202
|
+
try:
|
|
203
|
+
import yaml
|
|
204
|
+
|
|
205
|
+
document = yaml.safe_load(board_yaml.read_text(encoding="utf-8")) or {}
|
|
206
|
+
except Exception as err:
|
|
207
|
+
# `boardYaml` owns the verdict on the file itself and runs immediately
|
|
208
|
+
# above this, so the wording here stays scoped to the library line.
|
|
209
|
+
return LibraryReport("unreadable", (), (), f"could not read {board_yaml}: {err}")
|
|
210
|
+
|
|
211
|
+
raw = _raw_names(document)
|
|
212
|
+
if not raw:
|
|
213
|
+
return None
|
|
214
|
+
if sdk_root is None:
|
|
215
|
+
return _unverifiable(
|
|
216
|
+
raw, "no alp-sdk checkout resolved, so metadata/libraries/*.yaml could not be read"
|
|
217
|
+
)
|
|
218
|
+
return _resolve(board_yaml, Path(sdk_root), raw)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _resolve(board_yaml: Path, sdk_root: Path, raw: list[str]) -> LibraryReport | None:
|
|
222
|
+
"""The second half of `inspect_selection`: does what the customer asked
|
|
223
|
+
for actually resolve against THIS SDK checkout and THIS target? Split from
|
|
224
|
+
the first half at the point the planner is needed -- everything above it
|
|
225
|
+
is a read of the customer's own document and answers with no SDK at all.
|
|
226
|
+
"""
|
|
227
|
+
try:
|
|
228
|
+
load_board_yaml, load_manifest, resolve_selection, scoped_names = _planner(sdk_root)
|
|
229
|
+
except Exception as err:
|
|
230
|
+
return _unverifiable(raw, f"the library metadata in {sdk_root} could not be read: {err}")
|
|
231
|
+
|
|
232
|
+
metadata_root = sdk_root / "metadata"
|
|
233
|
+
try:
|
|
234
|
+
project = load_board_yaml(board_yaml, metadata_root=metadata_root)
|
|
235
|
+
except Exception as err:
|
|
236
|
+
# No loaded project means no normalised name list to take -- the raw
|
|
237
|
+
# spelling is all there is on this one path.
|
|
238
|
+
return LibraryReport(
|
|
239
|
+
"unresolved", tuple(raw), _labels(raw, load_manifest, metadata_root), str(err)
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# `scoped_names`, not the raw list: the loader's own view of the selection,
|
|
243
|
+
# covering BOTH declaration channels with aliases already applied -- see
|
|
244
|
+
# the module docstring for the defect that reading `raw` here produced.
|
|
245
|
+
names = tuple(scoped_names(project))
|
|
246
|
+
if not names:
|
|
247
|
+
return None
|
|
248
|
+
labels = _labels(names, load_manifest, metadata_root)
|
|
249
|
+
try:
|
|
250
|
+
resolve_selection(project, metadata_root)
|
|
251
|
+
except Exception as err:
|
|
252
|
+
return LibraryReport("unresolved", names, labels, str(err))
|
|
253
|
+
return LibraryReport("ok", names, labels)
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Pure text-mode rendering for `tan doctor`.
|
|
3
|
+
|
|
4
|
+
`doctor_cmd.py`'s text branch used to be one `print` per check, with `detail`
|
|
5
|
+
and `fix` as unwrapped strings: on a real terminal that wraps at column 0, a
|
|
6
|
+
long `fix:` body visually merges into the next `[ pass]` row, and there is
|
|
7
|
+
no colour to tell `fail` from `pass` at a glance. This module fixes both,
|
|
8
|
+
plus adds a failures-only footer so a customer with several failures gets an
|
|
9
|
+
action list without re-reading the whole log.
|
|
10
|
+
|
|
11
|
+
v2 (three changes, bundled because they interact -- see
|
|
12
|
+
`doctor-v2-brief.md`): (1) one FIXED continuation indent for both `detail`
|
|
13
|
+
and `fix` bodies, replacing the old per-check-name-length indent, which is
|
|
14
|
+
also what makes (3) possible -- a check can be rendered without knowing any
|
|
15
|
+
other check's name length; (2) the failures footer now names the start of
|
|
16
|
+
each fix, not just the bare check name; (3) `render_check_lines` (one
|
|
17
|
+
check) and `render_doctor_footer` (everything after the checks) are exposed
|
|
18
|
+
separately so `doctor_cmd`'s streaming path can print each check as it
|
|
19
|
+
completes and call the footer once, at the end, instead of only ever having
|
|
20
|
+
the all-at-once `render_doctor_lines` this module used to expose.
|
|
21
|
+
|
|
22
|
+
Pure and IO-free -- no terminal probe, no `print` -- so it is testable
|
|
23
|
+
without one; `doctor_cmd.doctor` is the only caller, and reads `width` off
|
|
24
|
+
`shutil.get_terminal_size()` and `color` off `tan.env.use_color` before
|
|
25
|
+
calling in.
|
|
26
|
+
"""
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
from tan.core.text_layout import wrap_block
|
|
30
|
+
|
|
31
|
+
# colorama Fore/Style literals, matching `tan.core.size`'s and
|
|
32
|
+
# `tan.core.faultdecode`'s convention rather than a new dependency.
|
|
33
|
+
_GREEN = "\x1b[32m"
|
|
34
|
+
_YELLOW = "\x1b[33m"
|
|
35
|
+
_RED = "\x1b[31m"
|
|
36
|
+
_CYAN = "\x1b[36m"
|
|
37
|
+
_RESET = "\x1b[0m"
|
|
38
|
+
|
|
39
|
+
_STATUS_HUE: dict[str, str] = {
|
|
40
|
+
"pass": _GREEN,
|
|
41
|
+
"warn": _YELLOW,
|
|
42
|
+
"fail": _RED,
|
|
43
|
+
"unknown": _CYAN,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#: Every continuation line -- wrapped `detail` text AND the `fix:` block --
|
|
47
|
+
#: lands on this ONE column now (review O-2: the old code re-derived the
|
|
48
|
+
#: detail indent from `len(prefix)`, so it drifted with name length (15 for
|
|
49
|
+
#: `sdk`, 23 for `pythonFloor`), while the fix block used its own separate
|
|
50
|
+
#: fixed 9 -- two rails that agreed with neither each other nor themselves
|
|
51
|
+
#: down the report). 10 is `len("[ fail] ")`, the bracket+status prefix
|
|
52
|
+
#: every check shares regardless of its name -- the floor a continuation can
|
|
53
|
+
#: never sit left of without reading as a new entry rather than one -- so a
|
|
54
|
+
#: continuation lands exactly under where the check's own name starts, for
|
|
55
|
+
#: every check, independent of that name's length.
|
|
56
|
+
_CONTINUATION_INDENT = 10
|
|
57
|
+
|
|
58
|
+
#: `fix: ` printed at `_CONTINUATION_INDENT`, like a second paragraph under
|
|
59
|
+
#: the check -- its own wrapped continuation lines land back on that SAME
|
|
60
|
+
#: column, not indented further under the label, which is the "same rail
|
|
61
|
+
#: for detail and fix" this task's change 1 asks for.
|
|
62
|
+
_FIX_PREFIX = " " * _CONTINUATION_INDENT + "fix: "
|
|
63
|
+
|
|
64
|
+
#: ~75 characters of a failing check's `fix` text in the footer row (review
|
|
65
|
+
#: range: 70-80). A CHARACTER budget, not a sentence split -- the `sdk`
|
|
66
|
+
#: check's own fix (its longest) is one comma-joined clause with no sentence
|
|
67
|
+
#: break, so first-sentence extraction would not shorten it at all.
|
|
68
|
+
_FOOTER_FIX_BUDGET = 75
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _truncate_fix(fix: str, budget: int = _FOOTER_FIX_BUDGET) -> str:
|
|
72
|
+
"""`fix`, cut to `budget` characters on a word boundary where the budget
|
|
73
|
+
lands inside one, with a single `…` appended -- never a mid-word cut,
|
|
74
|
+
never a bare truncation with no marker. Returns `fix` byte-for-byte when
|
|
75
|
+
it already fits, with no trailing `…` added to text that was not cut."""
|
|
76
|
+
if len(fix) <= budget:
|
|
77
|
+
return fix
|
|
78
|
+
cut = fix[:budget]
|
|
79
|
+
boundary = cut.rfind(" ")
|
|
80
|
+
if boundary > 0:
|
|
81
|
+
cut = cut[:boundary]
|
|
82
|
+
return f"{cut}…"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def render_check_lines(check: dict, width: int, color: bool) -> list[str]:
|
|
86
|
+
"""`[status] name: detail`, wrapped, plus an indented `fix:` block when
|
|
87
|
+
the check carries one -- ONE check's lines. `render_doctor_lines` below
|
|
88
|
+
(the batch path) and `doctor_cmd`'s streaming path (print the moment a
|
|
89
|
+
check completes, change 3) both call this SAME function, so the two
|
|
90
|
+
never fork into separate formatting code. The status token's OWN width
|
|
91
|
+
(`:>7`, matching `unknown`, the longest status word) is computed on the
|
|
92
|
+
plain text first, so a painted token never throws off the hanging
|
|
93
|
+
indent's alignment -- the ANSI bytes are invisible and must not count as
|
|
94
|
+
columns.
|
|
95
|
+
"""
|
|
96
|
+
status = check["status"]
|
|
97
|
+
name = check["name"]
|
|
98
|
+
prefix = f"[{status:>7}] {name}: "
|
|
99
|
+
indent = " " * _CONTINUATION_INDENT
|
|
100
|
+
lines = wrap_block(check["detail"], width, prefix, indent)
|
|
101
|
+
|
|
102
|
+
hue = _STATUS_HUE.get(status)
|
|
103
|
+
if color and hue:
|
|
104
|
+
painted = f"[{hue}{status:>7}{_RESET}] {name}: "
|
|
105
|
+
lines[0] = painted + lines[0][len(prefix) :]
|
|
106
|
+
|
|
107
|
+
fix = check.get("fix")
|
|
108
|
+
if fix is not None:
|
|
109
|
+
lines.extend(wrap_block(fix, width, _FIX_PREFIX, indent))
|
|
110
|
+
return lines
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def render_doctor_footer(
|
|
114
|
+
checks: list[dict], summary: dict, extra_issue_lines: list[str], width: int
|
|
115
|
+
) -> list[str]:
|
|
116
|
+
"""Everything AFTER the per-check blocks, in order:
|
|
117
|
+
|
|
118
|
+
1. `extra_issue_lines` verbatim (already filtered by the caller against
|
|
119
|
+
`checked_codes`, tan-cli#375), each wrapped through `wrap_block` at
|
|
120
|
+
`width` -- the same seam every check block above already goes
|
|
121
|
+
through. Measured before this wrapping existed: a real
|
|
122
|
+
`doctor.fix-suppressed` line ran to 262 columns, unwrapped, inside
|
|
123
|
+
the very report this whole task exists to wrap.
|
|
124
|
+
2. A failures-only footer, omitted entirely when nothing failed -- each
|
|
125
|
+
row names the START of its own fix, truncated to
|
|
126
|
+
`_FOOTER_FIX_BUDGET`, then wrapped the same way as (1): truncation
|
|
127
|
+
keeps the row SHORT, wrapping keeps whatever survives that cut
|
|
128
|
+
actually on screen. Measured unwrapped: `4 + len(name) + 2 +
|
|
129
|
+
_FOOTER_FIX_BUDGET + 1` (the trailing `+ 1` is `_truncate_fix`'s own
|
|
130
|
+
`…`) = 107 columns for `zephyrSdkAvailableForHost` (25 chars). A
|
|
131
|
+
failing check with no `fix` falls back to the bare `- <name>` row.
|
|
132
|
+
3. The summary line, last, never wrapped -- one short, fixed sentence.
|
|
133
|
+
|
|
134
|
+
`width` is the SAME value `doctor_cmd.doctor` resolves for the per-check
|
|
135
|
+
blocks above -- never a second, independently floored number. Every
|
|
136
|
+
continuation in (1)/(2) lands at `_CONTINUATION_INDENT`, the same rail
|
|
137
|
+
check `detail`/`fix` continuations already use.
|
|
138
|
+
|
|
139
|
+
Split out from `render_doctor_lines` so `doctor_cmd`'s streaming path
|
|
140
|
+
can print each check as it completes and call this once, for the tail.
|
|
141
|
+
"""
|
|
142
|
+
hanging = " " * _CONTINUATION_INDENT
|
|
143
|
+
lines: list[str] = []
|
|
144
|
+
for issue_line in extra_issue_lines:
|
|
145
|
+
lines.extend(wrap_block(issue_line, width, "", hanging))
|
|
146
|
+
|
|
147
|
+
failed = [c for c in checks if c["status"] == "fail"]
|
|
148
|
+
if failed:
|
|
149
|
+
lines.append("")
|
|
150
|
+
lines.append("Failed checks:")
|
|
151
|
+
for c in failed:
|
|
152
|
+
fix = c.get("fix")
|
|
153
|
+
body = f"{c['name']}: {_truncate_fix(fix)}" if fix else c["name"]
|
|
154
|
+
lines.extend(wrap_block(body, width, " - ", hanging))
|
|
155
|
+
|
|
156
|
+
lines.append("")
|
|
157
|
+
lines.append(
|
|
158
|
+
f"{summary['pass']} passed, {summary['warn']} warning(s), {summary['fail']} failed."
|
|
159
|
+
)
|
|
160
|
+
return lines
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def render_doctor_lines(
|
|
164
|
+
checks: list[dict],
|
|
165
|
+
summary: dict,
|
|
166
|
+
extra_issue_lines: list[str],
|
|
167
|
+
width: int,
|
|
168
|
+
color: bool,
|
|
169
|
+
) -> list[str]:
|
|
170
|
+
"""The lines `doctor_cmd.doctor`'s text branch prints, in order: one
|
|
171
|
+
block per check (`render_check_lines`, SAME order `checks` arrives in --
|
|
172
|
+
the order is a diagnostic progression, `hostPrerequisites` failing is why
|
|
173
|
+
`west` fails too, and reordering would hide that), then the tail
|
|
174
|
+
(`render_doctor_footer`).
|
|
175
|
+
|
|
176
|
+
The one-call batch form, for a caller holding the full check list up
|
|
177
|
+
front (this module's own tests); the streaming caller in `doctor_cmd`
|
|
178
|
+
calls `render_check_lines`/`render_doctor_footer` directly instead, see
|
|
179
|
+
their docstrings for why.
|
|
180
|
+
"""
|
|
181
|
+
lines: list[str] = []
|
|
182
|
+
for check in checks:
|
|
183
|
+
lines.extend(render_check_lines(check, width, color))
|
|
184
|
+
lines.extend(render_doctor_footer(checks, summary, extra_issue_lines, width))
|
|
185
|
+
return lines
|
tan/core/doctor_scope.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""The `scope` every `tan doctor` check carries on the wire (tan-cli#549).
|
|
3
|
+
|
|
4
|
+
A consumer rendering the doctor report has to split HOST rows from PROJECT
|
|
5
|
+
rows -- the dependency panel in `alp-sdk-vscode` shows the host half with no
|
|
6
|
+
folder open. Before this field the only handle was `checks[].name`, so the
|
|
7
|
+
split was a hand-written list of another program's identifiers. That list went
|
|
8
|
+
stale across two pin bumps and shipped `zephyrSdkHost`, an id this CLI stopped
|
|
9
|
+
emitting when the check was renamed `zephyrSdkAvailableForHost`; nothing
|
|
10
|
+
failed, the row was simply never admitted, and a missing row reads to a user
|
|
11
|
+
as "not a problem" rather than "not asked" (alp-sdk-vscode#472, patched
|
|
12
|
+
downstream in alp-sdk-vscode#487 with the caveat that a re-derived hand-list
|
|
13
|
+
rots again).
|
|
14
|
+
|
|
15
|
+
## The two values, and the rule that decides between them
|
|
16
|
+
|
|
17
|
+
The question is **what the check's verdict is ABOUT** -- its subject -- not
|
|
18
|
+
which inputs it happened to read on the way to that verdict.
|
|
19
|
+
|
|
20
|
+
`host`
|
|
21
|
+
The subject is THIS MACHINE: a tool on PATH, an OS setting, the host
|
|
22
|
+
interpreter, the home directory, whether the pinned Zephyr SDK publishes a
|
|
23
|
+
build for this OS/arch. A project or SDK fact may refine the threshold or
|
|
24
|
+
the wording (`hostPython`'s floor is the higher of the SDK manifest's and
|
|
25
|
+
the workspace Zephyr's), but the thing being verdicted is the host, and
|
|
26
|
+
the row is worth rendering with no folder open.
|
|
27
|
+
|
|
28
|
+
`project`
|
|
29
|
+
The subject is the SELECTED PROJECT and the alp-sdk checkout / Zephyr
|
|
30
|
+
workspace resolved for it: whether a `board.yaml` is there, whether a
|
|
31
|
+
workspace resolved, whether that workspace's Zephyr matches the SDK's own
|
|
32
|
+
pin, where this venv or SDK came from. With nothing selected these still
|
|
33
|
+
report -- honestly, against "nothing selected" -- but they answer a
|
|
34
|
+
question the user has not asked yet.
|
|
35
|
+
|
|
36
|
+
The one judgement call worth naming: `sdk` and `pythonFloor` are `project`.
|
|
37
|
+
`sdk`'s top resolution tier is the machine-global `~/.alp/sdk-default`, which
|
|
38
|
+
argues for `host`; its subject is still "which checkout will THIS project
|
|
39
|
+
build against", and its lower tiers (`.alp/sdk-path`, the positional walk) are
|
|
40
|
+
directory-dependent. `pythonFloor` reads as a host fact from its name, but
|
|
41
|
+
both its arms verdict a DECLARATION -- the SDK manifest's `pythonMinVersion`
|
|
42
|
+
against the workspace Zephyr's, or the absence of a manifest to read -- and
|
|
43
|
+
its remedy in the no-SDK arm is "resolve an alp-sdk checkout". The host-side
|
|
44
|
+
half of that pair is `hostPython`, which is `host`.
|
|
45
|
+
|
|
46
|
+
## Why the vocabulary lives here rather than beside `Check`
|
|
47
|
+
|
|
48
|
+
Nothing in this module imports typer or touches IO, so the gate that walks
|
|
49
|
+
every `Check(...)` call site (`python/tests/gates/test_doctor_check_scope.py`)
|
|
50
|
+
and `tan/commands/support_bundle_cmd.py`, which builds a doctor-shaped report
|
|
51
|
+
of its own, can both read the vocabulary without importing a command module.
|
|
52
|
+
"""
|
|
53
|
+
from __future__ import annotations
|
|
54
|
+
|
|
55
|
+
#: The check verdicts a fact about this machine. See the module docstring.
|
|
56
|
+
HOST = "host"
|
|
57
|
+
|
|
58
|
+
#: The check verdicts a fact about the selected project/SDK/workspace.
|
|
59
|
+
PROJECT = "project"
|
|
60
|
+
|
|
61
|
+
#: The complete wire vocabulary, in the order the contract documents it. A
|
|
62
|
+
#: value outside this tuple is refused at construction (`Check.__post_init__`)
|
|
63
|
+
#: rather than shipped: a consumer filtering on `scope` cannot tell an
|
|
64
|
+
#: unrecognised value from a row it should hide, which is the same fail-open
|
|
65
|
+
#: silence the name-matching seam had.
|
|
66
|
+
CHECK_SCOPES: tuple[str, ...] = (HOST, PROJECT)
|