@typecad/framework-zephyr 1.0.0-alpha.16 → 1.0.0-alpha.19

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 (210) hide show
  1. package/LICENSE +202 -21
  2. package/README.md +49 -87
  3. package/dist/as-built.d.ts +2 -2
  4. package/dist/as-built.js +2 -2
  5. package/dist/audit.d.ts +111 -0
  6. package/dist/audit.js +416 -0
  7. package/dist/boardgen.d.ts +1 -9
  8. package/dist/boardgen.js +288 -47
  9. package/dist/chips/resolve.js +20 -0
  10. package/dist/chips/types.d.ts +22 -1
  11. package/dist/debug-codegen.js +1 -1
  12. package/dist/display/bindings.d.ts +55 -0
  13. package/dist/display/bindings.js +316 -0
  14. package/dist/display/gfx.d.ts +2 -3
  15. package/dist/display/gfx.js +166 -154
  16. package/dist/display/index.js +20 -1
  17. package/dist/display/mipi-dbi-host.d.ts +9 -0
  18. package/dist/display/mipi-dbi-host.js +174 -0
  19. package/dist/display/profiles.d.ts +109 -4
  20. package/dist/display/profiles.js +270 -7
  21. package/dist/display/touch-adapter.js +119 -49
  22. package/dist/display/ui-adapter-eink.d.ts +2 -0
  23. package/dist/display/ui-adapter-eink.js +4 -0
  24. package/dist/display/ui-adapter-gray.d.ts +8 -0
  25. package/dist/display/ui-adapter-gray.js +170 -0
  26. package/dist/display/ui-adapter-mono.d.ts +13 -0
  27. package/dist/display/ui-adapter-mono.js +230 -0
  28. package/dist/display/ui-adapter-native.d.ts +10 -0
  29. package/dist/display/ui-adapter-native.js +295 -0
  30. package/dist/display/ui-adapter-shared.d.ts +11 -0
  31. package/dist/display/ui-adapter-shared.js +122 -0
  32. package/dist/display/ui-adapter.js +510 -558
  33. package/dist/doctor.js +4 -4
  34. package/dist/dt-config/custom-board.js +2 -2
  35. package/dist/dt-config/kconfig.d.ts +62 -1
  36. package/dist/dt-config/kconfig.js +141 -38
  37. package/dist/dt-config/overlay.d.ts +15 -2
  38. package/dist/dt-config/overlay.js +433 -18
  39. package/dist/framework.manifest.d.ts +10 -4
  40. package/dist/framework.manifest.js +133 -17
  41. package/dist/index.d.ts +2 -0
  42. package/dist/index.js +14 -6
  43. package/dist/licenses.d.ts +2 -2
  44. package/dist/licenses.js +13 -92
  45. package/dist/lowering/can.d.ts +25 -0
  46. package/dist/lowering/can.js +97 -0
  47. package/dist/lowering/clock.d.ts +17 -0
  48. package/dist/lowering/clock.js +58 -0
  49. package/dist/lowering/fs.js +1 -1
  50. package/dist/lowering/gpio.js +0 -32
  51. package/dist/lowering/hid.d.ts +27 -0
  52. package/dist/lowering/hid.js +244 -0
  53. package/dist/lowering/http.js +264 -32
  54. package/dist/lowering/i2c.d.ts +8 -0
  55. package/dist/lowering/i2c.js +137 -5
  56. package/dist/lowering/i2s.d.ts +27 -0
  57. package/dist/lowering/i2s.js +98 -0
  58. package/dist/lowering/index.d.ts +9 -1
  59. package/dist/lowering/index.js +25 -1
  60. package/dist/lowering/interrupts.js +6 -0
  61. package/dist/lowering/matrix.d.ts +15 -0
  62. package/dist/lowering/matrix.js +63 -0
  63. package/dist/lowering/mqtt.js +110 -8
  64. package/dist/lowering/power.d.ts +3 -2
  65. package/dist/lowering/power.js +20 -45
  66. package/dist/lowering/pwm.js +25 -0
  67. package/dist/lowering/sensor.d.ts +2 -2
  68. package/dist/lowering/sensor.js +8 -4
  69. package/dist/lowering/strip.d.ts +16 -0
  70. package/dist/lowering/strip.js +70 -0
  71. package/dist/lowering/thread.js +5 -1
  72. package/dist/lowering/trace.d.ts +44 -0
  73. package/dist/lowering/trace.js +239 -0
  74. package/dist/lowering/uart.js +6 -1
  75. package/dist/lowering/usb.d.ts +3 -1
  76. package/dist/lowering/usb.js +16 -13
  77. package/dist/lowering/wdt.js +2 -29
  78. package/dist/sbom.d.ts +181 -0
  79. package/dist/sbom.js +901 -0
  80. package/dist/sdk/board-catalog-sync.d.ts +1 -3
  81. package/dist/sdk/board-catalog-sync.js +4 -10
  82. package/dist/strategy.d.ts +82 -46
  83. package/dist/strategy.js +639 -182
  84. package/dist/tmp-probe.d.ts +2 -0
  85. package/dist/tmp-probe.js +9 -0
  86. package/dist/toolchain/debug-config.d.ts +50 -90
  87. package/dist/toolchain/debug-config.js +239 -510
  88. package/dist/toolchain/env-check.d.ts +1 -3
  89. package/dist/toolchain/env-check.js +2 -7
  90. package/dist/toolchain/index.d.ts +28 -3
  91. package/dist/toolchain/index.js +498 -62
  92. package/dist/toolchain/runners.d.ts +16 -0
  93. package/dist/toolchain/runners.js +75 -0
  94. package/dist/toolchain/scaffold.d.ts +5 -2
  95. package/dist/toolchain/scaffold.js +79 -15
  96. package/dist/toolchain/west-discover.d.ts +6 -0
  97. package/dist/toolchain/west-discover.js +36 -13
  98. package/dist/toolchain/west-spawn.js +8 -2
  99. package/dist/west-inventory.d.ts +25 -0
  100. package/dist/west-inventory.js +97 -0
  101. package/installer/README.md +328 -328
  102. package/installer/install.sh +2 -2
  103. package/installer/templates/project/.typecad/activate-zephyr.ps1 +1 -1
  104. package/installer/templates/project/.typecad/activate-zephyr.sh +1 -1
  105. package/installer/templates/project/.vscode/settings.json +1 -1
  106. package/installer/templates/project/README.md +2 -2
  107. package/package.json +5 -5
  108. package/src/as-built.ts +206 -206
  109. package/src/audit.ts +529 -0
  110. package/src/boardgen.ts +265 -50
  111. package/src/chips/resolve.ts +21 -0
  112. package/src/chips/types.ts +10 -1
  113. package/src/display/bindings.ts +347 -0
  114. package/src/display/gfx.ts +318 -306
  115. package/src/display/index.ts +87 -70
  116. package/src/display/mipi-dbi-host.ts +183 -0
  117. package/src/display/profiles.ts +458 -139
  118. package/src/display/touch-adapter.ts +119 -49
  119. package/src/display/ui-adapter-eink.ts +13 -0
  120. package/src/display/ui-adapter-gray.ts +178 -0
  121. package/src/display/ui-adapter-mono.ts +238 -0
  122. package/src/display/ui-adapter-native.ts +304 -0
  123. package/src/display/ui-adapter-shared.ts +125 -0
  124. package/src/display/ui-adapter.ts +732 -781
  125. package/src/doctor.ts +4 -4
  126. package/src/dt-config/custom-board.ts +2 -2
  127. package/src/dt-config/kconfig.ts +647 -505
  128. package/src/dt-config/overlay.ts +1475 -1058
  129. package/src/framework.manifest.ts +659 -535
  130. package/src/index.ts +16 -6
  131. package/src/licenses.ts +346 -425
  132. package/src/lowering/can.ts +140 -0
  133. package/src/lowering/clock.ts +91 -0
  134. package/src/lowering/fs.ts +135 -135
  135. package/src/lowering/gpio.ts +0 -33
  136. package/src/lowering/hid.ts +261 -0
  137. package/src/lowering/http.ts +264 -32
  138. package/src/lowering/i2c.ts +142 -5
  139. package/src/lowering/i2s.ts +143 -0
  140. package/src/lowering/index.ts +18 -1
  141. package/src/lowering/interrupts.ts +6 -0
  142. package/src/lowering/matrix.ts +70 -0
  143. package/src/lowering/mqtt.ts +109 -8
  144. package/src/lowering/power.ts +41 -0
  145. package/src/lowering/pwm.ts +192 -167
  146. package/src/lowering/sensor.ts +159 -155
  147. package/src/lowering/strip.ts +81 -0
  148. package/src/lowering/thread.ts +5 -1
  149. package/src/lowering/trace.ts +270 -0
  150. package/src/lowering/uart.ts +6 -1
  151. package/src/lowering/usb.ts +16 -13
  152. package/src/lowering/wdt.ts +2 -25
  153. package/src/sbom.ts +1117 -0
  154. package/src/sdk/board-catalog-sync.ts +4 -25
  155. package/src/strategy.ts +2680 -2309
  156. package/src/toolchain/debug-config.ts +262 -522
  157. package/src/toolchain/env-check.ts +279 -285
  158. package/src/toolchain/index.ts +1792 -1359
  159. package/src/toolchain/runners.ts +80 -0
  160. package/src/toolchain/scaffold.ts +355 -296
  161. package/src/toolchain/west-discover.ts +35 -13
  162. package/src/toolchain/west-spawn.ts +174 -168
  163. package/src/west-inventory.ts +102 -0
  164. package/dist/async/timer-polyfill.d.ts +0 -10
  165. package/dist/async/timer-polyfill.js +0 -95
  166. package/dist/chips/board-overrides.d.ts +0 -7
  167. package/dist/chips/board-overrides.js +0 -11
  168. package/dist/chips/esp32.d.ts +0 -2
  169. package/dist/chips/esp32.js +0 -71
  170. package/dist/chips/esp32s3.d.ts +0 -2
  171. package/dist/chips/esp32s3.js +0 -103
  172. package/dist/chips/soc/.d.ts +0 -2
  173. package/dist/chips/soc/.js +0 -129
  174. package/dist/chips/soc/esp32.d.ts +0 -2
  175. package/dist/chips/soc/esp32.js +0 -120
  176. package/dist/chips/soc/esp32c3.d.ts +0 -2
  177. package/dist/chips/soc/esp32c3.js +0 -90
  178. package/dist/chips/soc/esp32c6.d.ts +0 -2
  179. package/dist/chips/soc/esp32c6.js +0 -109
  180. package/dist/chips/soc/esp32s3.d.ts +0 -2
  181. package/dist/chips/soc/esp32s3.js +0 -189
  182. package/dist/chips/soc/index.d.ts +0 -2
  183. package/dist/chips/soc/index.js +0 -23
  184. package/dist/chips/soc/nrf52840.d.ts +0 -2
  185. package/dist/chips/soc/nrf52840.js +0 -130
  186. package/dist/chips/soc/rp2040.d.ts +0 -2
  187. package/dist/chips/soc/rp2040.js +0 -141
  188. package/dist/chips/soc/rp2350a.d.ts +0 -2
  189. package/dist/chips/soc/rp2350a.js +0 -145
  190. package/dist/chips/soc/samd21g18a.d.ts +0 -2
  191. package/dist/chips/soc/samd21g18a.js +0 -143
  192. package/dist/chips/soc/stm32f411xe.d.ts +0 -2
  193. package/dist/chips/soc/stm32f411xe.js +0 -251
  194. package/dist/chips/xiao-ble.d.ts +0 -2
  195. package/dist/chips/xiao-ble.js +0 -100
  196. package/dist/lowering/pulse.d.ts +0 -7
  197. package/dist/lowering/pulse.js +0 -51
  198. package/dist/lowering/tone.d.ts +0 -10
  199. package/dist/lowering/tone.js +0 -63
  200. package/dist/lowering/worker-backing.d.ts +0 -14
  201. package/dist/lowering/worker-backing.js +0 -79
  202. package/dist/lowering/worker.d.ts +0 -6
  203. package/dist/lowering/worker.js +0 -14
  204. package/dist/sdk/board-data.generated.d.ts +0 -2
  205. package/dist/sdk/board-data.generated.js +0 -4
  206. package/dist/sdk/catalog-walker.d.ts +0 -90
  207. package/dist/sdk/catalog-walker.js +0 -682
  208. package/dist/sdk/dts-reader.d.ts +0 -83
  209. package/dist/sdk/dts-reader.js +0 -596
  210. package/src/debug-codegen.ts +0 -207
