@typecad/framework-zephyr 1.0.0-alpha.13 → 1.0.0-alpha.14
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.
- package/README.md +13 -1
- package/dist/chips/controllers.d.ts +28 -8
- package/dist/chips/controllers.js +49 -12
- package/dist/chips/resolve.js +32 -6
- package/dist/chips/types.d.ts +63 -12
- package/dist/chips/xiao-ble.js +12 -0
- package/dist/dt-config/kconfig.d.ts +11 -0
- package/dist/dt-config/kconfig.js +1 -1
- package/dist/dt-config/overlay.js +85 -0
- package/dist/framework.manifest.d.ts +20 -30
- package/dist/framework.manifest.js +12 -3
- package/dist/lowering/adc.d.ts +7 -4
- package/dist/lowering/adc.js +25 -11
- package/dist/lowering/gpio.js +9 -6
- package/dist/lowering/mqtt.js +9 -1
- package/dist/lowering/pulse.js +7 -7
- package/dist/lowering/pwm.d.ts +21 -3
- package/dist/lowering/pwm.js +28 -4
- package/dist/lowering/spi.js +2 -2
- package/dist/lowering/tone.js +3 -2
- package/dist/lowering/wifi.js +28 -5
- package/dist/strategy.js +56 -4
- package/dist/toolchain/debug-config.js +1 -1
- package/dist/toolchain/index.d.ts +1 -1
- package/dist/toolchain/index.js +83 -8
- package/dist/toolchain/scaffold.d.ts +9 -0
- package/dist/toolchain/scaffold.js +45 -0
- package/dist/toolchain/west-discover.d.ts +4 -1
- package/dist/toolchain/west-discover.js +2 -0
- package/dist/toolchain/west-spawn.js +17 -5
- package/package.json +5 -5
- package/src/chips/controllers.ts +61 -12
- package/src/chips/resolve.ts +32 -5
- package/src/chips/types.ts +63 -12
- package/src/chips/xiao-ble.ts +82 -70
- package/src/dt-config/kconfig.ts +12 -1
- package/src/dt-config/overlay.ts +546 -450
- package/src/framework.manifest.ts +12 -3
- package/src/lowering/adc.ts +28 -12
- package/src/lowering/gpio.ts +9 -6
- package/src/lowering/mqtt.ts +9 -1
- package/src/lowering/pulse.ts +7 -7
- package/src/lowering/pwm.ts +29 -4
- package/src/lowering/spi.ts +2 -2
- package/src/lowering/tone.ts +3 -3
- package/src/lowering/wifi.ts +29 -5
- package/src/strategy.ts +52 -4
- package/src/toolchain/debug-config.ts +1 -1
- package/src/toolchain/index.ts +645 -565
- package/src/toolchain/scaffold.ts +43 -0
- package/src/toolchain/west-discover.ts +321 -316
- package/src/toolchain/west-spawn.ts +17 -5
|
@@ -1,316 +1,321 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// west discovery — find a usable `west` (and the Zephyr SDK / ZEPHYR_BASE)
|
|
3
|
-
//
|
|
4
|
-
// west installs into a Python venv that must be activated before `west` is on
|
|
5
|
-
// PATH. We resolve a working invocation WITHOUT requiring the user to have
|
|
6
|
-
// activated the venv, by preferring the robust `<python> -m west` form: it
|
|
7
|
-
// sidesteps shebang-launcher fragility on Windows and works with any venv
|
|
8
|
-
// once we know which Python interpreter has west installed.
|
|
9
|
-
//
|
|
10
|
-
// Discovery cascade (first usable wins):
|
|
11
|
-
// 1. `west` already on PATH (env already activated / global install).
|
|
12
|
-
// 2. $ZEPHYR_BASE venv: ${ZEPHYR_BASE}/../.venv/<python> -m west.
|
|
13
|
-
// 3. micromamba env from @typecad/zephyr-installer (invoked via `micromamba run`,
|
|
14
|
-
// so cuttlefish builds work with NO manual activation).
|
|
15
|
-
// 4. Well-known workspace layouts: ~/zephyrproject/.venv, /opt/zephyrproject/.
|
|
16
|
-
// venv, etc.
|
|
17
|
-
// 5. System pythons (`python`, `python3`, `py`) via `-m west`.
|
|
18
|
-
//
|
|
19
|
-
// Leaner than the ESP-IDF equivalent: west needs no env sourcing (no 15s
|
|
20
|
-
// export.sh) — only the right interpreter + ZEPHYR_BASE.
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
|
|
23
|
-
import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
24
|
-
import { dirname, join } from 'node:path';
|
|
25
|
-
import { homedir } from 'node:os';
|
|
26
|
-
import { spawnSync } from 'node:child_process';
|
|
27
|
-
|
|
28
|
-
const IS_WIN = process.platform === 'win32';
|
|
29
|
-
|
|
30
|
-
/** The Python executable name inside a venv's bin/ (POSIX) or Scripts/ (Win). */
|
|
31
|
-
function venvPython(venvDir: string): string {
|
|
32
|
-
return join(venvDir, IS_WIN ? 'Scripts' : 'bin', IS_WIN ? 'python.exe' : 'python');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** True if `exe` runs `python -m west --version` successfully. */
|
|
36
|
-
function pythonRunsWest(exe: string): boolean {
|
|
37
|
-
try {
|
|
38
|
-
const r = spawnSync(exe, ['-m', 'west', '--version'], {
|
|
39
|
-
encoding: 'utf8',
|
|
40
|
-
timeout: 15_000,
|
|
41
|
-
windowsHide: true,
|
|
42
|
-
});
|
|
43
|
-
return r.status === 0;
|
|
44
|
-
} catch {
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** True if `cmd` runs `west --version` successfully. */
|
|
50
|
-
function westOnPath(cmd: string): boolean {
|
|
51
|
-
try {
|
|
52
|
-
const r = spawnSync(cmd, ['--version'], {
|
|
53
|
-
encoding: 'utf8',
|
|
54
|
-
timeout: 15_000,
|
|
55
|
-
shell: IS_WIN,
|
|
56
|
-
windowsHide: true,
|
|
57
|
-
});
|
|
58
|
-
return r.status === 0;
|
|
59
|
-
} catch {
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* A discovered, usable west installation. `mode` tells the caller how to
|
|
66
|
-
* invoke it: 'launcher' = call `westExecutable` directly; 'module' = call
|
|
67
|
-
* `pythonExecutable -m west`.
|
|
68
|
-
*/
|
|
69
|
-
export interface WestInstall {
|
|
70
|
-
mode: 'launcher' | 'module' | 'micromamba';
|
|
71
|
-
/** Absolute path to a `west` launcher (mode 'launcher') or undefined. */
|
|
72
|
-
westExecutable?: string;
|
|
73
|
-
/** Absolute path to a Python interpreter with west installed (mode 'module'). */
|
|
74
|
-
pythonExecutable?: string;
|
|
75
|
-
/** Absolute path to the Zephyr
|
|
76
|
-
zephyrBase?: string;
|
|
77
|
-
/**
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
// micromamba
|
|
146
|
-
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
*
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
// .
|
|
183
|
-
m = text.match(new RegExp(`${varName}
|
|
184
|
-
if (m) return m[1]
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
if (!
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
export function
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
*
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// west discovery — find a usable `west` (and the Zephyr SDK / ZEPHYR_BASE)
|
|
3
|
+
//
|
|
4
|
+
// west installs into a Python venv that must be activated before `west` is on
|
|
5
|
+
// PATH. We resolve a working invocation WITHOUT requiring the user to have
|
|
6
|
+
// activated the venv, by preferring the robust `<python> -m west` form: it
|
|
7
|
+
// sidesteps shebang-launcher fragility on Windows and works with any venv
|
|
8
|
+
// once we know which Python interpreter has west installed.
|
|
9
|
+
//
|
|
10
|
+
// Discovery cascade (first usable wins):
|
|
11
|
+
// 1. `west` already on PATH (env already activated / global install).
|
|
12
|
+
// 2. $ZEPHYR_BASE venv: ${ZEPHYR_BASE}/../.venv/<python> -m west.
|
|
13
|
+
// 3. micromamba env from @typecad/zephyr-installer (invoked via `micromamba run`,
|
|
14
|
+
// so cuttlefish builds work with NO manual activation).
|
|
15
|
+
// 4. Well-known workspace layouts: ~/zephyrproject/.venv, /opt/zephyrproject/.
|
|
16
|
+
// venv, etc.
|
|
17
|
+
// 5. System pythons (`python`, `python3`, `py`) via `-m west`.
|
|
18
|
+
//
|
|
19
|
+
// Leaner than the ESP-IDF equivalent: west needs no env sourcing (no 15s
|
|
20
|
+
// export.sh) — only the right interpreter + ZEPHYR_BASE.
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
24
|
+
import { dirname, join } from 'node:path';
|
|
25
|
+
import { homedir } from 'node:os';
|
|
26
|
+
import { spawnSync } from 'node:child_process';
|
|
27
|
+
|
|
28
|
+
const IS_WIN = process.platform === 'win32';
|
|
29
|
+
|
|
30
|
+
/** The Python executable name inside a venv's bin/ (POSIX) or Scripts/ (Win). */
|
|
31
|
+
function venvPython(venvDir: string): string {
|
|
32
|
+
return join(venvDir, IS_WIN ? 'Scripts' : 'bin', IS_WIN ? 'python.exe' : 'python');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** True if `exe` runs `python -m west --version` successfully. */
|
|
36
|
+
function pythonRunsWest(exe: string): boolean {
|
|
37
|
+
try {
|
|
38
|
+
const r = spawnSync(exe, ['-m', 'west', '--version'], {
|
|
39
|
+
encoding: 'utf8',
|
|
40
|
+
timeout: 15_000,
|
|
41
|
+
windowsHide: true,
|
|
42
|
+
});
|
|
43
|
+
return r.status === 0;
|
|
44
|
+
} catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** True if `cmd` runs `west --version` successfully. */
|
|
50
|
+
function westOnPath(cmd: string): boolean {
|
|
51
|
+
try {
|
|
52
|
+
const r = spawnSync(cmd, ['--version'], {
|
|
53
|
+
encoding: 'utf8',
|
|
54
|
+
timeout: 15_000,
|
|
55
|
+
shell: IS_WIN,
|
|
56
|
+
windowsHide: true,
|
|
57
|
+
});
|
|
58
|
+
return r.status === 0;
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A discovered, usable west installation. `mode` tells the caller how to
|
|
66
|
+
* invoke it: 'launcher' = call `westExecutable` directly; 'module' = call
|
|
67
|
+
* `pythonExecutable -m west`.
|
|
68
|
+
*/
|
|
69
|
+
export interface WestInstall {
|
|
70
|
+
mode: 'launcher' | 'module' | 'micromamba';
|
|
71
|
+
/** Absolute path to a `west` launcher (mode 'launcher') or undefined. */
|
|
72
|
+
westExecutable?: string;
|
|
73
|
+
/** Absolute path to a Python interpreter with west installed (mode 'module'). */
|
|
74
|
+
pythonExecutable?: string;
|
|
75
|
+
/** Absolute path to the Zephyr workspace (for $ZEPHYR_BASE), if found. */
|
|
76
|
+
zephyrBase?: string;
|
|
77
|
+
/** Absolute path to the Zephyr SDK install dir (hosttools/openocd lives
|
|
78
|
+
* there), if found — used to put the SDK's openocd on the flash PATH. */
|
|
79
|
+
sdkInstallDir?: string;
|
|
80
|
+
/** mode 'micromamba': path to the micromamba binary (for `micromamba run -n …`). */
|
|
81
|
+
micromambaExe?: string;
|
|
82
|
+
/** mode 'micromamba': the conda env name (default 'zephyr'). */
|
|
83
|
+
envName?: string;
|
|
84
|
+
/** mode 'micromamba': MAMBA_ROOT_PREFIX, injected so micromamba finds its envs. */
|
|
85
|
+
mambaRootPrefix?: string;
|
|
86
|
+
/** Which discovery strategy found this install. */
|
|
87
|
+
source: 'path' | 'zephyr-base-venv' | 'well-known' | 'system-python' | 'micromamba';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** True if `dir` looks like a Zephyr SDK root: has CMakeLists.txt and the
|
|
91
|
+
* kernel header. */
|
|
92
|
+
export function isZephyrBase(dir: string): boolean {
|
|
93
|
+
if (!dir) return false;
|
|
94
|
+
return (
|
|
95
|
+
existsSync(join(dir, 'CMakeLists.txt')) &&
|
|
96
|
+
existsSync(join(dir, 'include', 'zephyr', 'kernel.h'))
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── Strategy 1: `west` on PATH ──────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
export function discoverFromPath(): WestInstall | null {
|
|
103
|
+
// shell only on Windows (where.exe resolution through cmd) — an args array
|
|
104
|
+
// with shell: true triggers Node's DEP0190 deprecation warning on Linux/
|
|
105
|
+
// macOS, where `which` is a plain executable that needs no shell.
|
|
106
|
+
const which = spawnSync(IS_WIN ? 'where' : 'which', ['west'], {
|
|
107
|
+
encoding: 'utf8',
|
|
108
|
+
shell: IS_WIN,
|
|
109
|
+
windowsHide: true,
|
|
110
|
+
});
|
|
111
|
+
if (which.status !== 0) return null;
|
|
112
|
+
const lines = (which.stdout ?? '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
113
|
+
for (const line of lines) {
|
|
114
|
+
if (!existsSync(line)) continue;
|
|
115
|
+
if (!westOnPath(line)) continue;
|
|
116
|
+
return {
|
|
117
|
+
mode: 'launcher',
|
|
118
|
+
westExecutable: line,
|
|
119
|
+
zephyrBase: process.env.ZEPHYR_BASE || undefined,
|
|
120
|
+
source: 'path',
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── Strategy 2: $ZEPHYR_BASE sibling venv ───────────────────────────────────
|
|
127
|
+
|
|
128
|
+
/** The canonical Zephyr workspace layout puts the venv beside the SDK:
|
|
129
|
+
* <workspace>/{.venv, zephyr}. So ${ZEPHYR_BASE}/../.venv is the venv. */
|
|
130
|
+
export function discoverFromZephyrBase(): WestInstall | null {
|
|
131
|
+
const zb = process.env.ZEPHYR_BASE;
|
|
132
|
+
if (!zb || !isZephyrBase(zb)) return null;
|
|
133
|
+
const workspaceDir = dirname(zb);
|
|
134
|
+
const venvDir = join(workspaceDir, '.venv');
|
|
135
|
+
const py = venvPython(venvDir);
|
|
136
|
+
if (!existsSync(py) || !pythonRunsWest(py)) return null;
|
|
137
|
+
return {
|
|
138
|
+
mode: 'module',
|
|
139
|
+
pythonExecutable: py,
|
|
140
|
+
zephyrBase: zb,
|
|
141
|
+
source: 'zephyr-base-venv',
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── Strategy 3: micromamba env (the @typecad/zephyr-installer install) ─────
|
|
146
|
+
|
|
147
|
+
// Locate the micromamba binary + root prefix. The installer downloads
|
|
148
|
+
// micromamba to $MAMBA_ROOT_PREFIX/bin (POSIX) or Library/bin (Windows); the
|
|
149
|
+
// root defaults to ~/micromamba. Returns null if the binary isn't present
|
|
150
|
+
// (the installer hasn't run on this machine).
|
|
151
|
+
function findMicromamba(): { exe: string; rootPrefix: string } | null {
|
|
152
|
+
const root = process.env.MAMBA_ROOT_PREFIX || join(homedir(), 'micromamba');
|
|
153
|
+
const exe = IS_WIN
|
|
154
|
+
? join(root, 'Library', 'bin', 'micromamba.exe')
|
|
155
|
+
: join(root, 'bin', 'micromamba');
|
|
156
|
+
return existsSync(exe) ? { exe, rootPrefix: root } : null;
|
|
157
|
+
}
|
|
158
|
+
|
|
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
|
+
/** Read a TYPECAD_ZEPHYR_* value from the installer-written env-vars file in
|
|
172
|
+
* a micromamba env. Handles .sh (export VAR="val"), .bat (set "VAR=val"),
|
|
173
|
+
* and .ps1 ($env:VAR = "val"). Returns undefined if absent/unreadable. */
|
|
174
|
+
function readMicromambaEnvVar(envDir: string, varName: string): string | undefined {
|
|
175
|
+
const candidates = IS_WIN
|
|
176
|
+
? [join(envDir, 'etc', 'conda', 'env-vars.ps1'), join(envDir, 'etc', 'conda', 'env-vars.bat')]
|
|
177
|
+
: [join(envDir, 'etc', 'conda', 'env-vars.sh')];
|
|
178
|
+
for (const f of candidates) {
|
|
179
|
+
if (!existsSync(f)) continue;
|
|
180
|
+
try {
|
|
181
|
+
const text = readFileSync(f, 'utf8');
|
|
182
|
+
// .sh/.ps1: VAR = "value" (quoted value after =).
|
|
183
|
+
let m = text.match(new RegExp(`${varName}\\s*=\\s*"([^"]+)"`));
|
|
184
|
+
if (m) return m[1];
|
|
185
|
+
// .bat: set "VAR=value" (value after VAR= inside quotes).
|
|
186
|
+
m = text.match(new RegExp(`${varName}=([^"\\r\\n]+)"`));
|
|
187
|
+
if (m) return m[1].trim();
|
|
188
|
+
} catch { /* ignore unreadable */ }
|
|
189
|
+
}
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function discoverFromMicromamba(
|
|
194
|
+
envName: string = process.env.TYPECAD_ZEPHYR_ENV || 'zephyr',
|
|
195
|
+
): WestInstall | null {
|
|
196
|
+
const mm = findMicromamba();
|
|
197
|
+
if (!mm) return null;
|
|
198
|
+
const envDir = join(mm.rootPrefix, 'envs', envName);
|
|
199
|
+
const westExe = join(envDir, IS_WIN ? 'Scripts' : 'bin', IS_WIN ? 'west.exe' : 'west');
|
|
200
|
+
if (!existsSync(envDir) || !existsSync(westExe)) return null;
|
|
201
|
+
// Read ZEPHYR_BASE from the installer's env-vars so the compat check (and
|
|
202
|
+
// anything else in the cuttlefish process) can detect the Zephyr version
|
|
203
|
+
// WITHOUT activation — micromamba run sets it only inside the west subprocess.
|
|
204
|
+
const zb = readMicromambaEnvVar(envDir, 'TYPECAD_ZEPHYR_BASE');
|
|
205
|
+
const sdk = readMicromambaEnvVar(envDir, 'TYPECAD_ZEPHYR_SDK_INSTALL_DIR');
|
|
206
|
+
return {
|
|
207
|
+
mode: 'micromamba',
|
|
208
|
+
micromambaExe: mm.exe,
|
|
209
|
+
envName,
|
|
210
|
+
mambaRootPrefix: mm.rootPrefix,
|
|
211
|
+
zephyrBase: zb && isZephyrBase(zb) ? zb : undefined,
|
|
212
|
+
sdkInstallDir: sdk && existsSync(sdk) ? sdk : undefined,
|
|
213
|
+
source: 'micromamba',
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ── Strategy 4: well-known workspace layouts ───────────────────────────────
|
|
218
|
+
|
|
219
|
+
/** Candidate Zephyr workspace directories. Each may contain both `.venv/`
|
|
220
|
+
* and `zephyr/` (the SDK). Exported for test injection. */
|
|
221
|
+
export function wellKnownWorkspaces(): string[] {
|
|
222
|
+
const home = homedir();
|
|
223
|
+
if (IS_WIN) {
|
|
224
|
+
return [
|
|
225
|
+
join(home, 'zephyrproject'),
|
|
226
|
+
join(home, 'zephyr'),
|
|
227
|
+
'C:\\zephyrproject',
|
|
228
|
+
'C:\\zephyr',
|
|
229
|
+
];
|
|
230
|
+
}
|
|
231
|
+
return [
|
|
232
|
+
join(home, 'zephyrproject'),
|
|
233
|
+
join(home, 'zephyr'),
|
|
234
|
+
'/opt/zephyrproject',
|
|
235
|
+
'/opt/zephyr',
|
|
236
|
+
];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function discoverFromWellKnown(
|
|
240
|
+
workspaces: string[] = wellKnownWorkspaces(),
|
|
241
|
+
): WestInstall | null {
|
|
242
|
+
for (const ws of workspaces) {
|
|
243
|
+
const venvDir = join(ws, '.venv');
|
|
244
|
+
const py = venvPython(venvDir);
|
|
245
|
+
if (!existsSync(py) || !pythonRunsWest(py)) continue;
|
|
246
|
+
// Resolve ZEPHYR_BASE if the SDK sits beside the venv.
|
|
247
|
+
const zb = join(ws, 'zephyr');
|
|
248
|
+
return {
|
|
249
|
+
mode: 'module',
|
|
250
|
+
pythonExecutable: py,
|
|
251
|
+
zephyrBase: isZephyrBase(zb) ? zb : undefined,
|
|
252
|
+
source: 'well-known',
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ── Strategy 5: system pythons via `-m west` ────────────────────────────────
|
|
259
|
+
|
|
260
|
+
/** Candidate system Python interpreters to probe with `-m west`. */
|
|
261
|
+
export function systemPythons(): string[] {
|
|
262
|
+
if (IS_WIN) return ['py', 'python', 'python3'];
|
|
263
|
+
return ['python3', 'python'];
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function discoverFromSystemPython(
|
|
267
|
+
pythons: string[] = systemPythons(),
|
|
268
|
+
): WestInstall | null {
|
|
269
|
+
for (const py of pythons) {
|
|
270
|
+
if (!pythonRunsWest(py)) continue;
|
|
271
|
+
return {
|
|
272
|
+
mode: 'module',
|
|
273
|
+
pythonExecutable: py,
|
|
274
|
+
zephyrBase: process.env.ZEPHYR_BASE || undefined,
|
|
275
|
+
source: 'system-python',
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ── Top-level cascade ────────────────────────────────────────────────────────
|
|
282
|
+
|
|
283
|
+
let cachedDiscover: WestInstall | null | undefined;
|
|
284
|
+
|
|
285
|
+
/** Clear the process-local discovery cache (for tests). */
|
|
286
|
+
export function resetWestDiscoveryCache(): void {
|
|
287
|
+
cachedDiscover = undefined;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Try each discovery strategy in order. The first usable install wins.
|
|
292
|
+
* Result is memoized for the process lifetime (west installs don't move).
|
|
293
|
+
*
|
|
294
|
+
* Order: PATH → $ZEPHYR_BASE venv → micromamba env → well-known workspaces →
|
|
295
|
+
* system pythons.
|
|
296
|
+
* Returns null when no usable west install is found.
|
|
297
|
+
*/
|
|
298
|
+
export function discoverWest(): WestInstall | null {
|
|
299
|
+
if (cachedDiscover !== undefined) return cachedDiscover;
|
|
300
|
+
const strategies: Array<() => WestInstall | null> = [
|
|
301
|
+
discoverFromPath,
|
|
302
|
+
discoverFromZephyrBase,
|
|
303
|
+
discoverFromMicromamba,
|
|
304
|
+
discoverFromWellKnown,
|
|
305
|
+
discoverFromSystemPython,
|
|
306
|
+
];
|
|
307
|
+
for (const strat of strategies) {
|
|
308
|
+
let install: WestInstall | null = null;
|
|
309
|
+
try {
|
|
310
|
+
install = strat();
|
|
311
|
+
} catch {
|
|
312
|
+
install = null;
|
|
313
|
+
}
|
|
314
|
+
if (install) {
|
|
315
|
+
cachedDiscover = install;
|
|
316
|
+
return install;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
cachedDiscover = null;
|
|
320
|
+
return null;
|
|
321
|
+
}
|