@typecad/framework-zephyr 1.0.0-alpha.15 → 1.0.0-alpha.18
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/LICENSE +202 -21
- package/README.md +49 -87
- package/dist/as-built.d.ts +2 -2
- package/dist/as-built.js +2 -2
- package/dist/boardgen.d.ts +1 -9
- package/dist/boardgen.js +228 -45
- package/dist/chips/types.d.ts +1 -1
- package/dist/debug-codegen.js +1 -1
- package/dist/display/touch-adapter.js +1 -1
- package/dist/display/ui-adapter.js +549 -549
- package/dist/doctor.js +4 -4
- package/dist/dt-config/custom-board.js +2 -2
- package/dist/dt-config/kconfig.js +18 -12
- package/dist/dt-config/overlay.d.ts +2 -2
- package/dist/dt-config/overlay.js +2 -2
- package/dist/framework.manifest.d.ts +3 -3
- package/dist/framework.manifest.js +10 -7
- package/dist/index.js +5 -5
- package/dist/licenses.d.ts +2 -2
- package/dist/licenses.js +8 -8
- package/dist/lowering/fs.js +1 -1
- package/dist/lowering/gpio.js +0 -32
- package/dist/lowering/http.js +264 -32
- package/dist/lowering/i2c.js +0 -8
- package/dist/lowering/interrupts.js +6 -0
- package/dist/lowering/mqtt.js +110 -8
- package/dist/lowering/usb.js +11 -11
- package/dist/lowering/wdt.js +2 -29
- package/dist/sdk/board-catalog-sync.d.ts +1 -3
- package/dist/sdk/board-catalog-sync.js +4 -10
- package/dist/strategy.d.ts +22 -44
- package/dist/strategy.js +152 -155
- package/dist/tmp-probe.d.ts +2 -0
- package/dist/tmp-probe.js +9 -0
- package/dist/toolchain/debug-config.d.ts +50 -90
- package/dist/toolchain/debug-config.js +241 -502
- package/dist/toolchain/env-check.d.ts +1 -3
- package/dist/toolchain/env-check.js +2 -7
- package/dist/toolchain/index.d.ts +65 -2
- package/dist/toolchain/index.js +432 -55
- package/dist/toolchain/runners.d.ts +16 -0
- package/dist/toolchain/runners.js +75 -0
- package/dist/toolchain/scaffold.d.ts +1 -1
- package/dist/toolchain/scaffold.js +12 -12
- package/dist/toolchain/west-discover.d.ts +6 -0
- package/dist/toolchain/west-discover.js +36 -13
- package/dist/toolchain/west-spawn.js +8 -2
- package/installer/README.md +328 -328
- package/installer/install.sh +2 -2
- package/installer/templates/project/.typecad/activate-zephyr.ps1 +1 -1
- package/installer/templates/project/.typecad/activate-zephyr.sh +1 -1
- package/installer/templates/project/.vscode/settings.json +1 -1
- package/installer/templates/project/README.md +2 -2
- package/package.json +5 -5
- package/src/as-built.ts +206 -206
- package/src/boardgen.ts +214 -48
- package/src/chips/types.ts +567 -567
- package/src/display/touch-adapter.ts +204 -204
- package/src/display/ui-adapter.ts +781 -781
- package/src/doctor.ts +4 -4
- package/src/dt-config/custom-board.ts +2 -2
- package/src/dt-config/kconfig.ts +18 -12
- package/src/dt-config/overlay.ts +1058 -1058
- package/src/framework.manifest.ts +538 -535
- package/src/index.ts +5 -5
- package/src/licenses.ts +425 -425
- package/src/lowering/fs.ts +135 -135
- package/src/lowering/gpio.ts +0 -33
- package/src/lowering/http.ts +264 -32
- package/src/lowering/i2c.ts +0 -9
- package/src/lowering/interrupts.ts +6 -0
- package/src/lowering/mqtt.ts +109 -8
- package/src/lowering/usb.ts +221 -221
- package/src/lowering/wdt.ts +2 -25
- package/src/sdk/board-catalog-sync.ts +4 -25
- package/src/strategy.ts +2265 -2309
- package/src/toolchain/debug-config.ts +264 -514
- package/src/toolchain/env-check.ts +279 -285
- package/src/toolchain/index.ts +1703 -1286
- package/src/toolchain/runners.ts +80 -0
- package/src/toolchain/scaffold.ts +296 -296
- package/src/toolchain/west-discover.ts +35 -13
- package/src/toolchain/west-spawn.ts +174 -168
- package/dist/async/timer-polyfill.d.ts +0 -10
- package/dist/async/timer-polyfill.js +0 -95
- package/dist/chips/board-overrides.d.ts +0 -7
- package/dist/chips/board-overrides.js +0 -11
- package/dist/chips/esp32.d.ts +0 -2
- package/dist/chips/esp32.js +0 -71
- package/dist/chips/esp32s3.d.ts +0 -2
- package/dist/chips/esp32s3.js +0 -103
- package/dist/chips/soc/.d.ts +0 -2
- package/dist/chips/soc/.js +0 -129
- package/dist/chips/soc/esp32.d.ts +0 -2
- package/dist/chips/soc/esp32.js +0 -120
- package/dist/chips/soc/esp32c3.d.ts +0 -2
- package/dist/chips/soc/esp32c3.js +0 -90
- package/dist/chips/soc/esp32c6.d.ts +0 -2
- package/dist/chips/soc/esp32c6.js +0 -109
- package/dist/chips/soc/esp32s3.d.ts +0 -2
- package/dist/chips/soc/esp32s3.js +0 -189
- package/dist/chips/soc/index.d.ts +0 -2
- package/dist/chips/soc/index.js +0 -23
- package/dist/chips/soc/nrf52840.d.ts +0 -2
- package/dist/chips/soc/nrf52840.js +0 -130
- package/dist/chips/soc/rp2040.d.ts +0 -2
- package/dist/chips/soc/rp2040.js +0 -141
- package/dist/chips/soc/rp2350a.d.ts +0 -2
- package/dist/chips/soc/rp2350a.js +0 -145
- package/dist/chips/soc/samd21g18a.d.ts +0 -2
- package/dist/chips/soc/samd21g18a.js +0 -143
- package/dist/chips/soc/stm32f411xe.d.ts +0 -2
- package/dist/chips/soc/stm32f411xe.js +0 -251
- package/dist/chips/xiao-ble.d.ts +0 -2
- package/dist/chips/xiao-ble.js +0 -100
- package/dist/lowering/power.d.ts +0 -9
- package/dist/lowering/power.js +0 -60
- package/dist/lowering/pulse.d.ts +0 -7
- package/dist/lowering/pulse.js +0 -51
- package/dist/lowering/tone.d.ts +0 -10
- package/dist/lowering/tone.js +0 -63
- package/dist/lowering/worker-backing.d.ts +0 -14
- package/dist/lowering/worker-backing.js +0 -79
- package/dist/lowering/worker.d.ts +0 -6
- package/dist/lowering/worker.js +0 -14
- package/dist/sdk/board-data.generated.d.ts +0 -2
- package/dist/sdk/board-data.generated.js +0 -4
- package/dist/sdk/catalog-walker.d.ts +0 -90
- package/dist/sdk/catalog-walker.js +0 -682
- package/dist/sdk/dts-reader.d.ts +0 -83
- package/dist/sdk/dts-reader.js +0 -596
- package/src/debug-codegen.ts +0 -207
package/dist/toolchain/index.js
CHANGED
|
@@ -10,22 +10,24 @@
|
|
|
10
10
|
// a SDK root is discovered. See west-discover.ts / west-spawn.ts.
|
|
11
11
|
//
|
|
12
12
|
// The board target is carried via frameworkData.buildTarget (populated as
|
|
13
|
-
// ToolchainOptions.buildTarget by the
|
|
13
|
+
// ToolchainOptions.buildTarget by the typecad-hal CLI), defaulting to the
|
|
14
14
|
// framework's canonical MVP target (xiao_ble).
|
|
15
15
|
//
|
|
16
16
|
// Mirrors framework-esp32/src/toolchain/index.ts structure: projectRoot derived
|
|
17
17
|
// from outputDir, prepare is a no-op (scaffold happens in compile when the
|
|
18
18
|
// target is known), GCC errors parsed via the shared parseCompileErrors helper.
|
|
19
19
|
// ---------------------------------------------------------------------------
|
|
20
|
-
import { spawnSync } from 'node:child_process';
|
|
21
|
-
import {
|
|
20
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
21
|
+
import { connect as netConnect } from 'node:net';
|
|
22
|
+
import { basename, delimiter, dirname, join } from 'node:path';
|
|
22
23
|
import { readdirSync, readFileSync, mkdirSync, rmSync, existsSync, writeFileSync } from 'node:fs';
|
|
23
24
|
import { parseCompileErrors } from '@typecad/cuttlefish/api/shared';
|
|
24
25
|
import { scaffoldZephyrProject, writeIfChanged, appendLibraryOverlayFragments } from './scaffold.js';
|
|
25
26
|
import { parseZephyrDts, asBuiltJson } from '../as-built.js';
|
|
26
27
|
import { westSpawn, buildEnv } from './west-spawn.js';
|
|
27
28
|
import { discoverWest } from './west-discover.js';
|
|
28
|
-
import { writeDebugConfig, resolveDebugLocations } from './debug-config.js';
|
|
29
|
+
import { writeDebugConfig, resolveDebugLocations, debugArtifactsNeedRewrite, DEBUG_SERVER_PORT, DEBUG_SERVER_TCL_PORT } from './debug-config.js';
|
|
30
|
+
import { readRunnersFacts } from './runners.js';
|
|
29
31
|
import { bossacTouchReset } from './bossac-touch.js';
|
|
30
32
|
import { ZephyrStrategy } from '../strategy.js';
|
|
31
33
|
import { generateOverlay } from '../dt-config/overlay.js';
|
|
@@ -255,12 +257,12 @@ function targetFromOptions(o) {
|
|
|
255
257
|
const fcTarget = o.frameworkConfig?.target;
|
|
256
258
|
const board = o.buildTarget ?? fcTarget;
|
|
257
259
|
if (!board) {
|
|
258
|
-
throw new Error('No build target: set board: in
|
|
260
|
+
throw new Error('No build target: set board: in typecad-hal.config.ts (or frameworkData.buildTarget for custom-board projects).');
|
|
259
261
|
}
|
|
260
262
|
return board;
|
|
261
263
|
}
|
|
262
264
|
/**
|
|
263
|
-
* Derive the Zephyr project root from the
|
|
265
|
+
* Derive the Zephyr project root from the typecad-hal-emitted source path.
|
|
264
266
|
*
|
|
265
267
|
* Cuttlefish emits `src/main.cpp` under the output dir. The CLI passes
|
|
266
268
|
* `sourcePath` = full path to `main.cpp` and `outputDir` = its parent (`src/`).
|
|
@@ -280,6 +282,23 @@ export function projectRootFromOptions(o) {
|
|
|
280
282
|
*/
|
|
281
283
|
const BUILD_TIMEOUT_MS = 600_000;
|
|
282
284
|
const FLASH_TIMEOUT_MS = 120_000;
|
|
285
|
+
/**
|
|
286
|
+
* The board a cached build dir was configured for (CMakeCache.txt's
|
|
287
|
+
* BOARD:STRING — the exact value passed to `west build -b`), or undefined
|
|
288
|
+
* when no cache exists. compile() compares it against the requested board
|
|
289
|
+
* and nukes the dir on mismatch: `west build`'s --pristine=auto covers
|
|
290
|
+
* cmake/config churn, NOT a board switch — west aborts with "refusing to
|
|
291
|
+
* proceed without --force", and cuttlefish doesn't forward that flag.
|
|
292
|
+
*/
|
|
293
|
+
export function cachedBuildBoard(buildDir) {
|
|
294
|
+
try {
|
|
295
|
+
return readFileSync(join(buildDir, 'CMakeCache.txt'), 'utf-8')
|
|
296
|
+
.match(/^BOARD:STRING=(.+)$/m)?.[1]?.trim() || undefined;
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
return undefined; // no build dir / unreadable cache — treat as fresh
|
|
300
|
+
}
|
|
301
|
+
}
|
|
283
302
|
export function resolveProbeMethod(zc, chip, purpose = 'flash') {
|
|
284
303
|
const probe = zc?.probe;
|
|
285
304
|
const runner = zc?.runner;
|
|
@@ -287,7 +306,7 @@ export function resolveProbeMethod(zc, chip, purpose = 'flash') {
|
|
|
287
306
|
if (probe && runner) {
|
|
288
307
|
return {
|
|
289
308
|
ok: false,
|
|
290
|
-
error: `
|
|
309
|
+
error: `typecad-hal.config.ts sets both zephyr.probe ('${probe}') and zephyr.runner ('${runner}'). ` +
|
|
291
310
|
`They are two ways to choose the probe method — remove one.`,
|
|
292
311
|
};
|
|
293
312
|
}
|
|
@@ -322,14 +341,31 @@ export function resolveProbeMethod(zc, chip, purpose = 'flash') {
|
|
|
322
341
|
}
|
|
323
342
|
return { ok: true, runner, args: userArgs };
|
|
324
343
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
344
|
+
export function resolveSessionOpenOcd(env = process.env, sdkInstallDir) {
|
|
345
|
+
const exeName = process.platform === 'win32' ? 'openocd.exe' : 'openocd';
|
|
346
|
+
if (env.OPENOCD && existsSync(env.OPENOCD)) {
|
|
347
|
+
return { exe: env.OPENOCD, searchDirs: [] };
|
|
348
|
+
}
|
|
349
|
+
const sdkRoot = env.ZEPHYR_SDK_INSTALL_DIR || sdkInstallDir;
|
|
350
|
+
if (sdkRoot) {
|
|
351
|
+
const exe = join(sdkRoot, 'hosttools', 'openocd', 'bin', exeName);
|
|
352
|
+
if (existsSync(exe)) {
|
|
353
|
+
const searchDirs = [
|
|
354
|
+
join(sdkRoot, 'hosttools', 'openocd', 'share', 'openocd', 'scripts'),
|
|
355
|
+
join(sdkRoot, 'hosttools', 'openocd', 'scripts'),
|
|
356
|
+
].filter((d) => existsSync(d));
|
|
357
|
+
return { exe, searchDirs };
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
for (const dir of (env.PATH ?? '').split(delimiter)) {
|
|
361
|
+
if (!dir)
|
|
362
|
+
continue;
|
|
363
|
+
const candidate = join(dir, exeName);
|
|
364
|
+
if (existsSync(candidate))
|
|
365
|
+
return { exe: candidate, searchDirs: [] };
|
|
366
|
+
}
|
|
367
|
+
return undefined;
|
|
368
|
+
}
|
|
333
369
|
function openocdProbeSession(buildDir, zc, chip, commands) {
|
|
334
370
|
// Config resolution — two sources, in order:
|
|
335
371
|
// 1. The named probe method's verbatim debugCfg from the board catalog
|
|
@@ -360,21 +396,15 @@ function openocdProbeSession(buildDir, zc, chip, commands) {
|
|
|
360
396
|
.map((a) => a.slice('--cmd-pre-init='.length)),
|
|
361
397
|
];
|
|
362
398
|
const install = discoverWest();
|
|
363
|
-
const
|
|
364
|
-
if (!
|
|
399
|
+
const sessionOpenOcd = resolveSessionOpenOcd(process.env, install?.sdkInstallDir);
|
|
400
|
+
if (!sessionOpenOcd)
|
|
365
401
|
return undefined;
|
|
366
|
-
const openocdExe =
|
|
367
|
-
|
|
368
|
-
return undefined;
|
|
369
|
-
// Script search path: the SDK layouts differ across versions — prefer the
|
|
370
|
-
// share/ form west's own runner uses, fall back to the scripts/ form.
|
|
371
|
-
const shareScripts = join(sdkRoot, 'hosttools', 'openocd', 'share', 'openocd', 'scripts');
|
|
372
|
-
const binScripts = join(sdkRoot, 'hosttools', 'openocd', 'scripts');
|
|
373
|
-
const searchDir = existsSync(shareScripts) ? shareScripts : binScripts;
|
|
402
|
+
const openocdExe = sessionOpenOcd.exe;
|
|
403
|
+
const searchArgs = sessionOpenOcd.searchDirs.flatMap((d) => ['-s', d]);
|
|
374
404
|
let cfgArgs;
|
|
375
405
|
let sessionCfg;
|
|
376
406
|
if (cfgLines && cfgLines.length > 0) {
|
|
377
|
-
sessionCfg = join(buildDir, '
|
|
407
|
+
sessionCfg = join(buildDir, 'typecad-hal-probe.cfg');
|
|
378
408
|
}
|
|
379
409
|
else {
|
|
380
410
|
// The board target's qualifier ('blackpill_f411ce/stm32f411xe' →
|
|
@@ -409,7 +439,7 @@ function openocdProbeSession(buildDir, zc, chip, commands) {
|
|
|
409
439
|
cfgArgs = ['-f', sessionCfg];
|
|
410
440
|
}
|
|
411
441
|
const res = spawnSync(openocdExe, [
|
|
412
|
-
|
|
442
|
+
...searchArgs, ...cfgArgs,
|
|
413
443
|
// Pre-init TCL AFTER the cfg (overrides its reset_config) and BEFORE
|
|
414
444
|
// init — the same position west gives --cmd-pre-init.
|
|
415
445
|
...preInit.map((c) => ['-c', c]).flat(),
|
|
@@ -503,6 +533,18 @@ function isUf2DriveVanishRace(output) {
|
|
|
503
533
|
&& /WinError 433/.test(output)
|
|
504
534
|
&& /copymode/.test(output);
|
|
505
535
|
}
|
|
536
|
+
/**
|
|
537
|
+
* Whether a `west flash` (openocd) output carries one of the known
|
|
538
|
+
* target-ignored-SWD signatures — the DAP connect failing ("init mode
|
|
539
|
+
* failed (unable to connect to the target)", i.e. the DPIDR read never
|
|
540
|
+
* succeeded) or a reset/halt never landing ("timed out while waiting for
|
|
541
|
+
* target halted" / "TARGET: <name> - Not halted"). Both mean the board (or
|
|
542
|
+
* probe) needs a power-cycle or the SWD-free DFU path, not a retry of the
|
|
543
|
+
* same command. Centralized so the upload hint stays testable.
|
|
544
|
+
*/
|
|
545
|
+
export function isTargetSwdFailure(output) {
|
|
546
|
+
return /unable to connect to the target|timed out while waiting for target halted|TARGET: \S+ - Not halted/.test(output);
|
|
547
|
+
}
|
|
506
548
|
/**
|
|
507
549
|
* Cleanse the `west flash` output shown to the user.
|
|
508
550
|
*
|
|
@@ -691,7 +733,7 @@ export const Toolchain = {
|
|
|
691
733
|
// Custom-board generation: an MCU-only target (no board package) has no
|
|
692
734
|
// upstream Zephyr board — generate one under boards/typecad/<name>/ from
|
|
693
735
|
// the chip's silicon data. Opt-in via `zephyr.customBoard: true` in
|
|
694
|
-
//
|
|
736
|
+
// typecad-hal.config.ts; the board takes its name from the build target.
|
|
695
737
|
// Idempotent — regenerated on every compile, before the overlay pass.
|
|
696
738
|
if (zc?.customBoard === true) {
|
|
697
739
|
const generated = generateCustomBoard(projectRoot, chip, board.split('/')[0]);
|
|
@@ -868,10 +910,15 @@ export const Toolchain = {
|
|
|
868
910
|
// .ninja_deps, after which every ninja run fails with `dependency cycle`.
|
|
869
911
|
// Plain source edits never reconfigure CMake, so they cannot trigger it —
|
|
870
912
|
// and the retry after the spawn below self-heals any path that still does.
|
|
871
|
-
// Board switches need
|
|
872
|
-
//
|
|
873
|
-
//
|
|
874
|
-
|
|
913
|
+
// Board switches DO need a nuke: `west build`'s --pristine=auto covers
|
|
914
|
+
// cmake/config churn, not a -b <board> mismatch — west aborts with
|
|
915
|
+
// "refusing to proceed without --force" and cuttlefish doesn't forward
|
|
916
|
+
// that flag, so the user would be stuck deleting the dir by hand. The
|
|
917
|
+
// cache names the board it was configured for (BOARD:STRING); detect the
|
|
918
|
+
// mismatch and apply west's own suggested remedy automatically.
|
|
919
|
+
const cachedBoard = cachedBuildBoard(buildDir);
|
|
920
|
+
const boardChanged = Boolean(cachedBoard && cachedBoard !== board);
|
|
921
|
+
if (configChanged || boardChanged) {
|
|
875
922
|
try {
|
|
876
923
|
rmSync(buildDir, { recursive: true, force: true });
|
|
877
924
|
}
|
|
@@ -892,7 +939,7 @@ export const Toolchain = {
|
|
|
892
939
|
}
|
|
893
940
|
}
|
|
894
941
|
catch { /* no overlay — let Zephyr auto-detect or build without one */ }
|
|
895
|
-
// Append user cmake args from
|
|
942
|
+
// Append user cmake args from typecad-hal.config.ts zephyr.cmakeArgs.
|
|
896
943
|
const userCmakeArgs = zc?.cmakeArgs;
|
|
897
944
|
if (userCmakeArgs && userCmakeArgs.length > 0) {
|
|
898
945
|
if (!buildArgs.includes('--'))
|
|
@@ -918,36 +965,39 @@ export const Toolchain = {
|
|
|
918
965
|
const stdout = typeof result.stdout === 'string' ? result.stdout : (result.stdout?.toString() ?? '');
|
|
919
966
|
const stderr = typeof result.stderr === 'string' ? result.stderr : (result.stderr?.toString() ?? '');
|
|
920
967
|
const output = stdout + stderr + (pristineRetry
|
|
921
|
-
? '\n[
|
|
968
|
+
? '\n[typecad-hal] dependency cycle detected in the cached build dir — retried with a pristine build'
|
|
922
969
|
: '');
|
|
923
970
|
// Prefix the build log with how west was resolved, for transparency.
|
|
924
971
|
const header = `Using west via ${inv.install.source}` +
|
|
925
972
|
(inv.install.zephyrBase ? ` (ZEPHYR_BASE=${inv.install.zephyrBase})` : '') + '\n';
|
|
926
|
-
// After a successful build
|
|
927
|
-
//
|
|
928
|
-
//
|
|
929
|
-
//
|
|
930
|
-
//
|
|
931
|
-
if (result.status === 0 &&
|
|
973
|
+
// After a successful build on a gdb-capable board, keep the VS Code debug
|
|
974
|
+
// artifacts current: always under --debug, or on a plain build when they
|
|
975
|
+
// need it (still in create-time starter shape, an outDir rename moved the
|
|
976
|
+
// app root, or a pre-west self-managed-server entry). Non-fatal on
|
|
977
|
+
// failure — a missing artifact doesn't block the build.
|
|
978
|
+
if (result.status === 0 && debugMode === 'gdb') {
|
|
932
979
|
try {
|
|
933
980
|
const { workspaceRoot, appRel } = resolveDebugLocations(projectRoot);
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
981
|
+
if (isGdbDebug
|
|
982
|
+
|| debugArtifactsNeedRewrite(workspaceRoot, appRel, readRunnersFacts(buildDir)?.gdb?.replace(/\\/g, '/'))) {
|
|
983
|
+
writeDebugConfig({
|
|
984
|
+
projectRoot,
|
|
985
|
+
workspaceRoot,
|
|
986
|
+
appRel,
|
|
987
|
+
target: board,
|
|
988
|
+
buildDir,
|
|
989
|
+
sourceMapPath: join(dirname(o.sourcePath), `${basename(o.sourcePath)}.thcppmap.json`),
|
|
990
|
+
});
|
|
991
|
+
}
|
|
942
992
|
}
|
|
943
993
|
catch (e) {
|
|
944
|
-
console.warn(`[
|
|
994
|
+
console.warn(`[typecad-hal] gdb debug config generation failed: ${e.message}`);
|
|
945
995
|
}
|
|
946
996
|
}
|
|
947
997
|
// As-built snapshot: after a successful build, the resolved devicetree
|
|
948
998
|
// at <buildDir>/zephyr/zephyr.dts carries the board's pinctrl labels —
|
|
949
999
|
// the STABLE name grammar, immune to vendor macro churn. Harvest its
|
|
950
|
-
// routes into .
|
|
1000
|
+
// routes into .typecad-hal/as-built.json; the next build's board-module
|
|
951
1001
|
// generation merges them per-pin over the catalog harvest (build wins,
|
|
952
1002
|
// silently when they agree). One-build freshness lag on first setup,
|
|
953
1003
|
// self-maintaining after. Best-effort — a missing/unparseable artifact
|
|
@@ -959,14 +1009,14 @@ export const Toolchain = {
|
|
|
959
1009
|
const facts = parseZephyrDts(dtsText);
|
|
960
1010
|
const total = facts.adc.length + facts.pwm.length + facts.dac.length;
|
|
961
1011
|
if (total > 0) {
|
|
962
|
-
// Write beside the project's board module — the .
|
|
1012
|
+
// Write beside the project's board module — the .typecad-hal dir the
|
|
963
1013
|
// config loader reads from, discovered by walking up to the
|
|
964
1014
|
// generated board.json (the scaffold root and the config root are
|
|
965
1015
|
// different dirs in the standard layout: src/out vs project root).
|
|
966
|
-
let cfDir = join(projectRoot, '.
|
|
1016
|
+
let cfDir = join(projectRoot, '.typecad-hal');
|
|
967
1017
|
for (let dir = projectRoot;; dir = dirname(dir)) {
|
|
968
|
-
if (existsSync(join(dir, '.
|
|
969
|
-
cfDir = join(dir, '.
|
|
1018
|
+
if (existsSync(join(dir, '.typecad-hal', 'board.json'))) {
|
|
1019
|
+
cfDir = join(dir, '.typecad-hal');
|
|
970
1020
|
break;
|
|
971
1021
|
}
|
|
972
1022
|
const parent = dirname(dir);
|
|
@@ -995,6 +1045,22 @@ export const Toolchain = {
|
|
|
995
1045
|
if (!probe.ok) {
|
|
996
1046
|
return { success: false, output: `-- west flash: ${probe.error}` };
|
|
997
1047
|
}
|
|
1048
|
+
// Flashing over a probe needs it EXCLUSIVE: a debug server still bound to
|
|
1049
|
+
// the gdb port (live session or an orphan whose wrapper died — a VS Code
|
|
1050
|
+
// window reload kills task terminals without killing their children on
|
|
1051
|
+
// Windows) makes openocd fail with LIBUSB_ERROR_ACCESS before any retry
|
|
1052
|
+
// logic can help. Reclaim it up front — it is ours by convention.
|
|
1053
|
+
{
|
|
1054
|
+
const holder = portOwnerPid(DEBUG_SERVER_PORT);
|
|
1055
|
+
if (holder !== undefined && holder !== process.pid) {
|
|
1056
|
+
console.log(`-- west flash: stopping debug server (pid ${holder}) — flashing needs exclusive probe access`);
|
|
1057
|
+
killPidTree(holder);
|
|
1058
|
+
try {
|
|
1059
|
+
rmSync(join(projectRoot, '.typecad-hal', 'debug-server.pid'), { force: true });
|
|
1060
|
+
}
|
|
1061
|
+
catch { /* already gone */ }
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
998
1064
|
// BOSSA bootloader boards with touch-reset data: open the app's console
|
|
999
1065
|
// port at 1200 baud (the firmware's USB shim reboots into the
|
|
1000
1066
|
// bootloader), wait for the bootloader identity, and flash THAT port.
|
|
@@ -1012,7 +1078,7 @@ export const Toolchain = {
|
|
|
1012
1078
|
if (uploadRequiresPort(flashRunner) && !flashPort) {
|
|
1013
1079
|
return {
|
|
1014
1080
|
success: false,
|
|
1015
|
-
output: `-- upload requires a port for ${flashRunner} flashing. Set --port <port> on the command line (or the
|
|
1081
|
+
output: `-- upload requires a port for ${flashRunner} flashing. Set --port <port> on the command line (or the TYPECAD_HAL_PORT env var).`,
|
|
1016
1082
|
};
|
|
1017
1083
|
}
|
|
1018
1084
|
if (flashRunner === 'bossac' && flashPort && chip.usb?.touchReset) {
|
|
@@ -1112,6 +1178,12 @@ export const Toolchain = {
|
|
|
1112
1178
|
]);
|
|
1113
1179
|
if (revived)
|
|
1114
1180
|
flashNotes.push(revived);
|
|
1181
|
+
// Known SWD-failure signatures get a recovery pointer — a board that
|
|
1182
|
+
// ignores SWD until power-cycled (low-power state, lockup, a wedged
|
|
1183
|
+
// probe) otherwise reads as a toolchain bug.
|
|
1184
|
+
if (isTargetSwdFailure(raw)) {
|
|
1185
|
+
flashNotes.push('-- target ignored SWD — if a retry fails too: power-cycle the board, replug the probe, or skip SWD entirely (hold BOOT0, tap reset, re-run with --probe dfu)');
|
|
1186
|
+
}
|
|
1115
1187
|
}
|
|
1116
1188
|
}
|
|
1117
1189
|
else {
|
|
@@ -1143,6 +1215,14 @@ export const Toolchain = {
|
|
|
1143
1215
|
// python we spawn resolves pyserial from the same venv). Falls back to the
|
|
1144
1216
|
// process env when no install is discovered.
|
|
1145
1217
|
const env = install ? buildEnv(install) : process.env;
|
|
1218
|
+
// Post-flash re-enumeration: the chip resets when `--upload` finishes, the
|
|
1219
|
+
// OS tears the serial device object down and re-creates it, and miniterm's
|
|
1220
|
+
// single open can land inside that window (a FileNotFoundError even though
|
|
1221
|
+
// the board never unplugged). Wait for the port to (re)appear first.
|
|
1222
|
+
if (!waitForSerialPort(py, env, projectRootFromOptions(o), o.port)) {
|
|
1223
|
+
process.exitCode = 1;
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1146
1226
|
spawnSync(py, ['-m', 'serial.tools.miniterm', o.port, String(baud)], {
|
|
1147
1227
|
cwd: projectRootFromOptions(o),
|
|
1148
1228
|
env,
|
|
@@ -1177,4 +1257,301 @@ export const Toolchain = {
|
|
|
1177
1257
|
});
|
|
1178
1258
|
spawnSync(inv.command, inv.args, inv.options);
|
|
1179
1259
|
},
|
|
1260
|
+
debugServer(o, action) {
|
|
1261
|
+
const projectRoot = projectRootFromOptions(o);
|
|
1262
|
+
const buildDir = join(projectRoot, 'build');
|
|
1263
|
+
const pidFile = join(projectRoot, '.typecad-hal', 'debug-server.pid');
|
|
1264
|
+
if (action === 'stop') {
|
|
1265
|
+
stopDebugServer(pidFile);
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
// start: wrap `west debugserver` as a long-running foreground process (the
|
|
1269
|
+
// VS Code background task owns this process; postDebugTask runs `stop`).
|
|
1270
|
+
if (!existsSync(join(buildDir, 'zephyr', 'runners.yaml'))) {
|
|
1271
|
+
console.error(`! No build at ${buildDir} — run 'npm run compile' (or F5's preLaunchTask) first.`);
|
|
1272
|
+
process.exitCode = 1;
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
const stale = readStaleServerPid(pidFile);
|
|
1276
|
+
if (stale !== undefined) {
|
|
1277
|
+
// Already running (pid alive): just re-emit the ready marker so the
|
|
1278
|
+
// task's problem matcher completes immediately.
|
|
1279
|
+
console.log(`[typecad-hal] west debugserver already running (pid ${stale})`);
|
|
1280
|
+
console.log(`TYPECAD_HAL: debug server ready on ${DEBUG_SERVER_PORT}`);
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
try {
|
|
1284
|
+
rmSync(pidFile, { force: true });
|
|
1285
|
+
}
|
|
1286
|
+
catch { /* already gone */ }
|
|
1287
|
+
// A wrapper/west that died without cleanup can leave openocd bound to
|
|
1288
|
+
// the gdb port with a stale pidfile — reclaim it or the new server
|
|
1289
|
+
// cannot bind (and gdb would attach to the orphan).
|
|
1290
|
+
const orphan = portOwnerPid(DEBUG_SERVER_PORT);
|
|
1291
|
+
if (orphan !== undefined && orphan !== process.pid) {
|
|
1292
|
+
console.log(`[typecad-hal] reclaiming orphaned debug server on :${DEBUG_SERVER_PORT} (pid ${orphan})`);
|
|
1293
|
+
killPidTree(orphan);
|
|
1294
|
+
}
|
|
1295
|
+
// Runner + quirk parity with flash (resolveProbeMethod, same as `west
|
|
1296
|
+
// debug`): an explicit zephyr.probe selects the runner; either way, an
|
|
1297
|
+
// srst-based openocd cfg behind an unwired NRST (the probeRunnerQuirks
|
|
1298
|
+
// condition, read from the board's own method data) makes `reset init`
|
|
1299
|
+
// time out — the IDE's post-attach reset would hang the session. Apply
|
|
1300
|
+
// the core-reset override server-side unless the config's runnerArgs
|
|
1301
|
+
// already carry it (the create flow bakes it in).
|
|
1302
|
+
const board = targetFromOptions(o);
|
|
1303
|
+
const chip = chipForBuild(projectRoot, board);
|
|
1304
|
+
const zc = o.zephyrConfig;
|
|
1305
|
+
const probe = resolveProbeMethod(zc, chip, 'debug');
|
|
1306
|
+
// The runner west will actually drive: the explicit choice, else the
|
|
1307
|
+
// board's declared debug-runner default from the build's runners.yaml.
|
|
1308
|
+
const runner = ((probe.ok && probe.runner) || undefined)
|
|
1309
|
+
?? readRunnersFacts(buildDir)?.debugRunner;
|
|
1310
|
+
const isOcd = runner === undefined || runner === 'openocd' || runner.startsWith('openocd');
|
|
1311
|
+
if (!isOcd) {
|
|
1312
|
+
console.warn(`! debug-server: the IDE wiring (ready marker, quirk args) targets the ` +
|
|
1313
|
+
`openocd runner; this build's debug runner is '${runner}'. Starting it plain — ` +
|
|
1314
|
+
`the F5 session may not connect.`);
|
|
1315
|
+
}
|
|
1316
|
+
const serverArgs = ['debugserver', '-d', buildDir];
|
|
1317
|
+
if (probe.ok) {
|
|
1318
|
+
if (probe.runner)
|
|
1319
|
+
serverArgs.push('--runner', probe.runner);
|
|
1320
|
+
serverArgs.push(...probe.args);
|
|
1321
|
+
}
|
|
1322
|
+
if (isOcd) {
|
|
1323
|
+
serverArgs.push('--gdb-port', String(DEBUG_SERVER_PORT),
|
|
1324
|
+
// Pinned so the readiness poll below has a deterministic port.
|
|
1325
|
+
'--tcl-port', String(DEBUG_SERVER_TCL_PORT),
|
|
1326
|
+
// The board's own openocd.cfg may declare gdb-attach/gdb-detach
|
|
1327
|
+
// events (reset-on-attach for standalone sessions). Under an
|
|
1328
|
+
// IDE-managed session a stop event mid-initialization aborts
|
|
1329
|
+
// debugger setup, so neutralize them: west's --cmd-pre-init lands
|
|
1330
|
+
// AFTER the cfg files in the openocd command line, so these win.
|
|
1331
|
+
'--cmd-pre-init', '$_TARGETNAME configure -event gdb-attach {}', '--cmd-pre-init', '$_TARGETNAME configure -event gdb-detach {}');
|
|
1332
|
+
const method = zc?.probe
|
|
1333
|
+
? chip.probeMethods?.find((m) => m.id === zc?.probe)
|
|
1334
|
+
: chip.probeMethods?.find((m) => m.debug !== false);
|
|
1335
|
+
const cfg = method?.debugCfg ?? [];
|
|
1336
|
+
const srst = cfg.some((l) => /reset_config\s+srst/.test(l));
|
|
1337
|
+
const connectAssert = cfg.some((l) => /connect_assert_srst/.test(l));
|
|
1338
|
+
if (method?.runner === 'openocd' && srst && !connectAssert
|
|
1339
|
+
&& !(probe.ok && probe.args.includes('--cmd-pre-init=reset_config none'))) {
|
|
1340
|
+
serverArgs.push('--cmd-pre-init', 'reset_config none');
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
const inv = westSpawn(serverArgs, { cwd: projectRoot });
|
|
1344
|
+
const child = spawn(inv.command, inv.args, {
|
|
1345
|
+
...inv.options,
|
|
1346
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1347
|
+
// Own process group on POSIX so `stop` can signal the whole tree.
|
|
1348
|
+
...(process.platform !== 'win32' ? { detached: true } : {}),
|
|
1349
|
+
});
|
|
1350
|
+
// @types/node 26 types ChildProcess's on/once through the internal
|
|
1351
|
+
// InternalEventEmitter base. When `tsc -b` rechecks this package in the
|
|
1352
|
+
// same solution pass that rebuilds its project references, that
|
|
1353
|
+
// inheritance can fail to surface and the spawned child's type loses
|
|
1354
|
+
// .on/.once (TS2339) — while stream types, which COPY the event
|
|
1355
|
+
// signatures, keep working. Subscribe through a structural copy of the
|
|
1356
|
+
// one signature this file needs: the copy-don't-inherit prescription
|
|
1357
|
+
// @types/node itself applies to multi-level emitter classes. The `unknown`
|
|
1358
|
+
// hop keeps the cast legal in both resolution states.
|
|
1359
|
+
const childExit = child;
|
|
1360
|
+
mkdirSync(join(projectRoot, '.typecad-hal'), { recursive: true });
|
|
1361
|
+
writeFileSync(pidFile, String(child.pid), 'utf-8');
|
|
1362
|
+
console.log(`[typecad-hal] starting west debugserver (gdb on localhost:${DEBUG_SERVER_PORT})`);
|
|
1363
|
+
// A reader that goes away (closed task terminal, piped head) must not
|
|
1364
|
+
// take the server down with an EPIPE.
|
|
1365
|
+
process.stdout?.on?.('error', () => { });
|
|
1366
|
+
process.stderr?.on?.('error', () => { });
|
|
1367
|
+
child.stdout?.on('data', (d) => process.stdout.write(d));
|
|
1368
|
+
child.stderr?.on('data', (d) => process.stderr.write(d));
|
|
1369
|
+
// Ready = the TCL port accepts connections. NOT the gdb port: openocd's
|
|
1370
|
+
// gdb server takes ONE client, so a TCP probe there both logs
|
|
1371
|
+
// "attempted 'gdb' connection rejected" and can race the real gdb
|
|
1372
|
+
// connection for the slot. The tcl listener opens at the END of openocd
|
|
1373
|
+
// init (after the gdb listener and the startup halt) — a truer signal —
|
|
1374
|
+
// and probes there are inert.
|
|
1375
|
+
const deadline = Date.now() + DEBUG_SERVER_START_TIMEOUT_MS;
|
|
1376
|
+
const poll = () => {
|
|
1377
|
+
const sock = netConnect(DEBUG_SERVER_TCL_PORT, '127.0.0.1');
|
|
1378
|
+
sock.once('connect', () => {
|
|
1379
|
+
sock.destroy();
|
|
1380
|
+
console.log(`TYPECAD_HAL: debug server ready on ${DEBUG_SERVER_PORT}`);
|
|
1381
|
+
});
|
|
1382
|
+
sock.once('error', () => {
|
|
1383
|
+
sock.destroy();
|
|
1384
|
+
if (child.exitCode !== null)
|
|
1385
|
+
return; // server died — exit handler reports
|
|
1386
|
+
if (Date.now() > deadline) {
|
|
1387
|
+
console.error(`! west debugserver did not open :${DEBUG_SERVER_TCL_PORT} within `
|
|
1388
|
+
+ `${DEBUG_SERVER_START_TIMEOUT_MS / 1000}s — see its output above.`);
|
|
1389
|
+
stopDebugServer(pidFile);
|
|
1390
|
+
process.exitCode = 1;
|
|
1391
|
+
return;
|
|
1392
|
+
}
|
|
1393
|
+
setTimeout(poll, 250);
|
|
1394
|
+
});
|
|
1395
|
+
};
|
|
1396
|
+
poll();
|
|
1397
|
+
childExit.on('exit', (code) => {
|
|
1398
|
+
try {
|
|
1399
|
+
rmSync(pidFile, { force: true });
|
|
1400
|
+
}
|
|
1401
|
+
catch { /* already gone */ }
|
|
1402
|
+
// Exit before ready: surface as a task failure (the debugger never
|
|
1403
|
+
// connects and VS Code reports the background task's non-zero exit).
|
|
1404
|
+
if (code !== null && code !== 0)
|
|
1405
|
+
process.exitCode = code;
|
|
1406
|
+
});
|
|
1407
|
+
const forwardSignal = () => {
|
|
1408
|
+
stopDebugServer(pidFile);
|
|
1409
|
+
childExit.once('exit', () => process.exit(0));
|
|
1410
|
+
setTimeout(() => process.exit(0), 1500).unref();
|
|
1411
|
+
};
|
|
1412
|
+
process.on('SIGINT', forwardSignal);
|
|
1413
|
+
process.on('SIGTERM', forwardSignal);
|
|
1414
|
+
},
|
|
1180
1415
|
};
|
|
1416
|
+
/** How long `debug-server start` waits for the gdb port before failing. */
|
|
1417
|
+
const DEBUG_SERVER_START_TIMEOUT_MS = 45_000;
|
|
1418
|
+
/**
|
|
1419
|
+
* Read the pidfile and return the pid when that process is still alive,
|
|
1420
|
+
* undefined otherwise (no file, dead pid, or unparseable). Best-effort.
|
|
1421
|
+
*/
|
|
1422
|
+
function readStaleServerPid(pidFile) {
|
|
1423
|
+
try {
|
|
1424
|
+
const pid = Number.parseInt(readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
1425
|
+
if (!Number.isInteger(pid))
|
|
1426
|
+
return undefined;
|
|
1427
|
+
process.kill(pid, 0); // throws ESRCH when dead
|
|
1428
|
+
return pid;
|
|
1429
|
+
}
|
|
1430
|
+
catch {
|
|
1431
|
+
return undefined;
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
/**
|
|
1435
|
+
* The pid of whatever process is LISTENING on the gdb port — the recovery
|
|
1436
|
+
* path for orphaned servers (the wrapper and west can die while openocd
|
|
1437
|
+
* survives, e.g. a killed task terminal; the pidfile is then stale but the
|
|
1438
|
+
* port stays bound and the next session would attach to the orphan).
|
|
1439
|
+
* Best-effort: netstat on Windows, lsof on POSIX; undefined when the port is
|
|
1440
|
+
* free or the platform tool is unavailable.
|
|
1441
|
+
*/
|
|
1442
|
+
function portOwnerPid(port) {
|
|
1443
|
+
try {
|
|
1444
|
+
if (process.platform === 'win32') {
|
|
1445
|
+
const out = spawnSync('netstat', ['-ano', '-p', 'tcp'], { encoding: 'utf-8' });
|
|
1446
|
+
if (out.status !== 0)
|
|
1447
|
+
return undefined;
|
|
1448
|
+
for (const line of (out.stdout ?? '').split(/\r?\n/)) {
|
|
1449
|
+
const cols = line.trim().split(/\s+/);
|
|
1450
|
+
if (cols.length >= 5 && cols[3] === 'LISTENING'
|
|
1451
|
+
&& cols[1].endsWith(`:${port}`)) {
|
|
1452
|
+
const pid = Number.parseInt(cols[4], 10);
|
|
1453
|
+
if (Number.isInteger(pid))
|
|
1454
|
+
return pid;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
return undefined;
|
|
1458
|
+
}
|
|
1459
|
+
const out = spawnSync('lsof', ['-ti', `tcp:${port}`], { encoding: 'utf-8' });
|
|
1460
|
+
if (out.status !== 0 || !out.stdout?.trim())
|
|
1461
|
+
return undefined;
|
|
1462
|
+
const pid = Number.parseInt(out.stdout.trim().split(/\s+/)[0], 10);
|
|
1463
|
+
return Number.isInteger(pid) ? pid : undefined;
|
|
1464
|
+
}
|
|
1465
|
+
catch {
|
|
1466
|
+
return undefined;
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
/** Kill a pid tree (Windows: taskkill /T; POSIX: the process group). */
|
|
1470
|
+
function killPidTree(pid) {
|
|
1471
|
+
if (process.platform === 'win32') {
|
|
1472
|
+
// /T: the whole tree (the pid may be the micromamba/west wrapper; openocd
|
|
1473
|
+
// is its grandchild). /F: force — the server has no stdin to close.
|
|
1474
|
+
spawnSync('taskkill', ['/PID', String(pid), '/T', '/F'], { encoding: 'utf-8' });
|
|
1475
|
+
}
|
|
1476
|
+
else {
|
|
1477
|
+
try {
|
|
1478
|
+
process.kill(-pid, 'SIGTERM'); // the detached process group
|
|
1479
|
+
}
|
|
1480
|
+
catch {
|
|
1481
|
+
try {
|
|
1482
|
+
process.kill(pid, 'SIGTERM');
|
|
1483
|
+
}
|
|
1484
|
+
catch { /* already gone */ }
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
/** Kill the debug server tree (micromamba/west → openocd) and drop the pidfile.
|
|
1489
|
+
* Falls back to the gdb-port owner when the recorded pid is already dead —
|
|
1490
|
+
* that orphan would otherwise serve stale sessions forever. */
|
|
1491
|
+
function stopDebugServer(pidFile) {
|
|
1492
|
+
const pid = readStaleServerPid(pidFile) ?? portOwnerPid(DEBUG_SERVER_PORT);
|
|
1493
|
+
if (pid === undefined) {
|
|
1494
|
+
try {
|
|
1495
|
+
rmSync(pidFile, { force: true });
|
|
1496
|
+
}
|
|
1497
|
+
catch { /* already gone */ }
|
|
1498
|
+
console.log('[typecad-hal] debug server not running');
|
|
1499
|
+
return;
|
|
1500
|
+
}
|
|
1501
|
+
killPidTree(pid);
|
|
1502
|
+
try {
|
|
1503
|
+
rmSync(pidFile, { force: true });
|
|
1504
|
+
}
|
|
1505
|
+
catch { /* already gone */ }
|
|
1506
|
+
console.log(`[typecad-hal] debug server stopped (pid ${pid})`);
|
|
1507
|
+
}
|
|
1508
|
+
// -- serial monitor: post-flash re-enumeration wait --------------------------
|
|
1509
|
+
/** How long `--monitor` waits for the port to (re)appear after a flash reset. */
|
|
1510
|
+
export const MONITOR_PORT_WAIT_MS = 8000;
|
|
1511
|
+
/**
|
|
1512
|
+
* The pyserial port wait run before miniterm (argv built by
|
|
1513
|
+
* serialPortWaitArgs). Polls list_ports — never OPENS the port, because
|
|
1514
|
+
* toggling DTR on an open can itself reset some boards — until the device
|
|
1515
|
+
* name matches case-insensitively or the timeout elapses. Exit codes:
|
|
1516
|
+
* 0 port present, 1 timeout (message printed to stderr), 2 pyserial
|
|
1517
|
+
* unavailable (the caller lets miniterm surface the real error instead of
|
|
1518
|
+
* reporting a bogus timeout).
|
|
1519
|
+
*/
|
|
1520
|
+
const SERIAL_PORT_WAIT_PY = [
|
|
1521
|
+
'import sys, time',
|
|
1522
|
+
'try:',
|
|
1523
|
+
' from serial.tools import list_ports',
|
|
1524
|
+
'except Exception:',
|
|
1525
|
+
' sys.exit(2)',
|
|
1526
|
+
'port, timeout = sys.argv[1], float(sys.argv[2])',
|
|
1527
|
+
'def present():',
|
|
1528
|
+
' return any(d.device.lower() == port.lower() for d in list_ports.comports())',
|
|
1529
|
+
'if not present():',
|
|
1530
|
+
" print('waiting for %s to re-enumerate (the board resets after flashing)...' % port, flush=True)",
|
|
1531
|
+
' end = time.time() + timeout',
|
|
1532
|
+
' while not present():',
|
|
1533
|
+
' if time.time() >= end:',
|
|
1534
|
+
" print('%s did not come back within %ds - some boards re-enumerate under a different port name' % (port, int(timeout)), file=sys.stderr, flush=True)",
|
|
1535
|
+
' sys.exit(1)',
|
|
1536
|
+
' time.sleep(0.25)',
|
|
1537
|
+
].join('\n');
|
|
1538
|
+
/** argv for the pyserial port wait — exported for the toolchain unit tests. */
|
|
1539
|
+
export function serialPortWaitArgs(port, timeoutMs) {
|
|
1540
|
+
return ['-c', SERIAL_PORT_WAIT_PY, port, String(timeoutMs / 1000)];
|
|
1541
|
+
}
|
|
1542
|
+
/**
|
|
1543
|
+
* Wait for `port` to be listed by the venv's pyserial before miniterm opens
|
|
1544
|
+
* it once. True = proceed to miniterm (port present, or the wait itself was
|
|
1545
|
+
* best-effort-skipped — a missing python/pyserial lets miniterm show the
|
|
1546
|
+
* underlying failure); false = the port never came back (message printed).
|
|
1547
|
+
*/
|
|
1548
|
+
function waitForSerialPort(py, env, cwd, port) {
|
|
1549
|
+
const res = spawnSync(py, serialPortWaitArgs(port, MONITOR_PORT_WAIT_MS), {
|
|
1550
|
+
cwd,
|
|
1551
|
+
env,
|
|
1552
|
+
stdio: 'inherit',
|
|
1553
|
+
});
|
|
1554
|
+
if (res.error || res.status === null || res.status === 2)
|
|
1555
|
+
return true;
|
|
1556
|
+
return res.status === 0;
|
|
1557
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface RunnersFacts {
|
|
2
|
+
/** The board's declared default debug runner (e.g. 'openocd', 'jlink'). */
|
|
3
|
+
readonly debugRunner?: string;
|
|
4
|
+
/** Absolute path to the arch gdb binary (config.gdb). */
|
|
5
|
+
readonly gdb?: string;
|
|
6
|
+
/** All runners the board configured. */
|
|
7
|
+
readonly runners: readonly string[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Parse the subset of runners.yaml cuttlefish consumes. Exported for tests.
|
|
11
|
+
* Returns undefined on shapes it does not understand (callers treat as
|
|
12
|
+
* "no facts" — never crash a build over diagnostics data).
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseRunnersYaml(text: string): RunnersFacts | undefined;
|
|
15
|
+
/** Read the resolved runner facts for a build dir. undefined when absent/unparseable. */
|
|
16
|
+
export declare function readRunnersFacts(buildDir: string): RunnersFacts | undefined;
|