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
@@ -0,0 +1,473 @@
1
+ #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """`--emit kconfig` -- the board-scoped, user-settable Kconfig symbol menu
4
+ for the vscode `prj.conf` LSP (#893); unblocks tan-cli #35 (`tan kconfig`).
5
+
6
+ Deliberately separate from `kconfig.py` (the alp.conf/local.conf string
7
+ templater, which never runs kconfiglib/west -- see its module docstring):
8
+ this module is the SDK's first workspace-dependent emit. Every other
9
+ `--emit` mode is hermetic (provable from board.yaml + this repo's own
10
+ metadata alone); this one needs a bootstrapped Zephyr workspace
11
+ (`ZEPHYR_BASE`, v4.4.0) because only the real Kconfig solver knows which
12
+ symbols are user-promptable for a given board.
13
+
14
+ Two independent halves:
15
+
16
+ * `_envelope` -- pure JSON shaping (the schemaVersion/board/core/symbols
17
+ wrap), unit-tested with NO Zephyr installed (see
18
+ tests/core/test_kconfig_symbols.py). The actual symbol projection
19
+ ("first prompt-bearing node, not nodes[0]" -- alp-sdk #893) lives ONLY
20
+ inside `_DUMPER_SOURCE` below, the rendered dumper script: it has to run
21
+ as a standalone subprocess inside Zephyr's own Kconfig env, importing
22
+ nothing from this package, so there is no separate copy here for it to
23
+ import -- `tests/core/test_kconfig_symbols.py` asserts the rendered
24
+ projection directly against a literal expected result (an independent
25
+ oracle) instead of hand-syncing a second copy.
26
+ * `_load_board_symbols` -- Approach A: a stub `west build --cmake-only`
27
+ registers Zephyr's `EXTRA_KCONFIG_TARGET` custom-target mechanism
28
+ (`cmake/modules/kconfig.cmake` ~199-243 -- the same seam `west build -t
29
+ menuconfig` uses) pointed at a dumper script this module RENDERS fresh
30
+ onto disk for every run (`_DUMPER_SOURCE`, written under the run's own
31
+ scratch tree -- never a file living in alp-sdk, and never a PyInstaller
32
+ onefile extraction path, which is deleted at process exit and would
33
+ leave the baked-in CMake command pointing at nothing); `west build -t
34
+ alpkconfigjson` then runs it INSIDE the exact Kconfig env Zephyr's own
35
+ CMake computed for that board/toolchain/module set (no env
36
+ reconstruction needed -- Zephyr hands it over directly). This is
37
+ workspace-dependent and is skipped locally without a bootstrapped
38
+ ZEPHYR_BASE; alp-sdk's own dumper copy under `scripts/kconfig/
39
+ alp_kconfig_dump.py` is verified by the pr-twister CI job's
40
+ `scripts/check_emit_kconfig_contract.py` -- that gate covers alp-sdk's
41
+ copy only, not tan's rendered one above.
42
+
43
+ An earlier version tried overriding `-DPYTHON_EXECUTABLE=<spy>` to
44
+ capture kconfig.py's own env instead -- discarded: Zephyr's
45
+ `cmake/modules/python.cmake` re-derives `PYTHON_EXECUTABLE` via a
46
+ plain `set()` (not `_ifndef`), so a `-D` override is silently
47
+ clobbered before `kconfig.cmake` ever runs, and kconfig.py never
48
+ actually invokes the spy.
49
+ """
50
+
51
+ from __future__ import annotations
52
+
53
+ import json
54
+ import os
55
+ import subprocess
56
+ import sys
57
+ import tempfile
58
+ import textwrap
59
+ from pathlib import Path
60
+ from typing import Any, Optional
61
+
62
+ from tan.core.document_guards import DocumentGuards
63
+ from tan.core.subprocess_env import spawn_env
64
+
65
+ from .models import BoardProject, OrchestratorError
66
+
67
+ #: The malformed-document register, bound to THIS module's curated class.
68
+ _GUARDS = DocumentGuards(OrchestratorError)
69
+
70
+ # RELOCATED divergence from alp-sdk's own scripts/alp_orchestrate/
71
+ # kconfig_symbols.py (tan-cli#459 review): `west_program` resolves the
72
+ # ABSOLUTE `west` inside `tan bootstrap`'s workspace venv, the same helper
73
+ # `build`/`flash`/`west_forward_cmd` already spawn `west` through -- alp-sdk's
74
+ # own copy runs AS a `west` extension, so a bare `"west"` on PATH is always
75
+ # already the one that found it; tan's frozen binary has no such shell, and a
76
+ # literal `"west"` here resolved to nothing (tan-cli#453's actual defect --
77
+ # the caller's PATH-prepend was a per-command mitigation for this, not a fix
78
+ # of it). `tan.core.venv` is importable here: this file, unlike alp-sdk's, is
79
+ # `tan/planner/kconfig_symbols.py`, part of the `tan` package.
80
+ from tan.core.venv import west_program
81
+
82
+ SCHEMA_VERSION = 1
83
+
84
+ # ---------------------------------------------------------------------
85
+ # Workspace guard (Task 1)
86
+ # ---------------------------------------------------------------------
87
+
88
+
89
+ def _kconfig_dir(zephyr_base: Path) -> Path:
90
+ return zephyr_base / "scripts" / "kconfig"
91
+
92
+
93
+ def _kconfiglib_path(zephyr_base: Path) -> Path:
94
+ return _kconfig_dir(zephyr_base) / "kconfiglib.py"
95
+
96
+
97
+ def _require_workspace() -> Path:
98
+ """Return the bootstrapped `ZEPHYR_BASE`, or print the actionable
99
+ message + exit(2) -- this mode's one deliberate deviation from the
100
+ rest of the orchestrator's `OrchestratorError` -> exit(1) convention,
101
+ marking "no workspace" as distinct from an ordinary usage error.
102
+
103
+ This process itself never imports kconfiglib (only the rendered dumper
104
+ does -- see `_DUMPER_SOURCE` -- in its own subprocess running inside
105
+ Zephyr's own env; see `_load_board_symbols`); this just probes
106
+ `kconfiglib.py`'s presence as the "is this ZEPHYR_BASE real" check.
107
+ """
108
+ raw = os.environ.get("ZEPHYR_BASE")
109
+ if raw:
110
+ zephyr_base = Path(raw)
111
+ if _kconfiglib_path(zephyr_base).is_file():
112
+ return zephyr_base
113
+ print(
114
+ "alp_orchestrate: --emit kconfig requires a bootstrapped Zephyr "
115
+ "workspace (set ZEPHYR_BASE; west init/update v4.4.0)",
116
+ file=sys.stderr,
117
+ )
118
+ sys.exit(2)
119
+
120
+
121
+ # ---------------------------------------------------------------------
122
+ # Hermetic symbol projection + envelope (Task 2) -- no kconfiglib import
123
+ # at module scope, so this half unit-tests with no Zephyr installed.
124
+ # ---------------------------------------------------------------------
125
+
126
+
127
+ def _envelope(board: str, core: str, symbols: list[dict[str, Any]]) -> dict[str, Any]:
128
+ return {
129
+ "schemaVersion": SCHEMA_VERSION,
130
+ "board": board,
131
+ "core": core,
132
+ "symbols": symbols,
133
+ }
134
+
135
+
136
+ # ---------------------------------------------------------------------
137
+ # Approach-A Zephyr load (Task 3) -- workspace-dependent
138
+ # ---------------------------------------------------------------------
139
+
140
+ _STUB_CMAKELISTS = textwrap.dedent("""\
141
+ # SPDX-License-Identifier: Apache-2.0
142
+ # Auto-generated by alp_orchestrate/kconfig_symbols.py (#893) -- a
143
+ # throwaway stub app whose sole purpose is to make Zephyr's own CMake
144
+ # configure the Kconfig tree for one board so kconfiglib can be
145
+ # pointed at the real env it computed. Never built.
146
+ cmake_minimum_required(VERSION 3.20.0)
147
+ find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
148
+ project(alp_emit_kconfig_stub)
149
+ target_sources(app PRIVATE src/main.c)
150
+ """)
151
+
152
+ _STUB_MAIN_C = "int main(void)\n{\n\treturn 0;\n}\n"
153
+
154
+ _KCONFIG_TARGET = "alpkconfigjson"
155
+
156
+ # The dumper Zephyr's `EXTRA_KCONFIG_TARGET` custom target runs, RENDERED onto
157
+ # disk fresh for every `_load_board_symbols` call rather than read off a fixed
158
+ # path -- see `_load_board_symbols`'s docstring for the PyInstaller-onefile /
159
+ # stale-build-dir hazard a fixed path would reintroduce. This replaces the
160
+ # earlier design: first a `__file__`-relative sibling walk, then a path
161
+ # anchored on the bound alp-sdk checkout (`REPO / "scripts" / "kconfig" /
162
+ # "alp_kconfig_dump.py"`) -- both coupled this module to a file living
163
+ # outside `tan`.
164
+ #
165
+ # Deliberately self-contained: this text becomes a standalone .py file that
166
+ # runs as a *subprocess*, inside Zephyr's own Kconfig environment, with its
167
+ # own path baked into the generated build tree by CMake -- so it must import
168
+ # NOTHING from `tan` or `alp_orchestrate` (neither package is guaranteed to
169
+ # be importable from wherever this file ends up, and under a frozen
170
+ # PyInstaller build there is no `tan` *source* on disk to import at all).
171
+ # Only the stdlib plus `kconfiglib`, which Zephyr's own env injects via
172
+ # ZEPHYR_BASE.
173
+ #
174
+ # `_project_symbols` therefore lives ONLY here, as plain literal source, not
175
+ # imported and not mirrored elsewhere in this module;
176
+ # `tests/core/test_kconfig_symbols.py` execs this constant in an isolated
177
+ # namespace (with a fake `kconfiglib` module) and asserts its projection
178
+ # output against a literal expected result -- an independent oracle, so a
179
+ # regression here fails a fast, hermetic test instead of surfacing only in a
180
+ # Zephyr-bootstrapped CI job.
181
+ _DUMPER_SOURCE = '''\
182
+ #!/usr/bin/env python3
183
+ # SPDX-License-Identifier: Apache-2.0
184
+ """Dumps one board's promptable Kconfig symbols as JSON.
185
+
186
+ RENDERED BY tan (tan.planner.kconfig_symbols._load_board_symbols) fresh for
187
+ every `--emit kconfig` run, into that run's own scratch build tree -- this
188
+ file is never checked in and never lives in alp-sdk.
189
+
190
+ Runs INSIDE Zephyr's own Kconfig environment via the `EXTRA_KCONFIG_TARGET`
191
+ mechanism (`cmake/modules/kconfig.cmake` ~199-243, the same seam
192
+ `menuconfig`/`guiconfig`/`hardenconfig`/`traceconfig` use): a
193
+ `west build --cmake-only -- -DEXTRA_KCONFIG_TARGETS=alpkconfigjson
194
+ -DEXTRA_KCONFIG_TARGET_COMMAND_FOR_alpkconfigjson=<this>;--output;<path>`
195
+ registers a custom target that (per kconfig.cmake:227-242) runs
196
+
197
+ ${CMAKE_COMMAND} -E env <COMMON_KCONFIG_ENV_SETTINGS...> \\\\
198
+ ${PYTHON_EXECUTABLE} <this> --output <path> ${KCONFIG_ROOT}
199
+
200
+ i.e. Zephyr's CMake hands this script the *exact* env it computed for that
201
+ board/toolchain/module set (srctree, ARCH, BOARD_DIR, module Kconfigs, ...)
202
+ plus `KCONFIG_ROOT` as the trailing positional -- no env reconstruction
203
+ needed. `west build -d <build> -t alpkconfigjson` (a second, separate
204
+ invocation -- `add_custom_target` isn't built by `--cmake-only` alone) is
205
+ what actually runs it. See `tan.planner.kconfig_symbols._load_board_symbols`
206
+ for the caller.
207
+
208
+ Deliberately self-contained (imports nothing from `tan` or
209
+ `alp_orchestrate`): this file's own path is baked into the generated build
210
+ tree by the CMake invocation above, so it has to keep working from wherever
211
+ `tan` chose to write it -- see the caller for why that path is a fresh
212
+ scratch directory rather than a fixed one.
213
+ """
214
+
215
+ from __future__ import annotations
216
+
217
+ import argparse
218
+ import json
219
+ import os
220
+ import sys
221
+ from pathlib import Path
222
+
223
+ # kconfiglib is a plain module file under $ZEPHYR_BASE/scripts/kconfig, not
224
+ # pip-installed -- ZEPHYR_BASE is guaranteed set in this script's env (see
225
+ # module docstring: COMMON_KCONFIG_ENV_SETTINGS always sets it).
226
+ sys.path.insert(0, os.path.join(os.environ["ZEPHYR_BASE"], "scripts", "kconfig"))
227
+ import kconfiglib # noqa: E402
228
+
229
+
230
+ def _project_symbols(syms):
231
+ """Project promptable symbols to `{name, type, prompt, depends,
232
+ default, help}`, sorted by name.
233
+
234
+ Only symbols with a prompt on ANY of their `MenuNode`s are kept -- a
235
+ symbol can be declared in multiple locations (Zephyr's own
236
+ `scripts/kconfig/kconfig.py::promptless()` checks the same way, with the
237
+ same reasoning: "the symbol might be defined in multiple locations, we
238
+ need to check all locations"). This matters in practice: many board/SoC
239
+ `Kconfig.defconfig` fragments redeclare a symbol with a promptless
240
+ `default`-only override block, and `Kconfig.zephyr` sources those BEFORE
241
+ the canonical declaration -- so taking `nodes[0]` alone silently dropped
242
+ real, user-facing symbols like `LOG` / `SERIAL` / `MAIN_STACK_SIZE`
243
+ (alp-sdk #893). This is the only copy of this projection logic (see the
244
+ module docstring) -- verified against a literal expected result in
245
+ `tests/core/test_kconfig_symbols.py`, so the "first prompt-bearing node,
246
+ not nodes[0]" behavior is pinned on purpose.
247
+ """
248
+ projected = []
249
+ for sym in syms:
250
+ node = next((n for n in sym.nodes if n.prompt), None)
251
+ if node is None:
252
+ continue
253
+ default = None
254
+ if sym.orig_defaults:
255
+ default_expr, _cond = sym.orig_defaults[0]
256
+ default = kconfiglib.expr_str(default_expr)
257
+ projected.append({
258
+ "name": sym.name,
259
+ "type": kconfiglib.TYPE_TO_STR.get(sym.type, "unknown"),
260
+ "prompt": node.prompt[0],
261
+ "depends": kconfiglib.expr_str(sym.direct_dep) or "",
262
+ "default": default,
263
+ "help": getattr(node, "help", None) or "",
264
+ })
265
+ projected.sort(key=lambda entry: entry["name"])
266
+ return projected
267
+
268
+
269
+ def main() -> int:
270
+ ap = argparse.ArgumentParser(description=__doc__)
271
+ ap.add_argument("--output", required=True, type=Path)
272
+ ap.add_argument("kconfig_root")
273
+ args = ap.parse_args()
274
+
275
+ kconf = kconfiglib.Kconfig(args.kconfig_root, warn=True, warn_to_stderr=False)
276
+ symbols = _project_symbols(kconf.unique_defined_syms)
277
+ # write-text-newline-exempt: scratch build dir, read back only by
278
+ # tan.planner.kconfig_symbols._load_board_symbols
279
+ args.output.write_text(json.dumps(symbols), encoding="utf-8")
280
+ return 0
281
+
282
+
283
+ if __name__ == "__main__":
284
+ sys.exit(main())
285
+ '''
286
+
287
+
288
+ def _write_stub_app(app_dir: Path) -> None:
289
+ (app_dir / "src").mkdir(parents=True)
290
+ # write-text-newline-exempt: throwaway stub app in a scratch build dir
291
+ (app_dir / "CMakeLists.txt").write_text(_STUB_CMAKELISTS, encoding="utf-8")
292
+ # write-text-newline-exempt: throwaway stub app in a scratch build dir
293
+ (app_dir / "prj.conf").write_text("", encoding="utf-8")
294
+ # write-text-newline-exempt: throwaway stub app in a scratch build dir
295
+ (app_dir / "src" / "main.c").write_text(_STUB_MAIN_C, encoding="utf-8")
296
+
297
+
298
+ def _write_dumper(dumper_path: Path) -> None:
299
+ """Render `_DUMPER_SOURCE` onto disk at `dumper_path`.
300
+
301
+ Called once per `_load_board_symbols` run, into that run's own scratch
302
+ tree (never a fixed/cached path) -- see `_DUMPER_SOURCE`'s docstring for
303
+ why: a fixed path under a PyInstaller onefile extraction directory would
304
+ be deleted at process exit while the CMake command baked into the build
305
+ tree still names it, and a fixed path anywhere else would let a stale
306
+ render survive a project change instead of being regenerated.
307
+ """
308
+ dumper_path.parent.mkdir(parents=True, exist_ok=True)
309
+ # write-text-newline-exempt: rendered fresh into a scratch build dir
310
+ dumper_path.write_text(_DUMPER_SOURCE, encoding="utf-8")
311
+
312
+
313
+ def _load_board_symbols(zephyr_base: Path, board_triple: str) -> list[dict[str, Any]]:
314
+ """Approach A: configure a stub app for `board_triple`, render the
315
+ dumper (`_DUMPER_SOURCE`) into this run's own scratch tree, then run it
316
+ INSIDE Zephyr's own Kconfig env via the `EXTRA_KCONFIG_TARGET` mechanism
317
+ (the same seam `west build -t menuconfig` uses -- see `_DUMPER_SOURCE`'s
318
+ own module docstring). Returns the already-projected `list[dict]` the
319
+ dumper wrote.
320
+
321
+ `west build` is a west extension command -- it needs to run from
322
+ inside a west workspace (a `.west/` upward from cwd), which
323
+ `alp_orchestrate` itself is never a part of. `$ZEPHYR_BASE/..` is
324
+ that workspace's topdir under every documented Zephyr `west init`
325
+ layout (Getting Started's `~/zephyrproject/{.west, zephyr,
326
+ modules,...}`, and the same layout pr-twister.yml's own `west init
327
+ -m .../zephyr .` step produces).
328
+
329
+ The dumper is rendered fresh into `tmp_dir` on every call and `tmp_dir`
330
+ is deleted in this function's own `finally` below -- well after both
331
+ `west build` invocations that read the rendered path have already run,
332
+ so the render neither survives to be reused stale by a later call nor
333
+ depends on anything outliving this process (see `_DUMPER_SOURCE`).
334
+ """
335
+ tmp_dir = Path(tempfile.mkdtemp(prefix="alp-emit-kconfig-"))
336
+ try:
337
+ app_dir = tmp_dir / "stub"
338
+ _write_stub_app(app_dir)
339
+
340
+ dumper_path = tmp_dir / "alp_kconfig_dump.py"
341
+ _write_dumper(dumper_path)
342
+
343
+ build_dir = tmp_dir / "build"
344
+ output_json = build_dir / "alp_kconfig.json"
345
+ # A CMake list (semicolon-separated -- COMMAND_EXPAND_LISTS on the
346
+ # custom target splits it into argv tokens): the dumper script,
347
+ # then its own `--output <path>` -- Zephyr appends ${KCONFIG_ROOT}
348
+ # as the final token itself (kconfig.cmake:238).
349
+ target_cmd = f"{dumper_path};--output;{output_json}"
350
+
351
+ # `zephyr_base.parent` IS the workspace topdir (this function's own
352
+ # docstring above), already the one `west_workspace_dir` verified
353
+ # before `ZEPHYR_BASE` was ever set -- so `sdk_root=None` here is not
354
+ # a second, unverified guess, just skipping a manifest re-check the
355
+ # caller already made.
356
+ west = west_program(str(zephyr_base.parent), None)
357
+ configure_cmd = [
358
+ west, "build", "--cmake-only",
359
+ "-b", board_triple,
360
+ "-d", str(build_dir),
361
+ str(app_dir),
362
+ "--",
363
+ f"-DEXTRA_KCONFIG_TARGETS={_KCONFIG_TARGET}",
364
+ f"-DEXTRA_KCONFIG_TARGET_COMMAND_FOR_{_KCONFIG_TARGET}={target_cmd}",
365
+ ]
366
+ # RELOCATED divergence from alp-sdk's own scripts/alp_orchestrate/
367
+ # kconfig_symbols.py: `env=spawn_env()` on both `west build` spawns
368
+ # below is a tan-only addition (tan-cli#992) -- alp-sdk's own copy
369
+ # has no such restore because it never ships as a frozen PyInstaller
370
+ # bundle whose LD_LIBRARY_PATH would otherwise leak into the child.
371
+ proc = subprocess.run(
372
+ configure_cmd, cwd=zephyr_base.parent,
373
+ capture_output=True, text=True, encoding="utf-8", errors="replace",
374
+ env=spawn_env({"PYTHONIOENCODING": "utf-8"}))
375
+ if proc.returncode != 0:
376
+ raise OrchestratorError(
377
+ f"--emit kconfig: `west build --cmake-only -b "
378
+ f"{board_triple}` failed:\n{proc.stderr.strip()}")
379
+
380
+ # `add_custom_target` never runs at configure time -- `-t` builds
381
+ # it explicitly (a second, separate `west build`).
382
+ build_cmd = [west, "build", "-d", str(build_dir), "-t", _KCONFIG_TARGET]
383
+ proc = subprocess.run(
384
+ build_cmd, cwd=zephyr_base.parent,
385
+ capture_output=True, text=True, encoding="utf-8", errors="replace",
386
+ env=spawn_env({"PYTHONIOENCODING": "utf-8"}))
387
+ if proc.returncode != 0:
388
+ raise OrchestratorError(
389
+ f"--emit kconfig: `west build -t {_KCONFIG_TARGET}` failed "
390
+ f"for board '{board_triple}':\n{proc.stderr.strip()}")
391
+ # tan-cli#1162, and a RELOCATED divergence from alp-sdk's own
392
+ # `scripts/alp_orchestrate/kconfig_symbols.py`, which still spells
393
+ # this as an `is_file()` pre-flight plus a bare `read_text`. This
394
+ # artefact is written by an EXTERNAL `west build -t` subprocess, so
395
+ # a directory in its place, a non-UTF-8 dump or an unreadable mode
396
+ # are things a third-party build step really leaves -- each escaped
397
+ # raw before, into `kconfig_cmd.py:541`'s `kconfig.emit-failed`
398
+ # envelope (or `generate_cmd.py:890`'s) naming the exception TYPE.
399
+ # `never wrote` is preserved byte for byte through `absent=`; what
400
+ # the pre-flight silently folded into it now says `cannot read`.
401
+ raw = _GUARDS.require_readable_text(
402
+ output_json,
403
+ what=f"the `{_KCONFIG_TARGET}` dumper's output",
404
+ absent=(f"--emit kconfig: `west build -t {_KCONFIG_TARGET}` "
405
+ f"completed but never wrote {output_json} -- never emit "
406
+ f"a partial/empty menu"))
407
+ if not raw.strip():
408
+ # `west build -t <target>` can report success (rc 0) while the
409
+ # custom target's own command silently produced nothing -- a
410
+ # 0-byte `output_json` used to reach `json.loads` below and
411
+ # surface as a bare, uncoded `JSONDecodeError` rather than a
412
+ # loud refusal. Never emit a partial/empty menu from an empty
413
+ # artefact.
414
+ raise OrchestratorError(
415
+ f"--emit kconfig: `west build -t {_KCONFIG_TARGET}` "
416
+ f"reported success but wrote an empty {output_json} -- "
417
+ f"never emit a partial/empty menu")
418
+ try:
419
+ symbols = json.loads(raw)
420
+ except json.JSONDecodeError as e:
421
+ raise OrchestratorError(
422
+ f"--emit kconfig: {output_json} is not valid JSON ({e}) -- "
423
+ f"the `{_KCONFIG_TARGET}` dumper wrote a corrupt artefact, "
424
+ f"not a partial/empty menu") from e
425
+ if not isinstance(symbols, list):
426
+ raise OrchestratorError(
427
+ f"--emit kconfig: {output_json} parsed as JSON but is not a "
428
+ f"list of symbols (got {type(symbols).__name__}) -- the "
429
+ f"`{_KCONFIG_TARGET}` dumper wrote a corrupt artefact, not "
430
+ f"a partial/empty menu")
431
+ return symbols
432
+ finally:
433
+ import shutil
434
+ shutil.rmtree(tmp_dir, ignore_errors=True)
435
+
436
+
437
+ # ---------------------------------------------------------------------
438
+ # Public entry point
439
+ # ---------------------------------------------------------------------
440
+
441
+
442
+ def emit_kconfig(project: BoardProject, core: Optional[str]) -> str:
443
+ """`--emit kconfig`: the board-scoped, user-settable Kconfig symbol
444
+ menu for the resolved `--core <id>` slice, as JSON.
445
+
446
+ Validates `core` against the project's own resolved slices first
447
+ (a plain `OrchestratorError` -- exit(1) via the cli's usual path),
448
+ THEN checks the Zephyr workspace is bootstrapped (exit(2) -- see
449
+ `_require_workspace`).
450
+ """
451
+ if not core:
452
+ raise OrchestratorError(
453
+ "--emit kconfig requires --core <id> (the SDK plans one "
454
+ "Kconfig symbol menu per board, not per project)")
455
+ if core not in project.cores:
456
+ raise OrchestratorError(
457
+ f"--core {core} not present in this board.yaml's cores "
458
+ f"(have: {', '.join(sorted(project.cores))})")
459
+ slice_ = project.cores[core]
460
+ if not slice_.board:
461
+ raise OrchestratorError(
462
+ f"core '{core}' has no resolved Zephyr board target "
463
+ f"(os: {slice_.os}); --emit kconfig only applies to "
464
+ f"Zephyr cores")
465
+
466
+ zephyr_base = _require_workspace()
467
+
468
+ # The rendered dumper (`_DUMPER_SOURCE`, run inside Zephyr's own Kconfig
469
+ # env -- see `_load_board_symbols`) already projects with the real
470
+ # kconfiglib, so this is just the envelope wrap.
471
+ symbols = _load_board_symbols(zephyr_base, slice_.board)
472
+ envelope = _envelope(slice_.board, core, symbols)
473
+ return json.dumps(envelope, indent=2) + "\n"