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