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.
Files changed (330) hide show
  1. tan/__init__.py +0 -0
  2. tan/__main__.py +120 -0
  3. tan/cli.py +939 -0
  4. tan/command_table.py +265 -0
  5. tan/commands/__init__.py +8 -0
  6. tan/commands/bootstrap_cmd.py +4856 -0
  7. tan/commands/bootstrap_patches.py +164 -0
  8. tan/commands/bootstrap_toolchain_fetch.py +155 -0
  9. tan/commands/bootstrap_toolchain_pin.py +36 -0
  10. tan/commands/build/__init__.py +1 -0
  11. tan/commands/build/configure_inputs.py +277 -0
  12. tan/commands/build/execute.py +1852 -0
  13. tan/commands/build/host_python.py +59 -0
  14. tan/commands/build/link_stale.py +78 -0
  15. tan/commands/build/manifest.py +428 -0
  16. tan/commands/build/materialise.py +81 -0
  17. tan/commands/build/token_substitution.py +381 -0
  18. tan/commands/build/toolchain.py +482 -0
  19. tan/commands/build/user_defines_wipe.py +115 -0
  20. tan/commands/build/workspace_patches.py +65 -0
  21. tan/commands/build_cmd.py +2236 -0
  22. tan/commands/build_output.py +331 -0
  23. tan/commands/clean_cmd.py +1183 -0
  24. tan/commands/completion_cmd.py +786 -0
  25. tan/commands/debug_config_cmd.py +2425 -0
  26. tan/commands/diff_cmd.py +940 -0
  27. tan/commands/doctor_cmd.py +4767 -0
  28. tan/commands/examples_cmd.py +644 -0
  29. tan/commands/explain_cmd.py +1319 -0
  30. tan/commands/faultdecode_cmd.py +931 -0
  31. tan/commands/flash_atoc_guard.py +240 -0
  32. tan/commands/flash_cmd.py +5731 -0
  33. tan/commands/flash_mram_guard.py +150 -0
  34. tan/commands/flash_ram.py +578 -0
  35. tan/commands/flash_raw.py +583 -0
  36. tan/commands/generate_cmd.py +1456 -0
  37. tan/commands/image_cmd.py +753 -0
  38. tan/commands/init_cmd.py +1740 -0
  39. tan/commands/inspect_cmd.py +377 -0
  40. tan/commands/kconfig_cmd.py +734 -0
  41. tan/commands/model_cmd.py +1514 -0
  42. tan/commands/model_device_cmd.py +351 -0
  43. tan/commands/model_device_live.py +234 -0
  44. tan/commands/model_host_cmd.py +430 -0
  45. tan/commands/model_zoo_cmd.py +363 -0
  46. tan/commands/monitor_actions.py +125 -0
  47. tan/commands/monitor_cmd.py +626 -0
  48. tan/commands/monitor_session.py +505 -0
  49. tan/commands/new_som_cmd.py +1522 -0
  50. tan/commands/pinmux_cmd.py +568 -0
  51. tan/commands/presets_cmd.py +1123 -0
  52. tan/commands/probe_cmd.py +421 -0
  53. tan/commands/reset_cmd.py +354 -0
  54. tan/commands/reset_confirm.py +168 -0
  55. tan/commands/run_cmd.py +637 -0
  56. tan/commands/scaffold_cmd.py +490 -0
  57. tan/commands/sdk_cmd.py +1723 -0
  58. tan/commands/size_cmd.py +1020 -0
  59. tan/commands/support_bundle_cmd.py +1073 -0
  60. tan/commands/trace_cmd.py +341 -0
  61. tan/commands/validate_cmd.py +2009 -0
  62. tan/commands/west_forward_cmd.py +736 -0
  63. tan/commands/workspace_patch_check.py +296 -0
  64. tan/core/__init__.py +1 -0
  65. tan/core/alp_conf_pregen.py +100 -0
  66. tan/core/artifact_provenance.py +240 -0
  67. tan/core/atoc_guard.py +276 -0
  68. tan/core/atoc_guard_messages.py +232 -0
  69. tan/core/atoc_replacement.py +118 -0
  70. tan/core/atomic_write.py +178 -0
  71. tan/core/board_context.py +351 -0
  72. tan/core/board_diagnostic.py +111 -0
  73. tan/core/board_files.py +221 -0
  74. tan/core/board_validator.py +542 -0
  75. tan/core/board_validator_compat.py +196 -0
  76. tan/core/board_validator_run.py +129 -0
  77. tan/core/board_validator_schema.py +186 -0
  78. tan/core/board_validator_skew.py +46 -0
  79. tan/core/board_yaml_edit.py +295 -0
  80. tan/core/board_yaml_pos.py +118 -0
  81. tan/core/bootstrap.py +2572 -0
  82. tan/core/build_plan.py +625 -0
  83. tan/core/consent.py +104 -0
  84. tan/core/console_filter.py +245 -0
  85. tan/core/debug_launch.py +1803 -0
  86. tan/core/devicetree_lint.py +168 -0
  87. tan/core/dir_removal.py +229 -0
  88. tan/core/doctor_git.py +172 -0
  89. tan/core/doctor_libraries.py +253 -0
  90. tan/core/doctor_render.py +185 -0
  91. tan/core/doctor_scope.py +66 -0
  92. tan/core/doctor_setools.py +237 -0
  93. tan/core/doctor_stale.py +180 -0
  94. tan/core/document_guards.py +723 -0
  95. tan/core/dp_id.py +97 -0
  96. tan/core/elf_load.py +63 -0
  97. tan/core/error_catalog.py +171 -0
  98. tan/core/example_catalog.py +499 -0
  99. tan/core/example_facets.py +151 -0
  100. tan/core/faultdecode.py +521 -0
  101. tan/core/flash_plan.py +2501 -0
  102. tan/core/flow_d_report.py +381 -0
  103. tan/core/flow_d_uptime.py +164 -0
  104. tan/core/fs_confine.py +130 -0
  105. tan/core/global_flags.py +230 -0
  106. tan/core/host_python.py +210 -0
  107. tan/core/image_bundle.py +247 -0
  108. tan/core/inert.py +147 -0
  109. tan/core/jlink_binary.py +128 -0
  110. tan/core/jlink_probe.py +481 -0
  111. tan/core/jsonc_splice.py +404 -0
  112. tan/core/launch_provenance.py +257 -0
  113. tan/core/link_refusal.py +76 -0
  114. tan/core/metadata_schema.py +376 -0
  115. tan/core/model_check.py +227 -0
  116. tan/core/model_device.py +449 -0
  117. tan/core/model_doctor.py +198 -0
  118. tan/core/model_host.py +48 -0
  119. tan/core/model_list.py +112 -0
  120. tan/core/model_zoo.py +353 -0
  121. tan/core/module_template.py +340 -0
  122. tan/core/mram_link.py +87 -0
  123. tan/core/new_som_ospi.py +94 -0
  124. tan/core/os_class.py +159 -0
  125. tan/core/patch_paths.py +87 -0
  126. tan/core/pending.py +40 -0
  127. tan/core/plain_zephyr_plan.py +138 -0
  128. tan/core/plan_exec.py +373 -0
  129. tan/core/plan_tokens.py +600 -0
  130. tan/core/probe.py +155 -0
  131. tan/core/probe_plan.py +237 -0
  132. tan/core/proxy.py +168 -0
  133. tan/core/publish.py +31 -0
  134. tan/core/python_floor.py +178 -0
  135. tan/core/ram_run.py +694 -0
  136. tan/core/ram_watch.py +231 -0
  137. tan/core/raw_write.py +160 -0
  138. tan/core/reset_plan.py +56 -0
  139. tan/core/run.py +161 -0
  140. tan/core/scaffold.py +2203 -0
  141. tan/core/scaffold_selftest_identity.py +225 -0
  142. tan/core/sdk_default_registry.py +565 -0
  143. tan/core/sdk_discovery.py +996 -0
  144. tan/core/sdk_removal.py +465 -0
  145. tan/core/serial_actions.py +175 -0
  146. tan/core/serial_capture.py +185 -0
  147. tan/core/serial_url.py +45 -0
  148. tan/core/setools.py +608 -0
  149. tan/core/setools_scratch.py +243 -0
  150. tan/core/shapes.py +194 -0
  151. tan/core/size.py +610 -0
  152. tan/core/som_buildability.py +204 -0
  153. tan/core/som_schema_version.py +62 -0
  154. tan/core/subprocess_env.py +201 -0
  155. tan/core/sysbuild_image.py +94 -0
  156. tan/core/system_manifest.py +827 -0
  157. tan/core/text_layout.py +73 -0
  158. tan/core/timestamp.py +182 -0
  159. tan/core/tool_lookup.py +383 -0
  160. tan/core/toolchain_pin.py +117 -0
  161. tan/core/toolchain_provision.py +1224 -0
  162. tan/core/trusted_path.py +134 -0
  163. tan/core/uboot_breakin.py +154 -0
  164. tan/core/uri_reference.py +415 -0
  165. tan/core/user_defines.py +152 -0
  166. tan/core/venv.py +332 -0
  167. tan/core/west_patches.py +143 -0
  168. tan/core/west_workspace_refusal.py +159 -0
  169. tan/core/zephyr_env.py +124 -0
  170. tan/env.py +185 -0
  171. tan/envelope.py +647 -0
  172. tan/exit_codes.py +12 -0
  173. tan/lazy_typer.py +185 -0
  174. tan/model/__init__.py +2 -0
  175. tan/model/_gen_fixture.py +132 -0
  176. tan/model/adapters/__init__.py +116 -0
  177. tan/model/adapters/cpu.py +26 -0
  178. tan/model/adapters/deepx.py +94 -0
  179. tan/model/adapters/drpai.py +242 -0
  180. tan/model/adapters/ethos_u.py +951 -0
  181. tan/model/adapters/executorch.py +55 -0
  182. tan/model/analyze.py +694 -0
  183. tan/model/build.py +304 -0
  184. tan/model/check.py +682 -0
  185. tan/model/manifest.py +625 -0
  186. tan/model/measure.py +216 -0
  187. tan/model/npu_ops_validate.py +130 -0
  188. tan/model/package.py +125 -0
  189. tan/model/perf.py +635 -0
  190. tan/model/perf_apply.py +721 -0
  191. tan/model/prep.py +193 -0
  192. tan/model/sram_fit.py +502 -0
  193. tan/model/targets.py +374 -0
  194. tan/model/tensorio.py +287 -0
  195. tan/net.py +73 -0
  196. tan/output_format.py +213 -0
  197. tan/planner/__init__.py +219 -0
  198. tan/planner/aperture.py +202 -0
  199. tan/planner/buildplan.py +1092 -0
  200. tan/planner/camera_owner.py +192 -0
  201. tan/planner/cameras.py +132 -0
  202. tan/planner/carveout.py +755 -0
  203. tan/planner/cli.py +274 -0
  204. tan/planner/headers.py +315 -0
  205. tan/planner/kconfig.py +2435 -0
  206. tan/planner/kconfig_symbols.py +473 -0
  207. tan/planner/libraries.py +677 -0
  208. tan/planner/link_target.py +237 -0
  209. tan/planner/linux_ownership.py +216 -0
  210. tan/planner/loader.py +1525 -0
  211. tan/planner/manifest.py +116 -0
  212. tan/planner/memory.py +161 -0
  213. tan/planner/memregion.py +56 -0
  214. tan/planner/models.py +533 -0
  215. tan/planner/orchestrator.py +874 -0
  216. tan/planner/ownership.py +203 -0
  217. tan/planner/partition.py +1160 -0
  218. tan/planner/paths.py +32 -0
  219. tan/planner/plain_slice.py +139 -0
  220. tan/planner/project_emit/__init__.py +60 -0
  221. tan/planner/project_emit/bom_netlist.py +375 -0
  222. tan/planner/project_emit/dts.py +580 -0
  223. tan/planner/project_emit/hw_info.py +221 -0
  224. tan/planner/project_emit/native_sim.py +136 -0
  225. tan/planner/project_emit/west_libs.py +213 -0
  226. tan/planner/project_loader.py +264 -0
  227. tan/planner/sdk_capability.py +203 -0
  228. tan/planner/sdk_compat.py +395 -0
  229. tan/planner/secure.py +350 -0
  230. tan/planner/slugs.py +394 -0
  231. tan/planner/som_metadata.py +325 -0
  232. tan/planner/strict_loaders.py +131 -0
  233. tan/planner/template.py +1037 -0
  234. tan/planner/template_pins.py +535 -0
  235. tan/planner/template_rewrite.py +714 -0
  236. tan/planner/topology.py +208 -0
  237. tan/planner/validate.py +365 -0
  238. tan/planner/whole_device_alias.py +67 -0
  239. tan/planner/zephyr_board.py +3510 -0
  240. tan/planner_cli.py +97 -0
  241. tan/planner_emit.py +617 -0
  242. tan/planner_root.py +119 -0
  243. tan/soc_ref.py +58 -0
  244. tan/templates/__init__.py +34 -0
  245. tan/templates/vendored/MANIFEST.md +1862 -0
  246. tan/templates/vendored/diagnostics/E1M-AEN801/CMakeLists.txt +8 -0
  247. tan/templates/vendored/diagnostics/E1M-AEN801/README.md +112 -0
  248. tan/templates/vendored/diagnostics/E1M-AEN801/board.yaml +38 -0
  249. tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.conf +11 -0
  250. tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.overlay +29 -0
  251. tan/templates/vendored/diagnostics/E1M-AEN801/prj.conf +5 -0
  252. tan/templates/vendored/diagnostics/E1M-AEN801/src/main.c +266 -0
  253. tan/templates/vendored/diagnostics/E1M-AEN801/testcase.yaml +53 -0
  254. tan/templates/vendored/diagnostics/E1M-V2N101/CMakeLists.txt +8 -0
  255. tan/templates/vendored/diagnostics/E1M-V2N101/README.md +112 -0
  256. tan/templates/vendored/diagnostics/E1M-V2N101/board.yaml +38 -0
  257. tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.conf +11 -0
  258. tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.overlay +29 -0
  259. tan/templates/vendored/diagnostics/E1M-V2N101/prj.conf +5 -0
  260. tan/templates/vendored/diagnostics/E1M-V2N101/src/main.c +266 -0
  261. tan/templates/vendored/diagnostics/E1M-V2N101/testcase.yaml +53 -0
  262. tan/templates/vendored/edge-ai/E1M-AEN801/CMakeLists.txt +10 -0
  263. tan/templates/vendored/edge-ai/E1M-AEN801/README.md +75 -0
  264. tan/templates/vendored/edge-ai/E1M-AEN801/board.yaml +38 -0
  265. tan/templates/vendored/edge-ai/E1M-AEN801/prj.conf +8 -0
  266. tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.c +371 -0
  267. tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.h +178 -0
  268. tan/templates/vendored/edge-ai/E1M-AEN801/src/main.c +373 -0
  269. tan/templates/vendored/edge-ai/E1M-AEN801/testcase.yaml +62 -0
  270. tan/templates/vendored/edge-ai/E1M-V2N101/CMakeLists.txt +10 -0
  271. tan/templates/vendored/edge-ai/E1M-V2N101/README.md +75 -0
  272. tan/templates/vendored/edge-ai/E1M-V2N101/board.yaml +37 -0
  273. tan/templates/vendored/edge-ai/E1M-V2N101/prj.conf +8 -0
  274. tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.c +371 -0
  275. tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.h +178 -0
  276. tan/templates/vendored/edge-ai/E1M-V2N101/src/main.c +373 -0
  277. tan/templates/vendored/edge-ai/E1M-V2N101/testcase.yaml +62 -0
  278. tan/templates/vendored/iot/E1M-AEN801/CMakeLists.txt +14 -0
  279. tan/templates/vendored/iot/E1M-AEN801/README.md +103 -0
  280. tan/templates/vendored/iot/E1M-AEN801/board.yaml +69 -0
  281. tan/templates/vendored/iot/E1M-AEN801/prj.conf +15 -0
  282. tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.c +196 -0
  283. tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.h +173 -0
  284. tan/templates/vendored/iot/E1M-AEN801/src/main.c +262 -0
  285. tan/templates/vendored/iot/E1M-AEN801/testcase.yaml +68 -0
  286. tan/templates/vendored/minimal/E1M-AEN801/CMakeLists.txt +8 -0
  287. tan/templates/vendored/minimal/E1M-AEN801/README.md +108 -0
  288. tan/templates/vendored/minimal/E1M-AEN801/board.yaml +32 -0
  289. tan/templates/vendored/minimal/E1M-AEN801/prj.conf +8 -0
  290. tan/templates/vendored/minimal/E1M-AEN801/src/main.c +111 -0
  291. tan/templates/vendored/minimal/E1M-AEN801/testcase.yaml +49 -0
  292. tan/templates/vendored/minimal/E1M-V2N101/CMakeLists.txt +8 -0
  293. tan/templates/vendored/minimal/E1M-V2N101/README.md +108 -0
  294. tan/templates/vendored/minimal/E1M-V2N101/board.yaml +32 -0
  295. tan/templates/vendored/minimal/E1M-V2N101/prj.conf +8 -0
  296. tan/templates/vendored/minimal/E1M-V2N101/src/main.c +111 -0
  297. tan/templates/vendored/minimal/E1M-V2N101/testcase.yaml +49 -0
  298. tan/templates/vendored/multicore-mailbox/E1M-AEN801/CMakeLists.txt +8 -0
  299. tan/templates/vendored/multicore-mailbox/E1M-AEN801/README.md +120 -0
  300. tan/templates/vendored/multicore-mailbox/E1M-AEN801/board.yaml +57 -0
  301. tan/templates/vendored/multicore-mailbox/E1M-AEN801/boards/native_sim_native_64.overlay +40 -0
  302. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/CMakeLists.txt +13 -0
  303. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/main.c +171 -0
  304. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/prj.conf +17 -0
  305. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/testcase.yaml +23 -0
  306. tan/templates/vendored/multicore-mailbox/E1M-AEN801/prj.conf +22 -0
  307. tan/templates/vendored/multicore-mailbox/E1M-AEN801/src/main.c +185 -0
  308. tan/templates/vendored/multicore-mailbox/E1M-AEN801/testcase.yaml +26 -0
  309. tan/templates/vendored/sensor/E1M-AEN801/CMakeLists.txt +8 -0
  310. tan/templates/vendored/sensor/E1M-AEN801/README.md +112 -0
  311. tan/templates/vendored/sensor/E1M-AEN801/board.yaml +57 -0
  312. tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.conf +10 -0
  313. tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.overlay +28 -0
  314. tan/templates/vendored/sensor/E1M-AEN801/prj.conf +5 -0
  315. tan/templates/vendored/sensor/E1M-AEN801/src/main.c +186 -0
  316. tan/templates/vendored/sensor/E1M-AEN801/testcase.yaml +52 -0
  317. tan/templates/vendored/sensor/E1M-V2N101/CMakeLists.txt +8 -0
  318. tan/templates/vendored/sensor/E1M-V2N101/README.md +112 -0
  319. tan/templates/vendored/sensor/E1M-V2N101/board.yaml +57 -0
  320. tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.conf +10 -0
  321. tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.overlay +28 -0
  322. tan/templates/vendored/sensor/E1M-V2N101/prj.conf +5 -0
  323. tan/templates/vendored/sensor/E1M-V2N101/src/main.c +186 -0
  324. tan/templates/vendored/sensor/E1M-V2N101/testcase.yaml +52 -0
  325. tan/version.py +102 -0
  326. tan_cli-0.7.0.dist-info/METADATA +125 -0
  327. tan_cli-0.7.0.dist-info/RECORD +330 -0
  328. tan_cli-0.7.0.dist-info/WHEEL +5 -0
  329. tan_cli-0.7.0.dist-info/entry_points.txt +2 -0
  330. tan_cli-0.7.0.dist-info/top_level.txt +1 -0
