@typecad/framework-zephyr 1.0.0-alpha.13 → 1.0.0-alpha.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -35
- package/dist/as-built.d.ts +33 -0
- package/dist/as-built.js +188 -0
- package/dist/async/timer-polyfill.js +1 -1
- package/dist/boardgen.d.ts +128 -0
- package/dist/boardgen.js +1366 -0
- package/dist/chips/board-overrides.d.ts +7 -0
- package/dist/chips/board-overrides.js +11 -0
- package/dist/chips/controllers.d.ts +28 -8
- package/dist/chips/controllers.js +49 -12
- package/dist/chips/esp32s3.js +49 -0
- package/dist/chips/index.d.ts +6 -12
- package/dist/chips/index.js +18 -34
- package/dist/chips/resolve.d.ts +16 -2
- package/dist/chips/resolve.js +298 -15
- package/dist/chips/soc/.d.ts +2 -0
- package/dist/chips/soc/.js +129 -0
- package/dist/chips/soc/esp32.d.ts +2 -0
- package/dist/chips/soc/esp32.js +120 -0
- package/dist/chips/soc/esp32c3.d.ts +2 -0
- package/dist/chips/soc/esp32c3.js +90 -0
- package/dist/chips/soc/esp32c6.d.ts +2 -0
- package/dist/chips/soc/esp32c6.js +109 -0
- package/dist/chips/soc/esp32s3.d.ts +2 -0
- package/dist/chips/soc/esp32s3.js +189 -0
- package/dist/chips/soc/index.d.ts +2 -0
- package/dist/chips/soc/index.js +23 -0
- package/dist/chips/soc/nrf52840.d.ts +2 -0
- package/dist/chips/soc/nrf52840.js +130 -0
- package/dist/chips/soc/rp2040.d.ts +2 -0
- package/dist/chips/soc/rp2040.js +141 -0
- package/dist/chips/soc/rp2350a.d.ts +2 -0
- package/dist/chips/soc/rp2350a.js +145 -0
- package/dist/chips/soc/samd21g18a.d.ts +2 -0
- package/dist/chips/soc/samd21g18a.js +143 -0
- package/dist/chips/soc/stm32f411xe.d.ts +2 -0
- package/dist/chips/soc/stm32f411xe.js +251 -0
- package/dist/chips/types.d.ts +382 -12
- package/dist/chips/xiao-ble.js +33 -0
- package/dist/display/profiles.js +1 -1
- package/dist/doctor.js +38 -4
- package/dist/dt-config/custom-board.d.ts +23 -0
- package/dist/dt-config/custom-board.js +227 -0
- package/dist/dt-config/kconfig.d.ts +83 -1
- package/dist/dt-config/kconfig.js +116 -27
- package/dist/dt-config/overlay.js +623 -18
- package/dist/framework.manifest.d.ts +20 -30
- package/dist/framework.manifest.js +124 -178
- package/dist/index.d.ts +4 -1
- package/dist/index.js +8 -2
- package/dist/lowering/adc.d.ts +26 -5
- package/dist/lowering/adc.js +137 -31
- package/dist/lowering/ble.js +31 -28
- package/dist/lowering/dac.js +9 -9
- package/dist/lowering/fs.js +18 -10
- package/dist/lowering/gpio.d.ts +2 -0
- package/dist/lowering/gpio.js +90 -10
- package/dist/lowering/http.js +39 -14
- package/dist/lowering/hwtimer.d.ts +4 -0
- package/dist/lowering/hwtimer.js +35 -10
- package/dist/lowering/i2c.js +46 -54
- package/dist/lowering/index.d.ts +5 -5
- package/dist/lowering/index.js +15 -16
- package/dist/lowering/interrupts.d.ts +22 -5
- package/dist/lowering/interrupts.js +104 -22
- package/dist/lowering/mqtt.js +12 -6
- package/dist/lowering/power.js +0 -4
- package/dist/lowering/preferences.js +49 -77
- package/dist/lowering/pulse.js +5 -36
- package/dist/lowering/pwm.d.ts +32 -3
- package/dist/lowering/pwm.js +105 -17
- package/dist/lowering/random.js +3 -3
- package/dist/lowering/sensor.d.ts +37 -0
- package/dist/lowering/sensor.js +119 -0
- package/dist/lowering/spi.d.ts +12 -0
- package/dist/lowering/spi.js +65 -40
- package/dist/lowering/thread.d.ts +14 -0
- package/dist/lowering/thread.js +55 -0
- package/dist/lowering/timing.js +20 -46
- package/dist/lowering/tone.js +18 -5
- package/dist/lowering/uart.d.ts +13 -0
- package/dist/lowering/uart.js +72 -51
- package/dist/lowering/usb.d.ts +34 -0
- package/dist/lowering/usb.js +206 -0
- package/dist/lowering/wdt.d.ts +1 -1
- package/dist/lowering/wdt.js +17 -7
- package/dist/lowering/wifi.d.ts +3 -2
- package/dist/lowering/wifi.js +120 -111
- package/dist/sdk/board-catalog-sync.d.ts +16 -0
- package/dist/sdk/board-catalog-sync.js +41 -0
- package/dist/sdk/board-data.generated.d.ts +2 -0
- package/dist/sdk/board-data.generated.js +4 -0
- package/dist/sdk/catalog-walker.d.ts +90 -0
- package/dist/sdk/catalog-walker.js +682 -0
- package/dist/sdk/dts-reader.d.ts +83 -0
- package/dist/sdk/dts-reader.js +596 -0
- package/dist/strategy.d.ts +136 -17
- package/dist/strategy.js +881 -212
- package/dist/toolchain/bossac-touch.d.ts +18 -0
- package/dist/toolchain/bossac-touch.js +88 -0
- package/dist/toolchain/compat.d.ts +2 -2
- package/dist/toolchain/compat.js +8 -3
- package/dist/toolchain/debug-config.d.ts +21 -26
- package/dist/toolchain/debug-config.js +212 -72
- package/dist/toolchain/env-check.d.ts +4 -4
- package/dist/toolchain/env-check.js +5 -5
- package/dist/toolchain/index.d.ts +66 -1
- package/dist/toolchain/index.js +713 -58
- package/dist/toolchain/scaffold.d.ts +9 -0
- package/dist/toolchain/scaffold.js +84 -7
- package/dist/toolchain/west-discover.d.ts +8 -1
- package/dist/toolchain/west-discover.js +19 -6
- package/dist/toolchain/west-spawn.js +17 -5
- package/installer/CHANGELOG.md +473 -0
- package/installer/README.md +328 -0
- package/installer/environment.yml +46 -0
- package/installer/etc/conda/activate.d/zephyr.bat +7 -0
- package/installer/etc/conda/activate.d/zephyr.ps1 +12 -0
- package/installer/etc/conda/activate.d/zephyr.sh +20 -0
- package/installer/etc/conda/deactivate.d/zephyr.bat +6 -0
- package/installer/etc/conda/deactivate.d/zephyr.ps1 +6 -0
- package/installer/etc/conda/deactivate.d/zephyr.sh +4 -0
- package/installer/install.mjs +580 -0
- package/installer/install.ps1 +619 -0
- package/installer/install.sh +310 -0
- package/installer/lib/detect-platform.sh +62 -0
- package/installer/lib/fetch-sdk.sh +304 -0
- package/installer/lib/init-workspace.sh +153 -0
- package/installer/lib/write-activation.sh +76 -0
- package/installer/patches/tfm-c-include-separators.patch +19 -0
- package/installer/patches/zephyr-tfm-nordic-zephyr-include.patch +24 -0
- package/installer/templates/project/.typecad/activate-zephyr.ps1 +31 -0
- package/installer/templates/project/.typecad/activate-zephyr.sh +31 -0
- package/installer/templates/project/.vscode/settings.json +30 -0
- package/installer/templates/project/README.md +58 -0
- package/installer/versions.env +110 -0
- package/package.json +16 -16
- package/src/as-built.ts +206 -0
- package/src/boardgen.ts +1428 -0
- package/src/chips/controllers.ts +61 -12
- package/src/chips/index.ts +35 -54
- package/src/chips/resolve.ts +316 -14
- package/src/chips/types.ts +388 -13
- package/src/display/profiles.ts +1 -1
- package/src/doctor.ts +110 -77
- package/src/dt-config/custom-board.ts +325 -0
- package/src/dt-config/kconfig.ts +177 -19
- package/src/dt-config/overlay.ts +1058 -450
- package/src/framework.manifest.ts +125 -178
- package/src/index.ts +57 -41
- package/src/lowering/adc.ts +222 -102
- package/src/lowering/ble.ts +32 -28
- package/src/lowering/dac.ts +82 -82
- package/src/lowering/fs.ts +18 -10
- package/src/lowering/gpio.ts +242 -152
- package/src/lowering/http.ts +512 -488
- package/src/lowering/hwtimer.ts +139 -101
- package/src/lowering/i2c.ts +116 -126
- package/src/lowering/index.ts +91 -93
- package/src/lowering/interrupts.ts +113 -21
- package/src/lowering/mqtt.ts +12 -6
- package/src/lowering/preferences.ts +327 -354
- package/src/lowering/pwm.ts +167 -74
- package/src/lowering/random.ts +3 -3
- package/src/lowering/sensor.ts +155 -0
- package/src/lowering/spi.ts +164 -124
- package/src/lowering/thread.ts +63 -0
- package/src/lowering/timing.ts +46 -72
- package/src/lowering/uart.ts +73 -51
- package/src/lowering/usb.ts +221 -0
- package/src/lowering/wdt.ts +95 -84
- package/src/lowering/wifi.ts +120 -111
- package/src/sdk/board-catalog-sync.ts +62 -0
- package/src/strategy.ts +888 -229
- package/src/toolchain/bossac-touch.ts +112 -0
- package/src/toolchain/compat.ts +8 -3
- package/src/toolchain/debug-config.ts +666 -522
- package/src/toolchain/env-check.ts +9 -9
- package/src/toolchain/index.ts +765 -44
- package/src/toolchain/scaffold.ts +81 -7
- package/src/toolchain/west-discover.ts +334 -316
- package/src/toolchain/west-spawn.ts +17 -5
- package/src/async/timer-polyfill.ts +0 -107
- package/src/chips/esp32.ts +0 -74
- package/src/chips/esp32s3.ts +0 -57
- package/src/chips/xiao-ble.ts +0 -70
- package/src/lowering/power.ts +0 -70
- package/src/lowering/pulse.ts +0 -93
- package/src/lowering/tone.ts +0 -60
- package/src/lowering/worker-backing.ts +0 -94
- package/src/lowering/worker.ts +0 -17
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// catalog-walker.ts — walk a Zephyr tree's boards/ and build the board data
|
|
3
|
+
// catalog (records + provenance).
|
|
4
|
+
//
|
|
5
|
+
// For every board variant yaml that carries an `identifier:` (the qualified
|
|
6
|
+
// west build target) and a same-basename .dts, extract the board-level facts
|
|
7
|
+
// with the tolerant dts-reader. One record per board VARIANT (identifier),
|
|
8
|
+
// keyed by the identifier.
|
|
9
|
+
//
|
|
10
|
+
// This is the ONE walker shared by both catalog producers:
|
|
11
|
+
// - repo build: scripts/gen-zephyr-board-data.mjs emits the compiled-in
|
|
12
|
+
// pack (packages/cuttlefish/src/create/board-catalog.generated.ts);
|
|
13
|
+
// - user runtime: `cuttlefish board sync` (sdk/board-catalog-sync.ts)
|
|
14
|
+
// regenerates a local overlay from the user's own Zephyr tree, so board
|
|
15
|
+
// add/change/remove tracks `west update` instead of cuttlefish releases.
|
|
16
|
+
//
|
|
17
|
+
// Silicon facts (ADC channels, PWM matrices) are NOT extracted — they never
|
|
18
|
+
// live in devicetree. They are the curated chips/soc/ descriptors.
|
|
19
|
+
// ----------------------------------------------------------------------------
|
|
20
|
+
import fs from 'node:fs';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
import { readBoardDts } from './dts-reader.js';
|
|
23
|
+
/** Cut an inline `# comment` — safe inside quoted values (variant yamls
|
|
24
|
+
* carry them, e.g. ai_m61_32s_kit's revision note). */
|
|
25
|
+
function stripYamlComment(value) {
|
|
26
|
+
const q = value[0];
|
|
27
|
+
if (q === '"' || q === "'") {
|
|
28
|
+
const end = value.indexOf(q, 1);
|
|
29
|
+
return end > 0 ? value.slice(0, end + 1) : value;
|
|
30
|
+
}
|
|
31
|
+
const hash = value.indexOf(' #');
|
|
32
|
+
return hash >= 0 ? value.slice(0, hash) : value;
|
|
33
|
+
}
|
|
34
|
+
/** Minimal line yaml reads (identifier:, name:) — full yaml not needed. */
|
|
35
|
+
function yamlField(file, field) {
|
|
36
|
+
const m = fs.readFileSync(file, 'utf8').match(new RegExp(`^${field}:\\s*(.+)$`, 'm'));
|
|
37
|
+
return m ? stripYamlComment(m[1]).trim() : undefined;
|
|
38
|
+
}
|
|
39
|
+
/** Read board.yml's board→soc index (see BoardYmlSocIndex). */
|
|
40
|
+
export function boardYmlSocIndex(dir) {
|
|
41
|
+
const yml = path.join(dir, 'board.yml');
|
|
42
|
+
if (!fs.existsSync(yml))
|
|
43
|
+
return { byName: new Map(), uniqueSocs: [], boardNames: [] };
|
|
44
|
+
const byName = new Map();
|
|
45
|
+
const uniqueSocs = [];
|
|
46
|
+
const boardNames = [];
|
|
47
|
+
let inBoardsList = false; // top-level `boards:` present (multi-board format)
|
|
48
|
+
let currentBoard;
|
|
49
|
+
let currentSocs = []; // socs collected for currentBoard
|
|
50
|
+
// A key line with its indent, innermost last. A `- name:` list entry is
|
|
51
|
+
// owned by the topmost key at an indent ≤ the entry's — the tree's
|
|
52
|
+
// board.ymls are not indent-consistent (xiao_ble puts soc entries at the
|
|
53
|
+
// same indent as the socs: key, lyra nests them two deeper), so the
|
|
54
|
+
// enclosing KEY, not the indentation depth, decides ownership.
|
|
55
|
+
const keyStack = [];
|
|
56
|
+
const flushEntry = () => {
|
|
57
|
+
// Only single-soc entries map board name → soc: multi-soc entries need
|
|
58
|
+
// qualified identifiers in their variant yamls.
|
|
59
|
+
if (currentBoard && currentSocs.length === 1)
|
|
60
|
+
byName.set(currentBoard, currentSocs[0]);
|
|
61
|
+
};
|
|
62
|
+
for (const rawLine of fs.readFileSync(yml, 'utf8').split('\n')) {
|
|
63
|
+
const line = rawLine.replace(/\r$/, '');
|
|
64
|
+
const trimmed = line.trim();
|
|
65
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
66
|
+
continue;
|
|
67
|
+
const indent = line.length - line.trimStart().length;
|
|
68
|
+
const entry = trimmed.match(/^- name:\s*(.+)$/);
|
|
69
|
+
const key = trimmed.match(/^([\w.-]+):(?:\s|$)/);
|
|
70
|
+
if (key) {
|
|
71
|
+
while (keyStack.length > 0 && keyStack[keyStack.length - 1].indent > indent)
|
|
72
|
+
keyStack.pop();
|
|
73
|
+
keyStack.push({ indent, key: key[1] });
|
|
74
|
+
if (indent === 0 && key[1] === 'boards')
|
|
75
|
+
inBoardsList = true;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
// Legacy single-board header: `board: xiao_ble` (value on the key line).
|
|
79
|
+
if (indent === 0) {
|
|
80
|
+
const legacyBoard = trimmed.match(/^board:\s*(\S.*)$/);
|
|
81
|
+
if (legacyBoard)
|
|
82
|
+
boardNames.push(stripYamlComment(legacyBoard[1]).trim());
|
|
83
|
+
}
|
|
84
|
+
if (entry) {
|
|
85
|
+
const name = stripYamlComment(entry[1]).trim();
|
|
86
|
+
if (!name)
|
|
87
|
+
continue;
|
|
88
|
+
// Innermost enclosing key that owns a list at this entry's indent.
|
|
89
|
+
let owner;
|
|
90
|
+
for (let i = keyStack.length - 1; i >= 0; i--) {
|
|
91
|
+
if (keyStack[i].indent <= indent) {
|
|
92
|
+
owner = keyStack[i];
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (owner?.key === 'boards') {
|
|
97
|
+
// A board entry under boards: — its socs follow under its own
|
|
98
|
+
// socs: key.
|
|
99
|
+
flushEntry();
|
|
100
|
+
currentBoard = name;
|
|
101
|
+
currentSocs = [];
|
|
102
|
+
boardNames.push(name);
|
|
103
|
+
}
|
|
104
|
+
else if (owner?.key === 'socs') {
|
|
105
|
+
if (inBoardsList)
|
|
106
|
+
currentSocs.push(name);
|
|
107
|
+
else
|
|
108
|
+
uniqueSocs.push(name);
|
|
109
|
+
}
|
|
110
|
+
// Every other list (variants:, …) is not ours.
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
flushEntry();
|
|
114
|
+
return { byName, uniqueSocs, boardNames };
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* west runner → probe-method metadata. Ids match the curated soc tables
|
|
118
|
+
* ('stlink', 'dfu', 'jlink', 'bossac', …) so `zephyr.probe` values stay
|
|
119
|
+
* stable across tiers; runners with no curated counterpart use their west
|
|
120
|
+
* runner name as the id (stm32cubeprogrammer, uf2, …). Flash-only tools
|
|
121
|
+
* carry debug: false (a bootloader is not a debugger — the debug path
|
|
122
|
+
* rejects them, resolveProbeMethod enforces it). Simulation-only runners
|
|
123
|
+
* (renode, simics) and debug-server helpers (stlink_gdbserver, trace32)
|
|
124
|
+
* stay unmapped — cuttlefish flashes real hardware.
|
|
125
|
+
*/
|
|
126
|
+
const RUNNER_METHODS = {
|
|
127
|
+
'openocd': { id: 'openocd', description: 'Any SWD/JTAG probe openocd supports', debug: true },
|
|
128
|
+
// Vendor-flavored openocd runner includes — the same openocd runner with
|
|
129
|
+
// vendor defaults (ST: onboard ST-Link; nRF5: nRF tap config). Without
|
|
130
|
+
// these, every ST board (118 in the tree) lost its primary probe method.
|
|
131
|
+
'openocd-stm32': { id: 'openocd', description: 'ST-Link onboard or any SWD probe openocd supports', debug: true },
|
|
132
|
+
'openocd-nrf5': { id: 'openocd', description: 'Any SWD/JTAG probe openocd supports (nRF5 defaults)', debug: true },
|
|
133
|
+
'jlink': { id: 'jlink', description: 'J-Link probe (SWD)', debug: true },
|
|
134
|
+
'dfu-util': { id: 'dfu', description: 'Built-in USB DFU bootloader: hold BOOT0, tap reset (no debug)', debug: false },
|
|
135
|
+
'pyocd': { id: 'pyocd', description: 'Any CMSIS-DAP probe via pyOCD (no debug)', debug: false },
|
|
136
|
+
'blackmagicprobe': { id: 'blackmagicprobe', description: 'Black Magic Probe (no debug)', debug: false },
|
|
137
|
+
'stm32flash': { id: 'stm32flash', description: 'Built-in UART bootloader (no debug)', debug: false },
|
|
138
|
+
'bossac': { id: 'bossac', description: 'Built-in USB bootloader: double-tap reset (no debug)', debug: false },
|
|
139
|
+
'nrfjprog': { id: 'nrfjprog', description: 'Segger nRF command-line flasher (no debug)', debug: false },
|
|
140
|
+
'esptool': { id: 'esptool', description: 'Espressif ROM bootloader over USB-serial (no debug)', debug: false },
|
|
141
|
+
// The ESP32 family's include name for the esptool runner.
|
|
142
|
+
'esp32': { id: 'esptool', description: 'Espressif ROM bootloader over USB-serial (no debug)', debug: false },
|
|
143
|
+
'linkserver': { id: 'linkserver', description: 'NXP LinkServer (no debug)', debug: false },
|
|
144
|
+
'ezflashcli': { id: 'ezflashcli', description: 'Renesas EZ flash CLI (no debug)', debug: false },
|
|
145
|
+
'stm32cubeprogrammer': { id: 'stm32cubeprogrammer', description: 'STM32CubeProgrammer CLI via ST-Link (no debug)', debug: false },
|
|
146
|
+
'nrfutil': { id: 'nrfutil', description: 'nrfutil device — Nordic USB DFU / serial bootloader (no debug)', debug: false },
|
|
147
|
+
'uf2': { id: 'uf2', description: 'UF2 bootloader: copy the firmware file (no debug)', debug: false },
|
|
148
|
+
'silabs_commander': { id: 'silabs_commander', description: 'Simplicity Commander CLI (no debug)', debug: false },
|
|
149
|
+
'probe-rs': { id: 'probe-rs', description: 'probe-rs flasher (no debug)', debug: false },
|
|
150
|
+
'rfp': { id: 'rfp', description: 'Renesas Flash Programmer CLI (no debug)', debug: false },
|
|
151
|
+
'bflb_mcu_tool': { id: 'bflb_mcu_tool', description: 'BouffaloLab bflb_mcu_tool (no debug)', debug: false },
|
|
152
|
+
'xsdb': { id: 'xsdb', description: 'Xilinx xsdb flasher (no debug)', debug: false },
|
|
153
|
+
'wchisp': { id: 'wchisp', description: 'WCH ISP bootloader (no debug)', debug: false },
|
|
154
|
+
'spsdk': { id: 'spsdk', description: 'NXP SPSDK blhost (no debug)', debug: false },
|
|
155
|
+
'minichlink': { id: 'minichlink', description: 'minichlink (WCH CH32, no debug)', debug: false },
|
|
156
|
+
'mdb-hw': { id: 'mdb-hw', description: 'Synopsys MetaWare Debugger, hardware target (no debug)', debug: false },
|
|
157
|
+
'wlink': { id: 'wlink', description: 'WCH-Link probe (no debug)', debug: false },
|
|
158
|
+
'gd32isp': { id: 'gd32isp', description: 'GD32 UART ISP bootloader (no debug)', debug: false },
|
|
159
|
+
'sftool': { id: 'sftool', description: 'SiFli sftool (no debug)', debug: false },
|
|
160
|
+
'teensy': { id: 'teensy', description: 'Teensy loader (no debug)', debug: false },
|
|
161
|
+
'nulink': { id: 'nulink', description: 'Nuvoton NuLink (no debug)', debug: false },
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* Scan board.cmake line-by-line tracking if/elseif/else/endif, recording
|
|
165
|
+
* every board_runner_args occurrence and every runner include with its
|
|
166
|
+
* condition stack. Variant selection happens later (pickArgs /
|
|
167
|
+
* activeRunners) — a dir shared by several boards guards each variant's
|
|
168
|
+
* runner args behind its own CONFIG_BOARD_<ID>, and boards guard whole
|
|
169
|
+
* RUNNERS behind core-specific configs (variscite's jlink sits inside
|
|
170
|
+
* if(CONFIG_SOC_MIMX8ML8_M7): the A53 target has no jlink at all).
|
|
171
|
+
* Args containing CMake variable references (${CONFIG_SOC}, …) only
|
|
172
|
+
* resolve inside the build — carried verbatim they would poison a real
|
|
173
|
+
* `west flash` invocation, so they are dropped at capture.
|
|
174
|
+
*/
|
|
175
|
+
function scanBoardCmake(text) {
|
|
176
|
+
const runnerArgs = new Map();
|
|
177
|
+
const includes = [];
|
|
178
|
+
const conds = [];
|
|
179
|
+
for (const line of text.split('\n')) {
|
|
180
|
+
const branch = line.match(/^\s*(if|elseif|else|endif)\b\s*\(?\s*(.*?)\s*\)?\s*$/);
|
|
181
|
+
if (branch) {
|
|
182
|
+
const kw = branch[1];
|
|
183
|
+
const cond = kw === 'else' ? '' : branch[2];
|
|
184
|
+
if (kw === 'if')
|
|
185
|
+
conds.push(cond);
|
|
186
|
+
else if (kw === 'elseif')
|
|
187
|
+
conds[conds.length - 1] = cond;
|
|
188
|
+
else if (kw === 'else')
|
|
189
|
+
conds[conds.length - 1] = '';
|
|
190
|
+
else if (kw === 'endif')
|
|
191
|
+
conds.pop();
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
const argsM = line.match(/^\s*board_runner_args\(([\w.-]+)\s+(.*)\)\s*$/);
|
|
195
|
+
if (argsM) {
|
|
196
|
+
const args = [...argsM[2].matchAll(/"([^"]*)"/g)].map((q) => q[1]).filter((q) => !q.includes('${'));
|
|
197
|
+
const list = runnerArgs.get(argsM[1]) ?? [];
|
|
198
|
+
list.push({ conds: [...conds], args });
|
|
199
|
+
runnerArgs.set(argsM[1], list);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const incM = line.match(/^\s*include\(\$\{ZEPHYR_BASE\}\/boards\/common\/([\w.-]+)\.board\.cmake\)/);
|
|
203
|
+
if (incM)
|
|
204
|
+
includes.push({ name: incM[1], conds: [...conds] });
|
|
205
|
+
}
|
|
206
|
+
return { runnerArgs, includes };
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Does one condition line apply to THIS target? Empty = always. A
|
|
210
|
+
* CONFIG_BOARD stem must match on a token boundary (a prefix must not
|
|
211
|
+
* match a longer board name); a CONFIG_SOC_<soc> guard matches any
|
|
212
|
+
* variant of that soc, and CONFIG_SOC_<soc>_<X> matches variants whose
|
|
213
|
+
* qualifiers include X.
|
|
214
|
+
*/
|
|
215
|
+
function conditionMatchesTarget(cond, boardConfigs, soc, quals) {
|
|
216
|
+
if (!cond)
|
|
217
|
+
return true;
|
|
218
|
+
const cu = cond.toUpperCase();
|
|
219
|
+
for (const cfg of boardConfigs) {
|
|
220
|
+
if (new RegExp(`CONFIG_BOARD_${cfg}(?![A-Za-z0-9_])`).test(cu))
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
if (soc) {
|
|
224
|
+
const socUpper = soc.toUpperCase();
|
|
225
|
+
if (new RegExp(`CONFIG_SOC_${socUpper}(?![A-Za-z0-9_])`).test(cu))
|
|
226
|
+
return true;
|
|
227
|
+
if (quals.some((q) => cu.includes(`_${q.toUpperCase()}`)) && cu.includes(`CONFIG_SOC_${socUpper}`))
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Pick the occurrence for one variant. Priority:
|
|
234
|
+
* 1. a condition naming one of this target's CONFIG_BOARD_<name> tokens
|
|
235
|
+
* (both Zephyr naming shapes: the board.yml entry name — duo_board_b —
|
|
236
|
+
* and the full underscored target — raytac's …_NRF5340_CPUNET), matched
|
|
237
|
+
* with a token boundary so CONFIG_BOARD_IMX8MM_EVK does not match
|
|
238
|
+
* …_EVK_MIMX8MM6_M4;
|
|
239
|
+
* 2. a CONFIG_SOC_<soc>_… condition mentioning one of the target's
|
|
240
|
+
* qualifier tokens (imx8mm guards its A53 args that way);
|
|
241
|
+
* 3. an unconditional occurrence;
|
|
242
|
+
* 4. the first (documented fallback).
|
|
243
|
+
*/
|
|
244
|
+
function pickArgs(list, boardConfigs, soc, quals) {
|
|
245
|
+
for (const cfg of boardConfigs) {
|
|
246
|
+
const boardGuarded = list.find((o) => o.conds.some((c) => conditionMatchesTarget(c, [cfg], undefined, [])));
|
|
247
|
+
if (boardGuarded)
|
|
248
|
+
return boardGuarded.args;
|
|
249
|
+
}
|
|
250
|
+
if (soc && quals.length > 0) {
|
|
251
|
+
const socGuarded = list.find((o) => o.conds.some((c) => c && conditionMatchesTarget(c, [], soc, quals) && /CONFIG_SOC_/i.test(c)));
|
|
252
|
+
if (socGuarded)
|
|
253
|
+
return socGuarded.args;
|
|
254
|
+
}
|
|
255
|
+
const plain = list.find((o) => o.conds.length === 0 || o.conds.every((c) => c === ''));
|
|
256
|
+
if (plain)
|
|
257
|
+
return plain.args;
|
|
258
|
+
return list[0]?.args ?? [];
|
|
259
|
+
}
|
|
260
|
+
export function boardProbeMethods(dir, variant) {
|
|
261
|
+
const cmake = path.join(dir, 'board.cmake');
|
|
262
|
+
if (!fs.existsSync(cmake))
|
|
263
|
+
return undefined;
|
|
264
|
+
const text = fs.readFileSync(cmake, 'utf8');
|
|
265
|
+
const scan = scanBoardCmake(text);
|
|
266
|
+
const boardConfigs = variant?.boardConfigs ?? [];
|
|
267
|
+
const soc = variant?.soc;
|
|
268
|
+
const quals = variant?.quals ?? [];
|
|
269
|
+
// Active runners for THIS variant: unconditional includes always apply;
|
|
270
|
+
// guarded includes apply only when their condition names this target
|
|
271
|
+
// (variscite's jlink is inside if(CONFIG_SOC_…_M7) — the A53 target has
|
|
272
|
+
// no jlink at all, and offering it would flash the wrong core). Only
|
|
273
|
+
// ~24 files in the tree guard every include; a target matching none of
|
|
274
|
+
// a file's guards honestly gets no probe methods.
|
|
275
|
+
let includeNames = scan.includes.map((i) => i.name);
|
|
276
|
+
const anyGuarded = scan.includes.some((i) => i.conds.length > 0 && i.conds.some((c) => c !== ''));
|
|
277
|
+
if (anyGuarded) {
|
|
278
|
+
const active = new Set();
|
|
279
|
+
for (const inc of scan.includes) {
|
|
280
|
+
if (inc.conds.length === 0 || inc.conds.every((c) => c === ''))
|
|
281
|
+
active.add(inc.name);
|
|
282
|
+
else if (inc.conds.some((c) => conditionMatchesTarget(c, boardConfigs, soc, quals)))
|
|
283
|
+
active.add(inc.name);
|
|
284
|
+
}
|
|
285
|
+
includeNames = [...active];
|
|
286
|
+
}
|
|
287
|
+
// First include = west's default runner (preserve scan order).
|
|
288
|
+
const runners = [...new Set(includeNames)];
|
|
289
|
+
if (runners.length === 0)
|
|
290
|
+
return undefined;
|
|
291
|
+
let cfgLines = null;
|
|
292
|
+
const cfgPath = path.join(dir, 'support', 'openocd.cfg');
|
|
293
|
+
if (fs.existsSync(cfgPath)) {
|
|
294
|
+
cfgLines = fs.readFileSync(cfgPath, 'utf8').split('\n').map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith('#'));
|
|
295
|
+
}
|
|
296
|
+
const methods = [];
|
|
297
|
+
for (const runner of runners) {
|
|
298
|
+
// The vendor-flavored openocd includes get the same cfg treatment as
|
|
299
|
+
// plain openocd (stlink interface detection + debugCfg attach).
|
|
300
|
+
if (runner === 'openocd' || runner === 'openocd-stm32' || runner === 'openocd-nrf5') {
|
|
301
|
+
// An ST-Link interface cfg means the method is really 'stlink' — the
|
|
302
|
+
// curated-table id users know. debugCfg carries the whole cfg file,
|
|
303
|
+
// original line order (source/transport/events), comma-free join is
|
|
304
|
+
// not assumed by the consumer.
|
|
305
|
+
const isStlink = cfgLines?.some((l) => l.includes('interface/stlink')) ?? false;
|
|
306
|
+
methods.push({
|
|
307
|
+
id: isStlink ? 'stlink' : 'openocd',
|
|
308
|
+
description: isStlink
|
|
309
|
+
? 'ST-Link or any SWD probe openocd supports (no BOOT0 needed) — also debugs'
|
|
310
|
+
: 'Any SWD/JTAG probe openocd supports — also debugs',
|
|
311
|
+
runner,
|
|
312
|
+
debug: true,
|
|
313
|
+
...(cfgLines ? { debugCfg: cfgLines } : {}),
|
|
314
|
+
});
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
const meta = RUNNER_METHODS[runner];
|
|
318
|
+
if (!meta)
|
|
319
|
+
continue;
|
|
320
|
+
// Per-variant args: the occurrence whose CONFIG_BOARD / CONFIG_SOC
|
|
321
|
+
// guard names THIS variant wins (pickArgs).
|
|
322
|
+
const args = pickArgs(scan.runnerArgs.get(runner) ?? [], boardConfigs, soc, quals);
|
|
323
|
+
if (runner === 'jlink') {
|
|
324
|
+
const device = args.find((a) => a.startsWith('--device='));
|
|
325
|
+
methods.push({
|
|
326
|
+
id: 'jlink',
|
|
327
|
+
description: 'J-Link probe (SWD) — also debugs',
|
|
328
|
+
runner,
|
|
329
|
+
debug: true,
|
|
330
|
+
...(device ? { debugDevice: device.slice('--device='.length) } : {}),
|
|
331
|
+
});
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
methods.push({ ...meta, runner, ...(args.length > 0 ? { args: [...args] } : {}) });
|
|
335
|
+
}
|
|
336
|
+
// Dedupe by method id: vendor-flavored includes can coexist with the
|
|
337
|
+
// plain one (openocd + openocd-stm32) — one method per id, first wins.
|
|
338
|
+
const seen = new Set();
|
|
339
|
+
const unique = methods.filter((m) => (seen.has(m.id) ? false : (seen.add(m.id), true)));
|
|
340
|
+
return unique.length > 0 ? unique : undefined;
|
|
341
|
+
}
|
|
342
|
+
/** VERSION file → 'MAJOR.MINOR.PATCH' ('' when unreadable). */
|
|
343
|
+
export function zephyrVersionOf(zephyrBase) {
|
|
344
|
+
try {
|
|
345
|
+
const text = fs.readFileSync(path.join(zephyrBase, 'VERSION'), 'utf8');
|
|
346
|
+
const m = text.match(/VERSION_MAJOR\s*=\s*(\d+)[\s\S]*?VERSION_MINOR\s*=\s*(\d+)/);
|
|
347
|
+
if (!m)
|
|
348
|
+
return '';
|
|
349
|
+
const patch = text.match(/PATCHLEVEL\s*=\s*(\d+)/);
|
|
350
|
+
return patch ? `${m[1]}.${m[2]}.${patch[1]}` : `${m[1]}.${m[2]}`;
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
return '';
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Git HEAD of the tree, read straight off the filesystem (no git spawn —
|
|
358
|
+
* sync runs where git may be absent). Handles the plain clone (.git dir,
|
|
359
|
+
* HEAD → ref → loose ref or packed-refs) and the worktree/submodule form
|
|
360
|
+
* (.git file pointing at a gitdir). Best-effort: undefined when unreadable.
|
|
361
|
+
*/
|
|
362
|
+
export function gitHeadOf(zephyrBase) {
|
|
363
|
+
try {
|
|
364
|
+
const dotGit = path.join(zephyrBase, '.git');
|
|
365
|
+
let gitDir = dotGit;
|
|
366
|
+
const st = fs.statSync(dotGit);
|
|
367
|
+
if (st.isFile()) {
|
|
368
|
+
const text = fs.readFileSync(dotGit, 'utf8');
|
|
369
|
+
const m = text.match(/gitdir:\s*(.+)/);
|
|
370
|
+
if (!m)
|
|
371
|
+
return undefined;
|
|
372
|
+
const dir = m[1].trim();
|
|
373
|
+
gitDir = path.isAbsolute(dir) ? dir : path.resolve(zephyrBase, dir);
|
|
374
|
+
}
|
|
375
|
+
const head = fs.readFileSync(path.join(gitDir, 'HEAD'), 'utf8').trim();
|
|
376
|
+
if (!head.startsWith('ref: '))
|
|
377
|
+
return head || undefined;
|
|
378
|
+
const ref = head.slice(5).trim();
|
|
379
|
+
const refFile = path.join(gitDir, ref);
|
|
380
|
+
if (fs.existsSync(refFile))
|
|
381
|
+
return fs.readFileSync(refFile, 'utf8').trim() || undefined;
|
|
382
|
+
const packed = fs.readFileSync(path.join(gitDir, 'packed-refs'), 'utf8');
|
|
383
|
+
const m = packed.match(new RegExp(`^([0-9a-f]{40,}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'm'));
|
|
384
|
+
return m ? m[1] : undefined;
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
return undefined;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Parse board.yml into its declared build targets, both shapes:
|
|
392
|
+
* multi-board (`boards:` list) and legacy (`board:` block), with socs and
|
|
393
|
+
* (nested) variants. Variant chains accumulate through nesting levels
|
|
394
|
+
* (board/soc/v1/v2).
|
|
395
|
+
*/
|
|
396
|
+
export function boardYmlTargets(dir) {
|
|
397
|
+
const yml = path.join(dir, 'board.yml');
|
|
398
|
+
if (!fs.existsSync(yml))
|
|
399
|
+
return [];
|
|
400
|
+
const targets = [];
|
|
401
|
+
const boards = [];
|
|
402
|
+
let board;
|
|
403
|
+
let soc;
|
|
404
|
+
const keyStack = [];
|
|
405
|
+
const chainBases = [];
|
|
406
|
+
let chain = [];
|
|
407
|
+
const flushSoc = () => {
|
|
408
|
+
if (board && soc) {
|
|
409
|
+
board.socs.set(soc.name, soc.chains);
|
|
410
|
+
soc = undefined;
|
|
411
|
+
chain = [];
|
|
412
|
+
chainBases.length = 0;
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
const flushBoard = () => {
|
|
416
|
+
flushSoc();
|
|
417
|
+
board = undefined;
|
|
418
|
+
};
|
|
419
|
+
for (const rawLine of fs.readFileSync(yml, 'utf8').split('\n')) {
|
|
420
|
+
const line = rawLine.replace(/\r$/, '');
|
|
421
|
+
const trimmed = line.trim();
|
|
422
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
423
|
+
continue;
|
|
424
|
+
const indent = line.length - line.trimStart().length;
|
|
425
|
+
const entry = trimmed.match(/^- name:\s*(.+)$/);
|
|
426
|
+
const key = trimmed.match(/^([\w.-]+):(?:\s+(.*))?$/);
|
|
427
|
+
if (key && !trimmed.startsWith('-')) {
|
|
428
|
+
while (keyStack.length > 0 && keyStack[keyStack.length - 1].indent > indent)
|
|
429
|
+
keyStack.pop();
|
|
430
|
+
const k = key[1];
|
|
431
|
+
const v = (key[2] ?? '').trim();
|
|
432
|
+
if (indent === 0 && k === 'boards') {
|
|
433
|
+
flushBoard();
|
|
434
|
+
}
|
|
435
|
+
else if (indent === 0 && k === 'board' && v) {
|
|
436
|
+
// Legacy inline form: board: xiao_ble
|
|
437
|
+
flushBoard();
|
|
438
|
+
board = { name: stripYamlComment(v).trim(), socs: new Map() };
|
|
439
|
+
boards.push(board);
|
|
440
|
+
}
|
|
441
|
+
else if (k === 'variants') {
|
|
442
|
+
// Chains under this level accumulate from the enclosing chain.
|
|
443
|
+
chainBases.push([...chain]);
|
|
444
|
+
}
|
|
445
|
+
else if ((k === 'name' || k === 'full_name' || k === 'vendor') && v) {
|
|
446
|
+
const val = stripYamlComment(v).trim();
|
|
447
|
+
// A name: under a legacy board: block names the board.
|
|
448
|
+
if (k === 'name' && !board && keyStack.some((s) => s.key === 'board')) {
|
|
449
|
+
board = { name: val, socs: new Map() };
|
|
450
|
+
boards.push(board);
|
|
451
|
+
}
|
|
452
|
+
else if (k !== 'name' && board && !soc) {
|
|
453
|
+
if (k === 'full_name')
|
|
454
|
+
board.fullName = val;
|
|
455
|
+
if (k === 'vendor')
|
|
456
|
+
board.vendor = val;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
keyStack.push({ indent, key: k });
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
if (entry) {
|
|
463
|
+
const name = stripYamlComment(entry[1]).trim().replace(/^['"]|['"]$/g, '');
|
|
464
|
+
if (!name)
|
|
465
|
+
continue;
|
|
466
|
+
let owner;
|
|
467
|
+
for (let i = keyStack.length - 1; i >= 0; i--) {
|
|
468
|
+
if (keyStack[i].indent <= indent) {
|
|
469
|
+
owner = keyStack[i].key;
|
|
470
|
+
break;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (owner === 'boards') {
|
|
474
|
+
flushBoard();
|
|
475
|
+
board = { name, socs: new Map() };
|
|
476
|
+
boards.push(board);
|
|
477
|
+
}
|
|
478
|
+
else if (owner === 'socs') {
|
|
479
|
+
flushSoc();
|
|
480
|
+
soc = { name, chains: [] };
|
|
481
|
+
}
|
|
482
|
+
else if (owner === 'variants') {
|
|
483
|
+
const base = chainBases[chainBases.length - 1] ?? [];
|
|
484
|
+
chain = [...base, name];
|
|
485
|
+
soc?.chains.push([...chain]);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
flushBoard();
|
|
490
|
+
for (const b of boards) {
|
|
491
|
+
for (const [socName, chains] of b.socs) {
|
|
492
|
+
targets.push({ identifier: `${b.name}/${socName}`, boardName: b.name, soc: socName, chain: [], ...(b.fullName ? { fullName: b.fullName } : {}), ...(b.vendor ? { vendor: b.vendor } : {}) });
|
|
493
|
+
for (const c of chains) {
|
|
494
|
+
targets.push({ identifier: [b.name, socName, ...c].join('/'), boardName: b.name, soc: socName, chain: c, ...(b.fullName ? { fullName: b.fullName } : {}), ...(b.vendor ? { vendor: b.vendor } : {}) });
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return targets;
|
|
499
|
+
}
|
|
500
|
+
/** Compose the catalog record from one DTS's extracted facts. The canonical
|
|
501
|
+
* LED/button picks follow the DEVICETREE numbering, not child order: led0
|
|
502
|
+
* when aliased (mm_swiftio declares led0 = green_led as the SECOND child —
|
|
503
|
+
* picking the first aliased child made the manifest's LED0 contradict the
|
|
504
|
+
* devicetree's led0). Same for sw0. Extras then carry the remaining
|
|
505
|
+
* aliased nodes and the LED<N>/BUTTON<N> indices line up with dtSpec
|
|
506
|
+
* numbering. */
|
|
507
|
+
function composeRecord(base, facts) {
|
|
508
|
+
const led = facts.leds.find((l) => l.alias === 'led0') ?? facts.leds.find((l) => l.alias);
|
|
509
|
+
const button = facts.buttons.find((b) => b.alias === 'sw0') ?? facts.buttons.find((b) => b.alias);
|
|
510
|
+
const rec = {
|
|
511
|
+
...base,
|
|
512
|
+
console: facts.chosen['zephyr,console'],
|
|
513
|
+
// Addressable user LED (ws2812) only when no gpio-leds LED exists —
|
|
514
|
+
// a board with both keeps its devicetree-chosen led0.
|
|
515
|
+
...(facts.stripLed && !led ? { stripLed: { controller: facts.stripLed.controller, pin: facts.stripLed.pin } } : {}),
|
|
516
|
+
...(facts.usbDevice ? { usbDevice: facts.usbDevice } : {}),
|
|
517
|
+
...(facts.usbController ? { usbController: facts.usbController } : {}),
|
|
518
|
+
...(facts.aliases['watchdog0'] ? { wdtNodeLabel: facts.aliases['watchdog0'] } : {}),
|
|
519
|
+
...(facts.buses.i2c.length > 0 || facts.buses.spi.length > 0 || facts.buses.uart.length > 0
|
|
520
|
+
? { buses: facts.buses }
|
|
521
|
+
: {}),
|
|
522
|
+
...(facts.pwmLeds.length > 0
|
|
523
|
+
? { pwmLeds: facts.pwmLeds.map((p) => ({
|
|
524
|
+
...(p.alias ? { alias: p.alias } : {}),
|
|
525
|
+
controller: p.controller, channel: p.channel,
|
|
526
|
+
...(p.periodNs != null ? { periodNs: p.periodNs } : {}),
|
|
527
|
+
...(p.flags && p.flags.length > 0 ? { flags: [...p.flags] } : {}),
|
|
528
|
+
})) }
|
|
529
|
+
: {}),
|
|
530
|
+
...(led ? { led: { dtSpec: led.alias, controller: led.controller, pin: led.pin, flags: led.flags } } : {}),
|
|
531
|
+
...(button ? { button: { dtSpec: button.alias, controller: button.controller, pin: button.pin, flags: button.flags } } : {}),
|
|
532
|
+
...(facts.leds.length > 1 || facts.buttons.length > 1
|
|
533
|
+
? {
|
|
534
|
+
extraLeds: facts.leds.filter((l) => l !== led && l.alias).map((l) => ({ dtSpec: l.alias, controller: l.controller, pin: l.pin, flags: l.flags })),
|
|
535
|
+
extraButtons: facts.buttons.filter((b) => b !== button && b.alias).map((b) => ({ dtSpec: b.alias, controller: b.controller, pin: b.pin, flags: b.flags })),
|
|
536
|
+
}
|
|
537
|
+
: {}),
|
|
538
|
+
...(facts.connectors.filter((c) => Object.keys(c.pins).length > 0).length > 0
|
|
539
|
+
? { connectors: facts.connectors.filter((c) => Object.keys(c.pins).length > 0).map((c) => ({ nodelabel: c.nodelabel, compatible: c.compatible, pins: c.pins })) }
|
|
540
|
+
: {}),
|
|
541
|
+
};
|
|
542
|
+
// prune empty arrays
|
|
543
|
+
for (const k of ['extraLeds', 'extraButtons'])
|
|
544
|
+
if (rec[k]?.length === 0)
|
|
545
|
+
delete rec[k];
|
|
546
|
+
const hasFacts = Boolean(rec.console || rec.led || rec.button || rec.connectors);
|
|
547
|
+
return { record: rec, hasFacts };
|
|
548
|
+
}
|
|
549
|
+
/** Probe-method pin for one identifier (CONFIG_BOARD stems + soc quals). */
|
|
550
|
+
function variantPinFor(identifier, socIndex) {
|
|
551
|
+
const parts = identifier.split('/');
|
|
552
|
+
const underscored = identifier.replace(/\//g, '_');
|
|
553
|
+
const entry = socIndex.boardNames.find((n) => identifier === n || identifier.startsWith(n + '/'));
|
|
554
|
+
return {
|
|
555
|
+
boardConfigs: [...new Set([...(entry ? [entry] : []), underscored])]
|
|
556
|
+
.map((n) => n.replace(/[^A-Za-z0-9]/g, '_').toUpperCase()),
|
|
557
|
+
...(parts[1] ? { soc: parts[1] } : {}),
|
|
558
|
+
...(parts.length > 2 ? { quals: parts.slice(2) } : {}),
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Walk `<zephyrBase>/boards` and extract one record per board variant.
|
|
563
|
+
* Reader failures degrade to the base record (identifier/name/vendor/dts +
|
|
564
|
+
* probe methods) — a board whose DTS defeats the reader still resolves, it
|
|
565
|
+
* just carries no pin facts.
|
|
566
|
+
*/
|
|
567
|
+
export function walkBoardCatalog(zephyrBase) {
|
|
568
|
+
const boardsRoot = path.join(zephyrBase, 'boards');
|
|
569
|
+
if (!fs.existsSync(boardsRoot)) {
|
|
570
|
+
throw new Error(`no boards/ directory at ${boardsRoot} — not a Zephyr tree?`);
|
|
571
|
+
}
|
|
572
|
+
const records = [];
|
|
573
|
+
let variants = 0, withFacts = 0, failures = 0;
|
|
574
|
+
const vendors = fs.readdirSync(boardsRoot, { withFileTypes: true })
|
|
575
|
+
.filter((d) => d.isDirectory());
|
|
576
|
+
for (const vendorDir of vendors) {
|
|
577
|
+
const boardDirs = fs.readdirSync(path.join(boardsRoot, vendorDir.name), { withFileTypes: true })
|
|
578
|
+
.filter((d) => d.isDirectory());
|
|
579
|
+
for (const boardDir of boardDirs) {
|
|
580
|
+
const dir = path.join(boardsRoot, vendorDir.name, boardDir.name);
|
|
581
|
+
const files = fs.readdirSync(dir);
|
|
582
|
+
const socIndex = boardYmlSocIndex(dir);
|
|
583
|
+
const seenIds = new Set();
|
|
584
|
+
for (const f of files) {
|
|
585
|
+
if (!f.endsWith('.yaml') || f === 'board.yml')
|
|
586
|
+
continue;
|
|
587
|
+
const raw = yamlField(path.join(dir, f), 'identifier');
|
|
588
|
+
if (!raw)
|
|
589
|
+
continue;
|
|
590
|
+
// Qualify: bare identifiers take the soc segment from board.yml —
|
|
591
|
+
// their own entry's soc in multi-board dirs, the sole soc in
|
|
592
|
+
// single-board dirs.
|
|
593
|
+
const identifier = raw.includes('/')
|
|
594
|
+
? raw
|
|
595
|
+
: (() => {
|
|
596
|
+
const soc = socIndex.byName.get(raw)
|
|
597
|
+
?? (socIndex.uniqueSocs.length === 1 ? socIndex.uniqueSocs[0] : null);
|
|
598
|
+
return soc ? `${raw}/${soc}` : null;
|
|
599
|
+
})();
|
|
600
|
+
if (!identifier || seenIds.has(identifier))
|
|
601
|
+
continue;
|
|
602
|
+
variants++;
|
|
603
|
+
seenIds.add(identifier);
|
|
604
|
+
const dtsBase = f.replace(/\.yaml$/, '');
|
|
605
|
+
const dtsFile = path.join(dir, dtsBase + '.dts');
|
|
606
|
+
if (!fs.existsSync(dtsFile))
|
|
607
|
+
continue;
|
|
608
|
+
// Per-variant: runner args are selected by the variant's own
|
|
609
|
+
// CONFIG_BOARD guard, so this must run per yaml, not per dir.
|
|
610
|
+
const probeMethods = boardProbeMethods(dir, variantPinFor(identifier, socIndex));
|
|
611
|
+
const base = {
|
|
612
|
+
identifier,
|
|
613
|
+
name: yamlField(path.join(dir, f), 'name') ?? identifier,
|
|
614
|
+
vendor: vendorDir.name,
|
|
615
|
+
dts: dtsBase + '.dts',
|
|
616
|
+
...(probeMethods ? { probeMethods } : {}),
|
|
617
|
+
};
|
|
618
|
+
try {
|
|
619
|
+
const facts = readBoardDts(dtsFile, { zephyrBoardsRoot: boardsRoot });
|
|
620
|
+
const { record, hasFacts } = composeRecord(base, facts);
|
|
621
|
+
if (hasFacts)
|
|
622
|
+
withFacts++;
|
|
623
|
+
records.push(record);
|
|
624
|
+
}
|
|
625
|
+
catch (err) {
|
|
626
|
+
failures++;
|
|
627
|
+
if (failures <= 5)
|
|
628
|
+
console.error(`reader failed on ${identifier}: ${err.message}`);
|
|
629
|
+
records.push(base);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
// Synthesis pass: board.yml-driven dirs ship NO per-variant yamls —
|
|
633
|
+
// nucleo_n657x0_q's `sb` target exists only as a board.yml entry.
|
|
634
|
+
// Emit every declared target the yaml flow did not already cover.
|
|
635
|
+
for (const t of boardYmlTargets(dir)) {
|
|
636
|
+
if (seenIds.has(t.identifier))
|
|
637
|
+
continue;
|
|
638
|
+
seenIds.add(t.identifier);
|
|
639
|
+
const dtsFile = [
|
|
640
|
+
path.join(dir, t.identifier.replace(/\//g, '_') + '.dts'),
|
|
641
|
+
path.join(dir, t.boardName + '.dts'),
|
|
642
|
+
].find((p) => fs.existsSync(p));
|
|
643
|
+
if (!dtsFile)
|
|
644
|
+
continue;
|
|
645
|
+
variants++;
|
|
646
|
+
const probeMethods = boardProbeMethods(dir, variantPinFor(t.identifier, socIndex));
|
|
647
|
+
const base = {
|
|
648
|
+
identifier: t.identifier,
|
|
649
|
+
name: t.fullName ?? t.identifier,
|
|
650
|
+
vendor: t.vendor ?? vendorDir.name,
|
|
651
|
+
dts: path.basename(dtsFile),
|
|
652
|
+
...(probeMethods ? { probeMethods } : {}),
|
|
653
|
+
};
|
|
654
|
+
try {
|
|
655
|
+
const facts = readBoardDts(dtsFile, { zephyrBoardsRoot: boardsRoot });
|
|
656
|
+
const { record, hasFacts } = composeRecord(base, facts);
|
|
657
|
+
if (hasFacts)
|
|
658
|
+
withFacts++;
|
|
659
|
+
records.push(record);
|
|
660
|
+
}
|
|
661
|
+
catch (err) {
|
|
662
|
+
failures++;
|
|
663
|
+
if (failures <= 5)
|
|
664
|
+
console.error(`reader failed on ${t.identifier}: ${err.message}`);
|
|
665
|
+
records.push(base);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return {
|
|
671
|
+
boards: Object.fromEntries(records.map((r) => [r.identifier, r])),
|
|
672
|
+
provenance: {
|
|
673
|
+
zephyrBase: path.resolve(zephyrBase),
|
|
674
|
+
version: zephyrVersionOf(zephyrBase),
|
|
675
|
+
...(gitHeadOf(zephyrBase) ? { gitHead: gitHeadOf(zephyrBase) } : {}),
|
|
676
|
+
boardsMtimeMs: fs.statSync(boardsRoot).mtimeMs,
|
|
677
|
+
generatedAt: new Date().toISOString(),
|
|
678
|
+
variants: records.length,
|
|
679
|
+
},
|
|
680
|
+
stats: { variants, withFacts, failures },
|
|
681
|
+
};
|
|
682
|
+
}
|