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,714 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""`--emit scaffold` text rewrites: board.yaml/CMakeLists.txt/README.md.
|
|
3
|
+
|
|
4
|
+
SPLIT out of `tan/planner/template.py` (tan-cli#1142; see `template_pins.py`'s
|
|
5
|
+
module docstring for why the split is allowed under `HAND_PORT_SOURCES` and
|
|
6
|
+
why `MIRRORED_PREFIX` does not bar it -- both files are the same story, not
|
|
7
|
+
repeated here).
|
|
8
|
+
|
|
9
|
+
WHAT'S HERE: every function that takes a rendered file's TEXT and a rename
|
|
10
|
+
(sku, core, pin, macro, doc, docs ref) and returns rewritten text -- the
|
|
11
|
+
`board.yaml` sku/core/pin substitutions, the CMakeLists.txt ALP_SDK_ROOT
|
|
12
|
+
hardening, and the README.md link/board/pin rewrites -- plus the two small
|
|
13
|
+
metadata-adjacent lookups those rewrites need (`_tag_resolves`, `_docs_ref`).
|
|
14
|
+
What is NOT here: anything that reads `metadata/e1m_modules/**` or
|
|
15
|
+
`metadata/boards/**` to DERIVE a rename in the first place -- that's
|
|
16
|
+
`template_pins.py`.
|
|
17
|
+
|
|
18
|
+
Every docstring below is UNCHANGED from `template.py` -- issue numbers,
|
|
19
|
+
tan-cli numbers and worked examples all still refer to that module's own
|
|
20
|
+
history, because this split moved code, not its provenance.
|
|
21
|
+
|
|
22
|
+
Imports `TemplateError` back FROM `.template`, the same circular-at-the-
|
|
23
|
+
module-object-level direction `template_pins.py` uses and explains at length
|
|
24
|
+
in its own docstring -- read that one first if this import looks backwards.
|
|
25
|
+
It works here for the same reason: `template.py` binds `TemplateError` (its
|
|
26
|
+
exception classes, near the top of the file) before its own
|
|
27
|
+
`from .template_rewrite import ...` line runs.
|
|
28
|
+
|
|
29
|
+
Also imports `_pin_pad_and_macro` from `.template_pins` -- NOT circular: by
|
|
30
|
+
the time `template.py` reaches its own `from .template_rewrite import ...`
|
|
31
|
+
line, its preceding `from .template_pins import ...` line (tan-cli#1142's
|
|
32
|
+
NameError fix) has already run `template_pins.py` to completion, so this
|
|
33
|
+
module sees a fully-built `tan.planner.template_pins`, same as any ordinary
|
|
34
|
+
import.
|
|
35
|
+
"""
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
import posixpath
|
|
39
|
+
import re
|
|
40
|
+
import subprocess
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
from typing import Any
|
|
43
|
+
|
|
44
|
+
import yaml
|
|
45
|
+
|
|
46
|
+
from tan.core.subprocess_env import spawn_env
|
|
47
|
+
|
|
48
|
+
from .template import TemplateError
|
|
49
|
+
from .template_pins import _pin_pad_and_macro
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# Matches board.yaml's `som:\n sku: E1M-...` line and the top-level
|
|
53
|
+
# `preset: <name>` line -- through end-of-line (incl. any trailing inline
|
|
54
|
+
# comment), so a value CHANGE can drop a comment describing the OLD SoM
|
|
55
|
+
# (e.g. `sku: E1M-AEN801 # Alif Ensemble E8 SoM` must not survive as a
|
|
56
|
+
# stale label once the value becomes E1M-V2N101). Unbounded (no count=):
|
|
57
|
+
# every match is inspected so a board.yaml with more than one matching
|
|
58
|
+
# `sku:`/`preset:` line -- ambiguous, could silently rewrite a decoy while
|
|
59
|
+
# the real som.sku/preset line survives untouched -- hard-errors instead
|
|
60
|
+
# of guessing which one is real.
|
|
61
|
+
_SOM_SKU_RE = re.compile(r"(?m)^(\s*sku:\s*)(E1M-[A-Z0-9]+)[^\n]*$")
|
|
62
|
+
_PRESET_RE = re.compile(r"(?m)^(preset:\s*)(\S+)[^\n]*$")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _substitute_board_yaml_sku(text: str, sku: str, preset: str) -> str:
|
|
66
|
+
def _sub_sku(m: re.Match[str]) -> str:
|
|
67
|
+
# Value unchanged -> leave the WHOLE line (incl. any comment)
|
|
68
|
+
# untouched: this is the byte-passthrough guarantee for sku ==
|
|
69
|
+
# the example's own default.
|
|
70
|
+
return m.group(0) if m.group(2) == sku else f"{m.group(1)}{sku}"
|
|
71
|
+
|
|
72
|
+
text, n_sku = _SOM_SKU_RE.subn(_sub_sku, text)
|
|
73
|
+
if n_sku != 1:
|
|
74
|
+
raise TemplateError(
|
|
75
|
+
f"board.yaml must have exactly one `som.sku:` line to "
|
|
76
|
+
f"substitute (found {n_sku})")
|
|
77
|
+
|
|
78
|
+
def _sub_preset(m: re.Match[str]) -> str:
|
|
79
|
+
return m.group(0) if m.group(2) == preset else f"{m.group(1)}{preset}"
|
|
80
|
+
|
|
81
|
+
text, n_preset = _PRESET_RE.subn(_sub_preset, text)
|
|
82
|
+
if n_preset != 1:
|
|
83
|
+
raise TemplateError(
|
|
84
|
+
f"board.yaml must have exactly one top-level `preset:` line "
|
|
85
|
+
f"to substitute (found {n_preset})")
|
|
86
|
+
return text
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
_LIBRARY_CORE_SCOPE_RE = re.compile(r"(cores:\s*\[)([^\]]*)(\])")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _strip_stale_core_prose(text: str, old: str) -> str:
|
|
93
|
+
"""Delete any full comment LINE naming `old` in PROSE form (issue
|
|
94
|
+
#864 Fable-review MINOR F) -- e.g. gpio-button-led's board.yaml
|
|
95
|
+
carries `# Single-core slice: M55-HP runs the demo. M55-HE
|
|
96
|
+
inherits...` directly above `cores:\\n m55_hp:`, which the plain
|
|
97
|
+
`m55_hp:` key-line regex below never touches (different case,
|
|
98
|
+
hyphen instead of underscore). Matches case-insensitively with `_`
|
|
99
|
+
/`-` interchangeable. A hardware-specific sentence about the
|
|
100
|
+
canonical SoM's OTHER core/topology doesn't have a sensible
|
|
101
|
+
equivalent on a different SoM family, so deleting the line is
|
|
102
|
+
safer than guessing a replacement."""
|
|
103
|
+
prose = re.escape(old).replace("_", "[_-]")
|
|
104
|
+
line_re = re.compile(rf"(?mi)^[ \t]*#.*\b{prose}\b.*\n?")
|
|
105
|
+
return line_re.sub("", text)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _substitute_board_yaml_core(text: str, old: str, new: str) -> str:
|
|
109
|
+
"""Rewrite the `cores:` mapping's single top-level `<old>:` key to
|
|
110
|
+
`<new>:`. The per-core content underneath (`app:`, `peripherals:`)
|
|
111
|
+
is core-id-agnostic -- metadata/schemas/board.schema.json's
|
|
112
|
+
`core_entry` says every field is optional and inherits the SoM
|
|
113
|
+
preset's `topology.<core_id>` default, so only the KEY changes.
|
|
114
|
+
|
|
115
|
+
Also renames `old` wherever a top-level `libraries:` entry scopes
|
|
116
|
+
itself to this core via a `cores: [<id>, ...]` flow list (e.g.
|
|
117
|
+
cold-chain-monitor's `libraries: [{name: tflite-micro, cores:
|
|
118
|
+
[m55_hp]}]`) -- `alp_orchestrate.loader._normalize_libraries` hard-
|
|
119
|
+
errors if that list still names a core id that no longer exists
|
|
120
|
+
once the `cores:` mapping key above is renamed ("libraries: entry
|
|
121
|
+
'<name>' is scoped to core '<old>', which is not declared under
|
|
122
|
+
`cores:`"). Also strips any comment line describing `old` in prose
|
|
123
|
+
(see `_strip_stale_core_prose`)."""
|
|
124
|
+
text = _strip_stale_core_prose(text, old)
|
|
125
|
+
pattern = re.compile(rf"(?m)^(\s*){re.escape(old)}:([ \t]*)$")
|
|
126
|
+
new_text, n = pattern.subn(lambda m: f"{m.group(1)}{new}:{m.group(2)}", text)
|
|
127
|
+
if n != 1:
|
|
128
|
+
raise TemplateError(
|
|
129
|
+
f"board.yaml must have exactly one `cores.{old}:` line to "
|
|
130
|
+
f"re-derive to {new!r} (found {n})")
|
|
131
|
+
|
|
132
|
+
def _fix_scope_list(m: re.Match[str]) -> str:
|
|
133
|
+
inner = re.sub(rf"\b{re.escape(old)}\b", new, m.group(2))
|
|
134
|
+
return f"{m.group(1)}{inner}{m.group(3)}"
|
|
135
|
+
|
|
136
|
+
return _LIBRARY_CORE_SCOPE_RE.sub(_fix_scope_list, new_text)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _substitute_board_yaml_pins(
|
|
140
|
+
text: str, renames: dict[str, str], original_pins: list[Any],
|
|
141
|
+
) -> str:
|
|
142
|
+
"""Rewrite each renamed pad wherever a `pins:` entry names it --
|
|
143
|
+
scoped to the two shapes a `pins:` list item can take (issue #876
|
|
144
|
+
review MINOR 3), not a blanket `\\b<pad>\\b` replace over the whole
|
|
145
|
+
file (a pad name can also appear in unrelated prose, e.g. gpio-
|
|
146
|
+
button-led's `preset:` header comment -- see `_strip_stale_core_
|
|
147
|
+
prose`, reused for pins in `render_to_envelope`):
|
|
148
|
+
|
|
149
|
+
* the dict form's `e1m: <old>` field (`(e1m:\\s*)<pad>\\b`), and
|
|
150
|
+
* the bare pad-string list-item form (`- <old>`,
|
|
151
|
+
`([ \\t]*-[ \\t]*)<pad>\\b`) the schema also allows -- a template
|
|
152
|
+
using this form had its pad left stale by the dict-only regex
|
|
153
|
+
(silent `--emit zephyr-conf` failure downstream: the exact class
|
|
154
|
+
of bug #876 exists to kill), and a MIXED bare + dict entry for
|
|
155
|
+
the same pad hid it entirely (the dict match alone satisfied the
|
|
156
|
+
old "at least one occurrence" guard).
|
|
157
|
+
|
|
158
|
+
`original_pins` supplies the EXPECTED occurrence count per pad (how
|
|
159
|
+
many entries -- bare or dict -- actually name it), so the rewrite
|
|
160
|
+
is verified exact rather than "found at least one"."""
|
|
161
|
+
for old, new in renames.items():
|
|
162
|
+
expected = sum(
|
|
163
|
+
1 for item in original_pins
|
|
164
|
+
if _pin_pad_and_macro(item)[0] == old
|
|
165
|
+
)
|
|
166
|
+
dict_pattern = re.compile(rf"(e1m:\s*){re.escape(old)}\b")
|
|
167
|
+
text, n_dict = dict_pattern.subn(lambda m: f"{m.group(1)}{new}", text)
|
|
168
|
+
bare_pattern = re.compile(rf"(?m)^([ \t]*-[ \t]*){re.escape(old)}\b")
|
|
169
|
+
text, n_bare = bare_pattern.subn(lambda m: f"{m.group(1)}{new}", text)
|
|
170
|
+
if n_dict + n_bare != expected:
|
|
171
|
+
raise TemplateError(
|
|
172
|
+
f"board.yaml `pins:` re-derive of `{old}` -> {new!r}: "
|
|
173
|
+
f"expected {expected} occurrence(s), rewrote "
|
|
174
|
+
f"{n_dict + n_bare}")
|
|
175
|
+
return text
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _substitute_board_yaml_pin_macros(text: str, renames: dict[str, str]) -> str:
|
|
179
|
+
"""Companion to `_substitute_board_yaml_pins`: rewrite each `pins:`
|
|
180
|
+
entry's `macro:` field per `_derive_pin_macro_renames`'s map, the
|
|
181
|
+
same scoped-to-the-key approach (`(macro:\\s*)<old>\\b`) -- `macro:`
|
|
182
|
+
only ever appears in the dict form (a bare pad-string entry has
|
|
183
|
+
no `macro:` at all)."""
|
|
184
|
+
for old, new in renames.items():
|
|
185
|
+
pattern = re.compile(rf"(macro:\s*){re.escape(old)}\b")
|
|
186
|
+
new_text, n = pattern.subn(lambda m: f"{m.group(1)}{new}", text)
|
|
187
|
+
if n < 1:
|
|
188
|
+
raise TemplateError(
|
|
189
|
+
f"board.yaml `pins:` has no `macro: {old}` entry to "
|
|
190
|
+
f"re-derive to {new!r}")
|
|
191
|
+
text = new_text
|
|
192
|
+
return text
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _substitute_board_yaml_pin_docs(text: str, renames: dict[str, str | None]) -> str:
|
|
196
|
+
"""Companion to `_substitute_board_yaml_pins`: rewrite (or drop) a
|
|
197
|
+
`pins:` entry's `doc:` field per `_derive_pin_doc_renames`'s map
|
|
198
|
+
(issue #876 review MAJOR 2) -- `doc:` only ever appears in the
|
|
199
|
+
dict form. A `None` value means the target route has no `doc:` of
|
|
200
|
+
its own; the loader falls back to the resolved board's own `doc:`
|
|
201
|
+
in that case, so the field is dropped entirely rather than left
|
|
202
|
+
describing the wrong board."""
|
|
203
|
+
for old_doc, new_doc in renames.items():
|
|
204
|
+
old_quoted = re.escape(f'"{old_doc}"')
|
|
205
|
+
if new_doc is not None:
|
|
206
|
+
pattern = re.compile(rf"(doc:\s*){old_quoted}")
|
|
207
|
+
new_text, n = pattern.subn(lambda m: f'{m.group(1)}"{new_doc}"', text)
|
|
208
|
+
else:
|
|
209
|
+
pattern = re.compile(rf",\s*doc:\s*{old_quoted}")
|
|
210
|
+
new_text, n = pattern.subn("", text)
|
|
211
|
+
if n < 1:
|
|
212
|
+
raise TemplateError(
|
|
213
|
+
f'board.yaml `pins:` has no `doc: "{old_doc}"` entry to '
|
|
214
|
+
f"re-derive")
|
|
215
|
+
text = new_text
|
|
216
|
+
return text
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# ---------------------------------------------------------------------
|
|
220
|
+
# --emit scaffold content adaptation (issue #864 follow-up)
|
|
221
|
+
# ---------------------------------------------------------------------
|
|
222
|
+
#
|
|
223
|
+
# Every catalog template's user_owned files are the SDK's own example,
|
|
224
|
+
# verbatim -- correct for render()'s documented byte-for-byte contract
|
|
225
|
+
# (validate()'s in-tree twister self-test relies on exactly that), but
|
|
226
|
+
# wrong for a scaffold a customer unpacks OUTSIDE the SDK tree: a
|
|
227
|
+
# `west build ... examples/<...>` argument naming a path that doesn't
|
|
228
|
+
# exist in their project, `../`-relative links that only resolve
|
|
229
|
+
# inside the SDK checkout, and a CMakeLists.txt that silently guesses
|
|
230
|
+
# `../../..` for ALP_SDK_ROOT (correct only for the in-tree example,
|
|
231
|
+
# never a copied-out scaffold -- the retired tan-cli generator hard-
|
|
232
|
+
# failed on exactly this: "ALP_SDK_ROOT is not set"). These transforms
|
|
233
|
+
# run ONLY in render_to_envelope() (the `--emit scaffold` path, for
|
|
234
|
+
# EVERY sku including the canonical example's own) -- render()/
|
|
235
|
+
# validate() stay byte-for-byte faithful to the real example, since
|
|
236
|
+
# that's what validate()'s temp-dir twister run is proving builds.
|
|
237
|
+
|
|
238
|
+
_ALP_SDK_ROOT_GUESS_RE = re.compile(
|
|
239
|
+
r"if\(DEFINED ENV\{ALP_SDK_ROOT\}\)\n"
|
|
240
|
+
r" set\(ALP_SDK_ROOT \$ENV\{ALP_SDK_ROOT\}\)\n"
|
|
241
|
+
r"else\(\)\n"
|
|
242
|
+
r" get_filename_component\(ALP_SDK_ROOT \$\{CMAKE_CURRENT_SOURCE_DIR\}(?:/\.\.)+ ABSOLUTE\)\n"
|
|
243
|
+
r"endif\(\)"
|
|
244
|
+
)
|
|
245
|
+
# Was `cold-chain-monitor`'s own shape until alp-sdk#1400 converted it to the
|
|
246
|
+
# guess shape above: no ALP_SDK_ROOT resolution at all, just a hardcoded
|
|
247
|
+
# in-tree-relative path straight to `alp_project.py` (worse than the guess --
|
|
248
|
+
# no override was even possible). No example carries it at the pinned SDK
|
|
249
|
+
# commit any more; kept as a defensive branch, not a live path.
|
|
250
|
+
_HARDCODED_ALP_PROJECT_PY_RE = re.compile(
|
|
251
|
+
r"\$\{CMAKE_CURRENT_SOURCE_DIR\}(?:/\.\.)+/scripts/alp_project\.py"
|
|
252
|
+
)
|
|
253
|
+
# Anything that only resolves against a real alp-sdk checkout, i.e. that a
|
|
254
|
+
# scaffold copied OUT of the SDK tree cannot satisfy unless ALP_SDK_ROOT has
|
|
255
|
+
# been rewritten into a hard requirement: the shared `cmake/alp.cmake`
|
|
256
|
+
# include, either helper it would define, or a direct `alp_project.py` shell
|
|
257
|
+
# (the `cmake/alp.cmake` include and its helpers are PLANNED and unmerged,
|
|
258
|
+
# tan-cli#825 -- `alp_project.py` is the only spelling any real example
|
|
259
|
+
# carries today; the other two are matched so this stays correct once that
|
|
260
|
+
# helper ships).
|
|
261
|
+
_SDK_ROOT_DEPENDENT_RE = re.compile(
|
|
262
|
+
r"cmake/alp\.cmake|alp_sdk_zephyr_conf|alp_sdk_ipc_contract_header"
|
|
263
|
+
r"|alp_project\.py")
|
|
264
|
+
_ALP_SDK_ROOT_REQUIRED_BLOCK = (
|
|
265
|
+
# Issue #864 Fable-review MAJOR E: the ORIGINAL block here checked
|
|
266
|
+
# only `ENV{ALP_SDK_ROOT}` while the message also advertised
|
|
267
|
+
# `-DALP_SDK_ROOT=...` -- a customer passing ONLY the -D cache
|
|
268
|
+
# variable still hit the FATAL_ERROR (ENV{} was never set), and
|
|
269
|
+
# even a customer setting BOTH had the -D value silently clobbered
|
|
270
|
+
# by `set(ALP_SDK_ROOT $ENV{ALP_SDK_ROOT})`. Check + prefer
|
|
271
|
+
# whichever is actually DEFINED; only fall back to the env var when
|
|
272
|
+
# the cache variable itself isn't set.
|
|
273
|
+
"if(NOT DEFINED ALP_SDK_ROOT AND NOT DEFINED ENV{ALP_SDK_ROOT})\n"
|
|
274
|
+
" message(FATAL_ERROR\n"
|
|
275
|
+
" \"ALP_SDK_ROOT is not set -- point it at your alp-sdk checkout, \"\n"
|
|
276
|
+
" \"e.g. `export ALP_SDK_ROOT=/path/to/alp-sdk` or "
|
|
277
|
+
"`-DALP_SDK_ROOT=/path/to/alp-sdk`.\")\n"
|
|
278
|
+
"endif()\n"
|
|
279
|
+
"if(NOT DEFINED ALP_SDK_ROOT)\n"
|
|
280
|
+
" set(ALP_SDK_ROOT $ENV{ALP_SDK_ROOT})\n"
|
|
281
|
+
"endif()"
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
# The guess block does not stand alone: most examples introduce it with
|
|
285
|
+
# a comment paragraph that TEACHES the in-tree `../../..` fallback --
|
|
286
|
+
# hello-world/cold-chain-monitor's "In-tree the SDK is the example's
|
|
287
|
+
# grandparent directory; out-of-tree customers point ALP_SDK_ROOT at
|
|
288
|
+
# their checkout", gpio-button-led's "in-tree we resolve it as the
|
|
289
|
+
# example's grandparent directory". Substituting only the code left
|
|
290
|
+
# that prose above a block that has NO fallback and hard-fails instead,
|
|
291
|
+
# so the emitted scaffold documented behaviour it did not have. Rewrite
|
|
292
|
+
# the paragraph with the code it describes.
|
|
293
|
+
_STALE_SDK_ROOT_PROSE_RE = re.compile(r"ALP_SDK_ROOT|grandparent", re.IGNORECASE)
|
|
294
|
+
_ALP_SDK_ROOT_ACCURATE_COMMENT = (
|
|
295
|
+
"# Resolve the alp-sdk root. This project lives OUTSIDE the SDK\n"
|
|
296
|
+
"# tree, so there is nothing to guess: ALP_SDK_ROOT must name your\n"
|
|
297
|
+
"# alp-sdk checkout, set in the environment or passed as\n"
|
|
298
|
+
"# `-DALP_SDK_ROOT=/path/to/alp-sdk`."
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _rewrite_stale_sdk_root_comment(head: str) -> str:
|
|
303
|
+
"""Rewrite the comment paragraph introducing the ALP_SDK_ROOT block.
|
|
304
|
+
|
|
305
|
+
`head` is everything in the CMakeLists.txt BEFORE the guess block.
|
|
306
|
+
Its trailing run of `#` lines (optionally separated from the block
|
|
307
|
+
by blank lines) is that block's prose. The run is split into
|
|
308
|
+
paragraphs on bare `#` separator lines, and the first paragraph
|
|
309
|
+
naming `ALP_SDK_ROOT` or the grandparent fallback is replaced with
|
|
310
|
+
`_ALP_SDK_ROOT_ACCURATE_COMMENT`; any further matching paragraph is
|
|
311
|
+
dropped rather than duplicating it. Paragraphs about anything else
|
|
312
|
+
are kept verbatim -- gpio-button-led's run leads with a "board.yaml
|
|
313
|
+
-> build/generated/alp.conf at configure time." banner that stays
|
|
314
|
+
true. A file whose block has no comment run above it (i2c-master,
|
|
315
|
+
mproc-mailbox) is returned unchanged.
|
|
316
|
+
"""
|
|
317
|
+
lines = head.split("\n")
|
|
318
|
+
i = len(lines) - 1
|
|
319
|
+
while i >= 0 and not lines[i].strip():
|
|
320
|
+
i -= 1
|
|
321
|
+
end = i + 1
|
|
322
|
+
while i >= 0 and lines[i].lstrip().startswith("#"):
|
|
323
|
+
i -= 1
|
|
324
|
+
start = i + 1
|
|
325
|
+
if start >= end:
|
|
326
|
+
return head
|
|
327
|
+
|
|
328
|
+
out: list[str] = []
|
|
329
|
+
para: list[str] = []
|
|
330
|
+
replaced = False
|
|
331
|
+
|
|
332
|
+
def _flush() -> None:
|
|
333
|
+
nonlocal replaced
|
|
334
|
+
if not para:
|
|
335
|
+
return
|
|
336
|
+
if _STALE_SDK_ROOT_PROSE_RE.search("\n".join(para)):
|
|
337
|
+
if not replaced:
|
|
338
|
+
out.extend(_ALP_SDK_ROOT_ACCURATE_COMMENT.split("\n"))
|
|
339
|
+
replaced = True
|
|
340
|
+
else:
|
|
341
|
+
out.extend(para)
|
|
342
|
+
para.clear()
|
|
343
|
+
|
|
344
|
+
for line in lines[start:end]:
|
|
345
|
+
if line.strip() == "#":
|
|
346
|
+
_flush()
|
|
347
|
+
out.append(line)
|
|
348
|
+
else:
|
|
349
|
+
para.append(line)
|
|
350
|
+
_flush()
|
|
351
|
+
lines[start:end] = out
|
|
352
|
+
return "\n".join(lines)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _scaffold_cmakelists(text: str) -> str:
|
|
356
|
+
"""Replace an in-tree-relative ALP_SDK_ROOT guess with a hard
|
|
357
|
+
requirement, and rewrite the comment paragraph that describes it.
|
|
358
|
+
|
|
359
|
+
One shape is live across the catalog's example CMakeLists.txt files
|
|
360
|
+
today: the `if(DEFINED ENV{ALP_SDK_ROOT}) ... else()
|
|
361
|
+
get_filename_component(...)` guess most examples carry immediately
|
|
362
|
+
above a direct `execute_process(... scripts/alp_project.py ...)`
|
|
363
|
+
call (PLANNED to become `include(${ALP_SDK_ROOT}/cmake/alp.cmake)`
|
|
364
|
+
once that helper merges -- unmerged, tan-cli#825). A second,
|
|
365
|
+
defensive-only shape is also matched -- see `_HARDCODED_ALP_PROJECT_PY_RE`
|
|
366
|
+
above for why it is no longer live and why the branch stays anyway. The
|
|
367
|
+
guess shape resolves only for the in-tree example; a scaffold a customer
|
|
368
|
+
unpacks elsewhere needs the value supplied, so it becomes a
|
|
369
|
+
FATAL_ERROR-if-unset block -- the guess shape's `include()` line
|
|
370
|
+
already names `${ALP_SDK_ROOT}` and needs no further rewriting; the
|
|
371
|
+
(defensive-only) hardcoded shape's path would be rewritten to
|
|
372
|
+
`${ALP_SDK_ROOT}/scripts/alp_project.py` alongside inserting the
|
|
373
|
+
block if it were ever matched.
|
|
374
|
+
|
|
375
|
+
Each guess-block hit is substituted through a loop rather than
|
|
376
|
+
`subn`: the block's own preceding comment run has to be rewritten
|
|
377
|
+
with it (`_rewrite_stale_sdk_root_comment`, alp-sdk#1390), and the
|
|
378
|
+
replacement block is not itself a guess block, so the next `search`
|
|
379
|
+
cannot re-find what was just substituted.
|
|
380
|
+
|
|
381
|
+
A CMakeLists.txt with no SDK-root-dependent line at all (e.g.
|
|
382
|
+
multicore-rpmsg's `linux/CMakeLists.txt`) is legitimately returned
|
|
383
|
+
unchanged. One that DOES depend on the SDK root but carries an
|
|
384
|
+
unrecognised resolution shape raises: this used to be a silent
|
|
385
|
+
best-effort no-op, which shipped every scaffolded project an
|
|
386
|
+
`include()`/`alp_project.py` path that resolves only inside an SDK
|
|
387
|
+
checkout -- broken on the very first thing a new customer does, with
|
|
388
|
+
nothing failing here to say so."""
|
|
389
|
+
pos, hit = 0, False
|
|
390
|
+
while True:
|
|
391
|
+
m = _ALP_SDK_ROOT_GUESS_RE.search(text, pos)
|
|
392
|
+
if not m:
|
|
393
|
+
break
|
|
394
|
+
hit = True
|
|
395
|
+
head = _rewrite_stale_sdk_root_comment(text[: m.start()])
|
|
396
|
+
text = head + _ALP_SDK_ROOT_REQUIRED_BLOCK + text[m.end():]
|
|
397
|
+
pos = len(head) + len(_ALP_SDK_ROOT_REQUIRED_BLOCK)
|
|
398
|
+
if hit:
|
|
399
|
+
return text
|
|
400
|
+
if _ALP_SDK_ROOT_REQUIRED_BLOCK in text:
|
|
401
|
+
return text # already hardened (idempotent)
|
|
402
|
+
if _HARDCODED_ALP_PROJECT_PY_RE.search(text):
|
|
403
|
+
text = _HARDCODED_ALP_PROJECT_PY_RE.sub(
|
|
404
|
+
"${ALP_SDK_ROOT}/scripts/alp_project.py", text)
|
|
405
|
+
return text.replace(
|
|
406
|
+
"execute_process(\n",
|
|
407
|
+
_ALP_SDK_ROOT_REQUIRED_BLOCK + "\n\nexecute_process(\n", 1)
|
|
408
|
+
dependent = _SDK_ROOT_DEPENDENT_RE.search(text)
|
|
409
|
+
if dependent:
|
|
410
|
+
raise TemplateError(
|
|
411
|
+
f"CMakeLists.txt depends on the SDK root (`{dependent.group(0)}`) "
|
|
412
|
+
f"but carries no recognised ALP_SDK_ROOT resolution block to "
|
|
413
|
+
f"rewrite into a hard requirement -- a scaffold of it would ship "
|
|
414
|
+
f"a path that only resolves inside an alp-sdk checkout. Use the "
|
|
415
|
+
f"`if(DEFINED ENV{{ALP_SDK_ROOT}}) ... else() "
|
|
416
|
+
f"get_filename_component(...) endif()` shape the other examples "
|
|
417
|
+
f"use, or teach `_scaffold_cmakelists` the new one.")
|
|
418
|
+
return text
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
_RELATIVE_LINK_RE = re.compile(r"\]\((\.\./[^)\s]+)\)")
|
|
422
|
+
|
|
423
|
+
# alp-sdk#1855: `_RELATIVE_LINK_RE` above only rewrites a MARKDOWN-style
|
|
424
|
+
# `](../docs/x.md)` link, and only inside README.md (the one file
|
|
425
|
+
# `_scaffold_readme` runs on). A board.yaml/src/main.c comment names the
|
|
426
|
+
# same kind of alp-sdk-tree-only path in prose instead -- no `[...](...)`
|
|
427
|
+
# around it at all -- so it never matches and survives a scaffold
|
|
428
|
+
# verbatim (e.g. i2c-master's board.yaml/src/main.c both say "see
|
|
429
|
+
# examples/v2n/v2n-temp-sensor for ..." with no disclaimer that it isn't
|
|
430
|
+
# part of this scaffolded project, unlike the DELIBERATELY-disclaimed
|
|
431
|
+
# i2c-scanner mention two paragraphs above it in the same file). Narrow
|
|
432
|
+
# on purpose: only the two prefixes actually found bare like this
|
|
433
|
+
# (`docs/*.md`, `examples/<category>/<name>[/<subpath>]`) -- a
|
|
434
|
+
# `scripts/`/`metadata/` mention is left alone, since every such mention
|
|
435
|
+
# in the catalog today is either already `${ALP_SDK_ROOT}`-qualified in
|
|
436
|
+
# a CMakeLists.txt or purely descriptive prose that names a script/file
|
|
437
|
+
# by convention rather than telling the customer to open it.
|
|
438
|
+
_BARE_REPO_PATH_RE = re.compile(
|
|
439
|
+
r"\b(?:docs/[\w./-]+\.md|examples/[\w-]+/[\w-]+(?:/[\w./-]+)?)\b")
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _scaffold_bare_repo_paths(text: str, docs_ref: str) -> str:
|
|
443
|
+
"""Rewrite a bare `docs/*.md` or `examples/<category>/<name>
|
|
444
|
+
[/<subpath>]` mention into the same absolute GitHub URL form
|
|
445
|
+
`_scaffold_readme`'s `_fix_link` gives a markdown-style link --
|
|
446
|
+
see `_BARE_REPO_PATH_RE`'s comment for why this exists as a
|
|
447
|
+
SEPARATE pass rather than widening that one. Best-effort / no-op
|
|
448
|
+
when neither prefix appears."""
|
|
449
|
+
def _sub(m: re.Match[str]) -> str:
|
|
450
|
+
target = m.group(0)
|
|
451
|
+
kind = "blob" if "." in target.rsplit("/", 1)[-1] else "tree"
|
|
452
|
+
return f"https://github.com/alplabai/alp-sdk/{kind}/{docs_ref}/{target}"
|
|
453
|
+
|
|
454
|
+
return _BARE_REPO_PATH_RE.sub(_sub, text)
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def _tag_resolves(base_dir: Path, tag: str) -> bool:
|
|
458
|
+
"""Whether `tag` exists in `base_dir`'s git checkout.
|
|
459
|
+
|
|
460
|
+
Local-only: `git rev-parse` against the checkout's own refs, never a
|
|
461
|
+
network call -- scaffolding must work offline, and a scaffold that
|
|
462
|
+
stalled on `git ls-remote` would be a worse defect than the dead link
|
|
463
|
+
this guards. A checkout that fetched from origin has origin's tags, so
|
|
464
|
+
"resolves here" is the closest offline proxy for "resolves on GitHub"
|
|
465
|
+
available, and every way it can be wrong (no git binary, tarball
|
|
466
|
+
export, `--no-tags` clone, shallow CI checkout) fails the same
|
|
467
|
+
direction: no tag found, pin to `main`, links stay live.
|
|
468
|
+
|
|
469
|
+
Ported verbatim from alp-sdk `scripts/alp_template.py::_tag_resolves`
|
|
470
|
+
(issue #1508 / alp-sdk#1535), except for one RELOCATED divergence:
|
|
471
|
+
`env=spawn_env()` below is a tan-only addition (tan-cli#992) -- alp-sdk's
|
|
472
|
+
own copy never restores it because alp-sdk never ships as a frozen
|
|
473
|
+
PyInstaller bundle whose LD_LIBRARY_PATH would otherwise leak into the
|
|
474
|
+
spawned `git`."""
|
|
475
|
+
try:
|
|
476
|
+
return subprocess.run(
|
|
477
|
+
["git", "-C", str(base_dir), "rev-parse", "--verify", "--quiet", f"refs/tags/{tag}"],
|
|
478
|
+
capture_output=True,
|
|
479
|
+
env=spawn_env(),
|
|
480
|
+
check=False,
|
|
481
|
+
).returncode == 0
|
|
482
|
+
except (OSError, subprocess.SubprocessError): # no git binary, not a repo
|
|
483
|
+
return False
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def _docs_ref(base_dir: Path) -> str:
|
|
487
|
+
"""The GitHub ref a scaffolded README's doc links should pin to
|
|
488
|
+
(issue #864 Fable-review MINOR H): `metadata/sdk_version.yaml`'s
|
|
489
|
+
own `v<version>` tag when `status: released` (a released checkout's
|
|
490
|
+
docs are stable at that tag; linking `main` could point at docs
|
|
491
|
+
that have since changed or moved), else `main` -- an unreleased/
|
|
492
|
+
development checkout has no matching tag yet to pin to.
|
|
493
|
+
|
|
494
|
+
The tag has to RESOLVE, not merely be declared (tan-cli#846, porting
|
|
495
|
+
alp-sdk#1535). Between an rc cut and its GA tag `sdk_version.yaml`
|
|
496
|
+
says `version: 0.16.0` / `status: released` while only
|
|
497
|
+
`v0.16.0-rc1` exists on the bound checkout -- branching on the
|
|
498
|
+
declared pair alone put a dead
|
|
499
|
+
`https://github.com/alplabai/alp-sdk/blob/v0.16.0/docs/...` link in
|
|
500
|
+
every project scaffolded in that window. A missing tag degrades to
|
|
501
|
+
`main` instead of shipping a 404.
|
|
502
|
+
|
|
503
|
+
A `sdk_version.yaml` that parses but is not itself a mapping (e.g.
|
|
504
|
+
a bare list) degrades to `main` the same way a missing file does,
|
|
505
|
+
rather than raising: this is a README doc-link decision, not a
|
|
506
|
+
fatal `--emit scaffold` input, so a malformed version file should
|
|
507
|
+
cost a stale-but-safe link, not the whole scaffold (tan-cli#1037 --
|
|
508
|
+
a PR #1034 round-two review nit had described this read as safe on
|
|
509
|
+
the reasoning that `yaml.safe_load(...) or {}` only ever produces a
|
|
510
|
+
dict-or-`{}` result; a non-empty bare-list/scalar document is
|
|
511
|
+
neither, and reached `doc.get(...)` bare, `AttributeError: 'list'
|
|
512
|
+
object has no attribute 'get'`).
|
|
513
|
+
|
|
514
|
+
tan-cli#1116 review round 2: `UnicodeDecodeError` and `yaml.YAMLError`
|
|
515
|
+
are caught alongside `OSError` -- neither is an `OSError`, so a non-UTF-8
|
|
516
|
+
or syntactically-invalid `sdk_version.yaml` used to raise raw past this
|
|
517
|
+
same "cost a stale-but-safe link, not the whole scaffold" contract
|
|
518
|
+
(both measured escaping before this fix)."""
|
|
519
|
+
try:
|
|
520
|
+
doc = yaml.safe_load(
|
|
521
|
+
(base_dir / "metadata" / "sdk_version.yaml").read_text(encoding="utf-8")
|
|
522
|
+
) or {}
|
|
523
|
+
except (OSError, UnicodeDecodeError, yaml.YAMLError):
|
|
524
|
+
return "main"
|
|
525
|
+
if not isinstance(doc, dict):
|
|
526
|
+
return "main"
|
|
527
|
+
version = doc.get("version")
|
|
528
|
+
if doc.get("status") == "released" and version and _tag_resolves(base_dir, f"v{version}"):
|
|
529
|
+
return f"v{version}"
|
|
530
|
+
return "main"
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
def _substitute_readme_pins(text: str, renames: dict[str, str]) -> str:
|
|
534
|
+
"""Rewrite a scaffolded README's `ALP_<old_pad>` mentions to
|
|
535
|
+
`ALP_<new_pad>` for every renamed pin (issue #876 review MINOR 4)
|
|
536
|
+
-- e.g. gpio-button-led's README teaches `ALP_E1M_GPIO_IO4` as THE
|
|
537
|
+
button pin, which becomes actively wrong prose once the pad itself
|
|
538
|
+
has changed for a cross-family sku.
|
|
539
|
+
|
|
540
|
+
Paragraph-scoped (split on blank lines): a paragraph that ALREADY
|
|
541
|
+
mentions BOTH the old and the new `ALP_<pad>` form (e.g. i2c-
|
|
542
|
+
master's "resolves to `ALP_E1M_I2C0` on the E1M EVK and
|
|
543
|
+
`ALP_E1M_X_I2C0` on the E1M-X EVK" cross-EVK teaching sentence) is
|
|
544
|
+
left alone -- it's correct, portable prose about the alias
|
|
545
|
+
mechanism itself, not a stale claim about which pad THIS scaffold
|
|
546
|
+
uses, and blindly substituting would turn it into a duplicate,
|
|
547
|
+
factually wrong statement ("... on the E1M EVK" would then name
|
|
548
|
+
the E1M-X pad).
|
|
549
|
+
|
|
550
|
+
A `` `ALP_<old_pad>` (index N) `` parenthetical (e.g. gpio-button-
|
|
551
|
+
led's "`ALP_E1M_GPIO_PWM0` (index 26)") names N per `old_pad`'s
|
|
552
|
+
OWN family's `ALP_E1M_GPIO_<class><N>` numbering
|
|
553
|
+
(`include/alp/e1m_pinout.h`'s canonical IO0..25 = 0..25, PWM0..7 =
|
|
554
|
+
26..33 order) -- a DIFFERENT numbering on a cross-family target
|
|
555
|
+
(E1M-X's `include/alp/e1m_x_pinout.h` has 36 IOs, so its PWM0..7
|
|
556
|
+
sits at 36..43, not the source family's index at all). The route
|
|
557
|
+
data available here (`metadata/boards/*.yaml` `e1m_routes:`
|
|
558
|
+
entries: `e1m`/`macro`/`board_alias`/`doc`, no index column) can't
|
|
559
|
+
re-derive `new_pad`'s own index, so rather than carry the stale
|
|
560
|
+
source-family number forward as if it were true of the target,
|
|
561
|
+
drop the parenthetical along with the pad it was describing."""
|
|
562
|
+
if not renames:
|
|
563
|
+
return text
|
|
564
|
+
paragraphs = text.split("\n\n")
|
|
565
|
+
for i, para in enumerate(paragraphs):
|
|
566
|
+
changed = para
|
|
567
|
+
for old, new in renames.items():
|
|
568
|
+
old_tok, new_tok = f"ALP_{old}", f"ALP_{new}"
|
|
569
|
+
if old_tok in para and new_tok in para:
|
|
570
|
+
continue # already-correct dual-EVK teaching prose
|
|
571
|
+
changed = re.sub(
|
|
572
|
+
rf"`{re.escape(old_tok)}`(?:\s*\(index\s+\d+\))?",
|
|
573
|
+
f"`{new_tok}`", changed)
|
|
574
|
+
changed = re.sub(rf"\b{re.escape(old_tok)}\b", new_tok, changed)
|
|
575
|
+
paragraphs[i] = changed
|
|
576
|
+
return "\n\n".join(paragraphs)
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def _scaffold_readme(
|
|
580
|
+
text: str,
|
|
581
|
+
example_path: str,
|
|
582
|
+
docs_ref: str,
|
|
583
|
+
example_sku: str = "",
|
|
584
|
+
sku: str = "",
|
|
585
|
+
source_board: str | None = None,
|
|
586
|
+
target_board: str | None = None,
|
|
587
|
+
pin_renames: dict[str, str] | None = None,
|
|
588
|
+
) -> str:
|
|
589
|
+
"""Every vendored README's `../`-relative links (`../../../docs/
|
|
590
|
+
x.md`, a sibling example's `../i2c-scanner/`, ...) resolve against
|
|
591
|
+
the CANONICAL example's OWN position inside the alp-sdk tree --
|
|
592
|
+
dangling once copied out as a standalone scaffold. Rewrite each to
|
|
593
|
+
an absolute GitHub URL (pinned to `docs_ref` -- see `_docs_ref`)
|
|
594
|
+
instead. Also rewrites the one non-existent-once-copied-out token
|
|
595
|
+
every Build section carries: a `west build ...` invocation naming
|
|
596
|
+
THIS template's own repo-relative example path -- the scaffold IS
|
|
597
|
+
the project root wherever the customer unpacks it, so that argument
|
|
598
|
+
becomes `.`. Best-effort (neither pattern found -> text returned
|
|
599
|
+
unchanged); per-template narrative prose (e.g. `tan build
|
|
600
|
+
alp-sdk/examples/...` invocations, cross-references phrased as
|
|
601
|
+
prose rather than a link) is intentionally not scaffold-normalised
|
|
602
|
+
by this pass.
|
|
603
|
+
|
|
604
|
+
Two more issue #864 Fable-review fixes, both applied unconditionally
|
|
605
|
+
(best-effort, no-op when the pattern is absent):
|
|
606
|
+
|
|
607
|
+
* MAJOR B -- `-DEXTRA_ZEPHYR_MODULES=$(pwd)` only registers the
|
|
608
|
+
alp-sdk checkout as a Zephyr module when `$(pwd)` IS that
|
|
609
|
+
checkout (true in-tree); in a copied-out scaffold `$(pwd)` is the
|
|
610
|
+
SCAFFOLD dir, so the module never registers and the documented
|
|
611
|
+
`west build` fails (`CONFIG_ALP_*` unset, `<alp/*.h>`
|
|
612
|
+
unresolvable). Rewritten to `$ALP_SDK_ROOT`, the same var the
|
|
613
|
+
hardened CMakeLists.txt now requires (`_scaffold_cmakelists`).
|
|
614
|
+
|
|
615
|
+
* MAJOR C -- the canonical example's own SoM label ("# Example for
|
|
616
|
+
E1M-AEN801:") and qualified Zephyr board target
|
|
617
|
+
(`alp_e1m_aen801_m55_hp/ae822fa0e5597ls0/rtss_hp`) otherwise
|
|
618
|
+
survive a cross-family sku swap untouched (a V2N101 scaffold
|
|
619
|
+
shipping `-b alp_e1m_aen801_m55_hp/...`; the real
|
|
620
|
+
`alp_e1m_v2n101_m33_sm/r9a09g056n48gbg/cm33` appears nowhere).
|
|
621
|
+
`source_board`/`target_board` are the qualified board id
|
|
622
|
+
(`_core_board`) for the example's own sku / the requested sku's
|
|
623
|
+
re-derived app core respectively. Every source README carries
|
|
624
|
+
the full `/<soc>/<core>` suffix (issue #720), so the exact
|
|
625
|
+
qualified `source_board` string is matched first, consuming that
|
|
626
|
+
suffix along with the short prefix; a SHORT board-id-prefix
|
|
627
|
+
(before the first `/`) word-boundary match then ALSO runs
|
|
628
|
+
unconditionally, for any remaining bare mention that names only
|
|
629
|
+
the board directory (no soc/core), e.g. a `zephyr/boards/alp/
|
|
630
|
+
<board>/` doc link -- a README carrying both shapes gets both
|
|
631
|
+
rewritten, not just whichever one matches first.
|
|
632
|
+
|
|
633
|
+
* `_m33_sm` (RZ/V2N system-manager) scaffold targets -- that board
|
|
634
|
+
family's DEFAULT flasher is `rzv2n_mtd_flash`
|
|
635
|
+
(zephyr/boards/alp/e1m_v2n101_m33_sm/board.cmake,
|
|
636
|
+
e1m_v2m101_m33_sm/board.cmake), which is SSH-to-the-booted-A55
|
|
637
|
+
and always needs `--host`/`ALP_V2N_SSH_HOST` -- a bare `west
|
|
638
|
+
flash` carried over verbatim from an AEN801 (JLink) source
|
|
639
|
+
README silently can't reach the board. Every `west flash` line
|
|
640
|
+
immediately following one of THIS scaffold's own board-target
|
|
641
|
+
lines is rewritten to `west flash --host <board-ip>`; an
|
|
642
|
+
unrelated `west flash` elsewhere in the prose is left alone.
|
|
643
|
+
|
|
644
|
+
`pin_renames` (issue #876 review MINOR 4) is `_derive_pin_renames`'s
|
|
645
|
+
map -- see `_substitute_readme_pins`.
|
|
646
|
+
"""
|
|
647
|
+
def _fix_link(m: re.Match[str]) -> str:
|
|
648
|
+
target = posixpath.normpath(f"{example_path}/{m.group(1)}")
|
|
649
|
+
kind = "blob" if "." in target.rsplit("/", 1)[-1] else "tree"
|
|
650
|
+
return f"](https://github.com/alplabai/alp-sdk/{kind}/{docs_ref}/{target})"
|
|
651
|
+
|
|
652
|
+
text = _RELATIVE_LINK_RE.sub(_fix_link, text)
|
|
653
|
+
# A bare (non-link) mention of the example's OWN path -- e.g. a
|
|
654
|
+
# `west build -b <board> <example_path>` argument -- becomes `.`
|
|
655
|
+
# (the scaffold IS the project root wherever it lands). alp-sdk#1855:
|
|
656
|
+
# a MULTI-slice template's README also names a bare SUBPATH of its
|
|
657
|
+
# own example dir this way (mproc-mailbox's `west build -b
|
|
658
|
+
# <board> examples/multicore/mproc-mailbox/peer`, for the HE-side
|
|
659
|
+
# peer/ core) -- the plain `example_path` match above never fires
|
|
660
|
+
# for that (its `(?!\S)` boundary fails on the following `/peer`),
|
|
661
|
+
# so the `/peer` suffix survived verbatim, naming a path that
|
|
662
|
+
# exists only inside the alp-sdk tree. Capture + keep any trailing
|
|
663
|
+
# `/<subpath>` so it becomes `./<subpath>` instead of vanishing.
|
|
664
|
+
text = re.sub(
|
|
665
|
+
rf"(?<!\S){re.escape(example_path)}(/\S+)?(?!\S)",
|
|
666
|
+
lambda m: "." + (m.group(1) or ""), text)
|
|
667
|
+
text = text.replace(
|
|
668
|
+
"-DEXTRA_ZEPHYR_MODULES=$(pwd)", "-DEXTRA_ZEPHYR_MODULES=$ALP_SDK_ROOT")
|
|
669
|
+
if source_board and target_board:
|
|
670
|
+
# Every source README carries the full `/<soc>/<core>` suffix
|
|
671
|
+
# (issue #720), so match the exact qualified string first --
|
|
672
|
+
# its `/<soc>/<core>` suffix is consumed along with the short
|
|
673
|
+
# prefix, avoiding the OLD soc/core suffix being left dangling
|
|
674
|
+
# after the NEW (already fully qualified) `target_board`, e.g.
|
|
675
|
+
# `alp_e1m_v2n101_m33_sm/r9a09g056n48gbg/cm33/ae822fa0e5597ls0/rtss_hp`.
|
|
676
|
+
# The short board-id-prefix (before the first `/`) word-
|
|
677
|
+
# boundary match then ALSO runs, unconditionally -- not only
|
|
678
|
+
# as a fallback when the qualified string is absent -- so a
|
|
679
|
+
# README naming the board BOTH ways (a qualified `west build`
|
|
680
|
+
# line and a separate bare `zephyr/boards/alp/<board>/` doc
|
|
681
|
+
# link) gets both rewritten. `(?!/)` keeps it from re-matching
|
|
682
|
+
# the prefix of a string that's ALREADY (still) fully
|
|
683
|
+
# qualified -- either one this same call just substituted in
|
|
684
|
+
# (leaving `target_board` intact) or, in the sku==example_sku
|
|
685
|
+
# passthrough case, `source_board` itself, still present
|
|
686
|
+
# verbatim after the no-op `replace` above -- which would
|
|
687
|
+
# otherwise get its own `/<soc>/<core>` suffix duplicated onto
|
|
688
|
+
# the end a second time.
|
|
689
|
+
if source_board in text:
|
|
690
|
+
text = text.replace(source_board, target_board)
|
|
691
|
+
source_marker = source_board.split("/", 1)[0]
|
|
692
|
+
text = re.sub(rf"\b{re.escape(source_marker)}\b(?!/)", target_board, text)
|
|
693
|
+
# The `_m33_sm` (RZ/V2N system-manager) board family's DEFAULT
|
|
694
|
+
# flasher is `rzv2n_mtd_flash` (zephyr/boards/alp/
|
|
695
|
+
# e1m_v2n101_m33_sm/board.cmake, e1m_v2m101_m33_sm/board.cmake),
|
|
696
|
+
# which is SSH-to-the-booted-A55 and always needs `--host`/
|
|
697
|
+
# `ALP_V2N_SSH_HOST` -- a bare `west flash` carried over
|
|
698
|
+
# verbatim from an AEN801 (JLink) source README silently can't
|
|
699
|
+
# reach the board. Every `west flash` line immediately
|
|
700
|
+
# following one of THIS scaffold's own board-target lines is
|
|
701
|
+
# rewritten (a multi-core README can carry more than one), so
|
|
702
|
+
# a two-core scaffold doesn't leave its second flash line
|
|
703
|
+
# bare; an unrelated `west flash` elsewhere in the prose is
|
|
704
|
+
# left alone.
|
|
705
|
+
if target_board.split("/", 1)[0].endswith("_m33_sm"):
|
|
706
|
+
marker = re.escape(target_board)
|
|
707
|
+
text = re.sub(
|
|
708
|
+
rf"({marker}[^\n]*\n)west flash\b",
|
|
709
|
+
r"\1west flash --host <board-ip>",
|
|
710
|
+
text)
|
|
711
|
+
if example_sku and sku and example_sku != sku:
|
|
712
|
+
text = text.replace(example_sku, sku)
|
|
713
|
+
text = _substitute_readme_pins(text, pin_renames or {})
|
|
714
|
+
return text
|