@@ -11,7 +11,7 @@
11
11
  // 1. `west` already on PATH (env already activated / global install).
12
12
  // 2. $ZEPHYR_BASE venv: ${ZEPHYR_BASE}/../.venv/<python> -m west.
13
13
  // 3. micromamba env from @typecad/zephyr-installer (invoked via `micromamba run`,
14
- // so cuttlefish builds work with NO manual activation).
14
+ // so typecad-hal builds work with NO manual activation).
15
15
  // 4. Well-known workspace layouts: ~/zephyrproject/.venv, /opt/zephyrproject/.
16
16
  // venv, etc.
17
17
  // 5. System pythons (`python`, `python3`, `py`) via `-m west`.
@@ -142,6 +142,33 @@ export function discoverFromZephyrBase(): WestInstall | null {
142
142
  };
143
143
  }
144
144
 
145
+ /** The installer-written SDK dir for the default micromamba env, when the
146
+ * installer has run on this machine. Read WITHOUT requiring micromamba
147
+ * mode — a PATH/venv west install is enriched with it so builds spawned
148
+ * through it still pin the installer's SDK (otherwise Zephyr's CMake
149
+ * searches $HOME and can pick up a stray, older SDK). */
150
+ export function installerSdkDir(): string | undefined {
151
+ const mm = findMicromamba();
152
+ if (!mm) return undefined;
153
+ const envName = process.env.TYPECAD_ZEPHYR_ENV || 'zephyr';
154
+ const envDir = join(mm.rootPrefix, 'envs', envName);
155
+ if (!existsSync(envDir)) return undefined;
156
+ const sdk = readMicromambaEnvVar(envDir, 'TYPECAD_ZEPHYR_SDK_INSTALL_DIR');
157
+ return sdk && existsSync(sdk) ? sdk : undefined;
158
+ }
159
+
160
+ /**
161
+ * The micromamba env created by `@typecad/zephyr-installer`. The env's west
162
+ * lives at envs/<name>/bin/west (POSIX) or Scripts/west.exe (Windows). Found
163
+ * installs are invoked via `micromamba run -n <name> west …` (see
164
+ * west-spawn.ts), which sets up the env's full PATH (cmake/ninja/dtc) AND runs
165
+ * the activation hook (ZEPHYR_BASE / ZEPHYR_SDK_INSTALL_DIR) — so builds work
166
+ * with NO manual `micromamba activate`. This is what makes a fresh
167
+ * `typecad-hal build` succeed in any project without the user activating.
168
+ *
169
+ * Env name defaults to "zephyr"; override via TYPECAD_ZEPHYR_ENV. File-check
170
+ * based (no spawn) so it's cheap to run on every invocation.
171
+ */
145
172
  // ── Strategy 3: micromamba env (the @typecad/zephyr-installer install) ─────
