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,240 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """The IO half of Flow A's whole-ATOC guard (tan-cli#1267): the file reads
3
+ and the one unlink `tan flash` needs around a `west flash` spawn. Every
4
+ decision lives in `tan.core.atoc_guard` and every sentence in
5
+ `tan.core.atoc_guard_messages`; this module only fetches their inputs.
6
+
7
+ What is read, none of it tan's own, all from the build west will flash:
8
+
9
+ * a sysbuild's top-level `domains.yaml`, for the one domain whose
10
+ `build_dir` the runner actually runs in (and writes its verdict under). A
11
+ multi-domain sysbuild is refused by the runner itself before its guard
12
+ runs (alp-sdk#2274), so tan says so instead of guessing a domain;
13
+ * that build's `zephyr/runners.yaml`, for the runner `west flash` falls back
14
+ to when the manifest names none;
15
+ * that build's `zephyr_modules.txt`, for the alp-sdk module checkout the
16
+ build used -- the one `west flash` imports `alif_flash` from, which need
17
+ not be the SDK tan is bound to. The bound SDK is the fallback only when
18
+ the build has no readable module list, and every message names the file;
19
+ * that module's `scripts/west_commands/runners/alif_flash.py`, for whether
20
+ the runner has the guard (`runner_has_guard`: the `--replace-atoc`
21
+ argument AND the v1 verdict schema);
22
+ * the runner's verdict, `<domain build dir>/alif_flash/atoc-guard.json`.
23
+
24
+ The verdict is removed BEFORE the spawn. The runner does the same itself at
25
+ the top of `do_run`, so that "no file" means "the guard never reached a
26
+ verdict for this attempt" (alp-sdk docs/aen-provisioning.md section 0.6). But
27
+ `do_run` is only reached once west has parsed its arguments and loaded the
28
+ runner; a `west flash` that fails before that leaves the LAST run's verdict
29
+ in place, and reading it would report an old `clear` (or an old refusal) as
30
+ this attempt's result. Removing it first closes that gap without trusting
31
+ timestamps.
32
+ """
33
+
34
+ from __future__ import annotations
35
+
36
+ import os
37
+ from collections.abc import Mapping
38
+ from typing import Any, Callable, Iterable
39
+
40
+ from tan.core.atoc_guard import (
41
+ ALIF_FLASH_RUNNER,
42
+ RUNNER_SOURCE_PARTS,
43
+ VERDICT_DIR,
44
+ VERDICT_FILE,
45
+ GuardVerdict,
46
+ ReplaceAtocDecision,
47
+ RunnerFacts,
48
+ decide_replace_atoc,
49
+ parse_domain_build_dirs,
50
+ parse_flash_runner,
51
+ parse_module_dir,
52
+ parse_verdict,
53
+ runner_has_guard,
54
+ )
55
+ from tan.core.atoc_guard_messages import (
56
+ no_verdict_after_success,
57
+ passed_note,
58
+ refusal_message,
59
+ unreadable_note,
60
+ )
61
+ from tan.core.flash_plan import fa_str, zephyr_west_build_dir
62
+
63
+ WEST_FLASH = "zephyr_west_flash"
64
+
65
+
66
+ def _read_text(path: str) -> str | None:
67
+ """`None` for a missing, unreadable or non-UTF-8 file -- every input here
68
+ is best-effort, and the pure layer turns `None` into "could not tell"."""
69
+ try:
70
+ with open(path, encoding="utf-8") as handle:
71
+ return handle.read()
72
+ except (OSError, UnicodeDecodeError):
73
+ return None
74
+
75
+
76
+ def absolutise_build_dir(flash_args: Any, base: str) -> Any:
77
+ """`flash_args` with a relative `build_dir` anchored on `base` -- the cwd
78
+ `west flash` is spawned in (the west topdir, else tan's own cwd). West
79
+ resolves `--build-dir` against ITS cwd, so tan must read the guard files
80
+ under the same directory, and passes the same absolute path on the argv
81
+ so the two can never name different trees. A new mapping; the manifest's
82
+ own is not touched. Anything else is returned unchanged."""
83
+ if not isinstance(flash_args, Mapping):
84
+ return flash_args
85
+ build_dir = fa_str(flash_args, "build_dir")
86
+ if build_dir is None or os.path.isabs(build_dir):
87
+ return flash_args
88
+ return {**flash_args, "build_dir": os.path.join(base, build_dir)}
89
+
90
+
91
+ def _image_dir(build_dir: str) -> tuple[str | None, str | None]:
92
+ """`(the build dir the runner runs in, None)`, or `(None, why not)`."""
93
+ domains_path = os.path.join(build_dir, "domains.yaml")
94
+ text = _read_text(domains_path)
95
+ if text is None:
96
+ return build_dir, None
97
+ dirs = parse_domain_build_dirs(text)
98
+ if dirs is None:
99
+ return None, f"{build_dir} is a sysbuild tree whose {domains_path} could not be read"
100
+ if len(dirs) > 1:
101
+ return None, (
102
+ f"{build_dir} is a sysbuild tree with {len(dirs)} domains ({domains_path}); "
103
+ "the alif_flash runner refuses a multi-domain sysbuild before its guard runs "
104
+ "and leaves no verdict (alp-sdk#2274)"
105
+ )
106
+ only = dirs[0]
107
+ return (only if os.path.isabs(only) else os.path.join(build_dir, only)), None
108
+
109
+
110
+ def _runner_source(image_dir: str, sdk_root: str) -> tuple[str, str]:
111
+ """`(the alif_flash.py to check, where that path came from)`."""
112
+ modules_path = os.path.join(image_dir, "zephyr_modules.txt")
113
+ modules = _read_text(modules_path)
114
+ if modules is None:
115
+ return (
116
+ os.path.join(sdk_root, *RUNNER_SOURCE_PARTS),
117
+ f"the bound alp-sdk; the build has no readable {modules_path}",
118
+ )
119
+ module_dir = parse_module_dir(modules)
120
+ if module_dir is None:
121
+ return modules_path, "that build lists no alp-sdk module, so its runner is not checkable"
122
+ return (
123
+ os.path.join(module_dir, *RUNNER_SOURCE_PARTS),
124
+ f"the alp-sdk module this build used, per {modules_path}",
125
+ )
126
+
127
+
128
+ def runner_facts(flash_args: Any, build_dir: str, sdk_root: str) -> RunnerFacts:
129
+ """Everything `decide_replace_atoc` needs about one west entry."""
130
+ image_dir, why_not = _image_dir(build_dir)
131
+ if image_dir is None:
132
+ return RunnerFacts(unknown_reason=why_not, verdict_dir=build_dir)
133
+ runner = fa_str(flash_args, "runner")
134
+ runners_path = os.path.join(image_dir, "zephyr", "runners.yaml")
135
+ if runner is None:
136
+ text = _read_text(runners_path)
137
+ runner = parse_flash_runner(text) if text is not None else None
138
+ if runner != ALIF_FLASH_RUNNER:
139
+ reason = (
140
+ f"flash_args.runner is unset and {runners_path} is missing or names no "
141
+ "flash-runner. Build first, or set flash_args.runner"
142
+ )
143
+ return RunnerFacts(runner=runner, unknown_reason=reason, verdict_dir=image_dir)
144
+ source, origin = _runner_source(image_dir, sdk_root)
145
+ text = _read_text(source) if source.endswith(".py") else None
146
+ return RunnerFacts(
147
+ runner=runner, source=source, origin=origin,
148
+ has_guard=text is not None and runner_has_guard(text), verdict_dir=image_dir,
149
+ )
150
+
151
+
152
+ def plan_atoc_guard(
153
+ method: str, entry_id: str, flash_args: Any, artefact_path: str, sdk_root: str,
154
+ replace_atoc: bool,
155
+ ) -> tuple[ReplaceAtocDecision, str]:
156
+ """The guard decision for one entry, plus the dir its verdict lands under
157
+ (`""` for a non-west entry). `flash_args` must already be absolutised."""
158
+ if method != WEST_FLASH:
159
+ return decide_replace_atoc(method, entry_id, RunnerFacts(), replace_atoc), ""
160
+ facts = runner_facts(flash_args, zephyr_west_build_dir(flash_args, artefact_path), sdk_root)
161
+ return decide_replace_atoc(method, entry_id, facts, replace_atoc), facts.verdict_dir
162
+
163
+
164
+ def entries_taking_the_flag(
165
+ candidates: Iterable[tuple[str, str, Any, str]], sdk_root: str
166
+ ) -> list[str]:
167
+ """The ids of every `(method, entry_id, flash_args, artefact_path)` that
168
+ `--replace-atoc` would actually be appended to."""
169
+ return [
170
+ entry_id
171
+ for method, entry_id, flash_args, artefact_path in candidates
172
+ if plan_atoc_guard(method, entry_id, flash_args, artefact_path, sdk_root, True)[0].append
173
+ ]
174
+
175
+
176
+ def verdict_path(verdict_dir: str) -> str:
177
+ return os.path.join(verdict_dir, VERDICT_DIR, VERDICT_FILE)
178
+
179
+
180
+ def clear_stale_verdict(verdict_dir: str, remove: Callable[[str], None] = os.remove) -> str | None:
181
+ """Remove a previous attempt's verdict before spawning (see the module
182
+ docstring). Returns why it could not be removed, else `None`; the caller
183
+ then distrusts whatever file is there afterwards."""
184
+ path = verdict_path(verdict_dir)
185
+ try:
186
+ remove(path)
187
+ except FileNotFoundError:
188
+ return None
189
+ except OSError as err:
190
+ return f"a verdict left by an earlier run at {path} could not be removed first ({err})"
191
+ return None
192
+
193
+
194
+ def read_verdict(verdict_dir: str) -> GuardVerdict | str:
195
+ """This attempt's verdict, or why there is none to use."""
196
+ path = verdict_path(verdict_dir)
197
+ if not os.path.exists(path):
198
+ return (
199
+ f"no {path} was written for this attempt -- the runner stopped before its "
200
+ "guard ran, or is not one that writes a verdict"
201
+ )
202
+ text = _read_text(path)
203
+ if text is None:
204
+ return f"{path} could not be read"
205
+ result = parse_verdict(text)
206
+ return result if isinstance(result, GuardVerdict) else f"{path}: {result}"
207
+
208
+
209
+ def guarded_failure_message(
210
+ west_message: str, entry_id: str, verdict_dir: str, stale: str | None,
211
+ earlier: Mapping[str, str], *, refused_west_message: str | None = None,
212
+ ) -> tuple[str, bool]:
213
+ """The message for a failed `west flash` on a guarded runner, and whether
214
+ the guard refused it. `earlier`: ATOC section -> the entry of this run
215
+ that wrote it. `refused_west_message`, when given, stands in for
216
+ `west_message` on a refusal only (tan-cli#1426: west's tail without its
217
+ runner-loading warnings)."""
218
+ verdict = stale if stale is not None else read_verdict(verdict_dir)
219
+ if isinstance(verdict, str):
220
+ return west_message + unreadable_note(verdict), False
221
+ if verdict.refused:
222
+ reported = west_message if refused_west_message is None else refused_west_message
223
+ return f"{refusal_message(entry_id, verdict, earlier)} West reported: {reported}", True
224
+ return f"{west_message} ({passed_note(verdict, failed=True)}; the failure came after it)", False
225
+
226
+
227
+ def guarded_success(
228
+ ok_message: str, entry_id: str, verdict_dir: str, stale: str | None, source: str
229
+ ) -> tuple[str, str | None, tuple[str, ...]]:
230
+ """`(message, unguarded warning or None, ATOC sections this write put
231
+ there)` for a `west flash` that succeeded on a guarded runner. No usable
232
+ verdict means the guard did not run for this write -- never silence."""
233
+ if stale is not None:
234
+ return ok_message + unreadable_note(stale), None, ()
235
+ verdict = read_verdict(verdict_dir)
236
+ if isinstance(verdict, GuardVerdict) and verdict.refused:
237
+ verdict = f"it says {verdict.status!r}, yet west flash succeeded"
238
+ if isinstance(verdict, str):
239
+ return ok_message, no_verdict_after_success(entry_id, verdict, source), ()
240
+ return f"{ok_message}; {passed_note(verdict)}", None, verdict.allowed