@typecad/framework-zephyr 1.0.0-alpha.10 → 1.0.0-alpha.11
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/dist/chips/esp32.js +12 -0
- package/dist/chips/types.d.ts +30 -0
- package/dist/chips/xiao-ble.js +6 -0
- package/dist/display/gfx.d.ts +12 -3
- package/dist/display/gfx.js +130 -17
- package/dist/display/profiles.js +13 -0
- package/dist/display/ui-adapter.js +6 -0
- package/dist/doctor.d.ts +3 -3
- package/dist/doctor.js +56 -29
- package/dist/dt-config/kconfig.d.ts +3 -0
- package/dist/dt-config/kconfig.js +18 -0
- package/dist/dt-config/overlay.js +5 -0
- package/dist/framework.manifest.js +37 -14
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/licenses.d.ts +59 -0
- package/dist/licenses.js +347 -0
- package/dist/lowering/dac.d.ts +15 -0
- package/dist/lowering/dac.js +69 -0
- package/dist/lowering/fs.d.ts +16 -0
- package/dist/lowering/fs.js +121 -0
- package/dist/lowering/hwtimer.d.ts +15 -0
- package/dist/lowering/hwtimer.js +84 -0
- package/dist/lowering/index.d.ts +4 -1
- package/dist/lowering/index.js +12 -3
- package/dist/strategy.js +186 -14
- package/dist/toolchain/compat.js +10 -1
- package/dist/toolchain/env-check.d.ts +93 -0
- package/dist/toolchain/env-check.js +190 -0
- package/dist/toolchain/scaffold.js +3 -0
- package/dist/toolchain/west-discover.d.ts +11 -3
- package/dist/toolchain/west-discover.js +80 -6
- package/dist/toolchain/west-spawn.js +15 -0
- package/package.json +4 -4
- package/src/chips/esp32.ts +12 -0
- package/src/chips/types.ts +29 -0
- package/src/chips/xiao-ble.ts +6 -0
- package/src/display/gfx.ts +135 -19
- package/src/display/profiles.ts +13 -0
- package/src/display/ui-adapter.ts +5 -0
- package/src/doctor.ts +77 -56
- package/src/dt-config/kconfig.ts +19 -0
- package/src/dt-config/overlay.ts +5 -0
- package/src/framework.manifest.ts +38 -14
- package/src/index.ts +6 -0
- package/src/licenses.ts +425 -0
- package/src/lowering/dac.ts +82 -0
- package/src/lowering/fs.ts +127 -0
- package/src/lowering/hwtimer.ts +101 -0
- package/src/lowering/index.ts +9 -2
- package/src/strategy.ts +180 -14
- package/src/toolchain/compat.ts +154 -145
- package/src/toolchain/env-check.ts +285 -0
- package/src/toolchain/scaffold.ts +3 -0
- package/src/toolchain/west-discover.ts +88 -8
- package/src/toolchain/west-spawn.ts +15 -0
package/src/toolchain/compat.ts
CHANGED
|
@@ -1,145 +1,154 @@
|
|
|
1
|
-
// Zephyr version compatibility + board-target normalization.
|
|
2
|
-
//
|
|
3
|
-
// Two mechanisms absorb Zephyr version churn:
|
|
4
|
-
// 1. checkZephyrCompat() — compare the installed Zephyr version against the
|
|
5
|
-
// declared range (manifest.compat.zephyr) so an incompatible Zephyr fails
|
|
6
|
-
// fast with a clear message instead of a cryptic west/CMake board error.
|
|
7
|
-
// 2. resolveBoardTarget() — normalize the board target for the installed
|
|
8
|
-
// Zephyr version. Zephyr 4.3+ rejects bare multi-core board names
|
|
9
|
-
// (esp32s3_devkitc) and requires a qualified target
|
|
10
|
-
// (esp32s3_devkitc/esp32s3/procpu). This rewrites stale configs at build
|
|
11
|
-
// time so users don't have to regenerate them after a Zephyr upgrade.
|
|
12
|
-
//
|
|
13
|
-
// This module is intentionally pure (no chalk/ui/console) so it unit-tests
|
|
14
|
-
// cleanly; the doctor and the toolchain decide how to present results.
|
|
15
|
-
|
|
16
|
-
import { readFileSync } from 'node:fs';
|
|
17
|
-
import { join } from 'node:path';
|
|
18
|
-
import manifest from '../framework.manifest.js';
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (pa[i]!
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
.
|
|
51
|
-
.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
case '
|
|
58
|
-
case '
|
|
59
|
-
case '
|
|
60
|
-
case '
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
*/
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
1
|
+
// Zephyr version compatibility + board-target normalization.
|
|
2
|
+
//
|
|
3
|
+
// Two mechanisms absorb Zephyr version churn:
|
|
4
|
+
// 1. checkZephyrCompat() — compare the installed Zephyr version against the
|
|
5
|
+
// declared range (manifest.compat.zephyr) so an incompatible Zephyr fails
|
|
6
|
+
// fast with a clear message instead of a cryptic west/CMake board error.
|
|
7
|
+
// 2. resolveBoardTarget() — normalize the board target for the installed
|
|
8
|
+
// Zephyr version. Zephyr 4.3+ rejects bare multi-core board names
|
|
9
|
+
// (esp32s3_devkitc) and requires a qualified target
|
|
10
|
+
// (esp32s3_devkitc/esp32s3/procpu). This rewrites stale configs at build
|
|
11
|
+
// time so users don't have to regenerate them after a Zephyr upgrade.
|
|
12
|
+
//
|
|
13
|
+
// This module is intentionally pure (no chalk/ui/console) so it unit-tests
|
|
14
|
+
// cleanly; the doctor and the toolchain decide how to present results.
|
|
15
|
+
|
|
16
|
+
import { readFileSync } from 'node:fs';
|
|
17
|
+
import { join } from 'node:path';
|
|
18
|
+
import manifest from '../framework.manifest.js';
|
|
19
|
+
import { discoverWest } from './west-discover.js';
|
|
20
|
+
|
|
21
|
+
// ── minimal semver ──────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
/** Parse "4.3.99" / "v4.3" / "4.3.99-rc1" → [4, 3, 99]. Undefined if unparseable. */
|
|
24
|
+
export function parseVersion(v: string | undefined): [number, number, number] | undefined {
|
|
25
|
+
if (!v) return undefined;
|
|
26
|
+
const m = v.replace(/^v/i, '').match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
|
|
27
|
+
if (!m) return undefined;
|
|
28
|
+
return [Number(m[1]), Number(m[2] ?? 0), Number(m[3] ?? 0)];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Compare two version strings: -1 if a<b, 0 if equal, 1 if a>b. */
|
|
32
|
+
export function compareVersion(a: string, b: string): number {
|
|
33
|
+
const pa = parseVersion(a) ?? [0, 0, 0];
|
|
34
|
+
const pb = parseVersion(b) ?? [0, 0, 0];
|
|
35
|
+
for (let i = 0; i < 3; i++) {
|
|
36
|
+
if (pa[i]! < pb[i]!) return -1;
|
|
37
|
+
if (pa[i]! > pb[i]!) return 1;
|
|
38
|
+
}
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Evaluate a simple range of space-separated comparators against a version.
|
|
44
|
+
* Supported operators: >= <= > < =. Each comparator glues the operator to its
|
|
45
|
+
* version (">=4.3", "<5.0"); spaces between comparators are AND.
|
|
46
|
+
* satisfiesRange("4.3.99", ">=4.3 <5.0") === true
|
|
47
|
+
*/
|
|
48
|
+
export function satisfiesRange(version: string, range: string): boolean {
|
|
49
|
+
return range
|
|
50
|
+
.trim()
|
|
51
|
+
.split(/\s+/)
|
|
52
|
+
.every((clause) => {
|
|
53
|
+
const m = clause.replace(/\s+/g, '').match(/^(>=|<=|>|<|=)(.+)$/);
|
|
54
|
+
if (!m) return true; // ignore anything that isn't a comparator
|
|
55
|
+
const cmp = compareVersion(version, m[2]!);
|
|
56
|
+
switch (m[1]) {
|
|
57
|
+
case '>=': return cmp >= 0;
|
|
58
|
+
case '<=': return cmp <= 0;
|
|
59
|
+
case '>': return cmp > 0;
|
|
60
|
+
case '<': return cmp < 0;
|
|
61
|
+
case '=': return cmp === 0;
|
|
62
|
+
default: return true;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── Zephyr version detection ────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Detect the installed Zephyr version from ZEPHYR_BASE/VERSION. Returns
|
|
71
|
+
* undefined when ZEPHYR_BASE is unset or VERSION can't be read/parsed. Handles
|
|
72
|
+
* the CMake-style file (VERSION_MAJOR = 4 / VERSION_MINOR = 3 / PATCHLEVEL = 99)
|
|
73
|
+
* and a bare "4.3.99".
|
|
74
|
+
*/
|
|
75
|
+
export function detectZephyrVersion(): string | undefined {
|
|
76
|
+
let base = process.env.ZEPHYR_BASE;
|
|
77
|
+
if (!base) {
|
|
78
|
+
// Fall back to the discovered west install's zephyrBase — covers the
|
|
79
|
+
// micromamba env from @typecad/zephyr-installer WITHOUT activation.
|
|
80
|
+
// (micromamba run sets ZEPHYR_BASE only inside the west subprocess; this
|
|
81
|
+
// makes the compat check work in the parent cuttlefish process too.)
|
|
82
|
+
const install = discoverWest();
|
|
83
|
+
base = install?.zephyrBase;
|
|
84
|
+
}
|
|
85
|
+
if (!base) return undefined;
|
|
86
|
+
let content: string;
|
|
87
|
+
try {
|
|
88
|
+
content = readFileSync(join(base, 'VERSION'), 'utf-8');
|
|
89
|
+
} catch {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
const field = (key: string): string | undefined => {
|
|
93
|
+
const m = content.match(new RegExp(`^\\s*${key}\\s*=\\s*(\\d+)`, 'm'));
|
|
94
|
+
return m ? m[1] : undefined;
|
|
95
|
+
};
|
|
96
|
+
const major = field('VERSION_MAJOR');
|
|
97
|
+
if (major !== undefined) {
|
|
98
|
+
return `${major}.${field('VERSION_MINOR') ?? 0}.${field('PATCHLEVEL') ?? 0}`;
|
|
99
|
+
}
|
|
100
|
+
const m = content.match(/(\d+\.\d+(?:\.\d+)?)/);
|
|
101
|
+
return m ? m[1] : undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── compat check ────────────────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
export type CompatStatus = 'ok' | 'undetectable' | 'out-of-range';
|
|
107
|
+
|
|
108
|
+
export interface CompatResult {
|
|
109
|
+
version: string | undefined;
|
|
110
|
+
/** Declared supported range (manifest.compat.zephyr), if any. */
|
|
111
|
+
range: string | undefined;
|
|
112
|
+
status: CompatStatus;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Compare the detected Zephyr version against the declared compat range. Pure:
|
|
117
|
+
* returns a status; the caller decides whether to throw/warn. 'ok' covers both
|
|
118
|
+
* "in range" and "no range declared".
|
|
119
|
+
*/
|
|
120
|
+
export function checkZephyrCompat(version: string | undefined): CompatResult {
|
|
121
|
+
const range = manifest.compat?.zephyr;
|
|
122
|
+
if (!range) return { version, range, status: 'ok' };
|
|
123
|
+
if (version === undefined) return { version, range, status: 'undetectable' };
|
|
124
|
+
return { version, range, status: satisfiesRange(version, range) ? 'ok' : 'out-of-range' };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ── board-target normalization ──────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* For Zephyr >=4.3, multi-core ESP32 boards require a qualified board target
|
|
131
|
+
* (board/<soc>/<core>) — the bare id is rejected with "Board qualifiers … not
|
|
132
|
+
* found". Map each known multi-core board id to its procpu (main app core)
|
|
133
|
+
* qualified form. procpu is the core that runs application firmware; appcpu is
|
|
134
|
+
* the secondary core (selected explicitly only when offloading to it).
|
|
135
|
+
*/
|
|
136
|
+
const QUALIFIED_TARGETS_GE_4_3: Record<string, string> = {
|
|
137
|
+
esp32_devkitc: 'esp32_devkitc/esp32/procpu',
|
|
138
|
+
esp32s3_devkitc: 'esp32s3_devkitc/esp32s3/procpu',
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Normalize a board target for the installed Zephyr version. Rewrites a stale
|
|
143
|
+
* bare id (esp32s3_devkitc) to the qualified form on Zephyr >=4.3; idempotent
|
|
144
|
+
* if the target is already qualified. Older Zephyr, single-core boards
|
|
145
|
+
* (xiao_ble), and unknown boards pass through unchanged.
|
|
146
|
+
*/
|
|
147
|
+
export function resolveBoardTarget(boardTarget: string, version: string | undefined): string {
|
|
148
|
+
const boardId = boardTarget.split('/')[0]!;
|
|
149
|
+
if (version !== undefined && satisfiesRange(version, '>=4.3')) {
|
|
150
|
+
const qualified = QUALIFIED_TARGETS_GE_4_3[boardId];
|
|
151
|
+
if (qualified) return qualified;
|
|
152
|
+
}
|
|
153
|
+
return boardTarget;
|
|
154
|
+
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Zephyr environment check — the shared detection behind `cuttlefish doctor`.
|
|
3
|
+
//
|
|
4
|
+
// Mirrors @typecad/arduino-cli's checkArduinoEnv(): gather the impure
|
|
5
|
+
// environment facts once (west presence + version, Zephyr version, board
|
|
6
|
+
// existence), then reduce them to a structured result the doctor (and, later,
|
|
7
|
+
// the build/test gates) can present uniformly. The check is side-effect-free
|
|
8
|
+
// and never throws — it never installs or mutates anything.
|
|
9
|
+
//
|
|
10
|
+
// Two parity checks vs. framework-arduino's doctor:
|
|
11
|
+
// 1. west (the Zephyr build tool) is discoverable + responsive — the direct
|
|
12
|
+
// analog of "arduino-cli is installed". discoverWest() already confirms
|
|
13
|
+
// responsiveness via `west --version`; we additionally capture the version
|
|
14
|
+
// string to report it.
|
|
15
|
+
// 2. the configured board target exists in the Zephyr checkout
|
|
16
|
+
// ($ZEPHYR_BASE/boards/) — the analog of "the required core is installed".
|
|
17
|
+
//
|
|
18
|
+
// The existing compat-range check (compat.ts) is folded in as a third check so
|
|
19
|
+
// the doctor reports everything through one entry point.
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
import { spawnSync } from 'node:child_process';
|
|
23
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
24
|
+
import { join } from 'node:path';
|
|
25
|
+
|
|
26
|
+
import { type WestInstall, discoverWest, resetWestDiscoveryCache } from './west-discover.js';
|
|
27
|
+
import { westSpawn } from './west-spawn.js';
|
|
28
|
+
import {
|
|
29
|
+
detectZephyrVersion,
|
|
30
|
+
checkZephyrCompat,
|
|
31
|
+
resolveBoardTarget,
|
|
32
|
+
type CompatStatus,
|
|
33
|
+
} from './compat.js';
|
|
34
|
+
|
|
35
|
+
// ---- probe data (impure facts, injectable for tests) -----------------------
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Raw west facts gathered from discovery + a `west --version` probe. Mirrors
|
|
39
|
+
* ArduinoCliProbeData: `westFound` is true when a usable west install was
|
|
40
|
+
* discovered (discovery itself probes responsiveness).
|
|
41
|
+
*/
|
|
42
|
+
export interface WestProbeData {
|
|
43
|
+
/** A usable west install was discovered. */
|
|
44
|
+
westFound: boolean;
|
|
45
|
+
/** west version string if the `--version` probe parsed one, e.g. "1.3.0". */
|
|
46
|
+
westVersion: string | undefined;
|
|
47
|
+
/** Which discovery strategy found west, for surfacing to the user. */
|
|
48
|
+
source: WestInstall['source'] | undefined;
|
|
49
|
+
/** Effective ZEPHYR_BASE (env var, else a base discovery surfaced). */
|
|
50
|
+
zephyrBase: string | undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Test-injection seam for checkZephyrEnv. Mirrors Arduino's
|
|
55
|
+
* CheckArduinoEnvOptions.fakeProbe so tests never spawn a real west/python.
|
|
56
|
+
*/
|
|
57
|
+
export interface CheckZephyrEnvOptions {
|
|
58
|
+
/** FOR TESTS ONLY: skip the real probe and use this data directly. */
|
|
59
|
+
fakeWestProbe?: WestProbeData;
|
|
60
|
+
/** FOR TESTS ONLY: override the board-existence lookup. */
|
|
61
|
+
fakeBoardExists?: (boardId: string, zephyrBase: string | undefined) => boolean | undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ---- result types (mirror ArduinoEnvResult's shape) ------------------------
|
|
65
|
+
|
|
66
|
+
export interface ZephyrEnvCheck {
|
|
67
|
+
/** west (the Zephyr build tool) was discovered and responsive. */
|
|
68
|
+
westFound: boolean;
|
|
69
|
+
/** west version string if known, e.g. "1.3.0". */
|
|
70
|
+
westVersion: string | undefined;
|
|
71
|
+
/** Discovery strategy that found west, for display. */
|
|
72
|
+
westSource: WestInstall['source'] | undefined;
|
|
73
|
+
/** Effective ZEPHYR_BASE (env var, else a discovered base). */
|
|
74
|
+
zephyrBase: string | undefined;
|
|
75
|
+
/** Detected Zephyr RTOS version from $ZEPHYR_BASE/VERSION, if readable. */
|
|
76
|
+
zephyrVersion: string | undefined;
|
|
77
|
+
/** Declared supported range (manifest.compat.zephyr), if any. */
|
|
78
|
+
compatRange: string | undefined;
|
|
79
|
+
/** Result of the compat-range check against the detected version. */
|
|
80
|
+
compatStatus: CompatStatus;
|
|
81
|
+
/** Raw board target from cuttlefish.config.ts, if configured. */
|
|
82
|
+
buildTarget: string | undefined;
|
|
83
|
+
/** buildTarget normalized for the installed Zephyr version (may equal it). */
|
|
84
|
+
resolvedBoardTarget: string | undefined;
|
|
85
|
+
/** Does the resolved board exist in the checkout? undefined = undetermined. */
|
|
86
|
+
boardTargetSupported: boolean | undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export type ZephyrEnvOk = { ok: true; check: ZephyrEnvCheck };
|
|
90
|
+
|
|
91
|
+
export type ZephyrEnvFailure = {
|
|
92
|
+
ok: false;
|
|
93
|
+
reason: 'west-not-found' | 'zephyr-out-of-range' | 'board-not-supported';
|
|
94
|
+
check: ZephyrEnvCheck;
|
|
95
|
+
/** Human-readable lines ready to print. */
|
|
96
|
+
messages: string[];
|
|
97
|
+
/** Exact remediation hint, when applicable. */
|
|
98
|
+
fixCommand: string | undefined;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export type ZephyrEnvResult = ZephyrEnvOk | ZephyrEnvFailure;
|
|
102
|
+
|
|
103
|
+
// ---- west probe (impure; isolated + cached + overridable) ------------------
|
|
104
|
+
|
|
105
|
+
let cachedProbe: WestProbeData | undefined;
|
|
106
|
+
|
|
107
|
+
/** Clear the west-probe cache (for tests). Also resets discovery cache. */
|
|
108
|
+
export function resetWestProbeCacheForTest(): void {
|
|
109
|
+
cachedProbe = undefined;
|
|
110
|
+
resetWestDiscoveryCache();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Gather west facts: discover a usable install, then run `west --version`
|
|
115
|
+
* through it to capture the version. Memoized for the process lifetime (west
|
|
116
|
+
* installs don't move). Never throws — returns westFound:false on any failure.
|
|
117
|
+
*/
|
|
118
|
+
export function probeWestEnv(): WestProbeData {
|
|
119
|
+
if (cachedProbe) return cachedProbe;
|
|
120
|
+
|
|
121
|
+
const install = discoverWest();
|
|
122
|
+
const envBase = process.env.ZEPHYR_BASE || undefined;
|
|
123
|
+
if (!install) {
|
|
124
|
+
const data: WestProbeData = {
|
|
125
|
+
westFound: false,
|
|
126
|
+
westVersion: undefined,
|
|
127
|
+
source: undefined,
|
|
128
|
+
zephyrBase: envBase,
|
|
129
|
+
};
|
|
130
|
+
cachedProbe = data;
|
|
131
|
+
return data;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Run `west --version` through the discovered install to capture the version.
|
|
135
|
+
// discoverWest() already confirmed responsiveness, so a parse failure here is
|
|
136
|
+
// not "unresponsive" — it just means we couldn't read a version token.
|
|
137
|
+
let westVersion: string | undefined;
|
|
138
|
+
try {
|
|
139
|
+
const inv = westSpawn(['--version'], {
|
|
140
|
+
encoding: 'utf8',
|
|
141
|
+
timeout: 15_000,
|
|
142
|
+
windowsHide: true,
|
|
143
|
+
});
|
|
144
|
+
const r = spawnSync(inv.command, inv.args, inv.options);
|
|
145
|
+
if (r.status === 0) {
|
|
146
|
+
// inv.options is a generic SpawnSyncOptions (no encoding literal), so
|
|
147
|
+
// coerce stdout to a string before matching.
|
|
148
|
+
const out = typeof r.stdout === 'string' ? r.stdout : '';
|
|
149
|
+
const m = out.match(/v?(\d+\.\d+\.\d+)/);
|
|
150
|
+
westVersion = m ? m[1] : undefined;
|
|
151
|
+
}
|
|
152
|
+
} catch {
|
|
153
|
+
// westSpawn throws only when discovery fails — but discovery already
|
|
154
|
+
// succeeded (install is non-null). Defensive: treat as no version read.
|
|
155
|
+
westVersion = undefined;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const data: WestProbeData = {
|
|
159
|
+
westFound: true,
|
|
160
|
+
westVersion,
|
|
161
|
+
source: install.source,
|
|
162
|
+
zephyrBase: envBase ?? install.zephyrBase,
|
|
163
|
+
};
|
|
164
|
+
cachedProbe = data;
|
|
165
|
+
return data;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ---- board existence (pure-ish fs probe) -----------------------------------
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Does `boardId` exist as a board directory in the Zephyr checkout? Checks the
|
|
172
|
+
* HWMv2 vendor layout used by Zephyr 4.x: $ZEPHYR_BASE/boards/<vendor>/<boardId>.
|
|
173
|
+
* Returns true/false when determinable; undefined when the base is unknown or
|
|
174
|
+
* the boards/ tree can't be read (so callers never fail on an inconclusive
|
|
175
|
+
* lookup — they just skip the board check).
|
|
176
|
+
*/
|
|
177
|
+
export function boardExistsInCheckout(
|
|
178
|
+
boardId: string,
|
|
179
|
+
zephyrBase: string | undefined,
|
|
180
|
+
): boolean | undefined {
|
|
181
|
+
if (!zephyrBase) return undefined;
|
|
182
|
+
const boards = join(zephyrBase, 'boards');
|
|
183
|
+
try {
|
|
184
|
+
const entries = readdirSync(boards, { withFileTypes: true });
|
|
185
|
+
for (const entry of entries) {
|
|
186
|
+
if (entry.isDirectory() && existsSync(join(boards, entry.name, boardId))) {
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return false;
|
|
191
|
+
} catch {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ---- main entry point -------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Verify the environment can build for `buildTarget`. Cheap and
|
|
200
|
+
* side-effect-free: discovers west, reads the Zephyr version, checks the compat
|
|
201
|
+
* range, and — when a target is configured — verifies the board exists in the
|
|
202
|
+
* checkout. Reports what (if anything) is wrong.
|
|
203
|
+
*
|
|
204
|
+
* - If `buildTarget` is undefined/empty, the board check is skipped (not a
|
|
205
|
+
* failure), mirroring Arduino's no-FQBN path.
|
|
206
|
+
* - Never installs anything. Never mutates the user environment.
|
|
207
|
+
* - Never throws — always returns a result. Callers decide how to react.
|
|
208
|
+
*
|
|
209
|
+
* `options` is for-test only (injects fake probe data / board lookup).
|
|
210
|
+
*/
|
|
211
|
+
export function checkZephyrEnv(
|
|
212
|
+
buildTarget?: string,
|
|
213
|
+
options?: CheckZephyrEnvOptions,
|
|
214
|
+
): ZephyrEnvResult {
|
|
215
|
+
const probe = options?.fakeWestProbe ?? probeWestEnv();
|
|
216
|
+
const boardLookup = options?.fakeBoardExists ?? boardExistsInCheckout;
|
|
217
|
+
|
|
218
|
+
const zephyrVersion = detectZephyrVersion();
|
|
219
|
+
const compat = checkZephyrCompat(zephyrVersion);
|
|
220
|
+
const resolvedBoardTarget = buildTarget ? resolveBoardTarget(buildTarget, zephyrVersion) : undefined;
|
|
221
|
+
const boardId = resolvedBoardTarget ? resolvedBoardTarget.split('/')[0]! : undefined;
|
|
222
|
+
const boardTargetSupported =
|
|
223
|
+
boardId !== undefined ? boardLookup(boardId, probe.zephyrBase) : undefined;
|
|
224
|
+
|
|
225
|
+
const check: ZephyrEnvCheck = {
|
|
226
|
+
westFound: probe.westFound,
|
|
227
|
+
westVersion: probe.westVersion,
|
|
228
|
+
westSource: probe.source,
|
|
229
|
+
zephyrBase: probe.zephyrBase,
|
|
230
|
+
zephyrVersion,
|
|
231
|
+
compatRange: compat.range,
|
|
232
|
+
compatStatus: compat.status,
|
|
233
|
+
buildTarget,
|
|
234
|
+
resolvedBoardTarget,
|
|
235
|
+
boardTargetSupported,
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// 1. west (the build tool) missing entirely — nothing else can run.
|
|
239
|
+
if (!probe.westFound) {
|
|
240
|
+
return {
|
|
241
|
+
ok: false,
|
|
242
|
+
reason: 'west-not-found',
|
|
243
|
+
check,
|
|
244
|
+
messages: [
|
|
245
|
+
"west (the Zephyr build tool) was not found.",
|
|
246
|
+
" Run the typeCAD Zephyr installer, activate an existing Zephyr venv,",
|
|
247
|
+
" set ZEPHYR_BASE to a Zephyr SDK root, or `pip install west`.",
|
|
248
|
+
],
|
|
249
|
+
fixCommand: undefined,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// 2. west healthy but the Zephyr RTOS is outside the supported range.
|
|
254
|
+
if (compat.status === 'out-of-range') {
|
|
255
|
+
return {
|
|
256
|
+
ok: false,
|
|
257
|
+
reason: 'zephyr-out-of-range',
|
|
258
|
+
check,
|
|
259
|
+
messages: [
|
|
260
|
+
`Zephyr ${zephyrVersion} is outside the supported range (${compat.range}) for @typecad/framework-zephyr.`,
|
|
261
|
+
" Set ZEPHYR_BASE to a compatible Zephyr checkout, or install one via '@typecad/zephyr-installer'.",
|
|
262
|
+
],
|
|
263
|
+
fixCommand: undefined,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// 3. west + version OK — only check the board when a target is configured and
|
|
268
|
+
// the lookup was able to answer. A missing/absent target is not a board
|
|
269
|
+
// problem; an inconclusive lookup (no base) is reported as a skip, not a fail.
|
|
270
|
+
if (buildTarget && boardTargetSupported === false) {
|
|
271
|
+
return {
|
|
272
|
+
ok: false,
|
|
273
|
+
reason: 'board-not-supported',
|
|
274
|
+
check,
|
|
275
|
+
messages: [
|
|
276
|
+
`Board target '${resolvedBoardTarget}' was not found in this Zephyr checkout` +
|
|
277
|
+
(probe.zephyrBase ? ` (${join(probe.zephyrBase, 'boards')}).` : '.'),
|
|
278
|
+
" Check the board id, or run `west boards` to list boards in this checkout.",
|
|
279
|
+
],
|
|
280
|
+
fixCommand: 'west boards',
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return { ok: true, check };
|
|
285
|
+
}
|
|
@@ -85,6 +85,9 @@ export function scaffoldZephyrProject(projectRoot: string, debug = false, userKc
|
|
|
85
85
|
const usage: KconfigUsage = {
|
|
86
86
|
usesAdc: uses('adc_'),
|
|
87
87
|
usesPwm: uses('pwm_'),
|
|
88
|
+
usesDac: uses('dac_') || uses('__tc_dac'),
|
|
89
|
+
usesFS: uses('__tc_fs'),
|
|
90
|
+
usesHwtimer: uses('counter_') || uses('__tc_hw'),
|
|
88
91
|
usesI2c: uses('i2c_'),
|
|
89
92
|
usesSpi: uses('spi_'),
|
|
90
93
|
usesUart: uses('uart_'),
|