tan/planner/cli.py ADDED
@@ -0,0 +1,274 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """The planner CLI: argparse + main(), over one mode->text dispatch.
3
+
4
+ Kept in-package (imported by __init__ for the public `main` re-export, and by
5
+ __main__ as the `python -m tan.planner` entry) so nothing has to import
6
+ __main__ -- importing __main__ re-enters the package under runpy and warns.
7
+
8
+ RELOCATED (was alp-sdk `scripts/alp_orchestrate/cli.py`). The mode dispatch was
9
+ lifted out of `main()` into `emit_artefact()` so the in-process caller
10
+ (`tan.planner_root.emit`, which is how `tan build` gets its plan) and this argv
11
+ path render through the SAME branch table. Two copies of an eight-way `--emit`
12
+ switch is how one surface silently starts emitting differently from the other.
13
+
14
+ `--emit` also accepts `alp_project.py`'s OWN modes (`zephyr-conf`,
15
+ `carrier-netlist`, ...), rendered through `tan.planner_emit` -- the same
16
+ in-process engine `tan generate` uses, so a mode still has exactly one renderer
17
+ whichever front door reached it. They print RAW ARTEFACT BYTES, undecorated,
18
+ because alp-sdk's `scripts/check_emit_snapshots.py` diffs this stdout against a
19
+ committed golden; a trailing newline of our own would fail it.
20
+
21
+ `--emit scaffold` is the third family, and the one mode that takes NO board.yaml
22
+ -- it materialises a NEW project, so it is dispatched before `--input` is read,
23
+ the same order `alp_project.main()` dispatches it in. It renders through
24
+ `tan.planner.template` (relocated from `scripts/alp_template.py`) against the
25
+ LIVE catalog of the bound SDK checkout. `tan init` does NOT come this way and
26
+ must not: it serves a customer with no SDK at all, from tan's own vendored
27
+ capture (`tan.core.scaffold`, invariant I-32).
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import sys
33
+ from pathlib import Path
34
+ from typing import Iterable, Optional
35
+
36
+ from tan import planner_emit
37
+
38
+ from . import (
39
+ OrchestratorError,
40
+ emit_build_plan,
41
+ emit_kconfig,
42
+ emit_storage_mounts_c,
43
+ emit_system_manifest,
44
+ load_board_yaml,
45
+ )
46
+ from .buildplan import _shared_artefact, _shared_tfm_conf
47
+ from .paths import REPO
48
+ from .template import (
49
+ TemplateError,
50
+ emit_scaffold,
51
+ find_template_by_cores,
52
+ load_catalog,
53
+ )
54
+
55
+ if False: # typing-only; keeps the runtime import graph unchanged
56
+ from .models import BoardProject
57
+
58
+ #: The eight modes alp-sdk's `alp_orchestrate` CLI exposed, every one of them
59
+ #: served by `emit_artefact`.
60
+ ORCHESTRATOR_MODES = (
61
+ "system-manifest", "ipc-contract-h", "dts-reservations", "dts-partitions",
62
+ "storage-mounts-c", "tfm-sysbuild-conf", "build-plan", "kconfig",
63
+ )
64
+
65
+ #: The rest of `--emit`: `alp_project.py`'s modes, served by `tan.planner_emit`.
66
+ #: DERIVED from `IN_PROCESS_MODES` rather than listed again -- a second list is
67
+ #: how `tan generate` and this entry would drift apart on which modes exist.
68
+ #: `ipc-contract-h` is in both sets and stays with `emit_artefact`; sorted, so
69
+ #: `--help` does not reorder itself between interpreters.
70
+ PROJECT_MODES = tuple(
71
+ sorted(planner_emit.IN_PROCESS_MODES.difference(ORCHESTRATOR_MODES)))
72
+
73
+ #: The modes served by `tan.planner.template`, which take `--template`/`--sku`
74
+ #: instead of a board.yaml. Deliberately NOT folded into `IN_PROCESS_MODES`:
75
+ #: that set is `tan generate`'s target list, and `tan generate --output` writes
76
+ #: ONE artefact, where a scaffold is a whole project's worth of files. The
77
+ #: customer surface is frozen (`tan init` scaffolds); this entry is the gate's.
78
+ TEMPLATE_MODES = ("scaffold",)
79
+
80
+
81
+ def emit_artefact(project: "BoardProject", mode: str, *, board_yaml: Path,
82
+ build_root: Path = Path("build"),
83
+ core: Optional[str] = None) -> str:
84
+ """Render one `--emit` mode for an already-loaded project.
85
+
86
+ `core` is honoured by `kconfig` alone; every other mode ignores it -- the
87
+ same asymmetry the flag's own help text records.
88
+ """
89
+ if mode == "system-manifest":
90
+ return emit_system_manifest(project)
91
+ if mode == "ipc-contract-h":
92
+ return _shared_artefact(project, "generated/alp/system_ipc.h")
93
+ if mode == "dts-reservations":
94
+ return _shared_artefact(project, "generated/dts-reservations.dtsi")
95
+ if mode == "dts-partitions":
96
+ return _shared_artefact(project, "generated/dts-partitions.dtsi")
97
+ if mode == "storage-mounts-c":
98
+ # The plan carries the table only when a partition is mountable
99
+ # (alp-sdk#2820); "" here means "none", and the standalone emit has
100
+ # always printed the empty table in that case, so keep it byte-identical.
101
+ return (_shared_artefact(project, "generated/storage_mount_table.c")
102
+ or emit_storage_mounts_c(project))
103
+ if mode == "tfm-sysbuild-conf":
104
+ return _shared_tfm_conf(project)
105
+ if mode == "build-plan":
106
+ return emit_build_plan(project, board_yaml=board_yaml,
107
+ build_root=build_root)
108
+ if mode == "kconfig":
109
+ return emit_kconfig(project, core)
110
+ raise OrchestratorError(f"unknown emit mode '{mode}'")
111
+
112
+
113
+ def _parse_cores_arg(raw: str) -> dict[str, str]:
114
+ """`--cores` (alp-sdk#1652): `core_id:os[,core_id:os...]` -- the
115
+ topology an IDE wizard already knows (which cores it's targeting and
116
+ which OS each runs), NOT a directory. RELOCATED verbatim from
117
+ `alp_project._parse_cores_arg`. `--cores` is a SELECTOR over the
118
+ catalog's existing templates (see `find_template_by_cores`'s
119
+ docstring for why this stops short of an arbitrary core -> app-dir
120
+ renderer): the directory each core lands in is whatever the matched
121
+ template already uses, same as picking that template by `--template`
122
+ would give.
123
+ """
124
+ cores: dict[str, str] = {}
125
+ for entry in raw.split(","):
126
+ entry = entry.strip()
127
+ if not entry:
128
+ continue
129
+ if ":" not in entry:
130
+ raise ValueError(
131
+ f"--cores entry {entry!r} must be core_id:os (e.g. "
132
+ f"m55_hp:zephyr)")
133
+ core_id, _, os_name = entry.partition(":")
134
+ core_id, os_name = core_id.strip(), os_name.strip()
135
+ if not core_id or not os_name:
136
+ raise ValueError(
137
+ f"--cores entry {entry!r} must be core_id:os (e.g. "
138
+ f"m55_hp:zephyr)")
139
+ if core_id in cores:
140
+ raise ValueError(f"--cores names {core_id!r} more than once")
141
+ cores[core_id] = os_name
142
+ if not cores:
143
+ raise ValueError("--cores must name at least one core_id:os pair")
144
+ return cores
145
+
146
+
147
+ def _emit_scaffold(
148
+ template: Optional[str], sku: Optional[str], cores: Optional[str] = None,
149
+ ) -> int:
150
+ """`--emit scaffold --template <id> --sku <SKU>`, or `--emit scaffold
151
+ --cores <core_id:os,...> --sku <SKU>` (alp-sdk#1652), straight to
152
+ stdout.
153
+
154
+ `--template`/`--cores` are mutually exclusive alternative ways to pick
155
+ the template -- `--cores` resolves to a `template_id` via
156
+ `find_template_by_cores`, then falls into the exact same
157
+ `emit_scaffold()` call `--template` would reach, exactly as
158
+ `alp_project._run_scaffold_emit` refuses/resolves them. `--sku` is
159
+ still required either way and refused ONE AT A TIME, in that order,
160
+ so a caller that omitted both still learns about `--template`/
161
+ `--cores` first. `argparse` cannot express "required only for this
162
+ `--emit`", which is why this is a hand-check.
163
+ """
164
+ if template and cores:
165
+ print("alp-orchestrate: --emit scaffold takes --template OR "
166
+ "--cores, not both", file=sys.stderr)
167
+ return 1
168
+ if not template and not cores:
169
+ print("alp-orchestrate: --emit scaffold requires --template <id> "
170
+ "or --cores <core_id:os,...>", file=sys.stderr)
171
+ return 1
172
+ if not sku:
173
+ print("alp-orchestrate: --emit scaffold requires --sku <SKU>",
174
+ file=sys.stderr)
175
+ return 1
176
+ if cores:
177
+ try:
178
+ cores_topology = _parse_cores_arg(cores)
179
+ except ValueError as e:
180
+ print(f"alp-orchestrate: {e}", file=sys.stderr)
181
+ return 1
182
+ try:
183
+ record = find_template_by_cores(load_catalog(), cores_topology)
184
+ except TemplateError as e:
185
+ print(f"alp-orchestrate: {e}", file=sys.stderr)
186
+ return 1
187
+ template = record["id"]
188
+ try:
189
+ sys.stdout.write(emit_scaffold(template, sku))
190
+ except TemplateError as e:
191
+ print(f"alp-orchestrate: {e}", file=sys.stderr)
192
+ return 1
193
+ return 0
194
+
195
+
196
+ def main(argv: Optional[Iterable[str]] = None) -> int:
197
+ import argparse
198
+ parser = argparse.ArgumentParser(
199
+ description="Planner/emit CLI for board.yaml (the executor was "
200
+ "retired -- ADR-0020 Phase 4).")
201
+ parser.add_argument("--input", type=Path, default=Path("board.yaml"),
202
+ help="Path to the project's board.yaml.")
203
+ parser.add_argument("--build-root", type=Path,
204
+ default=Path("build"),
205
+ help="Build root directory (used by --emit build-plan).")
206
+ parser.add_argument("--core", default=None,
207
+ help="Core id to scope a per-core emit mode to "
208
+ "(required by --emit kconfig; every other "
209
+ "mode ignores it).")
210
+ parser.add_argument("--template", default=None,
211
+ help="metadata/templates/catalog-v1.json template id; "
212
+ "required for --emit scaffold (unless --cores is "
213
+ "given instead).")
214
+ parser.add_argument("--cores", default=None,
215
+ help="core_id:os[,core_id:os...] topology (e.g. "
216
+ "'m55_hp:zephyr,m55_he:zephyr'); an alternative "
217
+ "to --template for --emit scaffold -- selects "
218
+ "whichever catalog template's own cores: "
219
+ "topology matches exactly (alp-sdk#1652). "
220
+ "Mutually exclusive with --template.")
221
+ parser.add_argument("--sku", default=None,
222
+ help="Target SoM SKU (e.g. the one a scaffold is "
223
+ "retargeted onto); required for --emit scaffold, "
224
+ "and must be one of the template's declared "
225
+ "supported.som_skus.")
226
+ parser.add_argument("--emit", default=None,
227
+ choices=[*ORCHESTRATOR_MODES, *PROJECT_MODES,
228
+ *TEMPLATE_MODES],
229
+ help="Skip the build; just emit one of the "
230
+ "generated artefacts to stdout.")
231
+ args = parser.parse_args(list(argv) if argv is not None else None)
232
+
233
+ if args.emit in TEMPLATE_MODES:
234
+ # Before `--input` is touched: a scaffold IS the new project, so there
235
+ # is no board.yaml to load yet.
236
+ return _emit_scaffold(args.template, args.sku, args.cores)
237
+
238
+ if args.emit in PROJECT_MODES:
239
+ # Straight to the relocated renderers, deliberately WITHOUT the
240
+ # `load_board_yaml` below: `carrier-netlist` / `composed-route-table`
241
+ # read the raw board.yaml dict (mirroring `alp_project.main()`, which
242
+ # dispatches them before its per-core machinery), so loading here would
243
+ # refuse a board the SDK's own front door serves.
244
+ try:
245
+ sys.stdout.write(planner_emit.render(
246
+ args.emit, sdk_root=REPO, board_yaml=args.input,
247
+ core=args.core))
248
+ except (OrchestratorError, planner_emit.PlannerEmitError) as e:
249
+ print(f"alp-orchestrate: {e}", file=sys.stderr)
250
+ return 1
251
+ return 0
252
+
253
+ try:
254
+ project = load_board_yaml(args.input)
255
+ except OrchestratorError as e:
256
+ print(f"alp-orchestrate: {e}", file=sys.stderr)
257
+ return 1
258
+
259
+ if args.emit:
260
+ try:
261
+ sys.stdout.write(emit_artefact(
262
+ project, args.emit, board_yaml=args.input,
263
+ build_root=args.build_root, core=args.core))
264
+ except OrchestratorError as e:
265
+ print(f"alp-orchestrate: {e}", file=sys.stderr)
266
+ return 1
267
+ return 0
268
+
269
+ # ADR-0020 Phase 4 (preview): the SDK-side executor was retired --
270
+ # this module only plans/emits now. Building is an external
271
+ # consumer's job (against `--emit build-plan`'s JSON contract).
272
+ print("alp-orchestrate: no executor -- pass --emit to print a "
273
+ "generated artefact (e.g. --emit build-plan)", file=sys.stderr)
274
+ return 2
tan/planner/headers.py ADDED
@@ -0,0 +1,315 @@
1
+ #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """Header / DTS / mount-table emitters from the resolved model.
4
+
5
+ Pure renderers off the parsed BoardProject + the resolved carve-outs / storage
6
+ partitions: the IPC contract header (ipc-contract-h), the DTS reserved-memory
7
+ overlay (dts-reservations), the DTS fixed-partitions overlay (dts-partitions),
8
+ and the static C mount table (storage-mounts-c). Each is a leaf emit -- it calls
9
+ only the carve-out / partition resolvers, never the loader/validator. Extracted
10
+ as the #285 headers emit seam.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from .carveout import resolve_carve_outs
16
+ from .models import BoardProject, ResolvedPartition
17
+ from .partition import resolve_storage_partitions
18
+
19
+
20
+ def emit_ipc_contract_h(project: BoardProject) -> str:
21
+ """Generate alp_system_ipc.h per spec §6.3."""
22
+ carve_outs = resolve_carve_outs(project)
23
+
24
+ lines: list[str] = [
25
+ "/*",
26
+ " * Auto-generated by scripts/alp_orchestrate.py -- do not edit.",
27
+ " * Regenerate after changes to board.yaml `ipc:` or the SoM's",
28
+ " * memory_map / mailbox blocks.",
29
+ " */",
30
+ "",
31
+ "#ifndef ALP_SYSTEM_IPC_H",
32
+ "#define ALP_SYSTEM_IPC_H",
33
+ "",
34
+ f'#define ALP_IPC_SKU "{project.sku}"',
35
+ "",
36
+ ]
37
+ if not carve_outs:
38
+ lines += [
39
+ "/* No ipc[] entries declared in board.yaml; nothing to emit. */",
40
+ "",
41
+ "#endif /* ALP_SYSTEM_IPC_H */",
42
+ "",
43
+ ]
44
+ return "\n".join(lines)
45
+
46
+ for c in carve_outs:
47
+ upper = c.name.upper()
48
+ lines.append(f"/* {c.kind} channel '{c.name}' -- endpoints "
49
+ f"{', '.join(c.endpoints)} */")
50
+ if c.status == "blocked":
51
+ # Blocked entries still need the SRC_EPT / DST_EPT / MBOX_CH /
52
+ # ADDR / SIZE macros so consumer source compiles: the
53
+ # canonical pattern (see examples/rpmsg-*/m*/src/main.c) is a
54
+ # static struct initialiser that references every field at
55
+ # compile time. Omitting the macros made every rpmsg
56
+ # consumer fail with `<macro> undeclared` long before the
57
+ # NOSUPPORT runtime check could even fire. Emit safe-zero
58
+ # stubs + a #warning so the build is green and the gap is
59
+ # surfaced via the alp_rpc_open() ALP_ERR_NOSUPPORT path
60
+ # the backend already returns when it sees a zero
61
+ # mailbox_channel or empty mailbox controller.
62
+ lines.append(f"/* BLOCKED: {c.reason or 'unknown reason'} */")
63
+ # Surface the gap as a structured comment block rather than
64
+ # a `#warning` (the slice builds under -Werror=cpp and an
65
+ # actual warning would trip it). The same reason text is
66
+ # also written into the build manifest by the orchestrator's
67
+ # fan-out step so reviewers see it.
68
+ lines.append(f"/* IPC channel '{c.name}' is blocked; "
69
+ "fix the SoM metadata before depending on this "
70
+ "channel at runtime. */")
71
+ lines.append(f'#define ALP_IPC_{upper}_NAME "{c.name}"')
72
+ lines.append(f"#define ALP_IPC_{upper}_ADDR 0x0u /* stub: blocked */")
73
+ lines.append(f"#define ALP_IPC_{upper}_SIZE 0x0u /* stub: blocked */")
74
+ lines.append(f"#define ALP_IPC_{upper}_SRC_EPT 0x0u /* stub: blocked */")
75
+ lines.append(f"#define ALP_IPC_{upper}_DST_EPT 0x0u /* stub: blocked */")
76
+ lines.append(f"#define ALP_IPC_{upper}_MBOX_CH 0u /* stub: blocked */")
77
+ lines.append("")
78
+ continue
79
+ lines.append(f'#define ALP_IPC_{upper}_NAME "{c.name}"')
80
+ lines.append(f'#define ALP_IPC_{upper}_ADDR 0x{c.base:08x}u')
81
+ lines.append(f'#define ALP_IPC_{upper}_SIZE 0x{c.size:08x}u')
82
+ lines.append(f'#define ALP_IPC_{upper}_SRC_EPT 0x{c.src_ept:08x}u')
83
+ lines.append(f'#define ALP_IPC_{upper}_DST_EPT 0x{c.dst_ept:08x}u')
84
+ lines.append(f'#define ALP_IPC_{upper}_MBOX_CH {c.mailbox_channel}u')
85
+ lines.append("")
86
+
87
+ lines.append("#endif /* ALP_SYSTEM_IPC_H */")
88
+ lines.append("")
89
+ return "\n".join(lines)
90
+
91
+
92
+ def emit_dts_reservations(project: BoardProject) -> str:
93
+ """Generate dts-reservations.dtsi per spec §6.2."""
94
+ carve_outs = resolve_carve_outs(project)
95
+ lines: list[str] = [
96
+ "/*",
97
+ " * Auto-generated by scripts/alp_orchestrate.py -- do not edit.",
98
+ " * Regenerate after changes to board.yaml `ipc:` or the SoM's",
99
+ " * memory_map block. #include this file from your kernel /",
100
+ " * Zephyr DT.",
101
+ " */",
102
+ "",
103
+ "/ {",
104
+ " reserved-memory {",
105
+ " #address-cells = <2>;",
106
+ " #size-cells = <2>;",
107
+ "",
108
+ ]
109
+ if not carve_outs:
110
+ lines.append(" /* No ipc[] carve-outs declared. */")
111
+ else:
112
+ for c in carve_outs:
113
+ if c.status == "blocked":
114
+ # Blocked carve-outs land as a comment so reviewers see
115
+ # the gap; the slice build trips elsewhere (the C
116
+ # header carries an #error directive).
117
+ lines.append(f" /* BLOCKED: {c.name} -- "
118
+ f"{c.reason or 'unknown reason'} */")
119
+ lines.append("")
120
+ continue
121
+ # Split base / size into two 32-bit halves for the
122
+ # #address-cells = <2>; #size-cells = <2>; addressing.
123
+ base_hi = (c.base >> 32) & 0xFFFFFFFF
124
+ base_lo = c.base & 0xFFFFFFFF
125
+ size_hi = (c.size >> 32) & 0xFFFFFFFF
126
+ size_lo = c.size & 0xFFFFFFFF
127
+ lines.append(
128
+ f" {c.name}: {c.name}@{c.base:x} {{")
129
+ lines.append(
130
+ ' compatible = "shared-dma-pool";')
131
+ lines.append(
132
+ f" reg = <0x{base_hi:x} 0x{base_lo:08x} "
133
+ f"0x{size_hi:x} 0x{size_lo:08x}>;")
134
+ lines.append(" no-map;")
135
+ lines.append(f' label = "{c.name}";')
136
+ lines.append(" };")
137
+ lines.append("")
138
+
139
+ lines.append(" };")
140
+ lines.append("};")
141
+ lines.append("")
142
+ return "\n".join(lines)
143
+
144
+
145
+ def emit_dts_partitions(project: BoardProject) -> str:
146
+ """Generate dts-partitions.dtsi from board.yaml `storage:`.
147
+
148
+ The overlay decorates each referenced flash device with a
149
+ `partitions { ... }` child node carrying every resolved
150
+ partition for that device. Zephyr's `fixed-partitions` binding
151
+ consumes the result; downstream Kconfig (CONFIG_FILE_SYSTEM_LITTLEFS
152
+ etc., emitted by `_slice_alp_conf()`) wires the filesystem driver.
153
+
154
+ Blocked partitions appear as bare comments so reviewers see the
155
+ gap; the slice build trips elsewhere (the per-fs Kconfig will not
156
+ have a backing partition and Zephyr's link-time fs-table assembly
157
+ surfaces the error).
158
+ """
159
+ partitions = resolve_storage_partitions(project)
160
+ lines: list[str] = [
161
+ "/*",
162
+ " * Auto-generated by scripts/alp_orchestrate.py -- do not edit.",
163
+ " * Regenerate after changes to board.yaml `storage:` or the SoM's",
164
+ " * memory_map / on_module.ospi_memories blocks. #include this file",
165
+ " * from your Zephyr DT.",
166
+ " */",
167
+ "",
168
+ ]
169
+ if not partitions:
170
+ lines += [
171
+ "/* No `storage:` entries declared in board.yaml; nothing to emit. */",
172
+ "",
173
+ ]
174
+ return "\n".join(lines)
175
+
176
+ # Group resolved partitions by dt_label; blocked entries get a
177
+ # standalone comment block so reviewers see the gap.
178
+ by_label: dict[str, list[ResolvedPartition]] = {}
179
+ blocked: list[ResolvedPartition] = []
180
+ for p in partitions:
181
+ if p.status == "blocked":
182
+ blocked.append(p)
183
+ continue
184
+ by_label.setdefault(p.dt_label, []).append(p)
185
+
186
+ for label in sorted(by_label.keys()):
187
+ lines.append(f"&{label} {{")
188
+ lines.append(" partitions {")
189
+ lines.append(' compatible = "fixed-partitions";')
190
+ lines.append(" #address-cells = <1>;")
191
+ lines.append(" #size-cells = <1>;")
192
+ lines.append("")
193
+ # Sort by base offset for human readability + DT-binding
194
+ # convention (low-address first).
195
+ for p in sorted(by_label[label], key=lambda x: x.base_kib):
196
+ base_bytes = p.base_kib * 1024
197
+ size_bytes = p.size_kib * 1024
198
+ lines.append(
199
+ f" {p.name}_partition: partition@{base_bytes:x} {{")
200
+ lines.append(f' label = "{p.name}";')
201
+ lines.append(
202
+ f" reg = <0x{base_bytes:x} 0x{size_bytes:x}>;")
203
+ lines.append(" };")
204
+ lines.append("")
205
+ lines.append(" };")
206
+ lines.append("};")
207
+ lines.append("")
208
+
209
+ if blocked:
210
+ lines.append(
211
+ "/* Blocked storage entries -- see system-manifest.yaml for details. */")
212
+ for p in blocked:
213
+ lines.append(f"/* BLOCKED: {p.name} -- "
214
+ f"{p.reason or 'unknown reason'} */")
215
+ lines.append("")
216
+
217
+ return "\n".join(lines)
218
+
219
+
220
+ def _mountable_partitions(project: BoardProject):
221
+ """Resolved storage partitions that get a row in the mount table."""
222
+ return [p for p in resolve_storage_partitions(project)
223
+ if p.status == "ok" and p.mount and p.fs != "raw"]
224
+
225
+
226
+ def has_storage_mounts(project: BoardProject) -> bool:
227
+ """True when `emit_storage_mounts_c` would emit a non-empty table."""
228
+ return bool(_mountable_partitions(project))
229
+
230
+
231
+ def emit_storage_mounts_c(project: BoardProject) -> str:
232
+ """Generate storage_mount_table.c -- a static fs_mount_t[] array.
233
+
234
+ Optional companion to emit_dts_partitions. Apps that want
235
+ deterministic boot-time mounting include this file and call
236
+ `alp_storage_mount_all()` (or iterate the table manually).
237
+ Partitions with no `mount:` declared are omitted from the table.
238
+ """
239
+ mountable = _mountable_partitions(project)
240
+
241
+ lines: list[str] = [
242
+ "/*",
243
+ " * Auto-generated by scripts/alp_orchestrate.py -- do not edit.",
244
+ " * Regenerate after changes to board.yaml `storage:`.",
245
+ " */",
246
+ "/* clang-format off */",
247
+ "",
248
+ "#include <zephyr/fs/fs.h>",
249
+ "#include <zephyr/storage/flash_map.h>",
250
+ "",
251
+ ]
252
+ if not mountable:
253
+ lines += [
254
+ "/* No mountable storage[] entries declared; emitting empty table. */",
255
+ "",
256
+ "const struct fs_mount_t *alp_storage_mounts[] = { 0 };",
257
+ "const size_t alp_storage_mount_count = 0;",
258
+ "/* clang-format on */",
259
+ "",
260
+ ]
261
+ return "\n".join(lines)
262
+
263
+ # Per-fs includes + per-partition fs_<name>_data + fs_mount_t.
264
+ fs_seen: set[str] = set()
265
+ for p in mountable:
266
+ if p.fs in fs_seen:
267
+ continue
268
+ fs_seen.add(p.fs)
269
+ if p.fs == "littlefs":
270
+ lines.append("#include <zephyr/fs/littlefs.h>")
271
+ elif p.fs == "fat":
272
+ lines.append("#include <ff.h>")
273
+ elif p.fs == "ext4":
274
+ lines.append("#include <zephyr/fs/ext2.h>")
275
+ lines.append("")
276
+
277
+ for p in mountable:
278
+ upper = p.name.upper()
279
+ if p.fs == "littlefs":
280
+ lines.append(f"FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(alp_lfs_{p.name});")
281
+ lines.append(f"static struct fs_mount_t alp_mnt_{p.name} = {{")
282
+ lines.append(" .type = FS_LITTLEFS,")
283
+ lines.append(f" .fs_data = &alp_lfs_{p.name},")
284
+ lines.append(f" .storage_dev = (void *)FIXED_PARTITION_ID({p.name}_partition),")
285
+ lines.append(f' .mnt_point = "{p.mount}",')
286
+ lines.append("};")
287
+ elif p.fs == "fat":
288
+ lines.append(f"static FATFS alp_fat_{p.name};")
289
+ lines.append(f"static struct fs_mount_t alp_mnt_{p.name} = {{")
290
+ lines.append(" .type = FS_FATFS,")
291
+ lines.append(f" .fs_data = &alp_fat_{p.name},")
292
+ lines.append(f" .storage_dev = (void *)FIXED_PARTITION_ID({p.name}_partition),")
293
+ lines.append(f' .mnt_point = "{p.mount}",')
294
+ lines.append("};")
295
+ elif p.fs == "ext4":
296
+ lines.append(f"static struct fs_mount_t alp_mnt_{p.name} = {{")
297
+ lines.append(" .type = FS_EXT2,")
298
+ lines.append(f" .storage_dev = (void *)FIXED_PARTITION_ID({p.name}_partition),")
299
+ lines.append(f' .mnt_point = "{p.mount}",')
300
+ lines.append("};")
301
+ lines.append("")
302
+ # Silence -Wunused on the per-partition mount when the app
303
+ # iterates only via alp_storage_mounts[]; the explicit symbol
304
+ # lets test fixtures reach a specific mount by name.
305
+ _ = upper
306
+
307
+ lines.append("const struct fs_mount_t *alp_storage_mounts[] = {")
308
+ for p in mountable:
309
+ lines.append(f" &alp_mnt_{p.name},")
310
+ lines.append("};")
311
+ lines.append("const size_t alp_storage_mount_count = "
312
+ f"{len(mountable)};")
313
+ lines.append("/* clang-format on */")
314
+ lines.append("")
315
+ return "\n".join(lines)