@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.
- 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/audit.d.ts +111 -0
- package/dist/audit.js +416 -0
- package/dist/boardgen.d.ts +1 -9
- package/dist/boardgen.js +288 -47
- package/dist/chips/resolve.js +20 -0
- package/dist/chips/types.d.ts +22 -1
- package/dist/debug-codegen.js +1 -1
- package/dist/display/bindings.d.ts +55 -0
- package/dist/display/bindings.js +316 -0
- package/dist/display/gfx.d.ts +2 -3
- package/dist/display/gfx.js +166 -154
- package/dist/display/index.js +20 -1
- package/dist/display/mipi-dbi-host.d.ts +9 -0
- package/dist/display/mipi-dbi-host.js +174 -0
- package/dist/display/profiles.d.ts +109 -4
- package/dist/display/profiles.js +270 -7
- package/dist/display/touch-adapter.js +119 -49
- package/dist/display/ui-adapter-eink.d.ts +2 -0
- package/dist/display/ui-adapter-eink.js +4 -0
- package/dist/display/ui-adapter-gray.d.ts +8 -0
- package/dist/display/ui-adapter-gray.js +170 -0
- package/dist/display/ui-adapter-mono.d.ts +13 -0
- package/dist/display/ui-adapter-mono.js +230 -0
- package/dist/display/ui-adapter-native.d.ts +10 -0
- package/dist/display/ui-adapter-native.js +295 -0
- package/dist/display/ui-adapter-shared.d.ts +11 -0
- package/dist/display/ui-adapter-shared.js +122 -0
- package/dist/display/ui-adapter.js +510 -558
- package/dist/doctor.js +4 -4
- package/dist/dt-config/custom-board.js +2 -2
- package/dist/dt-config/kconfig.d.ts +62 -1
- package/dist/dt-config/kconfig.js +141 -38
- package/dist/dt-config/overlay.d.ts +15 -2
- package/dist/dt-config/overlay.js +433 -18
- package/dist/framework.manifest.d.ts +10 -4
- package/dist/framework.manifest.js +133 -17
- package/dist/index.d.ts +2 -0
- package/dist/index.js +14 -6
- package/dist/licenses.d.ts +2 -2
- package/dist/licenses.js +13 -92
- package/dist/lowering/can.d.ts +25 -0
- package/dist/lowering/can.js +97 -0
- package/dist/lowering/clock.d.ts +17 -0
- package/dist/lowering/clock.js +58 -0
- package/dist/lowering/fs.js +1 -1
- package/dist/lowering/gpio.js +0 -32
- package/dist/lowering/hid.d.ts +27 -0
- package/dist/lowering/hid.js +244 -0
- package/dist/lowering/http.js +264 -32
- package/dist/lowering/i2c.d.ts +8 -0
- package/dist/lowering/i2c.js +137 -5
- package/dist/lowering/i2s.d.ts +27 -0
- package/dist/lowering/i2s.js +98 -0
- package/dist/lowering/index.d.ts +9 -1
- package/dist/lowering/index.js +25 -1
- package/dist/lowering/interrupts.js +6 -0
- package/dist/lowering/matrix.d.ts +15 -0
- package/dist/lowering/matrix.js +63 -0
- package/dist/lowering/mqtt.js +110 -8
- package/dist/lowering/power.d.ts +3 -2
- package/dist/lowering/power.js +20 -45
- package/dist/lowering/pwm.js +25 -0
- package/dist/lowering/sensor.d.ts +2 -2
- package/dist/lowering/sensor.js +8 -4
- package/dist/lowering/strip.d.ts +16 -0
- package/dist/lowering/strip.js +70 -0
- package/dist/lowering/thread.js +5 -1
- package/dist/lowering/trace.d.ts +44 -0
- package/dist/lowering/trace.js +239 -0
- package/dist/lowering/uart.js +6 -1
- package/dist/lowering/usb.d.ts +3 -1
- package/dist/lowering/usb.js +16 -13
- package/dist/lowering/wdt.js +2 -29
- package/dist/sbom.d.ts +181 -0
- package/dist/sbom.js +901 -0
- package/dist/sdk/board-catalog-sync.d.ts +1 -3
- package/dist/sdk/board-catalog-sync.js +4 -10
- package/dist/strategy.d.ts +82 -46
- package/dist/strategy.js +639 -182
- 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 +239 -510
- package/dist/toolchain/env-check.d.ts +1 -3
- package/dist/toolchain/env-check.js +2 -7
- package/dist/toolchain/index.d.ts +28 -3
- package/dist/toolchain/index.js +498 -62
- package/dist/toolchain/runners.d.ts +16 -0
- package/dist/toolchain/runners.js +75 -0
- package/dist/toolchain/scaffold.d.ts +5 -2
- package/dist/toolchain/scaffold.js +79 -15
- 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/dist/west-inventory.d.ts +25 -0
- package/dist/west-inventory.js +97 -0
- 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/audit.ts +529 -0
- package/src/boardgen.ts +265 -50
- package/src/chips/resolve.ts +21 -0
- package/src/chips/types.ts +10 -1
- package/src/display/bindings.ts +347 -0
- package/src/display/gfx.ts +318 -306
- package/src/display/index.ts +87 -70
- package/src/display/mipi-dbi-host.ts +183 -0
- package/src/display/profiles.ts +458 -139
- package/src/display/touch-adapter.ts +119 -49
- package/src/display/ui-adapter-eink.ts +13 -0
- package/src/display/ui-adapter-gray.ts +178 -0
- package/src/display/ui-adapter-mono.ts +238 -0
- package/src/display/ui-adapter-native.ts +304 -0
- package/src/display/ui-adapter-shared.ts +125 -0
- package/src/display/ui-adapter.ts +732 -781
- package/src/doctor.ts +4 -4
- package/src/dt-config/custom-board.ts +2 -2
- package/src/dt-config/kconfig.ts +647 -505
- package/src/dt-config/overlay.ts +1475 -1058
- package/src/framework.manifest.ts +659 -535
- package/src/index.ts +16 -6
- package/src/licenses.ts +346 -425
- package/src/lowering/can.ts +140 -0
- package/src/lowering/clock.ts +91 -0
- package/src/lowering/fs.ts +135 -135
- package/src/lowering/gpio.ts +0 -33
- package/src/lowering/hid.ts +261 -0
- package/src/lowering/http.ts +264 -32
- package/src/lowering/i2c.ts +142 -5
- package/src/lowering/i2s.ts +143 -0
- package/src/lowering/index.ts +18 -1
- package/src/lowering/interrupts.ts +6 -0
- package/src/lowering/matrix.ts +70 -0
- package/src/lowering/mqtt.ts +109 -8
- package/src/lowering/power.ts +41 -0
- package/src/lowering/pwm.ts +192 -167
- package/src/lowering/sensor.ts +159 -155
- package/src/lowering/strip.ts +81 -0
- package/src/lowering/thread.ts +5 -1
- package/src/lowering/trace.ts +270 -0
- package/src/lowering/uart.ts +6 -1
- package/src/lowering/usb.ts +16 -13
- package/src/lowering/wdt.ts +2 -25
- package/src/sbom.ts +1117 -0
- package/src/sdk/board-catalog-sync.ts +4 -25
- package/src/strategy.ts +2680 -2309
- package/src/toolchain/debug-config.ts +262 -522
- package/src/toolchain/env-check.ts +279 -285
- package/src/toolchain/index.ts +1792 -1359
- package/src/toolchain/runners.ts +80 -0
- package/src/toolchain/scaffold.ts +355 -296
- package/src/toolchain/west-discover.ts +35 -13
- package/src/toolchain/west-spawn.ts +174 -168
- package/src/west-inventory.ts +102 -0
- 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/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
|
@@ -1,213 +1,47 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
|
-
// debug-config.ts — VS Code
|
|
2
|
+
// debug-config.ts — west-driven VS Code debug artifacts (cortex-debug external
|
|
3
|
+
// server mode).
|
|
3
4
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
5
|
+
// The board-specific debug knowledge lives in Zephyr, not here: after any
|
|
6
|
+
// successful build, `west debugserver` serves the GDB connection using the
|
|
7
|
+
// board's own runner configuration (resolved in build/zephyr/runners.yaml —
|
|
8
|
+
// the exact arch gdb, the openocd/jlink binaries and flags, the Zephyr RTOS
|
|
9
|
+
// awareness, adapter quirks). The launch.json written here just connects
|
|
10
|
+
// cortex-debug to that server (`servertype: 'external'` +
|
|
11
|
+
// `gdbTarget: localhost:<port>`).
|
|
6
12
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// breakpoint is deferred until the bootloader maps the app flash region.
|
|
13
|
+
// Lifecycle: F5 runs the preLaunchTask (build + flash, unchanged), the
|
|
14
|
+
// background task `typecad-hal: debug server (west)` starts
|
|
15
|
+
// `typecad-hal debug-server start` (which wraps `west debugserver` and prints
|
|
16
|
+
// a ready marker the task's problem matcher waits for), and VS Code runs the
|
|
17
|
+
// postDebugTask (`typecad-hal: stop debug server`) when the session ends.
|
|
13
18
|
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
19
|
+
// Everything here is best-effort: a failed artifact write warns and never
|
|
20
|
+
// fails the build (a project without launch.json still builds fine).
|
|
16
21
|
// ---------------------------------------------------------------------------
|
|
17
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync
|
|
22
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
18
23
|
import { join, resolve, dirname, relative } from 'node:path';
|
|
19
24
|
import { ZephyrStrategy } from '../strategy.js';
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
* omits gdbPath and relies on Cortex-Debug's default resolution).
|
|
33
|
-
*/
|
|
34
|
-
export function resolveGdbPath(buildDir, target) {
|
|
35
|
-
const cachePath = join(buildDir, 'CMakeCache.txt');
|
|
36
|
-
if (existsSync(cachePath)) {
|
|
37
|
-
try {
|
|
38
|
-
const cache = readFileSync(cachePath, 'utf-8');
|
|
39
|
-
const m = cache.match(/^ZEPHYR_SDK_INSTALL_DIR:PATH=(.+)$/m);
|
|
40
|
-
if (m) {
|
|
41
|
-
const fromCache = gdbPathFromSdkRoot(m[1].trim(), target);
|
|
42
|
-
if (fromCache)
|
|
43
|
-
return fromCache;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
// unreadable cache — fall through to the SDK scan
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
// No build dir yet (project just created): probe known SDK locations.
|
|
51
|
-
for (const sdkRoot of discoverZephyrSdkRoots()) {
|
|
52
|
-
const p = gdbPathFromSdkRoot(sdkRoot, target);
|
|
53
|
-
if (p)
|
|
54
|
-
return p;
|
|
55
|
-
}
|
|
56
|
-
return undefined;
|
|
57
|
-
}
|
|
58
|
-
/** The GDB location inside a Zephyr SDK root, per target architecture:
|
|
59
|
-
* ARM boards use the arm-zephyr-eabi toolchain (verified against
|
|
60
|
-
* zephyr-sdk-0.17.4), Espressif the xtensa-espressif_esp32s3_zephyr-elf one.
|
|
61
|
-
* Returns a forward-slash absolute path or undefined. */
|
|
62
|
-
export function gdbPathFromSdkRoot(sdkRoot, target) {
|
|
63
|
-
// No target given (legacy callers/tests): the historical Espresif default.
|
|
64
|
-
const boardId = (target ?? '').split('/')[0];
|
|
65
|
-
const isArm = boardId.length > 0 && !boardId.startsWith('esp32');
|
|
66
|
-
const toolchainDir = isArm ? 'arm-zephyr-eabi' : 'xtensa-espressif_esp32s3_zephyr-elf';
|
|
67
|
-
const gdbName = isArm ? 'arm-zephyr-eabi-gdb.exe' : 'xtensa-espressif_esp32s3_zephyr-elf-gdb.exe';
|
|
68
|
-
const gdbPath = join(sdkRoot, toolchainDir, 'bin', gdbName);
|
|
69
|
-
return existsSync(gdbPath) ? gdbPath.replace(/\\/g, '/') : undefined;
|
|
70
|
-
}
|
|
71
|
-
/** Compare two dotted version strings numerically (0.17.10 > 0.17.4). */
|
|
72
|
-
function compareSdkVersions(a, b) {
|
|
73
|
-
const segsOf = (v) => v.split('.').map((s) => parseInt(s, 10) || 0);
|
|
74
|
-
const aa = segsOf(a);
|
|
75
|
-
const bb = segsOf(b);
|
|
76
|
-
for (let i = 0; i < Math.max(aa.length, bb.length); i++) {
|
|
77
|
-
const d = (aa[i] ?? 0) - (bb[i] ?? 0);
|
|
78
|
-
if (d !== 0)
|
|
79
|
-
return d;
|
|
80
|
-
}
|
|
81
|
-
return 0;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Probe the well-known Zephyr SDK install locations, newest version first:
|
|
85
|
-
* 1. $ZEPHYR_SDK_INSTALL_DIR (the var board.cmake reads)
|
|
86
|
-
* 2. <MAMBA_ROOT_PREFIX | ~/micromamba>/zephyr-sdk/zephyr-sdk-<ver> — the
|
|
87
|
-
* the bundled Zephyr installer layout
|
|
88
|
-
* 3. ~/zephyr-sdk-<ver> — the standalone download layout
|
|
89
|
-
*
|
|
90
|
-
* Only roots that actually contain the esp32s3 GDB are useful to callers;
|
|
91
|
-
* this returns candidate roots (gdbPathFromSdkRoot does the existence check)
|
|
92
|
-
* so tests can inject home/env overrides.
|
|
93
|
-
*/
|
|
94
|
-
export function discoverZephyrSdkRoots(opts) {
|
|
95
|
-
const env = opts?.env ?? process.env;
|
|
96
|
-
const home = opts?.home ?? (env.USERPROFILE || env.HOME || '');
|
|
97
|
-
const scanned = [];
|
|
98
|
-
const versionedDirs = (base) => {
|
|
99
|
-
try {
|
|
100
|
-
return readdirSync(base)
|
|
101
|
-
.filter((d) => existsSync(join(base, d)) && d.startsWith('zephyr-sdk-'))
|
|
102
|
-
.map((d) => join(base, d));
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
105
|
-
return []; // dir absent
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
const mambaRoot = env.MAMBA_ROOT_PREFIX || (home ? join(home, 'micromamba') : '');
|
|
109
|
-
if (mambaRoot)
|
|
110
|
-
scanned.push(...versionedDirs(join(mambaRoot, 'zephyr-sdk')));
|
|
111
|
-
if (home)
|
|
112
|
-
scanned.push(...versionedDirs(home));
|
|
113
|
-
// Scanned roots newest version first; the env var stays pinned first
|
|
114
|
-
// (explicit user intent outranks any discovered location).
|
|
115
|
-
scanned.sort((a, b) => {
|
|
116
|
-
const va = a.match(/zephyr-sdk-([\d.]+)/)?.[1] ?? '';
|
|
117
|
-
const vb = b.match(/zephyr-sdk-([\d.]+)/)?.[1] ?? '';
|
|
118
|
-
return compareSdkVersions(vb, va);
|
|
119
|
-
});
|
|
120
|
-
const roots = env.ZEPHYR_SDK_INSTALL_DIR
|
|
121
|
-
? [env.ZEPHYR_SDK_INSTALL_DIR, ...scanned]
|
|
122
|
-
: scanned;
|
|
123
|
-
// De-duplicate (an env var may repeat a scan hit) preserving order.
|
|
124
|
-
return roots.filter((r, i) => roots.indexOf(r) === i);
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Resolve the OpenOCD binary path. The esp32s3 needs the Espressif OpenOCD
|
|
128
|
-
* fork (openocd-esp32) — not the Zephyr SDK's openocd and not a
|
|
129
|
-
* generic/GDB-stub build — because only it carries the Xtensa + esp_usb_jtag
|
|
130
|
-
* support. ARM targets prefer the Zephyr SDK's own openocd (it ships the
|
|
131
|
-
* interface/target cfgs boards reference); the Espressif fork is only
|
|
132
|
-
* consulted for esp32 targets.
|
|
133
|
-
*
|
|
134
|
-
* Discovery order:
|
|
135
|
-
* 1. ESPRESSIF_TOOLCHAIN_PATH env (the var board.cmake reads) — esp32
|
|
136
|
-
* targets; its openocd-esp32/bin/openocd.exe.
|
|
137
|
-
* 2. The standard ESP-IDF install layout: ~/.espressif/tools/openocd-esp32/
|
|
138
|
-
* <version>/openocd-esp32/bin/openocd.exe (esp32 targets; newest first).
|
|
139
|
-
* 3. A Zephyr SDK root (discoverZephyrSdkRoots) — its
|
|
140
|
-
* tools/opt/openocd/bin/openocd(.exe), any target.
|
|
141
|
-
* Returns undefined if not found (the launch.json then omits openOCDPath and
|
|
142
|
-
* Cortex-Debug falls back to PATH / its openocdPath setting).
|
|
143
|
-
*/
|
|
144
|
-
export function resolveOpenOcdPath(target) {
|
|
145
|
-
const isEsp32Target = (target ?? '').split('/')[0].startsWith('esp32');
|
|
146
|
-
const candidates = [];
|
|
147
|
-
if (isEsp32Target) {
|
|
148
|
-
// 1. ESPRESSIF_TOOLCHAIN_PATH env
|
|
149
|
-
const envPath = process.env.ESPRESSIF_TOOLCHAIN_PATH;
|
|
150
|
-
if (envPath) {
|
|
151
|
-
candidates.push(join(envPath, 'openocd-esp32', 'bin', 'openocd.exe'));
|
|
152
|
-
}
|
|
153
|
-
// 2. ~/.espressif/tools/openocd-esp32/<version>/openocd-esp32/bin/openocd.exe
|
|
154
|
-
const home = process.env.USERPROFILE || process.env.HOME;
|
|
155
|
-
if (home) {
|
|
156
|
-
const base = join(home, '.espressif', 'tools', 'openocd-esp32');
|
|
157
|
-
let versions = [];
|
|
158
|
-
try {
|
|
159
|
-
versions = readdirSync(base).filter((v) => existsSync(join(base, v, 'openocd-esp32', 'bin', 'openocd.exe')));
|
|
160
|
-
}
|
|
161
|
-
catch {
|
|
162
|
-
// dir absent
|
|
163
|
-
}
|
|
164
|
-
// newest version last — sort then reverse so the highest wins on match.
|
|
165
|
-
versions.sort().reverse();
|
|
166
|
-
for (const v of versions) {
|
|
167
|
-
candidates.push(join(base, v, 'openocd-esp32', 'bin', 'openocd.exe'));
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
// 3. The Zephyr SDK's openocd (ARM targets' interface/target cfgs ship
|
|
172
|
-
// with it — interface/stlink-dap.cfg, target/stm32f4x.cfg, ...). Layout
|
|
173
|
-
// differs by SDK generation: 1.0.x hosttools/openocd, 0.17.x
|
|
174
|
-
// tools/opt/openocd (when present).
|
|
175
|
-
const exe = process.platform === 'win32' ? 'openocd.exe' : 'openocd';
|
|
176
|
-
for (const sdkRoot of discoverZephyrSdkRoots()) {
|
|
177
|
-
candidates.push(join(sdkRoot, 'hosttools', 'openocd', 'bin', exe));
|
|
178
|
-
candidates.push(join(sdkRoot, 'tools', 'opt', 'openocd', 'bin', exe));
|
|
179
|
-
}
|
|
180
|
-
for (const c of candidates) {
|
|
181
|
-
if (existsSync(c))
|
|
182
|
-
return resolve(c).replace(/\\/g, '/');
|
|
183
|
-
}
|
|
184
|
-
return undefined;
|
|
185
|
-
}
|
|
186
|
-
/**
|
|
187
|
-
* Resolve where to write the VS Code debug artifacts and how to express paths
|
|
188
|
-
* in them.
|
|
189
|
-
*
|
|
190
|
-
* VS Code reads `.vscode/` from the folder the user has OPENED — and for a
|
|
191
|
-
* cuttlefish project that is almost always the **cuttlefish project root**
|
|
192
|
-
* (the directory containing `cuttlefish.config.ts`), NOT the git repo root.
|
|
193
|
-
* The toolchain's `projectRoot` is the Zephyr *app* dir (e.g.
|
|
194
|
-
* `<projectRoot>/src/out`), which sits below the cuttlefish config dir. So we
|
|
195
|
-
* walk up from `projectRoot` to the nearest `cuttlefish.config.ts` and treat
|
|
196
|
-
* THAT as the workspace root. This makes F5 work when a user opens the project
|
|
197
|
-
* folder directly, and keeps launch.json paths relative to it.
|
|
198
|
-
*
|
|
199
|
-
* Returns { workspaceRoot, appRel } where workspaceRoot is the cuttlefish
|
|
200
|
-
* project root (the `.vscode/` target) and appRel is the Zephyr app dir
|
|
201
|
-
* (`projectRoot`) relative to it (e.g. 'src/out').
|
|
202
|
-
*/
|
|
25
|
+
import { readRunnersFacts } from './runners.js';
|
|
26
|
+
/** The gdb server port west's openocd/jlink runners default to. */
|
|
27
|
+
export const DEBUG_SERVER_PORT = 3333;
|
|
28
|
+
/** west's openocd tcl port — pinned in the debugserver invocation and used
|
|
29
|
+
* as the READINESS probe: its listener opens at the END of openocd's init
|
|
30
|
+
* (after the gdb listener and the startup halt), and probe connections
|
|
31
|
+
* there never consume the gdb server's single client slot. */
|
|
32
|
+
export const DEBUG_SERVER_TCL_PORT = 6333;
|
|
33
|
+
/** Task labels (also the preLaunchTask/postDebugTask references in launch.json). */
|
|
34
|
+
export const DEBUG_BUILD_TASK = 'typecad-hal: build + flash (debug)';
|
|
35
|
+
export const DEBUG_SERVER_TASK = 'typecad-hal: debug server (west)';
|
|
36
|
+
export const DEBUG_SERVER_STOP_TASK = 'typecad-hal: stop debug server';
|
|
203
37
|
export function resolveDebugLocations(projectRoot) {
|
|
204
|
-
// Walk up from the Zephyr app dir to find the
|
|
205
|
-
// nearest ancestor containing
|
|
38
|
+
// Walk up from the Zephyr app dir to find the typecad-hal project root (the
|
|
39
|
+
// nearest ancestor containing typecad-hal.config.ts). Fall back to projectRoot
|
|
206
40
|
// itself if none is found (single-dir project where the app sits at root).
|
|
207
41
|
let workspaceRoot = resolve(projectRoot);
|
|
208
42
|
let dir = resolve(projectRoot);
|
|
209
43
|
for (let i = 0; i < 20; i++) {
|
|
210
|
-
if (existsSync(join(dir, '
|
|
44
|
+
if (existsSync(join(dir, 'typecad-hal.config.ts'))) {
|
|
211
45
|
workspaceRoot = dir;
|
|
212
46
|
break;
|
|
213
47
|
}
|
|
@@ -220,402 +54,297 @@ export function resolveDebugLocations(projectRoot) {
|
|
|
220
54
|
return { workspaceRoot, appRel };
|
|
221
55
|
}
|
|
222
56
|
/** Read-merge-write a JSON file, adding/replacing a single config by a key. */
|
|
223
|
-
function mergeJsonArrayEntry(filePath, arrayKey,
|
|
57
|
+
function mergeJsonArrayEntry(filePath, arrayKey, nameKey, entry) {
|
|
224
58
|
let doc = {};
|
|
225
59
|
if (existsSync(filePath)) {
|
|
226
60
|
try {
|
|
227
|
-
|
|
61
|
+
const parsed = JSON.parse(readFileSync(filePath, 'utf-8'));
|
|
62
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
63
|
+
doc = parsed;
|
|
64
|
+
}
|
|
228
65
|
}
|
|
229
66
|
catch {
|
|
230
67
|
// malformed — start fresh
|
|
231
68
|
}
|
|
232
69
|
}
|
|
233
|
-
const arr = Array.isArray(doc[arrayKey])
|
|
234
|
-
|
|
70
|
+
const arr = Array.isArray(doc[arrayKey])
|
|
71
|
+
? doc[arrayKey]
|
|
72
|
+
: [];
|
|
73
|
+
const name = entry[nameKey];
|
|
74
|
+
const idx = arr.findIndex((e) => e && e[nameKey] === name);
|
|
235
75
|
if (idx >= 0)
|
|
236
76
|
arr[idx] = entry;
|
|
237
77
|
else
|
|
238
78
|
arr.push(entry);
|
|
239
79
|
doc[arrayKey] = arr;
|
|
240
80
|
mkdirSync(dirname(filePath), { recursive: true });
|
|
241
|
-
writeFileSync(filePath, JSON.stringify(doc, null, 2)
|
|
81
|
+
writeFileSync(filePath, `${JSON.stringify(doc, null, 2)}\n`, 'utf-8');
|
|
242
82
|
}
|
|
243
83
|
/**
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
* Ported verbatim from the deleted framework-esp32/toolchain/gdb-script.ts —
|
|
250
|
-
* the filter is cuttlefish-internal (lambda hoisting is framework-agnostic).
|
|
84
|
+
* Whether the workspace's gdb debug artifacts need rewriting: missing, still
|
|
85
|
+
* in an older (self-managed-server) shape, written before a build resolved
|
|
86
|
+
* the gdb path (the create-time starter omits gdbPath), or referencing an app
|
|
87
|
+
* root other than the current one (an outDir rename). Only OUR entries count
|
|
88
|
+
* (`TypeCAD Debug (Zephyr, …)`); user-authored ones are never judged.
|
|
251
89
|
*/
|
|
252
|
-
export function
|
|
253
|
-
|
|
254
|
-
return null;
|
|
255
|
-
let mapText = '';
|
|
90
|
+
export function debugArtifactsNeedRewrite(workspaceRoot, appRel, currentGdb) {
|
|
91
|
+
let doc;
|
|
256
92
|
try {
|
|
257
|
-
|
|
93
|
+
doc = JSON.parse(readFileSync(join(workspaceRoot, '.vscode', 'launch.json'), 'utf-8'));
|
|
258
94
|
}
|
|
259
95
|
catch {
|
|
260
|
-
return
|
|
96
|
+
return false; // no launch.json (or unreadable) — plain builds leave it absent
|
|
261
97
|
}
|
|
262
|
-
|
|
263
|
-
if (
|
|
264
|
-
return
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
' self.frame = frame',
|
|
287
|
-
' def __getattr__(self, name):',
|
|
288
|
-
' val = getattr(self.frame, name)',
|
|
289
|
-
' if name == "function":',
|
|
290
|
-
' m = isr_re.match(val)',
|
|
291
|
-
' if m: return "<lambda>"',
|
|
292
|
-
' return val',
|
|
293
|
-
'',
|
|
294
|
-
'gdb.frame_filters[CuttlefishLambdaFilter().name] = CuttlefishLambdaFilter()',
|
|
295
|
-
'',
|
|
296
|
-
].join('\n');
|
|
98
|
+
const configs = doc.configurations;
|
|
99
|
+
if (!Array.isArray(configs))
|
|
100
|
+
return false;
|
|
101
|
+
const prefix = `\${workspaceFolder}/${appRel ? `${appRel}/` : ''}`;
|
|
102
|
+
for (const c of configs) {
|
|
103
|
+
const e = c;
|
|
104
|
+
if (typeof e.name !== 'string' || !e.name.startsWith('TypeCAD Debug (Zephyr,'))
|
|
105
|
+
continue;
|
|
106
|
+
if (e.servertype !== 'external')
|
|
107
|
+
return true; // pre-west (self-managed server) shape
|
|
108
|
+
if (e.gdbPath === undefined)
|
|
109
|
+
return true; // create-time starter — upgrade on first build
|
|
110
|
+
if (currentGdb !== undefined && typeof e.gdbPath === 'string'
|
|
111
|
+
&& e.gdbPath.replace(/\\/g, '/') !== currentGdb) {
|
|
112
|
+
return true; // a wrong create-time silicon guess — west's answer wins
|
|
113
|
+
}
|
|
114
|
+
if (typeof e.executable === 'string'
|
|
115
|
+
&& e.executable.startsWith('${workspaceFolder}/')
|
|
116
|
+
&& !e.executable.startsWith(prefix)) {
|
|
117
|
+
return true; // outDir moved
|
|
118
|
+
}
|
|
119
|
+
return false; // our entry exists in the current shape — nothing to do
|
|
120
|
+
}
|
|
121
|
+
return false; // no our-entry (deleted launch entry): only --debug recreates
|
|
297
122
|
}
|
|
298
123
|
/**
|
|
299
|
-
* Build the cortex-debug launch.json config
|
|
300
|
-
* `
|
|
301
|
-
*
|
|
124
|
+
* Build the cortex-debug launch.json config connecting to the west-owned gdb
|
|
125
|
+
* server. `gdbPath` comes from the build's runners.yaml (west's own resolved
|
|
126
|
+
* arch gdb); undefined before the first build (create-time starter).
|
|
302
127
|
*/
|
|
303
|
-
function buildLaunchConfig(o, gdbScriptRel,
|
|
304
|
-
// The ELF is at <projectRoot>/build/zephyr/zephyr.elf (Zephyr's standard
|
|
305
|
-
// build output). cortex-debug uses `executable` (not `program`).
|
|
128
|
+
function buildLaunchConfig(o, gdbScriptRel, gdbPath) {
|
|
306
129
|
const executable = `\${workspaceFolder}/${o.appRel}/build/zephyr/zephyr.elf`;
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
|
|
310
|
-
//
|
|
311
|
-
const
|
|
312
|
-
//
|
|
313
|
-
//
|
|
314
|
-
//
|
|
315
|
-
|
|
316
|
-
//
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
// 0x4200xxxx isn't accessible until the bootloader runs).
|
|
320
|
-
// mem 0x42000000 0x44000000 ro cache — tells GDB the app flash region IS
|
|
321
|
-
// read-only so -break-insert uses hw breakpoints, not sw breakpoints
|
|
322
|
-
// (which would fail with "Cannot access memory at 0x4200xxxx").
|
|
323
|
-
// monitor reset init — reset target + halt (bootloader maps flash)
|
|
324
|
-
// thb main — temporary HW breakpoint at main()
|
|
325
|
-
// c — continue; bootloader maps flash, breaks at main()
|
|
326
|
-
//
|
|
327
|
-
// The trailing `c` is ESP32-only. On instant-reset ARM targets the
|
|
328
|
-
// thb-main stop lands within milliseconds — WHILE cortex-debug is still
|
|
329
|
-
// chewing through this command list — and a stop event mid-initialization
|
|
330
|
-
// leaves the session half-started (toolbar never enables, user breakpoints
|
|
331
|
-
// never bind). The ESP32's bootloader takes hundreds of milliseconds, so
|
|
332
|
-
// its stop safely arrives after init completes. ARM keeps the pending
|
|
333
|
-
// thb (the first user Continue stops at main()) but hands the run/stop
|
|
334
|
-
// transition to cortex-debug.
|
|
335
|
-
//
|
|
336
|
-
// Paths in GDB commands must be RELATIVE (resolved against GDB's own
|
|
337
|
-
// working directory), never ${workspaceFolder} literals or absolute paths:
|
|
338
|
-
// - cortex-debug passes command strings through verbatim — no variable
|
|
339
|
-
// expansion in postAttachCommands (verified against its gdb.ts) — so a
|
|
340
|
-
// ${workspaceFolder} would reach GDB as a literal.
|
|
341
|
-
// - Even where VS Code expands it, on Windows the expansion carries
|
|
342
|
-
// backslashes that GDB reads as escape sequences (\t → tab, etc.).
|
|
343
|
-
// - An absolute path bakes the generating machine's checkout location
|
|
344
|
-
// into launch.json and breaks on every other clone/checkout.
|
|
345
|
-
// cortex-debug spawns GDB with cwd from the config's `cwd` field
|
|
346
|
-
// (${workspaceFolder} above), so a relative path IS the workspace root —
|
|
347
|
-
// `set directories .` adds exactly the directory the old absolute form did,
|
|
348
|
-
// and `source <rel>` finds the frame-filter script.
|
|
349
|
-
// The Xtensa flash-mapping commands are ESP32-specific (app flash isn't
|
|
350
|
-
// readable until the bootloader maps it); ARM targets drop them.
|
|
351
|
-
const isEsp32Target = o.target.split('/')[0].startsWith('esp32');
|
|
130
|
+
// The gdb binary names the architecture (xtensa-espressif_* / arm-zephyr-* /
|
|
131
|
+
// riscv64-*): the Xtensa flash-mapping commands are only correct there —
|
|
132
|
+
// app flash is unreadable until the ESP32 bootloader maps it, and the
|
|
133
|
+
// trailing `c` rides the bootloader's slow boot past cortex-debug's init.
|
|
134
|
+
const isXtensa = (gdbPath ?? '').includes('xtensa');
|
|
135
|
+
// Post-attach commands executed after GDB connects to the west-owned server.
|
|
136
|
+
// set directories . — source lookup root (gdb's cwd is the
|
|
137
|
+
// config's cwd = workspace root)
|
|
138
|
+
// monitor reset init — reset target + halt via the server
|
|
139
|
+
// thb main — temporary HW breakpoint at main()
|
|
140
|
+
// Paths must stay RELATIVE (cortex-debug passes commands verbatim; Windows
|
|
141
|
+
// backslash expansion would corrupt absolute paths).
|
|
352
142
|
const postAttachCommands = [
|
|
353
143
|
'set directories .',
|
|
354
144
|
'set remote hardware-watchpoint-limit 2',
|
|
355
145
|
'set remote hardware-breakpoint-limit 2',
|
|
356
|
-
...(
|
|
146
|
+
...(isXtensa ? [
|
|
357
147
|
'set mem inaccessible-by-default off',
|
|
358
148
|
'mem 0x42000000 0x44000000 ro cache',
|
|
359
149
|
] : []),
|
|
360
150
|
'monitor reset init',
|
|
361
151
|
'thb main',
|
|
362
|
-
...(
|
|
152
|
+
...(isXtensa ? ['c'] : []),
|
|
363
153
|
];
|
|
364
154
|
if (gdbScriptRel) {
|
|
365
155
|
postAttachCommands.splice(1, 0, `source ${gdbScriptRel}`);
|
|
366
156
|
}
|
|
367
|
-
|
|
368
|
-
// jlink server (needs the device name from the board table); openocd-runner
|
|
369
|
-
// methods keep the openocd server with the generated cfg file. Without a
|
|
370
|
-
// method (ESP32 builtin-JTAG targets), the historical default applies.
|
|
371
|
-
const servertype = method?.runner === 'jlink' ? 'jlink' : 'openocd';
|
|
372
|
-
const isEsp32 = o.target.split('/')[0].startsWith('esp32');
|
|
373
|
-
const cfg = {
|
|
157
|
+
return {
|
|
374
158
|
name: `TypeCAD Debug (Zephyr, ${o.target.split('/')[0]})`,
|
|
375
159
|
type: 'cortex-debug',
|
|
376
|
-
// Attach mode: no download (the ELF is already flashed
|
|
377
|
-
//
|
|
378
|
-
//
|
|
379
|
-
// continue. HW breakpoints use debug registers and work before the
|
|
380
|
-
// bootloader maps the app flash region.
|
|
160
|
+
// Attach mode: no download (the ELF is already flashed by the
|
|
161
|
+
// preLaunchTask's dependency chain). The server is west-owned;
|
|
162
|
+
// cortex-debug only connects.
|
|
381
163
|
request: 'attach',
|
|
382
164
|
cwd: '${workspaceFolder}',
|
|
383
165
|
executable,
|
|
384
|
-
servertype,
|
|
385
|
-
|
|
386
|
-
...(method?.debugDevice ? { device: method.debugDevice } : {}),
|
|
387
|
-
interface: method?.debugInterface ?? (isEsp32 ? 'jtag' : 'swd'),
|
|
166
|
+
servertype: 'external',
|
|
167
|
+
gdbTarget: `localhost:${DEBUG_SERVER_PORT}`,
|
|
388
168
|
...(gdbPath ? { gdbPath } : {}),
|
|
389
|
-
...(servertype === 'openocd' && openocdPath ? { serverpath: openocdPath } : {}),
|
|
390
169
|
postAttachCommands,
|
|
391
|
-
preLaunchTask:
|
|
170
|
+
preLaunchTask: DEBUG_SERVER_TASK,
|
|
171
|
+
postDebugTask: DEBUG_SERVER_STOP_TASK,
|
|
392
172
|
};
|
|
393
|
-
// openOcdCfgRel is consumed by configFiles above. Referenced here only to
|
|
394
|
-
// keep the signature honest.
|
|
395
|
-
void openOcdCfgRel;
|
|
396
|
-
return cfg;
|
|
397
173
|
}
|
|
398
|
-
/**
|
|
399
|
-
* Build the tasks.json entry: rebuild + flash only. cortex-debug starts
|
|
400
|
-
* OpenOCD as a child process (servertype: "openocd"), so the preLaunch task
|
|
401
|
-
* just needs to build and upload — no isBackground / OpenOCD wrapping.
|
|
402
|
-
*/
|
|
174
|
+
/** The build+flash task (preLaunchTask) — unchanged shape from the old flow. */
|
|
403
175
|
function buildTask(o) {
|
|
404
176
|
return {
|
|
405
|
-
label:
|
|
177
|
+
label: DEBUG_BUILD_TASK,
|
|
406
178
|
type: 'shell',
|
|
407
|
-
command: 'npx
|
|
179
|
+
command: 'npx typecad-hal build --compile --upload --debug',
|
|
408
180
|
options: { cwd: `\${workspaceFolder}/${o.appRel}` },
|
|
409
181
|
group: { kind: 'build', isDefault: false },
|
|
410
182
|
problemMatcher: [],
|
|
411
183
|
};
|
|
412
184
|
}
|
|
413
185
|
/**
|
|
414
|
-
* The
|
|
415
|
-
*
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
421
|
-
* (the "debugger starts but never stops / buttons don't work" symptom).
|
|
422
|
-
*
|
|
423
|
-
* 4000 (4 MHz) is the empirically-stable speed for this peripheral.
|
|
186
|
+
* The background task that is the F5 preLaunchTask — ONE task, no dependsOn:
|
|
187
|
+
* `debug-server start --flash` runs the whole build pipeline (transpile →
|
|
188
|
+
* compile → upload, with --debug) and then serves. VS Code releases the
|
|
189
|
+
* debug session to connect gdbTarget when the matcher's endsPattern (the
|
|
190
|
+
* ready marker) fires, while the task keeps serving. (A dependsOn chain was
|
|
191
|
+
* tried: VS Code awaits the whole dependency GROUP, and a background task
|
|
192
|
+
* that never exits never releases the session — F5 hung with no debug UI.)
|
|
424
193
|
*/
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
depth = 0;
|
|
451
|
-
}
|
|
452
|
-
if (skipping) {
|
|
453
|
-
depth += (line.match(/\{/g) ?? []).length - (line.match(/\}/g) ?? []).length;
|
|
454
|
-
if (depth <= 0 && (line.includes('}') || !line.includes('{')))
|
|
455
|
-
skipping = false;
|
|
456
|
-
continue;
|
|
457
|
-
}
|
|
458
|
-
out.push(line);
|
|
459
|
-
}
|
|
460
|
-
return out;
|
|
194
|
+
function serverTask(o) {
|
|
195
|
+
return {
|
|
196
|
+
label: DEBUG_SERVER_TASK,
|
|
197
|
+
type: 'shell',
|
|
198
|
+
command: 'npx typecad-hal debug-server start --flash',
|
|
199
|
+
// NO_COLOR keeps chalk's ANSI codes out of the output — they break the
|
|
200
|
+
// background patterns below (the banner prints as ESC[36m⇳ Transpiling,
|
|
201
|
+
// and ^-anchored patterns never match past the escape byte). Same remedy
|
|
202
|
+
// as the scaffold's watch task.
|
|
203
|
+
options: { cwd: `\${workspaceFolder}/${o.appRel}`, env: { NO_COLOR: '1' } },
|
|
204
|
+
isBackground: true,
|
|
205
|
+
problemMatcher: {
|
|
206
|
+
owner: 'typecad-hal-debug-server',
|
|
207
|
+
// MUST never match a real line: matched lines become file-less
|
|
208
|
+
// problems (default severity: error), and VS Code then blocks F5 with
|
|
209
|
+
// "errors exist after running preLaunchTask". The sentinel literal
|
|
210
|
+
// cannot occur in pipeline or openocd output. (^$ was tried — it
|
|
211
|
+
// matches the blank lines openocd prints.)
|
|
212
|
+
pattern: { regexp: '__cuttlefish_never_matches__' },
|
|
213
|
+
background: {
|
|
214
|
+
beginsPattern: 'Transpiling',
|
|
215
|
+
endsPattern: `^TYPECAD_HAL: debug server ready on ${DEBUG_SERVER_PORT}`,
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
};
|
|
461
219
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
const rtosLine = isEsp32 ? [] : ['$_TARGETNAME configure -rtos Zephyr'];
|
|
472
|
-
if (method?.debugCfgSource && method.debugCfgSource.length > 0) {
|
|
473
|
-
// Probe-method-driven config: the board package's verified interface +
|
|
474
|
-
// target sources, then the method's quirk lines (e.g. reset_config for an
|
|
475
|
-
// unwired SRST) — the cfg-file form of the west runner args.
|
|
476
|
-
return [
|
|
477
|
-
...header,
|
|
478
|
-
`# Probe method '${method.id}' — from the board package's probeMethods table.`,
|
|
479
|
-
...method.debugCfgSource.map((src) => `source [find ${src}]`),
|
|
480
|
-
...stripGdbEventBlocks(method.debugCfg ?? []),
|
|
481
|
-
...rtosLine,
|
|
482
|
-
'',
|
|
483
|
-
].join('\n');
|
|
484
|
-
}
|
|
485
|
-
// Pack-derived methods (the board's own support/openocd.cfg, extracted by
|
|
486
|
-
// the board data pack) carry complete cfg lines — including their
|
|
487
|
-
// `source [find ...]` directives — verbatim in file order, minus the
|
|
488
|
-
// gdb-attach/detach events (IDE-managed lifecycle; see stripGdbEventBlocks).
|
|
489
|
-
if (method?.debugCfg && method.debugCfg.length > 0) {
|
|
490
|
-
return [
|
|
491
|
-
...header,
|
|
492
|
-
`# Probe method '${method.id}' — the board's own support/openocd.cfg,`,
|
|
493
|
-
`# extracted by the board data pack (gdb-attach/detach events removed —`,
|
|
494
|
-
`# the IDE owns the session lifecycle).`,
|
|
495
|
-
...stripGdbEventBlocks(method.debugCfg),
|
|
496
|
-
...rtosLine,
|
|
497
|
-
'',
|
|
498
|
-
].join('\n');
|
|
499
|
-
}
|
|
500
|
-
return [
|
|
501
|
-
...header,
|
|
502
|
-
'# Sources the board cfg (which loads the esp_usb_jtag adapter driver +',
|
|
503
|
-
'# esp32s3 target + ESP_RTOS Zephyr) then overrides the adapter speed to a',
|
|
504
|
-
'# USB-JTAG-stable value AFTER the driver is loaded. See debug-config.ts',
|
|
505
|
-
'# for the rationale.',
|
|
506
|
-
'source [find board/esp32s3-builtin.cfg]',
|
|
507
|
-
`adapter speed ${OPENOCD_ADAPTER_SPEED}`,
|
|
508
|
-
'',
|
|
509
|
-
].join('\n');
|
|
220
|
+
/** The postDebugTask — stops the west-owned server when the session ends. */
|
|
221
|
+
function serverStopTask(o) {
|
|
222
|
+
return {
|
|
223
|
+
label: DEBUG_SERVER_STOP_TASK,
|
|
224
|
+
type: 'shell',
|
|
225
|
+
command: 'npx typecad-hal debug-server stop',
|
|
226
|
+
options: { cwd: `\${workspaceFolder}/${o.appRel}` },
|
|
227
|
+
problemMatcher: [],
|
|
228
|
+
};
|
|
510
229
|
}
|
|
511
230
|
/**
|
|
512
|
-
* Write
|
|
513
|
-
*
|
|
514
|
-
*
|
|
515
|
-
* Writes (all idempotent):
|
|
516
|
-
* <workspaceRoot>/.vscode/launch.json (cortex-debug config)
|
|
517
|
-
* <workspaceRoot>/.vscode/tasks.json (build + flash preLaunch task)
|
|
518
|
-
* <projectRoot>/.cuttlefish/openocd.cfg (OpenOCD cfg, adapter speed override)
|
|
519
|
-
* <projectRoot>/.cuttlefish/.cuttlefish-gdb.py (lambda frame filter, conditional)
|
|
520
|
-
*/
|
|
521
|
-
/**
|
|
522
|
-
* Resolve the debug-side probe method for a project: the board constants the
|
|
523
|
-
* transpile persists + the workspace config's zephyr.probe selection.
|
|
524
|
-
* Returns the method object (for cfg/servertype data) or undefined — ESP32
|
|
525
|
-
* targets and boards without tables fall back to the framework's default
|
|
526
|
-
* (esp_usb_jtag openocd) behavior.
|
|
231
|
+
* Write the debug artifacts: the cortex-debug launch entry (merged by name)
|
|
232
|
+
* and the three tasks (merged by label). Returns the workspace-relative
|
|
233
|
+
* paths written, for CLI reporting.
|
|
527
234
|
*/
|
|
528
|
-
function resolveDebugProbeMethod(projectRoot, workspaceRoot) {
|
|
529
|
-
try {
|
|
530
|
-
const bcPath = [
|
|
531
|
-
join(projectRoot, 'src', 'board-constants.json'),
|
|
532
|
-
join(projectRoot, 'board-constants.json'),
|
|
533
|
-
].find((p) => existsSync(p));
|
|
534
|
-
if (!bcPath)
|
|
535
|
-
return undefined;
|
|
536
|
-
const raw = JSON.parse(readFileSync(bcPath, 'utf8'));
|
|
537
|
-
const chip = resolveChipFromBoard(new Map(Object.entries(raw)));
|
|
538
|
-
if (!chip)
|
|
539
|
-
return undefined;
|
|
540
|
-
const zc = loadCuttlefishConfig(workspaceRoot)?.zephyrConfig;
|
|
541
|
-
const probe = resolveProbeMethod(zc, chip, 'debug');
|
|
542
|
-
if (!probe.ok)
|
|
543
|
-
return undefined;
|
|
544
|
-
return chip.probeMethods?.find((m) => m.id === zc?.probe);
|
|
545
|
-
}
|
|
546
|
-
catch {
|
|
547
|
-
return undefined;
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
235
|
export function writeDebugConfig(o) {
|
|
551
236
|
const vscodeDir = join(o.workspaceRoot, '.vscode');
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
//
|
|
555
|
-
//
|
|
556
|
-
|
|
557
|
-
//
|
|
558
|
-
//
|
|
559
|
-
//
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
237
|
+
mkdirSync(vscodeDir, { recursive: true });
|
|
238
|
+
// west's resolved facts — present after any successful build. Before the
|
|
239
|
+
// first build the launch entry is written without gdbPath; the next build
|
|
240
|
+
// upgrades it (debugArtifactsNeedRewrite).
|
|
241
|
+
//
|
|
242
|
+
// west records the `-py` (python-enabled) gdb variant; cortex-debug derives
|
|
243
|
+
// objdump/nm paths from the gdb path by name substitution, and the -py
|
|
244
|
+
// variants of those do not exist in the SDK (ENOENT noise, degraded symbol
|
|
245
|
+
// classification). Prefer the plain gdb sibling when it exists.
|
|
246
|
+
let gdbPath = readRunnersFacts(o.buildDir)?.gdb?.replace(/\\/g, '/');
|
|
247
|
+
if (gdbPath && /-py(\.exe)?$/i.test(gdbPath)) {
|
|
248
|
+
const plain = gdbPath.replace(/-py(\.exe)?$/i, '$1');
|
|
249
|
+
if (existsSync(plain))
|
|
250
|
+
gdbPath = plain;
|
|
251
|
+
}
|
|
252
|
+
gdbPath = gdbPath ?? o.starterGdbPath;
|
|
253
|
+
// gdb frame-filter script (lambda frames) — conditional on a source map.
|
|
564
254
|
const gdbScript = generateGdbScript(o.sourceMapPath);
|
|
565
255
|
let gdbScriptRel;
|
|
566
256
|
if (gdbScript) {
|
|
567
|
-
const scriptPath = join(
|
|
257
|
+
const scriptPath = join(o.projectRoot, '.typecad-hal', '.typecad-hal-gdb.py');
|
|
258
|
+
mkdirSync(dirname(scriptPath), { recursive: true });
|
|
568
259
|
writeFileSync(scriptPath, gdbScript, 'utf-8');
|
|
569
|
-
gdbScriptRel = `${o.appRel}/.
|
|
260
|
+
gdbScriptRel = `${o.appRel}/.typecad-hal/.typecad-hal-gdb.py`;
|
|
570
261
|
}
|
|
571
|
-
const launchConfig = buildLaunchConfig(o, gdbScriptRel,
|
|
262
|
+
const launchConfig = buildLaunchConfig(o, gdbScriptRel, gdbPath);
|
|
572
263
|
mergeJsonArrayEntry(join(vscodeDir, 'launch.json'), 'configurations', 'name', launchConfig);
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
264
|
+
const tasksPath = join(vscodeDir, 'tasks.json');
|
|
265
|
+
for (const task of [buildTask(o), serverTask(o), serverStopTask(o)]) {
|
|
266
|
+
mergeJsonArrayEntry(tasksPath, 'tasks', 'label', task);
|
|
267
|
+
}
|
|
268
|
+
return ['.vscode/launch.json', '.vscode/tasks.json'];
|
|
577
269
|
}
|
|
578
270
|
/**
|
|
579
|
-
* The Zephyr app dir
|
|
580
|
-
* project root: the scaffold fixes entry `./src/main.ts` +
|
|
581
|
-
* the CLI resolves output.outDir against the ENTRY's
|
|
582
|
-
* the emitted app root
|
|
583
|
-
*
|
|
584
|
-
*
|
|
271
|
+
* The Zephyr app dir the standard `typecad-hal create` scaffold produces,
|
|
272
|
+
* relative to the project root: the scaffold fixes entry `./src/main.ts` +
|
|
273
|
+
* outDir `./out`, and the CLI resolves output.outDir against the ENTRY's
|
|
274
|
+
* directory (cli.ts), so the emitted app root lands at `src/out`. The create
|
|
275
|
+
* flow passes its actual resolution (typecad-hal create's starterAppRel) so a
|
|
276
|
+
* non-standard scaffold still gets correct starter paths; this is only the
|
|
277
|
+
* default.
|
|
585
278
|
*/
|
|
586
279
|
const STARTER_APP_REL = 'src/out';
|
|
587
280
|
/**
|
|
588
|
-
* Create-time starter debug artifacts
|
|
589
|
-
* (
|
|
590
|
-
*
|
|
591
|
-
*
|
|
592
|
-
* The launch.json's preLaunchTask runs `cuttlefish build --compile --upload
|
|
593
|
-
* --debug`, which builds + flashes AND rewrites this same launch entry (merged
|
|
594
|
-
* by name) with the CMakeCache-resolved gdbPath — so the starter files upgrade
|
|
595
|
-
* themselves on the first debug build.
|
|
596
|
-
*
|
|
597
|
-
* No-ops (returns []) for targets without native GDB support (debugMode() !==
|
|
598
|
-
* 'gdb'); the gdb frame-filter script is skipped (no source map exists yet).
|
|
281
|
+
* Create-time starter debug artifacts: the same west-driven launch entry and
|
|
282
|
+
* tasks, minus gdbPath (no build exists yet — runners.yaml is absent). The
|
|
283
|
+
* first build upgrades the entry (debugArtifactsNeedRewrite fires on the
|
|
284
|
+
* missing gdbPath), so F5 after a compile is fully resolved.
|
|
599
285
|
*
|
|
600
|
-
*
|
|
286
|
+
* No-ops (returns []) for targets without gdb debug facts (no debug-capable
|
|
287
|
+
* probe method in the board's table — printf instrumentation territory).
|
|
601
288
|
*/
|
|
602
289
|
export function writeProjectDebugArtifacts(o) {
|
|
603
290
|
if (new ZephyrStrategy().debugMode(o.buildTarget) !== 'gdb')
|
|
604
291
|
return [];
|
|
605
292
|
const workspaceRoot = resolve(o.workspaceRoot);
|
|
606
|
-
const
|
|
607
|
-
|
|
293
|
+
const appRel = o.appRel ?? STARTER_APP_REL;
|
|
294
|
+
const projectRoot = join(workspaceRoot, appRel);
|
|
295
|
+
return writeDebugConfig({
|
|
608
296
|
projectRoot,
|
|
609
297
|
workspaceRoot,
|
|
610
|
-
appRel
|
|
298
|
+
appRel,
|
|
611
299
|
target: o.buildTarget ?? '',
|
|
612
|
-
// No build dir exists yet — resolveGdbPath falls back to probing known
|
|
613
|
-
// Zephyr SDK locations so gdbPath is still filled in when possible.
|
|
614
300
|
buildDir: join(projectRoot, 'build'),
|
|
301
|
+
...(o.gdbPath ? { starterGdbPath: o.gdbPath } : {}),
|
|
615
302
|
});
|
|
303
|
+
}
|
|
304
|
+
// -- gdb frame-filter script -------------------------------------------------
|
|
305
|
+
export function generateGdbScript(sourceMapPath) {
|
|
306
|
+
if (!sourceMapPath || !existsSync(sourceMapPath))
|
|
307
|
+
return null;
|
|
308
|
+
let mapText = '';
|
|
309
|
+
try {
|
|
310
|
+
mapText = readFileSync(sourceMapPath, 'utf-8');
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
// Only emit when the emitted code contains hoisted-lambda symbols.
|
|
316
|
+
if (!/_isr_\d+/.test(mapText))
|
|
317
|
+
return null;
|
|
318
|
+
// A GDB Python frame-filter. Registered via the launch.json postAttachCommand
|
|
319
|
+
// `source <path>` so GDB auto-loads it on attach.
|
|
616
320
|
return [
|
|
617
|
-
'
|
|
618
|
-
'
|
|
619
|
-
|
|
620
|
-
|
|
321
|
+
'# Auto-generated by @typecad/framework-zephyr. GDB frame-filter that',
|
|
322
|
+
'# rewrites typecad-hal hoisted-lambda frame names (*_isr_N) into readable',
|
|
323
|
+
'# <lambda> form so the VS Code call stack is legible.',
|
|
324
|
+
'import gdb',
|
|
325
|
+
'import re',
|
|
326
|
+
'',
|
|
327
|
+
'class CuttlefishLambdaFilter:',
|
|
328
|
+
' def __init__(self):',
|
|
329
|
+
' self.name = "cuttlefish_lambda"',
|
|
330
|
+
' self.priority = 100',
|
|
331
|
+
' self.enabled = True',
|
|
332
|
+
'',
|
|
333
|
+
' def filter(self, frame_iter):',
|
|
334
|
+
' isr_re = re.compile(r"(.*)_isr_(\\d+)")',
|
|
335
|
+
' return (CuttlefishFrame(f) for f in frame_iter)',
|
|
336
|
+
'',
|
|
337
|
+
'class CuttlefishFrame:',
|
|
338
|
+
' def __init__(self, frame):',
|
|
339
|
+
' self.frame = frame',
|
|
340
|
+
' def __getattr__(self, name):',
|
|
341
|
+
' val = getattr(self.frame, name)',
|
|
342
|
+
' if name == "function":',
|
|
343
|
+
' m = isr_re.match(val)',
|
|
344
|
+
' if m: return "<lambda>"',
|
|
345
|
+
' return val',
|
|
346
|
+
'',
|
|
347
|
+
'gdb.frame_filters[CuttlefishLambdaFilter().name] = CuttlefishLambdaFilter()',
|
|
348
|
+
'',
|
|
349
|
+
].join('\n');
|
|
621
350
|
}
|