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
tan/planner/libraries.py
ADDED
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Curated third-party library layer (ADR 0018).
|
|
3
|
+
|
|
4
|
+
A project selects libraries with one top-level key::
|
|
5
|
+
|
|
6
|
+
libraries: [lvgl, cmsis-dsp, nanopb]
|
|
7
|
+
|
|
8
|
+
Each name resolves to a manifest at ``metadata/libraries/<name>.yaml`` (the
|
|
9
|
+
single source of truth, validated by ``scripts/validate_metadata.py``). This
|
|
10
|
+
module is the emit-side half: it loads the manifests, validates each selection
|
|
11
|
+
against the resolved SoM/core capabilities, and yields the per-OS wiring the
|
|
12
|
+
slice emitters (``kconfig.py``) splice into their fragments -- Zephyr Kconfig,
|
|
13
|
+
Yocto ``IMAGE_INSTALL``, baremetal cmake args -- through the ADR 0014 --emit
|
|
14
|
+
contract. No forking, no fetching: west/OE own the actual pin (ADR 0017).
|
|
15
|
+
|
|
16
|
+
Design invariants:
|
|
17
|
+
|
|
18
|
+
* **Zero-diff when unused.** A project that declares no ``libraries:`` gets
|
|
19
|
+
byte-identical emit output -- every helper returns ``[]`` for an empty
|
|
20
|
+
selection and the emitters guard on that.
|
|
21
|
+
* **Fail early, name the constraint.** An incompatible selection raises
|
|
22
|
+
``OrchestratorError`` with the failing ``requires:`` constraint named, the
|
|
23
|
+
same clear-error contract as schema validation, so ``tan doctor`` /
|
|
24
|
+
alp-studio (which read the same manifests) can never disagree with emit.
|
|
25
|
+
|
|
26
|
+
``_emit_library_hw_backends`` + ``_SOC_FAMILY_TOKEN`` are RELOCATED from
|
|
27
|
+
alp-sdk's ``scripts/alp_project_emit/west_libs.py``. They land here because
|
|
28
|
+
this is already the library layer and ``kconfig.py`` already imports it, so the
|
|
29
|
+
lazy cross-repo import it used at ``_slice_alp_conf`` time is gone entirely --
|
|
30
|
+
that import was the single edge keeping ``alp_project`` (and through its
|
|
31
|
+
``_load_yaml``, the whole of ``alp_orchestrate``) loaded on the in-process path.
|
|
32
|
+
``west_libs``' own ``--emit west-libraries`` half did NOT move: that mode is
|
|
33
|
+
still owned and spawned by alp-sdk.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
from typing import Any, Optional
|
|
40
|
+
|
|
41
|
+
from tan.core.document_guards import DocumentGuards
|
|
42
|
+
|
|
43
|
+
from .models import BoardProject, OrchestratorError, Slice
|
|
44
|
+
from .som_metadata import _sku_family, resolve_capabilities
|
|
45
|
+
|
|
46
|
+
#: The malformed-document register (`tan/core/document_guards.py`), bound to
|
|
47
|
+
#: THIS module's own curated class. See that module's docstring for why the
|
|
48
|
+
#: exception type is injected rather than fixed.
|
|
49
|
+
#:
|
|
50
|
+
#: There is NO module-scope `import yaml` here any more (tan-cli#1162):
|
|
51
|
+
#: `load_manifest` was its only user and now reads through
|
|
52
|
+
#: `read_yaml_mapping`, which defers the import into the function body --
|
|
53
|
+
#: the idiom `som_buildability.py:109` documents and seven `tan/core/**`
|
|
54
|
+
#: modules share.
|
|
55
|
+
_GUARDS = DocumentGuards(OrchestratorError)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _libraries_dir(metadata_root: Path) -> Path:
|
|
59
|
+
return metadata_root / "libraries"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# §D.lib.loader: map _sku_family() return values to the soc_family
|
|
63
|
+
# tokens used in the manifest's integration.zephyr.hw_backends model.
|
|
64
|
+
_SOC_FAMILY_TOKEN: dict[str, str] = {
|
|
65
|
+
"aen": "alif_ensemble",
|
|
66
|
+
"v2n": "renesas_rzv2n",
|
|
67
|
+
"v2n-m1": "renesas_rzv2n", # DEEPX add-on; HW-acc tokens still resolve via host family.
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _emit_library_hw_backends(
|
|
72
|
+
libs: list[str], sku: str, metadata_root: Path) -> list[str]:
|
|
73
|
+
"""Per-library HW-accelerator binding loader.
|
|
74
|
+
|
|
75
|
+
For each enabled library whose canonical manifest (resolved via the
|
|
76
|
+
metadata/library-aliases-v1.json token table) carries an
|
|
77
|
+
`integration.zephyr.hw_backends` block, pick the highest-priority
|
|
78
|
+
implemented matching backend per accelerator class given the active
|
|
79
|
+
SoM SKU and emit the matching `CONFIG_*=y` line.
|
|
80
|
+
|
|
81
|
+
Match rules (per priority entry; checked in order, all specified
|
|
82
|
+
keys must match):
|
|
83
|
+
- `silicon:` key, if present, must equal the SKU's `silicon:`
|
|
84
|
+
ref exactly (e.g. `alif:ensemble:e4`). Lets us pin a backend
|
|
85
|
+
to specific SoCs in a family -- Ethos-U85 on E4/E6/E8, but
|
|
86
|
+
not on E3/E5/E7 which carry only U55.
|
|
87
|
+
- `soc_family:` key, if present, must equal the SKU family
|
|
88
|
+
token (`alif_ensemble`, `renesas_rzv2n`, ...). Selects every
|
|
89
|
+
SKU in that family.
|
|
90
|
+
- `requires_cap:` key, if present, must name a capability flag
|
|
91
|
+
in the SKU's `metadata/e1m_modules/<sku>.yaml` `capabilities:`
|
|
92
|
+
block that resolves to a truthy value (`true` or a non-zero
|
|
93
|
+
count). Cleanest matcher when an accelerator is shared
|
|
94
|
+
across families.
|
|
95
|
+
- All three omitted = universal entry (e.g. plain FPU, generic
|
|
96
|
+
DMA), matches any SKU.
|
|
97
|
+
- `status: planned` / `status: stub` entries are retained as
|
|
98
|
+
metadata for the roadmap but are not emitted as active build
|
|
99
|
+
claims.
|
|
100
|
+
|
|
101
|
+
RELOCATED from `scripts/alp_project_emit/west_libs.py`. One mechanical
|
|
102
|
+
change, no behavioural one on the success path: a malformed metadata YAML
|
|
103
|
+
now raises `OrchestratorError` (the loader's own error) where alp-sdk's
|
|
104
|
+
delegating wrapper turned it into `sys.exit`. A refusal that raises is
|
|
105
|
+
reportable as a coded envelope; one that exits the interpreter is not, and
|
|
106
|
+
in-process that interpreter is `tan`'s.
|
|
107
|
+
"""
|
|
108
|
+
# An unrecognised SKU pattern (a synthetic/test-only SKU, or a real SKU
|
|
109
|
+
# from a family this matcher doesn't know) resolves to "no HW backend
|
|
110
|
+
# applies" rather than raising -- this is an OPTIONAL accelerator-wiring
|
|
111
|
+
# enhancement layered on top of the required baseline Kconfig
|
|
112
|
+
# (`_slice_alp_conf` calls this unconditionally for every Zephyr slice,
|
|
113
|
+
# including test fixtures that intentionally use non-family SKUs), never a
|
|
114
|
+
# reason to fail the whole fragment emit.
|
|
115
|
+
try:
|
|
116
|
+
family = _sku_family(sku)
|
|
117
|
+
except ValueError:
|
|
118
|
+
return []
|
|
119
|
+
soc_token = _SOC_FAMILY_TOKEN.get(family)
|
|
120
|
+
if soc_token is None:
|
|
121
|
+
return []
|
|
122
|
+
|
|
123
|
+
# `_library_alias_table` is `loader.py`'s, not a second copy: this module
|
|
124
|
+
# had its own byte-identical twin until tan-cli#868, and the two had
|
|
125
|
+
# already drifted by a docstring clause. `loader` cannot be imported at
|
|
126
|
+
# module scope here -- `loader` -> `validate` -> `libraries` is a cycle --
|
|
127
|
+
# so it joins `_load_yaml` on the same deferred line the module docstring
|
|
128
|
+
# explains.
|
|
129
|
+
from .loader import _library_alias_table, _load_yaml # noqa: PLC0415
|
|
130
|
+
|
|
131
|
+
out: list[str] = []
|
|
132
|
+
|
|
133
|
+
# Resolve the SKU's `silicon:` ref and merged capabilities (SoC JSON
|
|
134
|
+
# defaults + SoM-level overrides) via resolve_capabilities(). This
|
|
135
|
+
# replaces the former inline YAML text-parser so that silicon-determined
|
|
136
|
+
# capabilities removed from SoM YAMLs continue to resolve from the SoC JSON.
|
|
137
|
+
silicon_ref: str | None = None
|
|
138
|
+
sku_path = metadata_root / "e1m_modules" / f"{sku}.yaml"
|
|
139
|
+
sku_preset: dict[str, Any] = {}
|
|
140
|
+
if sku_path.exists():
|
|
141
|
+
sku_preset = _load_yaml(sku_path) or {}
|
|
142
|
+
silicon_ref = sku_preset.get("silicon")
|
|
143
|
+
|
|
144
|
+
merged_caps: dict[str, Any] = resolve_capabilities(sku_preset, metadata_root)
|
|
145
|
+
|
|
146
|
+
def _cap_truthy(name: str) -> bool:
|
|
147
|
+
v = merged_caps.get(name)
|
|
148
|
+
if v is None:
|
|
149
|
+
return False
|
|
150
|
+
if isinstance(v, bool):
|
|
151
|
+
return v
|
|
152
|
+
if isinstance(v, int):
|
|
153
|
+
return v > 0
|
|
154
|
+
# String value from a YAML-loaded dict (should not occur after
|
|
155
|
+
# resolve_capabilities, but guard for safety).
|
|
156
|
+
sv = str(v).lower()
|
|
157
|
+
if sv in ("true", "yes"):
|
|
158
|
+
return True
|
|
159
|
+
if sv in ("false", "no", "null", "none", "0"):
|
|
160
|
+
return False
|
|
161
|
+
try:
|
|
162
|
+
return int(sv) > 0
|
|
163
|
+
except ValueError:
|
|
164
|
+
return False
|
|
165
|
+
|
|
166
|
+
alias = _library_alias_table(metadata_root)
|
|
167
|
+
# Canonical -> legacy token, so the annotation comment names the library
|
|
168
|
+
# by its declared token regardless of whether the caller passed the legacy
|
|
169
|
+
# spelling (schemaVersion 1 per-core lists) or the canonical name (the
|
|
170
|
+
# schemaVersion 2 unified `libraries:` list resolves to canonical). The
|
|
171
|
+
# annotation is cosmetic; keeping it stable makes the v1->v2 migration a
|
|
172
|
+
# byte-identical emit (WS6-c #610 §6).
|
|
173
|
+
to_token = {canon: legacy for legacy, canon in alias.items()}
|
|
174
|
+
|
|
175
|
+
for lib in libs:
|
|
176
|
+
# Resolve the legacy per-core token to its canonical manifest and
|
|
177
|
+
# read the folded integration.zephyr.hw_backends block (WS6-c #610
|
|
178
|
+
# §6 -- replaces the retired metadata/library-profiles/ tree).
|
|
179
|
+
canonical = alias.get(lib, lib)
|
|
180
|
+
label = to_token.get(lib, lib)
|
|
181
|
+
manifest_path = metadata_root / "libraries" / f"{canonical}.yaml"
|
|
182
|
+
if not manifest_path.exists():
|
|
183
|
+
continue
|
|
184
|
+
manifest = _load_yaml(manifest_path) or {}
|
|
185
|
+
hw = ((manifest.get("integration") or {}).get("zephyr") or {}).get("hw_backends")
|
|
186
|
+
if not isinstance(hw, dict):
|
|
187
|
+
continue
|
|
188
|
+
|
|
189
|
+
# Per-class first-match, walking accelerator classes and their
|
|
190
|
+
# `priority:` lists in declaration order (identical to the retired
|
|
191
|
+
# hw-backends.yaml top-down walk). `sw_fallback:` is NOT emitted
|
|
192
|
+
# here -- the SW floor rides the base library-enable line.
|
|
193
|
+
for cls in (hw.get("accelerators") or []):
|
|
194
|
+
if not isinstance(cls, dict):
|
|
195
|
+
continue
|
|
196
|
+
current_class = cls.get("class")
|
|
197
|
+
for entry in (cls.get("priority") or []):
|
|
198
|
+
if not isinstance(entry, dict):
|
|
199
|
+
continue
|
|
200
|
+
kcv = entry.get("kconfig")
|
|
201
|
+
if not kcv:
|
|
202
|
+
continue
|
|
203
|
+
status = str(entry.get("status", "implemented")).strip().lower()
|
|
204
|
+
if status in {"planned", "stub"}:
|
|
205
|
+
continue
|
|
206
|
+
sili = entry.get("silicon")
|
|
207
|
+
sf = entry.get("soc_family")
|
|
208
|
+
cap = entry.get("requires_cap")
|
|
209
|
+
# All specified matchers must succeed.
|
|
210
|
+
if sili is not None and sili != silicon_ref:
|
|
211
|
+
continue
|
|
212
|
+
if sf is not None and sf != soc_token:
|
|
213
|
+
continue
|
|
214
|
+
if cap is not None and not _cap_truthy(str(cap)):
|
|
215
|
+
continue
|
|
216
|
+
out.append(f"{kcv} # {label} / {current_class}")
|
|
217
|
+
break # per-class first-match
|
|
218
|
+
|
|
219
|
+
return out
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def available_libraries(metadata_root: Path) -> list[str]:
|
|
223
|
+
"""Sorted list of every curated library token (manifest filename stem).
|
|
224
|
+
|
|
225
|
+
RELOCATED divergence from alp-sdk's own `scripts/alp_orchestrate/
|
|
226
|
+
libraries.py` (tan-cli#1171 review of #1162): upstream still spells this
|
|
227
|
+
as an `if not d.is_dir(): return []` pre-flight in front of the `glob`.
|
|
228
|
+
That pre-flight was the SAME tan-cli#1127 shape the four sites in #1162
|
|
229
|
+
removed, one directory further out, and it survived that pass because
|
|
230
|
+
nothing drove it: `_PlannerDocumentCases.test_permission_denied_parent`
|
|
231
|
+
denies `metadata/libraries/`, and the shape that breaks is a denied
|
|
232
|
+
`metadata/` -- the manifest's GRANDPARENT. Measured there, before this
|
|
233
|
+
change: raw `PermissionError` out of `Path.is_dir()` on 3.12.3 and
|
|
234
|
+
3.13.15, curated on 3.14.7.
|
|
235
|
+
|
|
236
|
+
Both halves had to go, not just the pre-flight: `Path.glob` is not
|
|
237
|
+
interpreter-uniform on this shape either. Measured on the same denied
|
|
238
|
+
`metadata/`, `sorted(d.glob("*.yaml"))` raises `PermissionError` on
|
|
239
|
+
3.12.3 and returns `[]` on 3.13.15 and 3.14.7 -- so the `except OSError`
|
|
240
|
+
is what makes the three interpreters agree, and the pre-flight is
|
|
241
|
+
redundant once it is there (`glob` on a directory that is simply not
|
|
242
|
+
present yields nothing on all three).
|
|
243
|
+
|
|
244
|
+
An unreadable library directory answers `[]` rather than raising because
|
|
245
|
+
this function is not a document read: its whole output is the
|
|
246
|
+
typo-correcting `Available: ...` HINT inside another function's error
|
|
247
|
+
message, and it is reached only once that function's own guarded read has
|
|
248
|
+
already raised. There is no caller for whom an exception here would be
|
|
249
|
+
more useful than an empty option list, and one -- `load_manifest`'s
|
|
250
|
+
`absent=` -- for whom it would replace a true, curated message with a raw
|
|
251
|
+
traceback.
|
|
252
|
+
"""
|
|
253
|
+
try:
|
|
254
|
+
return sorted(p.stem for p in _libraries_dir(metadata_root).glob("*.yaml"))
|
|
255
|
+
except OSError:
|
|
256
|
+
return []
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def load_manifest(name: str, metadata_root: Path) -> dict[str, Any]:
|
|
260
|
+
"""Load one library manifest, or raise OrchestratorError naming the options.
|
|
261
|
+
|
|
262
|
+
The error lists every available library so a typo in ``libraries: [...]``
|
|
263
|
+
is self-correcting.
|
|
264
|
+
|
|
265
|
+
RELOCATED divergence from alp-sdk's own `scripts/alp_orchestrate/
|
|
266
|
+
libraries.py` (tan-cli#1162): upstream still spells this as an
|
|
267
|
+
`is_file()` pre-flight followed by a wholly unguarded
|
|
268
|
+
`yaml.safe_load(path.read_text(...))`. Read + parse + shape-check now go
|
|
269
|
+
through `tan/core/document_guards.py`'s register, for the reasons
|
|
270
|
+
`read_yaml_mapping` documents: the pre-flight answered `False` to a
|
|
271
|
+
directory, a denied mode and an `ELOOP` alike, and on a `chmod 000`
|
|
272
|
+
PARENT it did not answer at all on 3.12.3/3.13.15 (`PermissionError`
|
|
273
|
+
straight out of `Path.is_file()`, tan-cli#1127). Measured before this
|
|
274
|
+
change, through the real `tan build` path: a non-UTF-8 manifest escaped
|
|
275
|
+
as a raw `UnicodeDecodeError`, a malformed one as a raw
|
|
276
|
+
`yaml.parser.ParserError`, a `chmod 000` one as a raw `PermissionError`
|
|
277
|
+
-- each absorbed two frames up by `build_cmd.py:505`'s broad `except
|
|
278
|
+
Exception` into a `build.plan-unavailable` envelope naming the EXCEPTION
|
|
279
|
+
TYPE rather than the file, which is the poor-message half of this
|
|
280
|
+
family rather than its bare-traceback half.
|
|
281
|
+
|
|
282
|
+
`available_libraries` is passed LAZILY, as the zero-argument callable
|
|
283
|
+
`absent=` also accepts (tan-cli#1171 review), so the directory listing
|
|
284
|
+
stays on the miss path exactly as the pre-flight-era code kept it. Built
|
|
285
|
+
eagerly it ran ahead of the guarded read on every call, which put its own
|
|
286
|
+
unguarded `is_dir()` first in line and reinstated the tan-cli#1127 split
|
|
287
|
+
the rest of this change removes -- raw `PermissionError` on 3.12.3 and
|
|
288
|
+
3.13.15, curated on 3.14.7, with `metadata/` denied. The typo-correcting
|
|
289
|
+
`Available: ...` message is preserved byte for byte either way.
|
|
290
|
+
"""
|
|
291
|
+
path = _libraries_dir(metadata_root) / f"{name}.yaml"
|
|
292
|
+
return _GUARDS.read_yaml_mapping(
|
|
293
|
+
path,
|
|
294
|
+
what=f"library manifest metadata/libraries/{name}.yaml",
|
|
295
|
+
absent=lambda: (
|
|
296
|
+
f"unknown library `{name}` in `libraries:` -- no manifest at "
|
|
297
|
+
f"metadata/libraries/{name}.yaml. Available: "
|
|
298
|
+
f"{', '.join(available_libraries(metadata_root)) or '<none>'}"))
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
# ---------------------------------------------------------------------------
|
|
302
|
+
# Target-capability resolution (the `requires:` right-hand side)
|
|
303
|
+
# ---------------------------------------------------------------------------
|
|
304
|
+
|
|
305
|
+
def _core_classes(project: BoardProject) -> set[str]:
|
|
306
|
+
"""The set of application-core classes the SoC provides: {"m"}, {"a"}, ...
|
|
307
|
+
|
|
308
|
+
Derived from the SoC spec's ``cores[].type`` (``cortex-m*`` -> m,
|
|
309
|
+
``cortex-a*`` -> a), matching how the loader picks a default OS per core.
|
|
310
|
+
"""
|
|
311
|
+
classes: set[str] = set()
|
|
312
|
+
for core in project.soc_spec.get("cores", []) or []:
|
|
313
|
+
ctype = str(core.get("type", "")).lower()
|
|
314
|
+
if ctype.startswith("cortex-m"):
|
|
315
|
+
classes.add("m")
|
|
316
|
+
elif ctype.startswith("cortex-a"):
|
|
317
|
+
classes.add("a")
|
|
318
|
+
return classes
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _core_class_of(project: BoardProject, slice_: Slice) -> Optional[str]:
|
|
322
|
+
"""The class (m|a) of a specific slice's core, from the SoC spec."""
|
|
323
|
+
for core in project.soc_spec.get("cores", []) or []:
|
|
324
|
+
if core.get("id") == slice_.core_id:
|
|
325
|
+
ctype = str(core.get("type", "")).lower()
|
|
326
|
+
if ctype.startswith("cortex-m"):
|
|
327
|
+
return "m"
|
|
328
|
+
if ctype.startswith("cortex-a"):
|
|
329
|
+
return "a"
|
|
330
|
+
return None
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _project_oses(project: BoardProject) -> set[str]:
|
|
334
|
+
"""OSes actually running on some core (excludes `off`)."""
|
|
335
|
+
return {s.os for s in project.cores.values() if s.os and s.os != "off"}
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _check_requires(
|
|
339
|
+
name: str,
|
|
340
|
+
manifest: dict[str, Any],
|
|
341
|
+
project: BoardProject,
|
|
342
|
+
metadata_root: Path,
|
|
343
|
+
) -> None:
|
|
344
|
+
"""Raise OrchestratorError if the project fails the manifest's `requires:`.
|
|
345
|
+
|
|
346
|
+
Every failure names the exact constraint and the observed target value so
|
|
347
|
+
the message is actionable ("... needs core_class m but SoM E1M-... has {a}").
|
|
348
|
+
"""
|
|
349
|
+
requires = manifest.get("requires") or {}
|
|
350
|
+
if not isinstance(requires, dict):
|
|
351
|
+
return
|
|
352
|
+
|
|
353
|
+
caps = resolve_capabilities(project.som_preset, metadata_root)
|
|
354
|
+
|
|
355
|
+
for cap in requires.get("capabilities") or []:
|
|
356
|
+
if not caps.get(cap):
|
|
357
|
+
raise OrchestratorError(
|
|
358
|
+
f"library `{name}` requires capability `{cap}`, which SoM "
|
|
359
|
+
f"`{project.sku}` does not provide")
|
|
360
|
+
|
|
361
|
+
min_ram = requires.get("min_ram_kib")
|
|
362
|
+
if min_ram is not None:
|
|
363
|
+
soc_ram = project.soc_spec.get("soc_ram_kb")
|
|
364
|
+
if soc_ram is not None and float(soc_ram) < float(min_ram):
|
|
365
|
+
raise OrchestratorError(
|
|
366
|
+
f"library `{name}` requires min_ram_kib {min_ram}, but SoC for "
|
|
367
|
+
f"SoM `{project.sku}` has {soc_ram} KB RAM")
|
|
368
|
+
|
|
369
|
+
min_flash = requires.get("min_flash_kib")
|
|
370
|
+
if min_flash is not None:
|
|
371
|
+
soc_flash_mb = project.soc_spec.get("soc_flash_mb")
|
|
372
|
+
if soc_flash_mb is not None and float(soc_flash_mb) * 1024 < float(min_flash):
|
|
373
|
+
raise OrchestratorError(
|
|
374
|
+
f"library `{name}` requires min_flash_kib {min_flash}, but SoC "
|
|
375
|
+
f"for SoM `{project.sku}` has {soc_flash_mb} MB flash")
|
|
376
|
+
|
|
377
|
+
core_class = requires.get("core_class")
|
|
378
|
+
if core_class is not None:
|
|
379
|
+
have = _core_classes(project)
|
|
380
|
+
if core_class not in have:
|
|
381
|
+
raise OrchestratorError(
|
|
382
|
+
f"library `{name}` requires core_class `{core_class}`, but SoM "
|
|
383
|
+
f"`{project.sku}` has core class(es) {sorted(have) or '<none>'}")
|
|
384
|
+
|
|
385
|
+
req_os = requires.get("os")
|
|
386
|
+
if req_os:
|
|
387
|
+
have_os = _project_oses(project)
|
|
388
|
+
if not (set(req_os) & have_os):
|
|
389
|
+
raise OrchestratorError(
|
|
390
|
+
f"library `{name}` requires os {sorted(req_os)}, but this "
|
|
391
|
+
f"project's cores run {sorted(have_os) or '<none>'}")
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def scoped_names(
|
|
395
|
+
project: BoardProject,
|
|
396
|
+
slice_: Optional[Slice] = None,
|
|
397
|
+
) -> list[str]:
|
|
398
|
+
"""Every library name in scope, in declaration order, deduped.
|
|
399
|
+
|
|
400
|
+
board.yaml declares each curated library ONCE, in the top-level
|
|
401
|
+
`libraries:` list. `loader._normalize_libraries` then folds each entry
|
|
402
|
+
into one of two internal channels: an entry with no `cores:` lands in
|
|
403
|
+
`project.libraries`, an entry with `cores:` lands in each named
|
|
404
|
+
`Slice.libraries`. Both channels are the SAME ADR-0018 selection and
|
|
405
|
+
must pass through the SAME checks -- reading only `project.libraries`
|
|
406
|
+
here is what let a core-scoped entry skip `load_manifest`'s unknown-name
|
|
407
|
+
refusal, its `requires:` constraints and its `integration:` sections
|
|
408
|
+
entirely (alplabai/tan-cli#555).
|
|
409
|
+
|
|
410
|
+
``slice_`` None -> project-wide + every core-scoped name declared on any
|
|
411
|
+
core: the whole-project view `tan validate` /
|
|
412
|
+
`tan doctor` check.
|
|
413
|
+
``slice_`` given -> project-wide + that one slice's core-scoped names:
|
|
414
|
+
what the per-slice emitters wire.
|
|
415
|
+
"""
|
|
416
|
+
names: list[str] = list(project.libraries or [])
|
|
417
|
+
slices = [slice_] if slice_ is not None else list(project.cores.values())
|
|
418
|
+
for s in slices:
|
|
419
|
+
for name in (s.libraries or []):
|
|
420
|
+
if name not in names:
|
|
421
|
+
names.append(name)
|
|
422
|
+
return names
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def resolve_selection(
|
|
426
|
+
project: BoardProject,
|
|
427
|
+
metadata_root: Path,
|
|
428
|
+
*,
|
|
429
|
+
slice_: Optional[Slice] = None,
|
|
430
|
+
) -> list[tuple[str, dict[str, Any]]]:
|
|
431
|
+
"""Validate every selected library and return [(name, manifest), ...].
|
|
432
|
+
|
|
433
|
+
Raises OrchestratorError on an unknown name, a failed `requires:`
|
|
434
|
+
constraint, or a library whose manifest has no `integration:` section for
|
|
435
|
+
ANY OS this project's cores run (you asked for a library that cannot be
|
|
436
|
+
wired anywhere on this target). Returns [] for an empty selection.
|
|
437
|
+
|
|
438
|
+
The selection covers BOTH declaration channels -- see `scoped_names` for
|
|
439
|
+
what ``slice_`` narrows it to.
|
|
440
|
+
|
|
441
|
+
`requires:` is checked at TWO altitudes, because the two channels ask
|
|
442
|
+
different questions. `_check_requires` is project-wide: a project-wide
|
|
443
|
+
`libraries: [ros2]` on a Yocto-A55 + Zephyr-M33 board is legitimate and
|
|
444
|
+
simply wires on the A55 only. A CORE-SCOPED entry (`{name: ros2, cores:
|
|
445
|
+
[m33_sm]}`) is instead an explicit instruction to wire it THERE, so it
|
|
446
|
+
additionally goes through `_check_slice_requires` against that one slice
|
|
447
|
+
(alplabai/tan-cli#555: "ros2's `requires: {os: [yocto], core_class: a}`
|
|
448
|
+
is never checked, so ros2 scoped to a Cortex-M core is silently
|
|
449
|
+
no-op'd"). The project-wide check cannot catch it -- `_project_oses`
|
|
450
|
+
contains `yocto` from the A55, so it passes.
|
|
451
|
+
"""
|
|
452
|
+
selected = scoped_names(project, slice_)
|
|
453
|
+
core_scoped = set(slice_.libraries or ()) if slice_ is not None else set()
|
|
454
|
+
resolved: list[tuple[str, dict[str, Any]]] = []
|
|
455
|
+
project_oses = _project_oses(project)
|
|
456
|
+
for name in selected:
|
|
457
|
+
manifest = load_manifest(name, metadata_root)
|
|
458
|
+
_check_requires(name, manifest, project, metadata_root)
|
|
459
|
+
integration = manifest.get("integration") or {}
|
|
460
|
+
wireable = set(integration.keys()) & project_oses
|
|
461
|
+
if project_oses and not wireable:
|
|
462
|
+
have_sections = ", ".join(sorted(integration.keys())) or "<none>"
|
|
463
|
+
raise OrchestratorError(
|
|
464
|
+
f"library `{name}` cannot be wired on this project: its manifest "
|
|
465
|
+
f"has integration section(s) [{have_sections}] but this project's "
|
|
466
|
+
f"cores run {sorted(project_oses)}")
|
|
467
|
+
if name in core_scoped:
|
|
468
|
+
assert slice_ is not None # core_scoped is empty when slice_ is None
|
|
469
|
+
_check_slice_requires(name, manifest, project, slice_)
|
|
470
|
+
resolved.append((name, manifest))
|
|
471
|
+
return resolved
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def _check_slice_requires(
|
|
475
|
+
name: str,
|
|
476
|
+
manifest: dict[str, Any],
|
|
477
|
+
project: BoardProject,
|
|
478
|
+
slice_: Slice,
|
|
479
|
+
) -> None:
|
|
480
|
+
"""`requires:` for a library board.yaml explicitly scoped to THIS core.
|
|
481
|
+
|
|
482
|
+
`cores: [<id>]` is a positive instruction, not a hint, so a `requires:`
|
|
483
|
+
key that the named core violates has to fail early and name the
|
|
484
|
+
constraint rather than resolve to nothing. Checks the two keys that are
|
|
485
|
+
per-core facts:
|
|
486
|
+
|
|
487
|
+
* `os:` -- against this slice's own OS, not the project-wide
|
|
488
|
+
union `_check_requires` uses.
|
|
489
|
+
* `core_class:` -- delegated to `_check_core_class`, which used to be
|
|
490
|
+
reachable only from `zephyr_kconfig_lines` AFTER an
|
|
491
|
+
`if not zephyr: continue`, so a manifest with no
|
|
492
|
+
`integration.zephyr:` (ros2) never reached it.
|
|
493
|
+
|
|
494
|
+
Deliberately does NOT refuse a manifest that merely has no
|
|
495
|
+
`integration.<this os>:` section: mbedtls and nlohmann-json are scoped to
|
|
496
|
+
the Yocto cores of four shipped examples and have no `integration.yocto:`,
|
|
497
|
+
and the honest handling for those is the explanatory `yocto_unwireable`
|
|
498
|
+
comment, not a hard error.
|
|
499
|
+
"""
|
|
500
|
+
requires = manifest.get("requires") or {}
|
|
501
|
+
req_os = requires.get("os")
|
|
502
|
+
if req_os and slice_.os not in set(req_os):
|
|
503
|
+
raise OrchestratorError(
|
|
504
|
+
f"library `{name}` is scoped to core `{slice_.core_id}` "
|
|
505
|
+
f"(os `{slice_.os}`) but requires os {sorted(req_os)}")
|
|
506
|
+
_check_core_class(name, manifest, project, slice_)
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
# ---------------------------------------------------------------------------
|
|
510
|
+
# Per-slice emit helpers (called from kconfig.py)
|
|
511
|
+
# ---------------------------------------------------------------------------
|
|
512
|
+
|
|
513
|
+
def zephyr_library_kconfig(manifest: dict[str, Any]) -> list[str]:
|
|
514
|
+
"""Kconfig lines for ONE library manifest's Zephyr integration.
|
|
515
|
+
|
|
516
|
+
The upstream module-enable line(s) (`integration.zephyr.kconfig`)
|
|
517
|
+
followed by the SDK SW-fallback floor
|
|
518
|
+
(`integration.zephyr.hw_backends.sw_fallback.kconfig`), module-enable
|
|
519
|
+
first and the fallback marker last, deduped. Header-only libraries whose
|
|
520
|
+
SW floor is a doc comment surface that comment only when there is no
|
|
521
|
+
module-enable line to attach it to.
|
|
522
|
+
|
|
523
|
+
The ONE derivation both declaration channels call for the base+fallback
|
|
524
|
+
set -- `zephyr_kconfig_lines` (project-wide) and
|
|
525
|
+
`kconfig.py::_per_core_library_kconfig` (core-scoped) -- so the same
|
|
526
|
+
library manifest can never emit a different symbol set depending on
|
|
527
|
+
which `libraries:` channel declared it (#1359: the project-wide channel
|
|
528
|
+
used to skip the SW-fallback line entirely, e.g. cmsis-dsp's project-wide
|
|
529
|
+
form never emitted `CONFIG_ALP_CMSIS_DSP_SCALAR=y` while its core-scoped
|
|
530
|
+
form did).
|
|
531
|
+
"""
|
|
532
|
+
zephyr = (manifest.get("integration") or {}).get("zephyr") or {}
|
|
533
|
+
out: list[str] = []
|
|
534
|
+
for kc in zephyr.get("kconfig") or []:
|
|
535
|
+
kc = str(kc)
|
|
536
|
+
if kc not in out:
|
|
537
|
+
out.append(kc)
|
|
538
|
+
swf = ((zephyr.get("hw_backends") or {}).get("sw_fallback") or {}).get("kconfig")
|
|
539
|
+
if swf:
|
|
540
|
+
swf = str(swf)
|
|
541
|
+
if swf.lstrip().startswith("#"):
|
|
542
|
+
# Header-only library: the SW floor is a documentation comment,
|
|
543
|
+
# not a real knob. Surface it only when there is no module enable.
|
|
544
|
+
if not out:
|
|
545
|
+
out.append(swf)
|
|
546
|
+
elif swf not in out:
|
|
547
|
+
out.append(swf)
|
|
548
|
+
return out
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def zephyr_kconfig_lines(
|
|
552
|
+
project: BoardProject,
|
|
553
|
+
slice_: Slice,
|
|
554
|
+
metadata_root: Path,
|
|
555
|
+
) -> list[str]:
|
|
556
|
+
"""Kconfig lines for every project-wide library with a Zephyr integration.
|
|
557
|
+
|
|
558
|
+
Emitted into a Zephyr slice's alp.conf. Returns [] when the project
|
|
559
|
+
selects no libraries (keeps existing emit byte-identical).
|
|
560
|
+
|
|
561
|
+
Both channels are RESOLVED here (so a core-scoped entry gets the same
|
|
562
|
+
unknown-name / `requires:` / wireability / core-class refusals as a
|
|
563
|
+
project-wide one), but only the project-wide half is EMITTED: the Zephyr
|
|
564
|
+
Kconfig for a core-scoped entry rides `kconfig.py::_emit_libraries`'
|
|
565
|
+
per-core channel, which reads the very same manifest. Emitting it twice
|
|
566
|
+
would duplicate every CONFIG_ line. Both halves derive their per-library
|
|
567
|
+
CONFIG_ set from the same `zephyr_library_kconfig` helper (#1359).
|
|
568
|
+
"""
|
|
569
|
+
if slice_.os != "zephyr":
|
|
570
|
+
return []
|
|
571
|
+
project_wide = set(project.libraries or ())
|
|
572
|
+
lines: list[str] = []
|
|
573
|
+
for name, manifest in resolve_selection(project, metadata_root, slice_=slice_):
|
|
574
|
+
zephyr = (manifest.get("integration") or {}).get("zephyr")
|
|
575
|
+
if not zephyr:
|
|
576
|
+
continue
|
|
577
|
+
_check_core_class(name, manifest, project, slice_)
|
|
578
|
+
if name not in project_wide:
|
|
579
|
+
continue
|
|
580
|
+
module = zephyr.get("module")
|
|
581
|
+
version = str(manifest.get("version", "?"))
|
|
582
|
+
display_version = version if version.startswith("v") else f"v{version}"
|
|
583
|
+
tag = f"{name} {display_version}"
|
|
584
|
+
if module:
|
|
585
|
+
tag += f" (west module `{module}`)"
|
|
586
|
+
lines.append(f"# library: {tag}")
|
|
587
|
+
for kc in zephyr_library_kconfig(manifest):
|
|
588
|
+
lines.append(kc)
|
|
589
|
+
return lines
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def yocto_image_install(
|
|
593
|
+
project: BoardProject,
|
|
594
|
+
slice_: Slice,
|
|
595
|
+
metadata_root: Path,
|
|
596
|
+
) -> list[str]:
|
|
597
|
+
"""IMAGE_INSTALL recipe names for libraries with a Yocto integration.
|
|
598
|
+
|
|
599
|
+
Covers BOTH declaration channels: a `libraries:` entry scoped to this
|
|
600
|
+
Yocto core contributes its manifest's `integration.yocto.image_install`
|
|
601
|
+
recipes, exactly like a project-wide one. The recipe names come from the
|
|
602
|
+
manifest and nowhere else -- they are never derived from the library name
|
|
603
|
+
(alplabai/tan-cli#555: the old per-core path invented `lib-<name>`, which
|
|
604
|
+
RPROVIDES nothing in meta-alp-sdk or OE).
|
|
605
|
+
"""
|
|
606
|
+
if slice_.os != "yocto":
|
|
607
|
+
return []
|
|
608
|
+
packages: list[str] = []
|
|
609
|
+
for name, manifest in resolve_selection(project, metadata_root, slice_=slice_):
|
|
610
|
+
yocto = (manifest.get("integration") or {}).get("yocto")
|
|
611
|
+
if not yocto:
|
|
612
|
+
continue
|
|
613
|
+
for pkg in yocto.get("image_install") or []:
|
|
614
|
+
if pkg not in packages:
|
|
615
|
+
packages.append(pkg)
|
|
616
|
+
return packages
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
def yocto_unwireable(
|
|
620
|
+
project: BoardProject,
|
|
621
|
+
slice_: Slice,
|
|
622
|
+
metadata_root: Path,
|
|
623
|
+
) -> list[str]:
|
|
624
|
+
"""In-scope library names whose manifest has NO `integration.yocto:`.
|
|
625
|
+
|
|
626
|
+
Such a library contributes nothing to this slice's `IMAGE_INSTALL` -- the
|
|
627
|
+
honest emit is nothing. The caller surfaces the names as a comment so the
|
|
628
|
+
no-op is visible in the generated local.conf instead of silent.
|
|
629
|
+
"""
|
|
630
|
+
if slice_.os != "yocto":
|
|
631
|
+
return []
|
|
632
|
+
return [
|
|
633
|
+
name
|
|
634
|
+
for name, manifest in resolve_selection(project, metadata_root, slice_=slice_)
|
|
635
|
+
if not (manifest.get("integration") or {}).get("yocto")
|
|
636
|
+
]
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
def baremetal_cmake_args(
|
|
640
|
+
project: BoardProject,
|
|
641
|
+
slice_: Slice,
|
|
642
|
+
metadata_root: Path,
|
|
643
|
+
) -> list[str]:
|
|
644
|
+
"""cmake hint lines for libraries with a baremetal integration.
|
|
645
|
+
|
|
646
|
+
Covers both declaration channels (see `scoped_names`)."""
|
|
647
|
+
if slice_.os != "baremetal":
|
|
648
|
+
return []
|
|
649
|
+
args: list[str] = []
|
|
650
|
+
for name, manifest in resolve_selection(project, metadata_root, slice_=slice_):
|
|
651
|
+
baremetal = (manifest.get("integration") or {}).get("baremetal")
|
|
652
|
+
if not baremetal:
|
|
653
|
+
continue
|
|
654
|
+
cmake = baremetal.get("cmake")
|
|
655
|
+
if cmake:
|
|
656
|
+
args.append(f"# library {name}: {cmake}")
|
|
657
|
+
return args
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
def _check_core_class(
|
|
661
|
+
name: str,
|
|
662
|
+
manifest: dict[str, Any],
|
|
663
|
+
project: BoardProject,
|
|
664
|
+
slice_: Slice,
|
|
665
|
+
) -> None:
|
|
666
|
+
"""A per-slice guard: a core_class-constrained library must not be wired
|
|
667
|
+
onto a core of the wrong class, even if some OTHER core satisfies the
|
|
668
|
+
project-wide `requires:` check.
|
|
669
|
+
"""
|
|
670
|
+
core_class = (manifest.get("requires") or {}).get("core_class")
|
|
671
|
+
if core_class is None:
|
|
672
|
+
return
|
|
673
|
+
have = _core_class_of(project, slice_)
|
|
674
|
+
if have is not None and have != core_class:
|
|
675
|
+
raise OrchestratorError(
|
|
676
|
+
f"library `{name}` requires core_class `{core_class}` but is "
|
|
677
|
+
f"selected on core `{slice_.core_id}` (class {have})")
|