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,542 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""board.yaml validator with rich diagnostics, in-process.
|
|
3
|
+
|
|
4
|
+
PORTED from alp-sdk `scripts/alp_cli/validator.py` (tan-cli#270). Runs three
|
|
5
|
+
passes -- schema (ALP-B001..B004, in `board_validator_schema`), xref
|
|
6
|
+
(ALP-B005..B009, here) and compat (ALP-B010, in `board_validator_compat`).
|
|
7
|
+
|
|
8
|
+
The only deliberate departures from the SDK file, none behavioural:
|
|
9
|
+
|
|
10
|
+
* No module-scope root. The SDK file derived `REPO`/`METADATA` from
|
|
11
|
+
`__file__`; inside tan that would be tan's own tree. `metadata_root` and
|
|
12
|
+
`PlannerFacts.repo_root` are required parameters, rooted at the BOUND SDK
|
|
13
|
+
checkout (ADR-0017).
|
|
14
|
+
* The three things the SDK file lazily imported from `alp_orchestrate`
|
|
15
|
+
(`_BLOCK_SLUGS`, `plan_cameras`, `resolve_cores`) arrive in a `PlannerFacts`
|
|
16
|
+
instead, so this module loads without `tan.planner` -- which needs a bound
|
|
17
|
+
SDK root at import. `board_validator_run` builds it from `tan.planner`;
|
|
18
|
+
`PlannerFacts()` with no callables degrades exactly as the SDK file did when
|
|
19
|
+
that import failed (chip manifests only, no camera-owner checks).
|
|
20
|
+
* `fast_safe_load` is local: `tan.planner.strict_loaders` cannot be imported
|
|
21
|
+
before an SDK root is bound.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from difflib import get_close_matches
|
|
27
|
+
from collections.abc import Callable
|
|
28
|
+
from dataclasses import dataclass
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
import yaml
|
|
33
|
+
|
|
34
|
+
from tan.core.board_diagnostic import Diagnostic, DiagnosticCollector
|
|
35
|
+
from tan.core.board_validator_compat import _compat_pass
|
|
36
|
+
from tan.core.board_validator_schema import _schema_pass
|
|
37
|
+
from tan.core.board_yaml_pos import load_with_positions, node_position
|
|
38
|
+
|
|
39
|
+
_YAML_LOADER = getattr(yaml, "CSafeLoader", yaml.SafeLoader)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class PlannerFacts:
|
|
44
|
+
"""The SDK-tree facts and planner leaves the validator consults."""
|
|
45
|
+
|
|
46
|
+
repo_root: Path
|
|
47
|
+
block_slugs: frozenset[str] = frozenset()
|
|
48
|
+
plan_cameras: Callable[..., Any] | None = None
|
|
49
|
+
resolve_cores: Callable[..., Any] | None = None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def fast_safe_load(text: str) -> Any:
|
|
53
|
+
"""Exactly `yaml.safe_load(text)`, parsed by libyaml when available."""
|
|
54
|
+
return yaml.load(text, Loader=_YAML_LOADER)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def validate_board_yaml(
|
|
58
|
+
path: Path, *, metadata_root: Path, facts: PlannerFacts
|
|
59
|
+
) -> DiagnosticCollector:
|
|
60
|
+
"""Validate a board.yaml file. Returns a DiagnosticCollector.
|
|
61
|
+
|
|
62
|
+
*metadata_root* is the SoM / board-preset / SoC / schema search root:
|
|
63
|
+
the bound SDK checkout's ``metadata/`` (or a customer's out-of-tree copy
|
|
64
|
+
of it, which validates identically everywhere).
|
|
65
|
+
"""
|
|
66
|
+
collector = DiagnosticCollector()
|
|
67
|
+
text = path.read_text(encoding="utf-8")
|
|
68
|
+
try:
|
|
69
|
+
data = load_with_positions(text, source=path)
|
|
70
|
+
except Exception as exc: # YAML parse error
|
|
71
|
+
collector.add(
|
|
72
|
+
Diagnostic(
|
|
73
|
+
severity="error",
|
|
74
|
+
path=path,
|
|
75
|
+
line=1,
|
|
76
|
+
col=1,
|
|
77
|
+
span=1,
|
|
78
|
+
code="ALP-B000",
|
|
79
|
+
message=f"YAML parse error: {exc}",
|
|
80
|
+
hint=None,
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
return collector
|
|
84
|
+
|
|
85
|
+
root = metadata_root
|
|
86
|
+
som_dir = root / "e1m_modules"
|
|
87
|
+
preset_dir = root / "boards"
|
|
88
|
+
soc_dir = root / "socs"
|
|
89
|
+
chip_dir = root / "chips"
|
|
90
|
+
camera_module_dir = root / "camera_modules"
|
|
91
|
+
schema_path = root / "schemas" / "board.schema.json"
|
|
92
|
+
|
|
93
|
+
_schema_pass(data, path, collector, schema_path=schema_path)
|
|
94
|
+
_xref_pass(data, path, collector, som_dir=som_dir, preset_dir=preset_dir,
|
|
95
|
+
chip_dir=chip_dir, camera_module_dir=camera_module_dir,
|
|
96
|
+
facts=facts)
|
|
97
|
+
_compat_pass(data, path, collector, som_dir=som_dir, soc_dir=soc_dir)
|
|
98
|
+
return collector
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _xref_pass(
|
|
102
|
+
data: dict[str, Any],
|
|
103
|
+
path: Path,
|
|
104
|
+
collector: DiagnosticCollector,
|
|
105
|
+
*,
|
|
106
|
+
som_dir: Path,
|
|
107
|
+
preset_dir: Path,
|
|
108
|
+
chip_dir: Path,
|
|
109
|
+
camera_module_dir: Path,
|
|
110
|
+
facts: PlannerFacts,
|
|
111
|
+
) -> None:
|
|
112
|
+
som = data.get("som")
|
|
113
|
+
# #602: a schema-invalid `som:` (wrong type, e.g. a string/list instead
|
|
114
|
+
# of a mapping) is already reported by _schema_pass as ALP-B004 -- don't
|
|
115
|
+
# let this semantic pass crash on it, just skip SoM/preset xref work.
|
|
116
|
+
som = som if isinstance(som, dict) else {}
|
|
117
|
+
sku = som.get("sku")
|
|
118
|
+
som_doc: dict[str, Any] | None = None
|
|
119
|
+
if isinstance(sku, str):
|
|
120
|
+
sku_path = _sku_path(sku, som_dir=som_dir)
|
|
121
|
+
if sku_path is None:
|
|
122
|
+
line, col = node_position(som, "sku", target="value")
|
|
123
|
+
collector.add(
|
|
124
|
+
Diagnostic(
|
|
125
|
+
severity="error",
|
|
126
|
+
path=path,
|
|
127
|
+
line=line,
|
|
128
|
+
col=col,
|
|
129
|
+
span=len(sku),
|
|
130
|
+
code="ALP-B005",
|
|
131
|
+
message=f"SoM SKU '{sku}' does not resolve to a known module",
|
|
132
|
+
hint=_sku_suggestion(sku, som_dir=som_dir),
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
else:
|
|
136
|
+
som_doc = _load_metadata_yaml(sku_path)
|
|
137
|
+
|
|
138
|
+
preset = data.get("preset")
|
|
139
|
+
board_doc: dict[str, Any] | None = None
|
|
140
|
+
if isinstance(preset, str):
|
|
141
|
+
preset_path = preset_dir / f"{preset}.yaml"
|
|
142
|
+
if not preset_path.is_file():
|
|
143
|
+
line, col = node_position(data, "preset", target="value")
|
|
144
|
+
collector.add(
|
|
145
|
+
Diagnostic(
|
|
146
|
+
severity="error",
|
|
147
|
+
path=path,
|
|
148
|
+
line=line,
|
|
149
|
+
col=col,
|
|
150
|
+
span=len(preset),
|
|
151
|
+
code="ALP-B006",
|
|
152
|
+
message=f"board preset '{preset}' does not exist",
|
|
153
|
+
hint=_preset_suggestion(preset, preset_dir=preset_dir),
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
else:
|
|
157
|
+
board_doc = _load_metadata_yaml(preset_path)
|
|
158
|
+
|
|
159
|
+
if isinstance(preset, str) and isinstance(sku, str) and som_doc and board_doc:
|
|
160
|
+
_check_board_hosts_som_family(
|
|
161
|
+
data, path, collector, sku, som_doc, preset, board_doc,
|
|
162
|
+
preset_dir=preset_dir)
|
|
163
|
+
|
|
164
|
+
_check_chips_known(data, path, collector, chip_dir=chip_dir,
|
|
165
|
+
block_slugs=facts.block_slugs)
|
|
166
|
+
# With a `preset:` the connectors come from that preset; if the preset is
|
|
167
|
+
# missing (ALP-B006 already reported) there is nothing to check against.
|
|
168
|
+
if not isinstance(preset, str):
|
|
169
|
+
_check_cameras(data, path, collector, data.get("camera_connectors"),
|
|
170
|
+
camera_module_dir=camera_module_dir, inline=True,
|
|
171
|
+
som_doc=som_doc, facts=facts)
|
|
172
|
+
elif board_doc is not None:
|
|
173
|
+
_check_cameras(data, path, collector, board_doc.get("camera_connectors"),
|
|
174
|
+
camera_module_dir=camera_module_dir, som_doc=som_doc,
|
|
175
|
+
facts=facts)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def camera_connector_problems(connectors: Any, e1m_routes: Any, *,
|
|
179
|
+
shield_dir: Path,
|
|
180
|
+
families: Any = None) -> list[str]:
|
|
181
|
+
"""Cross-checks of `camera_connectors:` that the schema cannot express,
|
|
182
|
+
shared by this validator (inline project boards) and
|
|
183
|
+
scripts/validate_metadata.py (board presets):
|
|
184
|
+
|
|
185
|
+
* every macro a connector names (`i2c`, `enable`, `reset`, `select[].gpio`)
|
|
186
|
+
must be declared in the same board's `e1m_routes:` (`buses` for `i2c`,
|
|
187
|
+
`gpio` for the rest) -- connectors reference routes, never restate pads;
|
|
188
|
+
* `lane_polarity` carries one flag per clock + data lane: `lanes + 1`;
|
|
189
|
+
* every `zephyr_shields` entry is a directory under zephyr/boards/shields/;
|
|
190
|
+
* `linux: true` only on CAM0 of a board for a `renesas-rzv2n*` SoM family
|
|
191
|
+
(`families`: the preset's `hosts_som_families`, or the project SoM's
|
|
192
|
+
family for an inline board; None skips the family half) -- the only
|
|
193
|
+
place a Linux camera path exists.
|
|
194
|
+
Follow-up: tighten to "scripts/gen_camera_dt.py (#2736) emits a
|
|
195
|
+
<board>-cam<n>-<module>.dtsi for this connector"; that needs the board
|
|
196
|
+
name, which an inline project board does not have.
|
|
197
|
+
"""
|
|
198
|
+
if not isinstance(connectors, dict):
|
|
199
|
+
return []
|
|
200
|
+
routes = e1m_routes if isinstance(e1m_routes, dict) else {}
|
|
201
|
+
|
|
202
|
+
def macros(section: str) -> set[Any]:
|
|
203
|
+
return {e.get("macro") for e in (routes.get(section) or [])
|
|
204
|
+
if isinstance(e, dict)}
|
|
205
|
+
|
|
206
|
+
gpio, buses = macros("gpio"), macros("buses")
|
|
207
|
+
msgs: list[str] = []
|
|
208
|
+
for name, c in connectors.items():
|
|
209
|
+
if not isinstance(c, dict):
|
|
210
|
+
continue
|
|
211
|
+
refs = [("i2c", c.get("i2c"), buses, "buses"),
|
|
212
|
+
("enable", c.get("enable"), gpio, "gpio"),
|
|
213
|
+
("reset", c.get("reset"), gpio, "gpio")]
|
|
214
|
+
refs += [("select.gpio", sel.get("gpio"), gpio, "gpio")
|
|
215
|
+
for sel in (c.get("select") or []) if isinstance(sel, dict)]
|
|
216
|
+
for field, macro, known, section in refs:
|
|
217
|
+
if macro is not None and macro not in known:
|
|
218
|
+
msgs.append(f"camera_connectors.{name}.{field}: `{macro}` is not a "
|
|
219
|
+
f"macro in e1m_routes.{section}")
|
|
220
|
+
if c.get("linux"):
|
|
221
|
+
if name != "CAM0":
|
|
222
|
+
msgs.append(f"camera_connectors.{name}.linux: only CAM0 has a Linux "
|
|
223
|
+
f"camera path (the sensor DT is generated for CAM0)")
|
|
224
|
+
if families is not None and not any(
|
|
225
|
+
str(f).startswith("renesas-rzv2n") for f in families):
|
|
226
|
+
msgs.append(f"camera_connectors.{name}.linux: no Linux camera path "
|
|
227
|
+
f"for SoM family {', '.join(map(str, families)) or 'none'} "
|
|
228
|
+
f"(only renesas-rzv2n*)")
|
|
229
|
+
for sh in c.get("zephyr_shields") or []:
|
|
230
|
+
if not (shield_dir / str(sh)).is_dir():
|
|
231
|
+
msgs.append(f"camera_connectors.{name}.zephyr_shields: `{sh}` is not "
|
|
232
|
+
f"a shield under zephyr/boards/shields/")
|
|
233
|
+
pol, lanes = c.get("lane_polarity"), c.get("lanes")
|
|
234
|
+
if isinstance(pol, list) and isinstance(lanes, int) and len(pol) != lanes + 1:
|
|
235
|
+
msgs.append(f"camera_connectors.{name}.lane_polarity: {len(pol)} entries "
|
|
236
|
+
f"for {lanes} data lane(s); expected {lanes + 1} "
|
|
237
|
+
f"(clock lane + data lanes)")
|
|
238
|
+
return msgs
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _check_cameras(
|
|
242
|
+
data: dict[str, Any],
|
|
243
|
+
path: Path,
|
|
244
|
+
collector: DiagnosticCollector,
|
|
245
|
+
connectors: Any,
|
|
246
|
+
*,
|
|
247
|
+
camera_module_dir: Path,
|
|
248
|
+
facts: PlannerFacts,
|
|
249
|
+
inline: bool = False,
|
|
250
|
+
som_doc: Any = None,
|
|
251
|
+
) -> None:
|
|
252
|
+
"""ALP-B003 for camera declarations the schema cannot judge:
|
|
253
|
+
|
|
254
|
+
* a `cameras:` entry naming a connector the resolved board does not
|
|
255
|
+
expose, a module with no `metadata/camera_modules/<module>.yaml`, or a
|
|
256
|
+
connector listed twice (both valid identifiers to the schema, so a typo
|
|
257
|
+
would only surface when a generator looks the name up);
|
|
258
|
+
* a module with no `zephyr_shield:` while a Zephyr core is in use (it
|
|
259
|
+
could never be selected: the Zephyr build takes `-DSHIELD` from it);
|
|
260
|
+
* for an INLINE board, a `camera_connectors:` block whose macros do not
|
|
261
|
+
resolve in the project's own `e1m_routes:` or whose `lane_polarity` has
|
|
262
|
+
the wrong length (presets get the same check from validate_metadata.py).
|
|
263
|
+
|
|
264
|
+
Anchored on the `cameras` (or `camera_connectors`) key because the
|
|
265
|
+
position loader does not track individual list items.
|
|
266
|
+
"""
|
|
267
|
+
def report(key: str, message: str) -> None:
|
|
268
|
+
line, col = node_position(data, key, target="key")
|
|
269
|
+
collector.add(
|
|
270
|
+
Diagnostic(severity="error", path=path, line=line, col=col,
|
|
271
|
+
span=len(key), code="ALP-B003", message=message))
|
|
272
|
+
|
|
273
|
+
if inline:
|
|
274
|
+
fam = som_doc.get("family") if isinstance(som_doc, dict) else None
|
|
275
|
+
for message in camera_connector_problems(
|
|
276
|
+
connectors, data.get("e1m_routes"),
|
|
277
|
+
shield_dir=facts.repo_root / "zephyr" / "boards" / "shields",
|
|
278
|
+
families=[fam] if fam else None):
|
|
279
|
+
report("camera_connectors", message)
|
|
280
|
+
|
|
281
|
+
cameras = data.get("cameras")
|
|
282
|
+
if not isinstance(cameras, list):
|
|
283
|
+
return
|
|
284
|
+
known_connectors = sorted(connectors) if isinstance(connectors, dict) else []
|
|
285
|
+
seen: set[str] = set()
|
|
286
|
+
for entry in cameras:
|
|
287
|
+
if not isinstance(entry, dict):
|
|
288
|
+
continue # schema pass reports the wrong type
|
|
289
|
+
connector = entry.get("connector")
|
|
290
|
+
module = entry.get("module")
|
|
291
|
+
if isinstance(connector, str):
|
|
292
|
+
if connector not in known_connectors:
|
|
293
|
+
report("cameras",
|
|
294
|
+
f"cameras: connector '{connector}' is not a camera connector of "
|
|
295
|
+
f"this board (known: {', '.join(known_connectors) or 'none'})")
|
|
296
|
+
if connector in seen:
|
|
297
|
+
report("cameras",
|
|
298
|
+
f"cameras: connector '{connector}' is listed more than once "
|
|
299
|
+
f"(one module per connector)")
|
|
300
|
+
seen.add(connector)
|
|
301
|
+
if isinstance(module, str) and not (camera_module_dir / f"{module}.yaml").is_file():
|
|
302
|
+
report("cameras",
|
|
303
|
+
f"cameras: unknown camera module '{module}' "
|
|
304
|
+
f"(no metadata/camera_modules/{module}.yaml)")
|
|
305
|
+
_check_camera_owners(data, cameras, connectors, som_doc, report,
|
|
306
|
+
camera_module_dir=camera_module_dir, facts=facts)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _check_camera_owners(data, cameras, connectors, som_doc, report, *,
|
|
310
|
+
camera_module_dir: Path, facts: PlannerFacts) -> None:
|
|
311
|
+
"""Ownership + static build checks via the shared leaf
|
|
312
|
+
`alp_orchestrate/camera_owner.py` (the same rule the planner applies):
|
|
313
|
+
an explicit/implied owner core, a module `zephyr_shield`, connector
|
|
314
|
+
`zephyr_shields`, and a shield overlay for the owner's board target.
|
|
315
|
+
|
|
316
|
+
The two leaves come in through *facts* (see the module docstring); absent
|
|
317
|
+
ones skip the check, as the SDK file's failed lazy import did."""
|
|
318
|
+
if not isinstance(som_doc, dict):
|
|
319
|
+
return
|
|
320
|
+
plan_cameras, resolve_cores = facts.plan_cameras, facts.resolve_cores
|
|
321
|
+
if plan_cameras is None or resolve_cores is None:
|
|
322
|
+
return
|
|
323
|
+
modules = {}
|
|
324
|
+
for entry in cameras:
|
|
325
|
+
mod = entry.get("module") if isinstance(entry, dict) else None
|
|
326
|
+
if isinstance(mod, str) and (camera_module_dir / f"{mod}.yaml").is_file():
|
|
327
|
+
modules[mod] = _load_metadata_yaml(camera_module_dir / f"{mod}.yaml") or {}
|
|
328
|
+
cores = resolve_cores(data.get("cores"), som_doc.get("topology"))
|
|
329
|
+
for plan in plan_cameras(cameras, connectors, cores, modules, facts.repo_root):
|
|
330
|
+
for message in plan.errors:
|
|
331
|
+
report("cameras", message)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _known_chip_slugs(*, chip_dir: Path, block_slugs: frozenset[str]) -> set[str]:
|
|
335
|
+
"""Every valid `chips:` token: real chip manifests plus the small set
|
|
336
|
+
of non-chip SDK 'block' helpers `chips:` may also legitimately name
|
|
337
|
+
(`button_led`, `pdm_mic`) -- see `_BLOCK_SLUGS` in
|
|
338
|
+
`alp_orchestrate/slugs.py`, the dependency-free leaf that is the single
|
|
339
|
+
source of truth for that distinction (kconfig.py's emitter and
|
|
340
|
+
check_example_portability.py both consult the same set; e.g.
|
|
341
|
+
`examples/connectivity/iot-connected-camera/board.yaml`'s
|
|
342
|
+
`chips: [ssd1306, button_led]`).
|
|
343
|
+
|
|
344
|
+
A manifest with `driver_status: planned` OR `driver_status: none`
|
|
345
|
+
(issue #1224 review; #1270 widened this from `planned`-only after
|
|
346
|
+
`metadata/chips/dp83825.yaml` shipped the tree's first `none`) ships no
|
|
347
|
+
`chips/<id>/` driver and declares no `ALP_SDK_CHIP_<NAME>` Kconfig
|
|
348
|
+
symbol -- naming either in `chips:` would emit the same
|
|
349
|
+
undeclared-symbol Kconfig line ALP-B008 exists to catch, so both
|
|
350
|
+
statuses are excluded from "known" the same way an unknown chip is.
|
|
351
|
+
`partial`/`complete`/`stub` all ship a real `chips/<id>/` driver (even
|
|
352
|
+
a stub has a `.c` file and a declared Kconfig symbol -- see
|
|
353
|
+
`chips/ar0234/`) so only these two "no driver at all" tiers apply.
|
|
354
|
+
|
|
355
|
+
*block_slugs* is that `_BLOCK_SLUGS` set, passed in (see the module
|
|
356
|
+
docstring); empty means "chip manifests only", which is also what the
|
|
357
|
+
SDK file did when it could not import `alp_orchestrate`.
|
|
358
|
+
"""
|
|
359
|
+
known = {
|
|
360
|
+
p.stem for p in chip_dir.glob("*.yaml")
|
|
361
|
+
if (_load_metadata_yaml(p) or {}).get("driver_status") not in (
|
|
362
|
+
"planned", "none")
|
|
363
|
+
}
|
|
364
|
+
return known | block_slugs
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _check_chips_known(
|
|
368
|
+
data: dict[str, Any],
|
|
369
|
+
path: Path,
|
|
370
|
+
collector: DiagnosticCollector,
|
|
371
|
+
*,
|
|
372
|
+
chip_dir: Path,
|
|
373
|
+
block_slugs: frozenset[str],
|
|
374
|
+
) -> None:
|
|
375
|
+
"""Reject a `chips:` entry that names no real chip manifest or SDK
|
|
376
|
+
block helper (issue #1224).
|
|
377
|
+
|
|
378
|
+
`chips:` is schema-validated only against a permissive identifier
|
|
379
|
+
regex (`metadata/schemas/board.schema.json`), so a typo (e.g.
|
|
380
|
+
`no_such_chip_xyz`) passes schema validation clean and is emitted
|
|
381
|
+
verbatim into the generated `alp.conf` as
|
|
382
|
+
`CONFIG_ALP_SDK_CHIP_<TYPO>=y` -- a Kconfig symbol nothing declares,
|
|
383
|
+
so the driver is silently not built and the app fails at runtime
|
|
384
|
+
instead of at validation time. Cross-check every token against the
|
|
385
|
+
real chip registry (`metadata/chips/*.yaml`, `chip_id:` == filename
|
|
386
|
+
stem for every manifest) the same way `som.sku` (ALP-B005) and
|
|
387
|
+
`preset:` (ALP-B006) are already cross-checked.
|
|
388
|
+
"""
|
|
389
|
+
chips = data.get("chips")
|
|
390
|
+
if not isinstance(chips, list):
|
|
391
|
+
return
|
|
392
|
+
|
|
393
|
+
known = _known_chip_slugs(chip_dir=chip_dir, block_slugs=block_slugs)
|
|
394
|
+
# Point at the `chips` KEY, not the list's VALUE node: for a
|
|
395
|
+
# multi-entry list target="value" gives every offending entry the
|
|
396
|
+
# position of the FIRST list item, so a caret sized to a LATER
|
|
397
|
+
# unknown entry (`span=len(chip)`) would underline an unrelated,
|
|
398
|
+
# valid entry instead (review round 3). The YAML position loader
|
|
399
|
+
# (alp_cli/yaml_pos.py) doesn't track individual list-item
|
|
400
|
+
# positions, so the precise fix -- a caret on the offending token
|
|
401
|
+
# itself -- needs a loader change; anchoring on the unambiguous
|
|
402
|
+
# `chips` key is the correct diagnostic today.
|
|
403
|
+
line, col = node_position(data, "chips", target="key")
|
|
404
|
+
seen: set[str] = set()
|
|
405
|
+
for chip in chips:
|
|
406
|
+
# Non-string / pattern-invalid entries are already reported by
|
|
407
|
+
# the schema pass (ALP-B003/ALP-B004); nothing new to say here.
|
|
408
|
+
if not isinstance(chip, str) or chip in known or chip in seen:
|
|
409
|
+
continue
|
|
410
|
+
seen.add(chip)
|
|
411
|
+
# A manifest with `driver_status: planned` or `driver_status: none`
|
|
412
|
+
# IS a real, committed metadata/chips/<chip>.yaml -- `_known_chip_slugs`
|
|
413
|
+
# excludes it because it declares no ALP_SDK_CHIP_<NAME> Kconfig
|
|
414
|
+
# symbol, not because the manifest is missing. Say that, not "no
|
|
415
|
+
# metadata/chips/<chip>.yaml" (false -- the file is right there)
|
|
416
|
+
# and don't offer a "did you mean" against an unrelated chip for
|
|
417
|
+
# an entry that was spelled correctly (review round 3 major).
|
|
418
|
+
chip_path = chip_dir / f"{chip}.yaml"
|
|
419
|
+
if chip_path.is_file():
|
|
420
|
+
status = (_load_metadata_yaml(chip_path) or {}).get("driver_status")
|
|
421
|
+
message = (
|
|
422
|
+
f"chips: '{chip}' has driver_status: {status} "
|
|
423
|
+
f"-- no Alp SDK driver or ALP_SDK_CHIP_{chip.upper()} "
|
|
424
|
+
f"symbol yet"
|
|
425
|
+
)
|
|
426
|
+
hint = None
|
|
427
|
+
else:
|
|
428
|
+
message = (
|
|
429
|
+
f"chips: unknown chip '{chip}' "
|
|
430
|
+
f"(no metadata/chips/{chip}.yaml)"
|
|
431
|
+
)
|
|
432
|
+
hint = _chip_suggestion(chip, chip_dir=chip_dir, block_slugs=block_slugs)
|
|
433
|
+
collector.add(
|
|
434
|
+
Diagnostic(
|
|
435
|
+
severity="error",
|
|
436
|
+
path=path,
|
|
437
|
+
line=line,
|
|
438
|
+
col=col,
|
|
439
|
+
span=len("chips"),
|
|
440
|
+
code="ALP-B008",
|
|
441
|
+
message=message,
|
|
442
|
+
hint=hint,
|
|
443
|
+
)
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def _chip_suggestion(
|
|
448
|
+
chip: str, *, chip_dir: Path, block_slugs: frozenset[str]
|
|
449
|
+
) -> str | None:
|
|
450
|
+
known = sorted(_known_chip_slugs(chip_dir=chip_dir, block_slugs=block_slugs))
|
|
451
|
+
match = get_close_matches(chip, known, n=1)
|
|
452
|
+
return f"did you mean '{match[0]}'?" if match else None
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def _load_metadata_yaml(path: Path) -> dict[str, Any] | None:
|
|
456
|
+
try:
|
|
457
|
+
# libyaml-backed yaml.safe_load: the hottest metadata read in the
|
|
458
|
+
# validator (#2328).
|
|
459
|
+
doc = fast_safe_load(path.read_text(encoding="utf-8")) or {}
|
|
460
|
+
except yaml.YAMLError:
|
|
461
|
+
return None
|
|
462
|
+
return doc if isinstance(doc, dict) else None
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def _sku_path(sku: str, *, som_dir: Path) -> Path | None:
|
|
466
|
+
for candidate in som_dir.rglob(f"{sku}.yaml"):
|
|
467
|
+
return candidate
|
|
468
|
+
return None
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def _check_board_hosts_som_family(
|
|
472
|
+
data: dict[str, Any],
|
|
473
|
+
path: Path,
|
|
474
|
+
collector: DiagnosticCollector,
|
|
475
|
+
sku: str,
|
|
476
|
+
som_doc: dict[str, Any],
|
|
477
|
+
preset: str,
|
|
478
|
+
board_doc: dict[str, Any],
|
|
479
|
+
*,
|
|
480
|
+
preset_dir: Path,
|
|
481
|
+
) -> None:
|
|
482
|
+
family = som_doc.get("family")
|
|
483
|
+
allowed = board_doc.get("hosts_som_families") or []
|
|
484
|
+
if not isinstance(family, str) or not isinstance(allowed, list):
|
|
485
|
+
return
|
|
486
|
+
allowed = [str(item) for item in allowed]
|
|
487
|
+
if family in allowed:
|
|
488
|
+
return
|
|
489
|
+
|
|
490
|
+
line, col = node_position(data, "preset", target="value")
|
|
491
|
+
compatible = _compatible_presets(family, preset_dir=preset_dir)
|
|
492
|
+
hint = (
|
|
493
|
+
f"use a board preset whose hosts_som_families includes '{family}'"
|
|
494
|
+
)
|
|
495
|
+
if compatible:
|
|
496
|
+
hint += f" (for example: {', '.join(compatible)})"
|
|
497
|
+
hint += ", or define a compatible board inline"
|
|
498
|
+
collector.add(
|
|
499
|
+
Diagnostic(
|
|
500
|
+
severity="error",
|
|
501
|
+
path=path,
|
|
502
|
+
line=line,
|
|
503
|
+
col=col,
|
|
504
|
+
span=len(preset),
|
|
505
|
+
code="ALP-B007",
|
|
506
|
+
message=(
|
|
507
|
+
f"board preset '{preset}' hosts SoM families {allowed}, "
|
|
508
|
+
f"but {sku} is family '{family}'"
|
|
509
|
+
),
|
|
510
|
+
hint=hint,
|
|
511
|
+
)
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def _compatible_presets(family: str, *, preset_dir: Path) -> list[str]:
|
|
516
|
+
out: list[str] = []
|
|
517
|
+
for board_path in sorted(preset_dir.glob("*.yaml")):
|
|
518
|
+
doc = _load_metadata_yaml(board_path) or {}
|
|
519
|
+
families = doc.get("hosts_som_families") or []
|
|
520
|
+
if isinstance(families, list) and family in [str(item) for item in families]:
|
|
521
|
+
out.append(board_path.stem)
|
|
522
|
+
return out
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def _all_skus(*, som_dir: Path) -> list[str]:
|
|
526
|
+
return sorted(p.stem for p in som_dir.rglob("*.yaml") if p.stem.startswith("E1M-"))
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def _all_presets(*, preset_dir: Path) -> list[str]:
|
|
530
|
+
return sorted(p.stem for p in preset_dir.glob("*.yaml"))
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
def _sku_suggestion(sku: str, *, som_dir: Path) -> str | None:
|
|
534
|
+
match = get_close_matches(sku, _all_skus(som_dir=som_dir), n=1)
|
|
535
|
+
return f"did you mean '{match[0]}'?" if match else None
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def _preset_suggestion(preset: str, *, preset_dir: Path) -> str | None:
|
|
539
|
+
match = get_close_matches(preset, _all_presets(preset_dir=preset_dir), n=1)
|
|
540
|
+
return f"did you mean '{match[0]}'?" if match else None
|
|
541
|
+
|
|
542
|
+
|