146
173
 
147
174
  // Locate the micromamba binary + root prefix. The installer downloads
@@ -156,18 +183,6 @@ function findMicromamba(): { exe: string; rootPrefix: string } | null {
156
183
  return existsSync(exe) ? { exe, rootPrefix: root } : null;
157
184
  }
158
185
 
159
- /**
160
- * The micromamba env created by `@typecad/zephyr-installer`. The env's west
161
- * lives at envs/<name>/bin/west (POSIX) or Scripts/west.exe (Windows). Found
162
- * installs are invoked via `micromamba run -n <name> west …` (see
163
- * west-spawn.ts), which sets up the env's full PATH (cmake/ninja/dtc) AND runs
164
- * the activation hook (ZEPHYR_BASE / ZEPHYR_SDK_INSTALL_DIR) — so cuttlefish
165
- * builds work with NO manual `micromamba activate`. This is what makes a fresh
166
- * `cuttlefish build` succeed in any project without the user activating.
167
- *
168
- * Env name defaults to "zephyr"; override via TYPECAD_ZEPHYR_ENV. File-check
169
- * based (no spawn) so it's cheap to run on every cuttlefish invocation.
170
- */
171
186
  /** Read a TYPECAD_ZEPHYR_* value from the installer-written env-vars file in
172
187
  * a micromamba env. Handles .sh (export VAR="val"), .bat (set "VAR=val"),
173
188
  * and .ps1 ($env:VAR = "val"). Returns undefined if absent/unreadable. */
@@ -325,6 +340,13 @@ export function discoverWest(): WestInstall | null {
325
340
  install = null;
326
341
  }
