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,178 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """The Python floor `tan build` and `tan doctor` both enforce -- ONE
3
+ composition (tan-cli#1317), pure `tan/core` so neither command imports the other.
4
+
5
+ `effective_python_floor` is the higher of the alp-sdk manifest's
6
+ `prerequisites.pythonMinVersion` and the floor Zephyr's CMake itself enforces
7
+ (`zephyr_python_floor`).
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import re
13
+ from pathlib import Path
14
+
15
+ from tan.core.bootstrap import BOOTSTRAP_MANIFEST_SCHEMA_VERSION
16
+
17
+ #: Zephyr's own floor, from `<zephyr>/cmake/modules/python.cmake`'s
18
+ #: `set(PYTHON_MINIMUM_REQUIRED 3.12)`. The LAST-resort fallback --
19
+ #: `zephyr_python_floor` reads the real file when a workspace resolves, and
20
+ #: (tan-cli#606) prefers alp-sdk's own manifest-declared
21
+ #: `zephyr.pythonMinVersion` over this constant when no workspace resolves but
22
+ #: a manifest does; this is what is left once BOTH are unavailable, so a
23
+ #: Zephyr bump raises the floor on the customer's machine without waiting for
24
+ #: a tan release only via one of those two live reads, never this one.
25
+ ZEPHYR_PYTHON_FLOOR = (3, 12)
26
+
27
+ #: The floor `metadata/bootstrap.json` is assumed to declare when no manifest
28
+ #: resolves at all -- used ONLY as the `manifest_floor` input to `max()` below,
29
+ #: never as a verdict by itself. It mirrors `crate::util::MIN_PYTHON`
30
+ #: (`crates/tan-cli/src/util.rs`), which is frozen at 3.10 and does NOT track
31
+ #: `metadata/bootstrap.json` -- that Rust constant and the manifest's declared
32
+ #: `pythonMinVersion` are two independently-edited numbers, not one fact, and
33
+ #: they can and do drift apart (the manifest is mid-raise to 3.12 as of this
34
+ #: writing; the oracle constant is not). The manifest is the authority: when it
35
+ #: resolves AND declares `pythonMinVersion`, that number is read live and this
36
+ #: constant is not consulted for the verdict -- but a manifest that resolves
37
+ #: while omitting the key still falls back to this same constant (see
38
+ #: `resolve_manifest_python_floor`/`_collect` below), so this is not a
39
+ #: no-manifest-only fallback. `ZEPHYR_PYTHON_FLOOR` above still composes with
40
+ #: it via `max()` either way, so a resolvable SDK checkout with the key present
41
+ #: never depends on this value being current.
42
+ FALLBACK_PYTHON_FLOOR = (3, 10)
43
+
44
+
45
+ def _read_text(path: Path) -> str | None:
46
+ try:
47
+ return path.read_text(encoding="utf-8", errors="replace")
48
+ except (OSError, ValueError):
49
+ return None
50
+
51
+
52
+ def parse_two(raw: str) -> tuple[int, int] | None:
53
+ match = re.search(r"(\d+)\.(\d+)", raw)
54
+ return (int(match.group(1)), int(match.group(2))) if match else None
55
+
56
+
57
+ def zephyr_python_floor(
58
+ zephyr_base: str | None, *, manifest_zephyr_floor: tuple[int, int] | None = None
59
+ ) -> tuple[tuple[int, int], str]:
60
+ """The floor Zephyr's CMake will actually enforce, and where it came from.
61
+
62
+ Read from `<zephyr_base>/cmake/modules/python.cmake` when that resolves,
63
+ because THAT is the file whose `PYTHON_MINIMUM_REQUIRED` aborts the build --
64
+ a constant compiled into tan goes stale the moment Zephyr bumps it, and a
65
+ stale floor here reintroduces exactly the silent gap this command exists to
66
+ close.
67
+
68
+ When it does NOT resolve, `manifest_zephyr_floor` -- alp-sdk's OWN declared
69
+ `zephyr.pythonMinVersion` (tan-cli#606), when the caller's manifest read
70
+ found one -- is now preferred over `ZEPHYR_PYTHON_FLOOR`: a fact alp-sdk
71
+ already publishes beats a constant compiled into tan, the same reasoning
72
+ that prefers `python.cmake` itself one level up. `ZEPHYR_PYTHON_FLOOR`
73
+ remains the LAST resort, for an SDK whose manifest predates that key (or
74
+ when no manifest resolves at all) -- every host at `tan bootstrap` time
75
+ used to land here unconditionally; now only a manifest-less one does.
76
+
77
+ `zephyr_base` is a plain path in, not necessarily `$ZEPHYR_BASE` itself --
78
+ THIS function has no opinion on where it came from, only `_collect` (this
79
+ module's `hostPython`/`pythonFloor` caller) does. As of tan-cli#301,
80
+ `_collect` passes the resolved workspace's `zephyr/` subtree -- the SAME
81
+ `tan.core.venv.west_workspace_dir` result `zephyrWorkspace` reports -- when
82
+ one resolved, a literal `$ZEPHYR_BASE` read only when no workspace resolved
83
+ at all, and `None` (landing on `ZEPHYR_PYTHON_FLOOR` below) when neither
84
+ does; that is the three-way split the resulting `source` string names. The
85
+ OTHER caller, `tan.commands.bootstrap_cmd.resolve_python_floor`, still
86
+ passes a literal `$ZEPHYR_BASE` read directly -- `tan bootstrap` runs before
87
+ any workspace can have resolved, so there is nothing else for it to prefer.
88
+
89
+ **The fallback names WHICH of three causes fired (tan-cli#488 defect 7).**
90
+ It used to be one hardcoded string -- "no $ZEPHYR_BASE workspace on this
91
+ host to read `cmake/modules/python.cmake` from" -- for every way the read
92
+ could fail, but only ONE of the three causes below makes that true. A
93
+ `.west` workspace mid-`west update` (`zephyr_workspace_check`'s own
94
+ "legitimate, working-in-progress host state") resolves a real
95
+ `zephyr_base` whose `cmake/modules/python.cmake` simply is not there yet
96
+ -- reported by `_collect` as `workspace`/`zephyrWorkspace` BOTH passing,
97
+ in the same envelope that then blamed a `$ZEPHYR_BASE` env var never
98
+ consulted for this call (`_collect` feeds this function the RESOLVED
99
+ workspace's own `zephyr/` subtree, never `$ZEPHYR_BASE` itself, once a
100
+ workspace resolves -- see above). `jlink_flash_device` fixed the identical
101
+ shape for its own three-cause fallback in tan-cli#310; this mirrors it.
102
+ """
103
+ if manifest_zephyr_floor is not None:
104
+ fallback_floor = manifest_zephyr_floor
105
+ fallback_label = (
106
+ f"alp-sdk metadata/bootstrap.json zephyr.pythonMinVersion "
107
+ f"{fallback_floor[0]}.{fallback_floor[1]}"
108
+ )
109
+ else:
110
+ fallback_floor = ZEPHYR_PYTHON_FLOOR
111
+ fallback_label = f"tan's built-in pin {fallback_floor[0]}.{fallback_floor[1]}"
112
+
113
+ if zephyr_base:
114
+ path = Path(zephyr_base) / "cmake" / "modules" / "python.cmake"
115
+ text = _read_text(path)
116
+ if text is not None:
117
+ match = re.search(r"PYTHON_MINIMUM_REQUIRED\s+(\d+)\.(\d+)", text)
118
+ if match is not None:
119
+ return (int(match.group(1)), int(match.group(2))), str(path)
120
+ return fallback_floor, (
121
+ f"Zephyr's PYTHON_MINIMUM_REQUIRED, from {fallback_label} -- {path} was "
122
+ f"read but did not declare a parseable PYTHON_MINIMUM_REQUIRED"
123
+ )
124
+ return fallback_floor, (
125
+ f"Zephyr's PYTHON_MINIMUM_REQUIRED, from {fallback_label} -- {path} could not "
126
+ f"be read (a `.west` workspace mid-`west update` is a legitimate, "
127
+ f"working-in-progress host state, not a broken one)"
128
+ )
129
+ return fallback_floor, (
130
+ f"Zephyr's PYTHON_MINIMUM_REQUIRED, from {fallback_label} -- no $ZEPHYR_BASE "
131
+ f"workspace on this host to read `cmake/modules/python.cmake` from"
132
+ )
133
+
134
+
135
+ def read_manifest_floors(sdk_root: str | None) -> tuple[tuple[int, int], tuple[int, int] | None]:
136
+ """`(manifest_floor, zephyr_manifest_floor)` from `<sdk>/metadata/bootstrap.json`
137
+ with the SAME fallbacks `tan doctor`'s `_load_manifest` applies: an absent,
138
+ unparseable, wrong-schema or `prerequisites`-less manifest yields
139
+ `(FALLBACK_PYTHON_FLOOR, None)`."""
140
+ if sdk_root is None:
141
+ return FALLBACK_PYTHON_FLOOR, None
142
+ text = _read_text(Path(sdk_root) / "metadata" / "bootstrap.json")
143
+ if text is None:
144
+ return FALLBACK_PYTHON_FLOOR, None
145
+ try:
146
+ doc = json.loads(text)
147
+ except ValueError:
148
+ return FALLBACK_PYTHON_FLOOR, None
149
+ if not isinstance(doc, dict):
150
+ return FALLBACK_PYTHON_FLOOR, None
151
+ version = doc.get("schemaVersion")
152
+ if (
153
+ not isinstance(version, int)
154
+ or isinstance(version, bool)
155
+ or version != BOOTSTRAP_MANIFEST_SCHEMA_VERSION
156
+ ):
157
+ return FALLBACK_PYTHON_FLOOR, None
158
+ prerequisites = doc.get("prerequisites")
159
+ if not isinstance(prerequisites, dict):
160
+ return FALLBACK_PYTHON_FLOOR, None
161
+ manifest = parse_two(str(prerequisites.get("pythonMinVersion") or "")) or FALLBACK_PYTHON_FLOOR
162
+ zephyr = doc.get("zephyr")
163
+ raw = zephyr.get("pythonMinVersion") if isinstance(zephyr, dict) else None
164
+ return manifest, parse_two(raw) if isinstance(raw, str) else None
165
+
166
+
167
+ def effective_python_floor(
168
+ manifest_floor: tuple[int, int],
169
+ zephyr_base: str | None,
170
+ manifest_zephyr_floor: tuple[int, int] | None = None,
171
+ ) -> tuple[tuple[int, int], str]:
172
+ """`(floor, source)`: the highest anything in the build chain enforces."""
173
+ zephyr_floor, zephyr_source = zephyr_python_floor(
174
+ zephyr_base, manifest_zephyr_floor=manifest_zephyr_floor
175
+ )
176
+ if zephyr_floor >= manifest_floor:
177
+ return zephyr_floor, zephyr_source
178
+ return manifest_floor, "alp-sdk metadata/bootstrap.json pythonMinVersion"