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,198 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Pure logic for `tan model doctor`: one row per compiler-backend toolchain.
3
+
4
+ Under alp-sdk ADR-0028 the model engine (`tan.model.build`, the adapters
5
+ under `tan.model.adapters`) moved OUT of alp-sdk and into tan, so tan is now
6
+ the customer's only diagnostic surface for the NPU compiler toolchains
7
+ `tan model build` shells out to. When a compile silently produces nothing,
8
+ this is what turns "nothing happened" into "`dxcom` is not on PATH; it is
9
+ license-gated and Linux-only" -- that is the whole job.
10
+
11
+ **This module never spawns anything and never touches `metadata/`.** Every
12
+ fact it renders comes from an already-computed `available`/`version` pair the
13
+ caller (`tan.commands.model_cmd`) obtained by calling each registered
14
+ adapter's own `is_available()` -- `shutil.which(...)`/an env-var directory
15
+ check in every adapter today (`tan/model/adapters/{cpu,ethos_u,drpai,
16
+ deepx,executorch}.py`) -- and, for the one backend with a non-spawning
17
+ version probe (`ethos_u`'s `importlib.metadata` read), a version string. No
18
+ function here calls `subprocess.run`/`Popen`, imports an adapter, or reads a
19
+ SoM/SoC file: `doctor` must be safe to run on a completely broken host, and
20
+ "safe" here specifically means it cannot invoke a compiler as a side effect
21
+ of asking whether one is installed.
22
+
23
+ `data.optional[]` (`optional_row`) is the one row shape that is NOT a
24
+ "can tan compile for this backend" verdict: an unavailable OPTIONAL
25
+ prerequisite -- today the vendor vela `.ini` a licensed customer points
26
+ `ALP_VELA_CONFIG` at -- is not a fault and must not read as one. It is kept out
27
+ of `backends[]` for that reason; see `optional_row`.
28
+
29
+ `registry_backends` still reads the *shape* of `tan.model.build._ADAPTERS`
30
+ (which backends the registry actually declares, in what order) rather than
31
+ hard-coding the four-entry list a snapshot of that registry happens to be
32
+ today -- so a fifth adapter (or a genuinely distinct `"executorch"` backend,
33
+ should one ever replace `ExecutorchAdapter`'s current `backend == "cpu"`)
34
+ shows up here automatically instead of silently going unprobed.
35
+ """
36
+ from __future__ import annotations
37
+
38
+ from dataclasses import dataclass
39
+
40
+ from tan.model.adapters import CompilerAdapter
41
+
42
+ #: The external binary/script each backend's adapter actually spawns to
43
+ #: compile a model -- `None` for a backend with no external tool at all.
44
+ #: `cpu`'s two adapters (`CpuAdapter`, `ExecutorchAdapter`) are both pure
45
+ #: byte passthroughs (`tan/model/adapters/cpu.py`,
46
+ #: `tan/model/adapters/executorch.py`) -- neither spawns anything, so `cpu`
47
+ #: reports no tool.
48
+ #:
49
+ #: `drpai`'s entry names the vendor TUTORIAL SCRIPT the adapter invokes
50
+ #: (`compile_onnx_model_quant.py`, `tan/model/adapters/drpai.py`'s own
51
+ #: `cmd = ["python3", str(script), ...]`), not the `python3` interpreter --
52
+ #: `python3` is present on essentially every host and naming it would tell a
53
+ #: customer nothing they can act on; the toolchain checkout the script lives
54
+ #: inside (`$ALP_DRPAI_TVM_HOME/tutorials/...`) is the thing that is
55
+ #: actually absent.
56
+ BACKEND_TOOLS: dict[str, str | None] = {
57
+ "cpu": None,
58
+ "ethos_u": "vela",
59
+ "drpai": "compile_onnx_model_quant.py",
60
+ "deepx_dxm1": "dxcom",
61
+ }
62
+
63
+ #: Actionable unavailability guidance, keyed by backend -- surfaced ONLY when
64
+ #: that backend's `available` is False (`backend_row` below). Each string is
65
+ #: derived from what that backend's adapter actually checks in
66
+ #: `is_available()`; never an install instruction this repo cannot support.
67
+ _UNAVAILABLE_REASONS: dict[str, str] = {
68
+ # VelaAdapter.is_available(): shutil.which("vela") is not None
69
+ # (tan/model/adapters/ethos_u.py). This repo's own pinned path is the
70
+ # `model-compile` extra (`pyproject.toml`: `ethos-u-vela>=3.9`), which
71
+ # carries the floor -- name it instead of a bare `pip install
72
+ # ethos-u-vela` that could drift from that pin.
73
+ "ethos_u": "vela not on PATH; pip install tan-cli[model-compile]",
74
+ # deepx_dxm1's row is NOT `DeepxAdapter.is_available()` (see
75
+ # `_deepx_dxm1_status` in `tan.commands.model_cmd`): that adapter method
76
+ # ORs in a second arm -- `ALP_DEEPX_SDK_HOME` pointing at a directory --
77
+ # that `DeepxAdapter.compile()` never reads (it always shells the bare
78
+ # `dxcom` off PATH), so gating on it reported this row green on hosts
79
+ # where the next `model build` immediately raised `FileNotFoundError:
80
+ # 'dxcom'`. This default reason covers the case neither PATH nor the env
81
+ # var is set; the ALP_DEEPX_SDK_HOME-set-but-no-dxcom case gets its own
82
+ # caveat reason from `_deepx_dxm1_status`, passed through `backend_row`'s
83
+ # `reason` override below. Its module doc: the dx-com wheel "is not
84
+ # redistributable" and is "verified -- Linux x86_64, CPython 3.12" only,
85
+ # so "license-gated, Linux-only" is what a customer needs to know before
86
+ # chasing a `pip install` that does not exist for them.
87
+ "deepx_dxm1": "dxcom not on PATH; license-gated, Linux-only",
88
+ # drpai's row is likewise NOT the bare `DrpaiAdapter.is_available()`
89
+ # (see `_drpai_status` in `tan.commands.model_cmd`): that adapter method
90
+ # only checks `ALP_DRPAI_TVM_HOME` names a directory, never that the
91
+ # vendor tutorial script `DrpaiAdapter.compile()` actually spawns
92
+ # (`tutorials/compile_onnx_model_quant.py`) exists under it, so an
93
+ # unpacked-but-unbuilt toolchain tree reported green here too. This
94
+ # default reason covers the case the env var isn't set at all; the
95
+ # var-set-but-script-missing case gets its own caveat reason from
96
+ # `_drpai_status`. Its module doc: the toolchain is "large and
97
+ # account-/source-gated ... so it is NOT bundled" -- there is no
98
+ # pip/apt install to name here either.
99
+ "drpai": (
100
+ "ALP_DRPAI_TVM_HOME not set to a built rzv_drp-ai_tvm install; "
101
+ "account-gated toolchain, not bundled"
102
+ ),
103
+ }
104
+
105
+
106
+ @dataclass(frozen=True)
107
+ class BackendRow:
108
+ """One backend's toolchain verdict -- the wire shape `tan model doctor`
109
+ reports per backend under `data.backends[]`."""
110
+
111
+ backend: str
112
+ tool: str | None
113
+ available: bool
114
+ version: str | None
115
+ reason: str | None
116
+
117
+ def as_dict(self) -> dict[str, object]:
118
+ return {
119
+ "backend": self.backend,
120
+ "tool": self.tool,
121
+ "available": self.available,
122
+ "version": self.version,
123
+ "reason": self.reason,
124
+ }
125
+
126
+
127
+ def backend_row(
128
+ backend: str, *, available: bool, version: str | None, reason: str | None = None
129
+ ) -> BackendRow:
130
+ """Assemble one row from an ALREADY-PROBED `available`/`version` pair.
131
+
132
+ Pure -- no IO here at all, real or otherwise; the caller did the probing.
133
+ `reason` is populated only when `available` is False (never a "why it
134
+ works" note on a green row); `version` is discarded when `available` is
135
+ False even if the caller passed one in, since an unavailable backend has
136
+ no running toolchain for a version to describe.
137
+
138
+ `reason` is an explicit override for `_UNAVAILABLE_REASONS[backend]`'s
139
+ default, used when the caller's own probe (`_deepx_dxm1_status`/
140
+ `_drpai_status` in `tan.commands.model_cmd`) found a more specific
141
+ situation than "nothing at all is set" -- e.g. the toolchain env var IS
142
+ set but the thing `compile()` actually needs under/via it is still
143
+ absent. `None` (the default) falls back to the generic per-backend
144
+ reason, same as before this parameter existed.
145
+ """
146
+ fallback_reason = reason if reason is not None else _UNAVAILABLE_REASONS.get(backend)
147
+ return BackendRow(
148
+ backend=backend,
149
+ tool=BACKEND_TOOLS.get(backend),
150
+ available=available,
151
+ version=version if available else None,
152
+ reason=None if available else fallback_reason,
153
+ )
154
+
155
+
156
+ def optional_row(
157
+ backend: str, *, tool: str, available: bool, reason: str | None
158
+ ) -> BackendRow:
159
+ """One OPTIONAL prerequisite's row -- the same five keys as `backend_row`,
160
+ reported under `data.optional[]` rather than `data.backends[]`.
161
+
162
+ A SEPARATE LIST, not a fifth backend row, because the two answer different
163
+ questions and a consumer keyed on `backends[]` reads one row per backend.
164
+ `available: false` there means "tan cannot compile for this backend at
165
+ all"; here it means "this backend works, and a vendor-tuned enhancement is
166
+ not installed". Rendering the second as the first would tell an unlicensed
167
+ customer their toolchain is broken when it is complete and correct: without
168
+ a vendor `.ini` vela uses Arm's own built-in system config, which is what
169
+ the arena/SRAM figures tan reports already describe.
170
+
171
+ `version` is always None: this is a data file, not a toolchain, and there
172
+ is no version of it to probe without reading a customer's proprietary file.
173
+ `reason` is dropped when available, exactly as `backend_row` drops it --
174
+ and it is REQUIRED from the caller when it is not, because
175
+ `_UNAVAILABLE_REASONS[backend]` answers the other question (`ethos_u`'s
176
+ entry is about `vela` not being on PATH) and must never fall through to
177
+ this row."""
178
+ return BackendRow(
179
+ backend=backend,
180
+ tool=tool,
181
+ available=available,
182
+ version=None,
183
+ reason=None if available else reason,
184
+ )
185
+
186
+
187
+ def registry_backends(adapters: list[CompilerAdapter]) -> list[str]:
188
+ """Distinct `backend` values across `adapters`, in first-seen registry
189
+ order -- mirrors `build_model`'s own `by_backend` grouping
190
+ (`tan/model/build.py`): a backend with MORE THAN ONE adapter (`cpu`
191
+ today: `CpuAdapter` + `ExecutorchAdapter`, both `backend == "cpu"`)
192
+ reports as one row, not two -- `doctor` answers "can tan compile for
193
+ cpu", not "how many adapter classes register under cpu"."""
194
+ seen: list[str] = []
195
+ for adapter in adapters:
196
+ if adapter.backend not in seen:
197
+ seen.append(adapter.backend)
198
+ return seen
tan/core/model_host.py ADDED
@@ -0,0 +1,48 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """What the host-tier `tan model prep` / `run` / `ab` need installed
3
+ (tan-cli#1287).
4
+
5
+ Their numeric dependencies (`numpy`, `onnxruntime`, `onnx`, `sympy`) live in the
6
+ optional `model` extra, never in the base install and never in the frozen
7
+ release assets (`.[monitor]` only): onnxruntime ships no macOS x86_64 wheel and
8
+ adds tens of MB for a verb most users never run. A missing one is a coded
9
+ refusal naming `pip install "tan-cli[model]"`, the `monitor.pyserial-missing`
10
+ pattern -- found with `importlib.util.find_spec`, so the check itself imports
11
+ nothing heavy.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import importlib.util
17
+
18
+ #: Modules each verb imports (lazily) from the `model` extra.
19
+ REQUIRED_MODULES = {
20
+ "prep": ("numpy", "onnxruntime", "onnx", "sympy"),
21
+ "run": ("numpy", "onnxruntime"),
22
+ "ab": ("numpy", "onnxruntime"),
23
+ }
24
+
25
+ #: The one install hint every refusal quotes.
26
+ INSTALL_HINT = 'pip install "tan-cli[model]"'
27
+
28
+
29
+ def missing_extra_modules(verb: str) -> list[str]:
30
+ """The `verb`'s extra modules that are not importable, in declared order."""
31
+ return [m for m in REQUIRED_MODULES[verb] if importlib.util.find_spec(m) is None]
32
+
33
+
34
+ def extra_missing_message(verb: str, missing: list[str]) -> str:
35
+ return (
36
+ f"`tan model {verb}` needs the optional `model` extra ({', '.join(missing)} not "
37
+ f"installed). Install it with `{INSTALL_HINT}`. A frozen `tan` binary does not "
38
+ "bundle it; use a pip-installed tan for this command."
39
+ )
40
+
41
+
42
+ def broken_extra_message(verb: str, detail: str) -> str:
43
+ """The same refusal when the modules are findable but fail to import (a
44
+ damaged install): the import error is quoted, not mistaken for a module name."""
45
+ return (
46
+ f"`tan model {verb}` could not import the optional `model` extra's dependencies "
47
+ f"({detail}). Reinstall it with `{INSTALL_HINT}`."
48
+ )
tan/core/model_list.py ADDED
@@ -0,0 +1,112 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Pure engine for `tan model list` (tan-cli#674): what `board.yaml` declares
3
+ under `models:`, next to what `--out` already holds for each one -- read-only,
4
+ no SDK metadata consulted at all. Unlike `build`/`check`, nothing this reports
5
+ comes out of `metadata/**`: there is no compile target to resolve and no NPU
6
+ support table to read, only a board.yaml already in hand and a directory to
7
+ `stat()`. `tan.commands.model_cmd` does the board.yaml IO/validation shared
8
+ with `build`/`check` (`_load_board`/`_require_models_list`/
9
+ `_require_model_entry`); this module is the per-model shaping, mirroring the
10
+ `tan.core.model_check`/`tan.core.model_doctor` split (the engine computes,
11
+ this renders/serialises).
12
+
13
+ The one piece of real IO here -- reading a package's own manifest back off
14
+ disk (`tan.model.package.read_manifest_file`) to report its size and whether
15
+ it is stale -- is itself read-only and already the pattern `model_cmd.
16
+ _shipped_caveat_issues` uses for the same reason: describe the ARTIFACT, not
17
+ what a build call happened to hold in memory.
18
+
19
+ A readback failure there (a corrupt/truncated package, or a `source` that no
20
+ longer resolves) is reported the SAME way `_shipped_caveat_issues` reports its
21
+ own readback failure: a `model.artifact-stale-unknown` WARNING `Issue`, never
22
+ silent. Silence would be indistinguishable from "not stale" -- a `board.yaml`
23
+ naming a `source` that no longer exists, with a perfectly good package already
24
+ on disk, used to answer `{"exists": true, "bytes": N}` with no sign the
25
+ staleness check never ran at all (tan-cli#674 review MAJOR 1). The row itself
26
+ still degrades to the bare `exists: True` shape (see `_artifact_status`): the
27
+ package IS there, and downgrading that to a failure over a diagnostic
28
+ comparison would be the wrong trade -- exactly `_shipped_caveat_issues`'s own
29
+ reasoning, applied to a second readback.
30
+ """
31
+ from __future__ import annotations
32
+
33
+ import hashlib
34
+ from pathlib import Path
35
+ from typing import Any
36
+
37
+ from tan.envelope import Issue
38
+ from tan.model.package import read_manifest_file
39
+
40
+
41
+ def declared_sku(board_doc: dict) -> str | None:
42
+ """`board.yaml`'s `som.sku`, or `None`. Unlike `build`/`check`'s
43
+ `_require_sku`, a missing or malformed SKU does not refuse `list` --
44
+ naming what is declared and what is built needs no real SoM at all, so
45
+ this degrades instead of raising."""
46
+ som = board_doc.get("som")
47
+ sku = som.get("sku") if isinstance(som, dict) else None
48
+ return sku if isinstance(sku, str) and sku else None
49
+
50
+
51
+ def _artifact_status(
52
+ name: str, out_path: Path, source: Path
53
+ ) -> tuple[dict[str, Any], Issue | None]:
54
+ """`({"exists": False}, None)` for a package `tan model build` has not
55
+ written (or no longer holds); `({"exists": True, "bytes", "stale"}, None)`
56
+ for one that is on disk and whose staleness could be determined. `stale`
57
+ is `True` when the package's own recorded `src_sha` (read back from the
58
+ FILE, not a cached value) no longer matches the CURRENT `source` file's
59
+ hash -- i.e. the model was edited since the last `tan model build`.
60
+ `bytes`/`stale` are both best-effort: a corrupt/truncated package or a
61
+ `source` that no longer resolves degrades to the bare `exists: True` row
62
+ rather than refusing the whole list -- staleness is a bonus fact about a
63
+ build that DID succeed, not a reason to hide that it exists.
64
+
65
+ That degradation is never silent, though (tan-cli#674 review MAJOR 1): the
66
+ second element is a `model.artifact-stale-unknown` WARNING `Issue` whenever
67
+ the readback failed, `None` when it did not need to fall back at all --
68
+ the caller (`list_entry`) collects it into the envelope's `issues` rather
69
+ than reporting a clean row with no sign anything failed."""
70
+ if not out_path.is_file():
71
+ return {"exists": False}, None
72
+ status: dict[str, Any] = {"exists": True, "bytes": out_path.stat().st_size}
73
+ try:
74
+ manifest = read_manifest_file(out_path)
75
+ current_sha = hashlib.sha256(source.read_bytes()).digest()
76
+ except (OSError, ValueError) as err:
77
+ return status, Issue(
78
+ "model.artifact-stale-unknown",
79
+ "warning",
80
+ f"model '{name}': found {out_path} but could not confirm whether "
81
+ f"it is stale against {source}: {type(err).__name__}: {err}",
82
+ )
83
+ status["stale"] = manifest.src_sha != current_sha
84
+ return status, None
85
+
86
+
87
+ def list_entry(name: str, source: Path, out_dir: Path) -> tuple[dict[str, Any], Issue | None]:
88
+ """One declared model's `list` row: its name, resolved source path, and
89
+ the artifact `tan model build` would write for it under `out_dir` -- the
90
+ SAME `{name}.alpmodel` naming `build_model` uses (`tan/model/build.py`),
91
+ read here, never compiled. The second element is `_artifact_status`'s own
92
+ readback-failure `Issue`, or `None`."""
93
+ artifact, issue = _artifact_status(name, out_dir / f"{name}.alpmodel", source)
94
+ return {"name": name, "source": str(source), "artifact": artifact}, issue
95
+
96
+
97
+ def render_list_text(data: dict[str, Any]) -> list[str]:
98
+ """Every declared model's line, in board.yaml order -- `[]` for no
99
+ declared models, the same split `build`/`check`'s own text branches make
100
+ between "no lines" and "needs a filler saying so", decided by the caller
101
+ (`model_cmd.finish`)."""
102
+ lines: list[str] = []
103
+ for m in data.get("models") or []:
104
+ artifact = m.get("artifact") or {}
105
+ if not artifact.get("exists"):
106
+ lines.append(f"{m['name']}: not built ({m['source']})")
107
+ continue
108
+ size = artifact.get("bytes")
109
+ size_note = f", {size} bytes" if size is not None else ""
110
+ stale_note = ", STALE (source changed since last build)" if artifact.get("stale") else ""
111
+ lines.append(f"{m['name']}: built{size_note}{stale_note}")
112
+ return lines