327
342
  if (install) {
343
+ // A PATH/venv west install knows nothing about the installer's SDK.
344
+ // Enrich it: west-spawn pins the child's ZEPHYR_SDK_INSTALL_DIR to the
345
+ // installer dir, so Zephyr's CMake cannot pick up a stray SDK from
346
+ // $HOME (a version mismatch that only surfaces at configure time).
347
+ if (!install.sdkInstallDir) {
348
+ install.sdkInstallDir = installerSdkDir();
349
+ }
328
350
  cachedDiscover = install;
329
351
  return install;
330
352
  }
@@ -1,168 +1,174 @@
1
- // ---------------------------------------------------------------------------
2
- // west spawn helper — build a spawnSync invocation for `west <args>`
3
- //
4
- // Mirrors framework-esp32's idfSpawn(): given the discovered WestInstall,
5
- // produce {command, args, env} that the toolchain destructures into spawnSync.
6
- //
7
- // Two modes:
8
- // - 'launcher': spawn the `west` launcher directly (mode 'launcher').
9
- // - 'module' : spawn `<python> -m west ...` (mode 'module'). This is the
10
- // robust cross-platform form — it activates the venv's
11
- // interpreter for the one process without sourcing anything.
12
- //
13
- // ZEPHYR_BASE is injected into the spawn env when a SDK root was discovered
14
- // (find_package(Zephyr) needs it). west's prj.conf/CMakeLists are found via
15
- // the project dir regardless.
16
- // ---------------------------------------------------------------------------
17
-
18
- import type { SpawnSyncOptions } from 'node:child_process';
19
- import { dirname, join } from 'node:path';
20
- import { existsSync } from 'node:fs';
21
- import { type WestInstall, discoverWest } from './west-discover.js';
22
-
23
- export interface WestInvocation {
24
- /** The command to spawnSync: a `west` launcher or a Python interpreter. */
25
- command: string;
26
- /** Args for the command (includes `-m west` when in module mode). */
27
- args: string[];
28
- /** Base spawn options with cwd/timeout/encoding/stdio, plus the env carrying
29
- * ZEPHYR_BASE when discovered. */
30
- options: SpawnSyncOptions;
31
- /** The install the invocation was built from, for surfacing to the user. */
32
- install: WestInstall;
33
- }
34
-
35
- /**
36
- * The Scripts/ (Windows) or bin/ (POSIX) directory of the venv the discovered
37
- * west runs under. `pythonExecutable` lives in that directory, so it is its
38
- * dirname. Returns undefined for launcher-mode installs where no venv is known.
39
- */
40
- function venvBinDir(install: WestInstall): string | undefined {
41
- return install.pythonExecutable ? dirname(install.pythonExecutable) : undefined;
42
- }
43
-
44
- /**
45
- * The env to pass to the west spawn: the process env, with ZEPHYR_BASE injected
46
- * when a SDK root was discovered, AND the venv's bin/Scripts dir prepended to
47
- * PATH when west was found via a venv Python.
48
- *
49
- * The PATH prepend matters: `west flash` shells out to bare runner tools
50
- * (`esptool`, `openocd`, `nrfjprog`, …) via check_call, so they resolve from
51
- * PATH. Without the prepend, the user's PATH may surface a *different* tool
52
- * ahead of the venv's — e.g. an older esptool whose argument spelling is
53
- * incompatible with the runner. Putting the venv's bin first makes west's
54
- * delegated subprocesses resolve to the same versions west itself runs under.
55
- */
56
- export function buildEnv(install: WestInstall): NodeJS.ProcessEnv {
57
- const env = { ...process.env };
58
- if (install.zephyrBase && !env.ZEPHYR_BASE) {
59
- env.ZEPHYR_BASE = install.zephyrBase;
60
- }
61
- // SWD flashing (`zephyr.runner: 'openocd'`): west's openocd runner resolves
62
- // a bare `openocd` from PATH. The Zephyr SDK ships it under
63
- // hosttools/openocd/bin (the SDK's own setup.cmd puts that dir on PATH for
64
- // activated terminals) — do the same for spawned west processes so ST-Link
65
- // flashing works without activation. $ZEPHYR_SDK_INSTALL_DIR (set by an
66
- // activated env) wins over the discovered install dir.
67
- const sdkRoot = env.ZEPHYR_SDK_INSTALL_DIR || install.sdkInstallDir;
68
- const openocdBin = sdkRoot ? join(sdkRoot, 'hosttools', 'openocd', 'bin') : undefined;
69
- const prepend = [
70
- openocdBin !== undefined && existsSync(openocdBin) ? openocdBin : undefined,
71
- venvBinDir(install),
72
- ].filter((d): d is string => d !== undefined);
73
- if (prepend.length > 0) {
74
- const sep = process.platform === 'win32' ? ';' : ':';
75
- // On Windows the PATH environment variable may be cased as `Path` (the
76
- // registry-native form, the only one populated when node is launched from
77
- // PowerShell/cmd) or `PATH` (POSIX form, set by Git Bash). Writing only one
78
- // casing can leave the other stale/empty, which under PowerShell would drop
79
- // the user's real PATH (cmake, ninja, …) — breaking `west` configure. Read
80
- // whichever casing is populated and write that same casing back, preserving
81
- // the full existing value with the discovered dirs prepended.
82
- const existing = env.Path ?? env.PATH ?? '';
83
- const updated = prepend.join(sep) + sep + existing;
84
- if (env.Path !== undefined || (env.PATH === undefined && process.platform === 'win32')) {
85
- env.Path = updated;
86
- } else {
87
- env.PATH = updated;
88
- }
89
- }
90
- return env;
91
- }
92
-
93
- /**
94
- * Build a spawn invocation that runs `west <westArgs>`.
95
- *
96
- * `baseOptions` carries the cwd/timeout/stdio settings the caller wants;
97
- * westSpawn preserves them and injects the discovery env. The returned object
98
- * destructures directly into spawnSync:
99
- *
100
- * const inv = westSpawn(['build', '-b', 'xiao_ble', projectRoot], { cwd, timeout });
101
- * spawnSync(inv.command, inv.args, inv.options);
102
- *
103
- * Throws a clear, actionable error when no usable west install is discovered.
104
- */
105
- export function westSpawn(
106
- westArgs: string[],
107
- baseOptions: SpawnSyncOptions,
108
- ): WestInvocation {
109
- const install = discoverWest();
110
- if (!install) {
111
- throw new Error(
112
- [
113
- 'west (the Zephyr build tool) was not found.',
114
- '',
115
- 'cuttlefish looked for it on PATH, in $ZEPHYR_BASE/.venv, in common',
116
- 'Zephyr workspace dirs (~/zephyrproject/.venv), and as a system',
117
- "Python module (`python -m west`). To fix:",
118
- '',
119
- ' • Run the typeCAD Zephyr installer (one command, any OS — needs Node ≥18):',
120
- ' node packages/zephyr-installer/install.mjs',
121
- ' then `micromamba activate zephyr` and retry.',
122
- ' • Or activate an existing Zephyr venv in this shell, or',
123
- ' • Or set ZEPHYR_BASE to your Zephyr SDK root (the venv at $ZEPHYR_BASE/../.venv is then used), or',
124
- ' • Or install west into a discoverable Python: pip install west',
125
- '',
126
- 'See https://docs.zephyrproject.org/latest/develop/getting_started/index.html',
127
- ].join('\n'),
128
- );
129
- }
130
-
131
- const env = buildEnv(install);
132
- // Strip `shell` if present — we pass absolute paths / known commands, and
133
- // an explicit shell changes arg-quoting semantics on Windows.
134
- const { shell: _drop, ...optsWithoutShell } = baseOptions as any;
135
-
136
- if (install.mode === 'micromamba' && install.micromambaExe) {
137
- // `micromamba run -n <env> west …` sets up the env's full PATH
138
- // (cmake/ninja/dtc) and runs the activation hook (ZEPHYR_BASE /
139
- // ZEPHYR_SDK_INSTALL_DIR), so cuttlefish builds work WITHOUT the user
140
- // activating the env. Inject MAMBA_ROOT_PREFIX so micromamba finds envs.
141
- const mmEnv = { ...env };
142
- if (install.mambaRootPrefix) mmEnv.MAMBA_ROOT_PREFIX = install.mambaRootPrefix;
143
- return {
144
- command: install.micromambaExe,
145
- args: ['run', '-n', install.envName ?? 'zephyr', 'west', ...westArgs],
146
- options: { ...optsWithoutShell, env: mmEnv },
147
- install,
148
- };
149
- }
150
-
151
- if (install.mode === 'launcher' && install.westExecutable) {
152
- return {
153
- command: install.westExecutable,
154
- args: westArgs,
155
- options: { ...optsWithoutShell, env },
156
- install,
157
- };
158
- }
159
-
160
- // module mode: <python> -m west <args>
161
- const py = install.pythonExecutable!;
162
- return {
163
- command: py,
164
- args: ['-m', 'west', ...westArgs],
165
- options: { ...optsWithoutShell, env },
166
- install,
167
- };
168
- }
1
+ // ---------------------------------------------------------------------------
2
+ // west spawn helper — build a spawnSync invocation for `west <args>`
3
+ //
4
+ // Mirrors framework-esp32's idfSpawn(): given the discovered WestInstall,
5
+ // produce {command, args, env} that the toolchain destructures into spawnSync.
6
+ //
7
+ // Two modes:
8
+ // - 'launcher': spawn the `west` launcher directly (mode 'launcher').
9
+ // - 'module' : spawn `<python> -m west ...` (mode 'module'). This is the
10
+ // robust cross-platform form — it activates the venv's
11
+ // interpreter for the one process without sourcing anything.
12
+ //
13
+ // ZEPHYR_BASE is injected into the spawn env when a SDK root was discovered
14
+ // (find_package(Zephyr) needs it). west's prj.conf/CMakeLists are found via
15
+ // the project dir regardless.
16
+ // ---------------------------------------------------------------------------
17
+
18
+ import type { SpawnSyncOptions } from 'node:child_process';
19
+ import { dirname, join } from 'node:path';
20
+ import { existsSync } from 'node:fs';
21
+ import { type WestInstall, discoverWest } from './west-discover.js';
22
+
23
+ export interface WestInvocation {
24
+ /** The command to spawnSync: a `west` launcher or a Python interpreter. */
25
+ command: string;
26
+ /** Args for the command (includes `-m west` when in module mode). */
27
+ args: string[];
28
+ /** Base spawn options with cwd/timeout/encoding/stdio, plus the env carrying
29
+ * ZEPHYR_BASE when discovered. */
30
+ options: SpawnSyncOptions;
31
+ /** The install the invocation was built from, for surfacing to the user. */
32
+ install: WestInstall;
33
+ }
34
+
35
+ /**
36
+ * The Scripts/ (Windows) or bin/ (POSIX) directory of the venv the discovered
37
+ * west runs under. `pythonExecutable` lives in that directory, so it is its
38
+ * dirname. Returns undefined for launcher-mode installs where no venv is known.
39
+ */
40
+ function venvBinDir(install: WestInstall): string | undefined {
41
+ return install.pythonExecutable ? dirname(install.pythonExecutable) : undefined;
42
+ }
43
+
44
+ /**
45
+ * The env to pass to the west spawn: the process env, with ZEPHYR_BASE injected
46
+ * when a SDK root was discovered, AND the venv's bin/Scripts dir prepended to
47
+ * PATH when west was found via a venv Python.
48
+ *
49
+ * The PATH prepend matters: `west flash` shells out to bare runner tools
50
+ * (`esptool`, `openocd`, `nrfjprog`, …) via check_call, so they resolve from
51
+ * PATH. Without the prepend, the user's PATH may surface a *different* tool
52
+ * ahead of the venv's — e.g. an older esptool whose argument spelling is
53
+ * incompatible with the runner. Putting the venv's bin first makes west's
54
+ * delegated subprocesses resolve to the same versions west itself runs under.
55
+ */
56
+ export function buildEnv(install: WestInstall): NodeJS.ProcessEnv {
57
+ const env = { ...process.env };
58
+ if (install.zephyrBase && !env.ZEPHYR_BASE) {
59
+ env.ZEPHYR_BASE = install.zephyrBase;
60
+ }
61
+ // SWD flashing (`zephyr.runner: 'openocd'`): west's openocd runner resolves
62
+ // a bare `openocd` from PATH. The Zephyr SDK ships it under
63
+ // hosttools/openocd/bin (the SDK's own setup.cmd puts that dir on PATH for
64
+ // activated terminals) — do the same for spawned west processes so ST-Link
65
+ // flashing works without activation. $ZEPHYR_SDK_INSTALL_DIR (set by an
66
+ // activated env) wins over the discovered install dir.
67
+ const sdkRoot = env.ZEPHYR_SDK_INSTALL_DIR || install.sdkInstallDir;
68
+ // Pin the west/cmake child to the discovered SDK: without this, Zephyr's
69
+ // FindZephyr-sdk searches $HOME and can latch onto a stray, older SDK —
70
+ // a configure-time version failure that only surfaces in the build log.
71
+ if (!env.ZEPHYR_SDK_INSTALL_DIR && install.sdkInstallDir) {
72
+ env.ZEPHYR_SDK_INSTALL_DIR = install.sdkInstallDir;
73
+ }
74
+ const openocdBin = sdkRoot ? join(sdkRoot, 'hosttools', 'openocd', 'bin') : undefined;
75
+ const prepend = [
76
+ openocdBin !== undefined && existsSync(openocdBin) ? openocdBin : undefined,
77
+ venvBinDir(install),
78
+ ].filter((d): d is string => d !== undefined);
79
+ if (prepend.length > 0) {
80
+ const sep = process.platform === 'win32' ? ';' : ':';
81
+ // On Windows the PATH environment variable may be cased as `Path` (the
82
+ // registry-native form, the only one populated when node is launched from
83
+ // PowerShell/cmd) or `PATH` (POSIX form, set by Git Bash). Writing only one
84
+ // casing can leave the other stale/empty, which under PowerShell would drop
85
+ // the user's real PATH (cmake, ninja, …) — breaking `west` configure. Read
86
+ // whichever casing is populated and write that same casing back, preserving
87
+ // the full existing value with the discovered dirs prepended.
88
+ const existing = env.Path ?? env.PATH ?? '';
89
+ const updated = prepend.join(sep) + sep + existing;
90
+ if (env.Path !== undefined || (env.PATH === undefined && process.platform === 'win32')) {
91
+ env.Path = updated;
92
+ } else {
93
+ env.PATH = updated;
94
+ }
95
+ }
96
+ return env;
97
+ }
98
+
99
+ /**
100
+ * Build a spawn invocation that runs `west <westArgs>`.
101
+ *
102
+ * `baseOptions` carries the cwd/timeout/stdio settings the caller wants;
103
+ * westSpawn preserves them and injects the discovery env. The returned object
104
+ * destructures directly into spawnSync:
105
+ *
106
+ * const inv = westSpawn(['build', '-b', 'xiao_ble', projectRoot], { cwd, timeout });
107
+ * spawnSync(inv.command, inv.args, inv.options);
108
+ *
109
+ * Throws a clear, actionable error when no usable west install is discovered.
110
+ */
111
+ export function westSpawn(
112
+ westArgs: string[],
113
+ baseOptions: SpawnSyncOptions,
114
+ ): WestInvocation {
115
+ const install = discoverWest();
116
+ if (!install) {
117
+ throw new Error(
118
+ [
119
+ 'west (the Zephyr build tool) was not found.',
120
+ '',
121
+ 'typecad-hal looked for it on PATH, in $ZEPHYR_BASE/.venv, in common',
122
+ 'Zephyr workspace dirs (~/zephyrproject/.venv), and as a system',
123
+ "Python module (`python -m west`). To fix:",
124
+ '',
125
+ ' • Run the typeCAD Zephyr installer (one command, any OS — needs Node ≥18):',
126
+ ' node packages/zephyr-installer/install.mjs',
127
+ ' then `micromamba activate zephyr` and retry.',
128
+ ' • Or activate an existing Zephyr venv in this shell, or',
129
+ ' • Or set ZEPHYR_BASE to your Zephyr SDK root (the venv at $ZEPHYR_BASE/../.venv is then used), or',
130
+ ' • Or install west into a discoverable Python: pip install west',
131
+ '',
132
+ 'See https://docs.zephyrproject.org/latest/develop/getting_started/index.html',
133
+ ].join('\n'),
134
+ );
135
+ }
136
+
137
+ const env = buildEnv(install);
138
+ // Strip `shell` if present — we pass absolute paths / known commands, and
139
+ // an explicit shell changes arg-quoting semantics on Windows.
140
+ const { shell: _drop, ...optsWithoutShell } = baseOptions as any;
141
+
142
+ if (install.mode === 'micromamba' && install.micromambaExe) {
143
+ // `micromamba run -n <env> west …` sets up the env's full PATH
144
+ // (cmake/ninja/dtc) and runs the activation hook (ZEPHYR_BASE /
145
+ // ZEPHYR_SDK_INSTALL_DIR), so typecad-hal builds work WITHOUT the user
146
+ // activating the env. Inject MAMBA_ROOT_PREFIX so micromamba finds envs.
147
+ const mmEnv = { ...env };
148
+ if (install.mambaRootPrefix) mmEnv.MAMBA_ROOT_PREFIX = install.mambaRootPrefix;
149
+ return {
150
+ command: install.micromambaExe,
151
+ args: ['run', '-n', install.envName ?? 'zephyr', 'west', ...westArgs],
152
+ options: { ...optsWithoutShell, env: mmEnv },
153
+ install,
154
+ };
155
+ }
156
+
157
+ if (install.mode === 'launcher' && install.westExecutable) {
158
+ return {
159
+ command: install.westExecutable,
160
+ args: westArgs,
161
+ options: { ...optsWithoutShell, env },
162
+ install,
163
+ };
164
+ }
165
+
166
+ // module mode: <python> -m west <args>
167
+ const py = install.pythonExecutable!;
168
+ return {
169
+ command: py,
170
+ args: ['-m', 'west', ...westArgs],
171
+ options: { ...optsWithoutShell, env },
172
+ install,
173
+ };
174
+ }
@@ -0,0 +1,102 @@
1
+ // ---------------------------------------------------------------------------
2
+ // @typecad/framework-zephyr — shared west workspace ground truth
3
+ //
4
+ // The as-built inventory both `typecad-hal licenses` and `typecad-hal sbom`
5
+ // report from: which west manifest modules the last build ACTUALLY compiled
6
+ // (linked-module filtering via the build's compile_commands.json), plus the
7
+ // build-directory discovery that locates it. A west manifest carries every
8
+ // vendor HAL/library; almost none are linked by a single project — both the
9
+ // license table and the SBOM scope to the linked set by default, so they stay
10
+ // honest about what is actually inside the firmware.
11
+ //
12
+ // Moved out of licenses.ts when the sbom command grew a second consumer; the
13
+ // behavior (case-insensitive, separator-agnostic path matching; degrade to
14
+ // "no filtering" on unparseable build data) is unchanged.
15
+ // ---------------------------------------------------------------------------
16
+
17
+ import * as path from 'node:path';
18
+ import type { ReadDir, ReadFile } from '@typecad/cuttlefish/api/shared';
19
+
20
+ /** Normalize a path for case-insensitive, separator-agnostic comparison. */
21
+ export function norm(p: string): string {
22
+ return p.split('\\').join('/').toLowerCase();
23
+ }
24
+
25
+ /**
26
+ * Filter the west manifest to the modules whose sources were compiled in the
27
+ * last build. A module is "linked" iff some compiled translation unit lives
28
+ * under its abspath. If the build data is unparseable, no filtering is applied
29
+ * (degrade to listing all modules rather than reporting nothing).
30
+ */
31
+ export function filterLinkedModules(
32
+ ccText: string,
33
+ modules: { name: string; abspath: string }[],
34
+ ): { name: string; abspath: string }[] {
35
+ let arr: unknown;
36
+ try {
37
+ arr = JSON.parse(ccText);
38
+ } catch {
39
+ return modules;
40
+ }
41
+ if (!Array.isArray(arr)) return modules;
42
+ // One normalized blob of every compiled source path; membership is then a
43
+ // substring check per module (O(modules) after an O(TUs) join).
44
+ const blob = arr
45
+ .map((e) => norm(typeof (e as { file?: unknown })?.file === 'string' ? (e as { file: string }).file : ''))
46
+ .join('\n');
47
+ return modules.filter((m) => blob.includes(norm(m.abspath) + '/'));
48
+ }
49
+
50
+ /**
51
+ * Find the build's compile_commands.json path under `cwd`. Cuttlefish's Zephyr
52
+ * project root is the transpile output dir (e.g. `<cwd>/src/out`), so the
53
+ * build dir is `<root>/build` — not necessarily `<cwd>/build`. Check the
54
+ * common layouts, then fall back to a bounded search (skipping
55
+ * node_modules/.git/dist and dot-directories). Returns the PATH (the caller
56
+ * decides whether to read it); undefined when no build exists.
57
+ */
58
+ export function findCompileCommandsPath(
59
+ readFile: ReadFile,
60
+ readdir: ReadDir,
61
+ cwd = process.cwd(),
62
+ ): string | undefined {
63
+ const direct = [
64
+ path.join(cwd, 'build', 'compile_commands.json'),
65
+ path.join(cwd, 'out', 'build', 'compile_commands.json'),
66
+ path.join(cwd, 'src', 'out', 'build', 'compile_commands.json'),
67
+ ];
68
+ for (const c of direct) {
69
+ if (readFile(c)) return c;
70
+ }
71
+ // Bounded recursive search for a build/compile_commands.json.
72
+ const isCc = (p: string) => {
73
+ const n = norm(p);
74
+ return n.endsWith('/build/compile_commands.json') || n.endsWith('\\build\\compile_commands.json');
75
+ };
76
+ let found: string | undefined;
77
+ const seen = new Set<string>();
78
+ const walk = (dir: string, depth: number): void => {
79
+ if (found || depth > 4 || seen.has(dir)) return;
80
+ seen.add(dir);
81
+ let entries: string[];
82
+ try {
83
+ entries = readdir(dir);
84
+ } catch {
85
+ return;
86
+ }
87
+ for (const e of entries) {
88
+ const full = path.join(dir, e);
89
+ if (e === 'compile_commands.json' && isCc(full)) {
90
+ found = full;
91
+ return;
92
+ }
93
+ }
94
+ for (const e of entries) {
95
+ if (found) return;
96
+ if (e === 'node_modules' || e === '.git' || e === 'dist' || e.startsWith('.')) continue;
97
+ walk(path.join(dir, e), depth + 1);
98
+ }
99
+ };
100
+ walk(cwd, 0);
101
+ return found;
102
+ }
@@ -1,10 +0,0 @@
1
- import type { RuntimePolyfillIR } from '@typecad/cuttlefish/api/shared';
2
- export interface TimerPolyfillIR extends RuntimePolyfillIR {
3
- /** The clamped pool size the C++ was generated with. */
4
- maxTimers: number;
5
- }
6
- /**
7
- * Build the timer_methods polyfill IR. `requestedTimers` is sized from
8
- * ctx.analysis.timerCallCount; it is clamped to [1, 16].
9
- */
10
- export declare function buildTimerPolyfill(requestedTimers: number): TimerPolyfillIR;
@@ -1,95 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // Timer methods polyfill — Zephyr k_timer + k_work (heap-free)
3
- //
4
- // setInterval / setTimeout / clearInterval / clearTimeout backed by a fixed
5
- // pool of k_timer + k_work pairs. The timer's expiry function (ISR context)
6
- // submits a k_work item to the system workqueue; the work handler runs the
7
- // user callback in thread context. This split keeps the ISR cheap and lets the
8
- // callback run on a real thread (so it can do real work — but must not block,
9
- // since the system workqueue is shared).
10
- //
11
- // EMIT BOUNDARY: emitted bytes land in user firmware. Covered by the TypeCAD
12
- // Runtime Exception (RUNTIME_EXCEPTION.md at the repo root).
13
- // ---------------------------------------------------------------------------
14
- const MIN_TIMERS = 1;
15
- const MAX_TIMERS_CAP = 16;
16
- /**
17
- * Build the timer_methods polyfill IR. `requestedTimers` is sized from
18
- * ctx.analysis.timerCallCount; it is clamped to [1, 16].
19
- */
20
- export function buildTimerPolyfill(requestedTimers) {
21
- const maxTimers = Math.min(MAX_TIMERS_CAP, Math.max(MIN_TIMERS, requestedTimers));
22
- const helperStructs = [`
23
- // cuttlefish timer runtime — k_timer + k_work pool (heap-free).
24
- struct __tc_TimerSlot {
25
- struct k_timer timer;
26
- struct k_work work;
27
- void (*callback)(void);
28
- bool active;
29
- };
30
-
31
- static struct __tc_TimerSlot __tc_timer_slots[${maxTimers}];
32
-
33
- static void __tc_timer_work_handler(struct k_work* w) {
34
- // Runs on the system workqueue thread (NOT ISR). Find the owning slot by
35
- // address and invoke the callback. Must not block.
36
- for (int32_t i = 0; i < ${maxTimers}; i++) {
37
- if (&__tc_timer_slots[i].work == w) {
38
- if (__tc_timer_slots[i].callback != nullptr) { __tc_timer_slots[i].callback(); }
39
- return;
40
- }
41
- }
42
- }
43
-
44
- static void __tc_timer_expiry_fn(struct k_timer* t) {
45
- // ISR context: submit the work item, do NOT run the callback here.
46
- for (int32_t i = 0; i < ${maxTimers}; i++) {
47
- if (&__tc_timer_slots[i].timer == t) {
48
- (void)k_work_submit(&__tc_timer_slots[i].work);
49
- return;
50
- }
51
- }
52
- }
53
-
54
- static int32_t __tc_timer_add(void (*cb)(void), int32_t ms, bool repeat) {
55
- for (int32_t i = 0; i < ${maxTimers}; i++) {
56
- if (!__tc_timer_slots[i].active) {
57
- __tc_timer_slots[i].callback = cb;
58
- __tc_timer_slots[i].active = true;
59
- k_timer_init(&__tc_timer_slots[i].timer, __tc_timer_expiry_fn, nullptr);
60
- k_work_init(&__tc_timer_slots[i].work, __tc_timer_work_handler);
61
- k_timer_start(&__tc_timer_slots[i].timer, K_MSEC(ms), repeat ? K_MSEC(ms) : K_FOREVER);
62
- return i + 1; // 1-based id
63
- }
64
- }
65
- return 0; // pool full
66
- }
67
-
68
- static void __tc_timer_clear(int32_t id) {
69
- if (id > 0 && id <= ${maxTimers}) {
70
- int32_t i = id - 1;
71
- k_timer_stop(&__tc_timer_slots[i].timer);
72
- __tc_timer_slots[i].active = false;
73
- __tc_timer_slots[i].callback = nullptr;
74
- }
75
- }
76
- `];
77
- const helperFunctions = [`
78
- int32_t __tc_setInterval(void (*cb)(), int32_t ms) { return __tc_timer_add(cb, ms, true); }
79
- int32_t __tc_setTimeout(void (*cb)(), int32_t ms) { return __tc_timer_add(cb, ms, false); }
80
- void __tc_clearInterval(int32_t id) { __tc_timer_clear(id); }
81
- void __tc_clearTimeout(int32_t id) { __tc_timer_clear(id); }
82
- `];
83
- return {
84
- kind: 'polyfill',
85
- id: 'timer_methods',
86
- domain: 'embedded',
87
- requiredIncludes: [],
88
- forwardDeclarations: [],
89
- helperStructs,
90
- helperFunctions,
91
- shimMacros: [],
92
- dependencies: [],
93
- maxTimers,
94
- };
95
- }
@@ -1,7 +0,0 @@
1
- export interface BoardOverride {
2
- /** LED pin name for boards whose user LED is not a gpio-leds node (the
3
- * ESP32-S3 DevKitC's addressable RGB on GPIO48 — driven as plain GPIO,
4
- * same as the deleted board package did). */
5
- readonly led?: string;
6
- }
7
- export declare const BOARD_OVERRIDES: Readonly<Record<string, BoardOverride>>;