@typecad/framework-zephyr 1.0.0-alpha.10 → 1.0.0-alpha.12
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.d.ts +25 -0
- package/dist/display/profiles.js +30 -0
- package/dist/display/touch-adapter.d.ts +3 -4
- package/dist/display/touch-adapter.js +119 -16
- package/dist/display/ui-adapter.js +308 -139
- package/dist/doctor.d.ts +3 -3
- package/dist/doctor.js +56 -29
- package/dist/dt-config/kconfig.d.ts +8 -1
- package/dist/dt-config/kconfig.js +40 -5
- package/dist/dt-config/overlay.d.ts +18 -1
- package/dist/dt-config/overlay.js +124 -26
- package/dist/framework.manifest.d.ts +29 -28
- package/dist/framework.manifest.js +46 -17
- 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/ble.js +3 -1
- 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.d.ts +4 -0
- package/dist/strategy.js +275 -35
- 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/index.js +39 -9
- package/dist/toolchain/scaffold.js +7 -2
- package/dist/toolchain/west-discover.d.ts +11 -3
- package/dist/toolchain/west-discover.js +84 -7
- 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 +53 -0
- package/src/display/touch-adapter.ts +119 -15
- package/src/display/ui-adapter.ts +311 -139
- package/src/doctor.ts +77 -56
- package/src/dt-config/kconfig.ts +45 -6
- package/src/dt-config/overlay.ts +159 -29
- package/src/framework.manifest.ts +47 -17
- package/src/index.ts +6 -0
- package/src/licenses.ts +425 -0
- package/src/lowering/ble.ts +3 -1
- 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 +271 -35
- package/src/toolchain/compat.ts +154 -145
- package/src/toolchain/env-check.ts +285 -0
- package/src/toolchain/index.ts +40 -9
- package/src/toolchain/scaffold.ts +7 -2
- package/src/toolchain/west-discover.ts +92 -9
- package/src/toolchain/west-spawn.ts +15 -0
|
@@ -104,9 +104,13 @@ export default defineFrameworkManifest({
|
|
|
104
104
|
},
|
|
105
105
|
},
|
|
106
106
|
dac: {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
// ESP32 DAC (2× 8-bit channels on GPIO25/26) via the Zephyr DAC driver
|
|
108
|
+
// (dac_channel_setup + dac_write_value). nRF52840 / ESP32-S3 have no DAC;
|
|
109
|
+
// usage there lowers to a comment and profileDiagnostics flags it
|
|
110
|
+
// (zephyr-dac-pin-unavailable).
|
|
111
|
+
supported: true,
|
|
112
|
+
partialCoverage: true,
|
|
113
|
+
ops: { 'dac.write': 'supported' },
|
|
110
114
|
},
|
|
111
115
|
interrupts: {
|
|
112
116
|
supported: true,
|
|
@@ -276,9 +280,14 @@ export default defineFrameworkManifest({
|
|
|
276
280
|
},
|
|
277
281
|
display: {
|
|
278
282
|
supported: true,
|
|
279
|
-
partialCoverage:
|
|
280
|
-
|
|
281
|
-
|
|
283
|
+
partialCoverage: true,
|
|
284
|
+
// Partial: mono profiles (ssd1306-zephyr) drive display.* ops via the
|
|
285
|
+
// direct GFX runtime only — no CuttlefishGFX UI rendering path. The
|
|
286
|
+
// ILI9341 UI adapter shares the ST7796S direct-drive transport with a
|
|
287
|
+
// per-controller init table (16-bit RGB565 wire format); hardware-tuned
|
|
288
|
+
// on ST7796S only. E-ink panels are out of scope at this time.
|
|
289
|
+
unsupportedReason: 'Mono panels (ssd1306) are direct-op only (no UI rendering); ili9341 UI path is ported but not yet hardware-verified; e-ink is out of scope at this time.',
|
|
290
|
+
drivers: ['ili9341-zephyr', 'st7796-zephyr', 'ssd1306-zephyr'],
|
|
282
291
|
colorFormat: 'rgb565',
|
|
283
292
|
ops: {
|
|
284
293
|
'display.init': 'supported',
|
|
@@ -356,10 +365,17 @@ export default defineFrameworkManifest({
|
|
|
356
365
|
},
|
|
357
366
|
},
|
|
358
367
|
fs: {
|
|
359
|
-
|
|
360
|
-
|
|
368
|
+
// littlefs on the board's storage_partition, via <zephyr/fs/fs.h>. The
|
|
369
|
+
// shim mounts at /lfs lazily (formats on first use) and the HAL paths are
|
|
370
|
+
// treated as paths within the filesystem. Requires CONFIG_FILE_SYSTEM +
|
|
371
|
+
// CONFIG_FILE_SYSTEM_LITTLEFS (emitted by the scaffold when fs.* is used)
|
|
372
|
+
// and the storage_partition node.
|
|
373
|
+
supported: true,
|
|
361
374
|
partialCoverage: false,
|
|
362
|
-
ops:
|
|
375
|
+
ops: {
|
|
376
|
+
'fs.begin': 'supported', 'fs.read_text': 'supported', 'fs.write_text': 'supported',
|
|
377
|
+
'fs.exists': 'supported', 'fs.remove': 'supported',
|
|
378
|
+
},
|
|
363
379
|
},
|
|
364
380
|
mdns: {
|
|
365
381
|
supported: false,
|
|
@@ -396,10 +412,17 @@ export default defineFrameworkManifest({
|
|
|
396
412
|
ops: { 'temp.read': 'unsupported' },
|
|
397
413
|
},
|
|
398
414
|
hwtimer: {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
415
|
+
// Hardware timers via the Zephyr counter driver (<zephyr/drivers/counter.h>).
|
|
416
|
+
// set_frequency → top value (counter_freq/hz) + on_overflow callback;
|
|
417
|
+
// start arms both; stop halts. A chip declares its free counters
|
|
418
|
+
// (e.g. nRF RTC1; RTC0 is kernel-owned). This is distinct from the JS
|
|
419
|
+
// setInterval/setTimeout k_timer polyfill, which is unaffected.
|
|
420
|
+
supported: true,
|
|
421
|
+
partialCoverage: true,
|
|
422
|
+
ops: {
|
|
423
|
+
'hwtimer.set_frequency': 'supported', 'hwtimer.on_overflow': 'supported',
|
|
424
|
+
'hwtimer.start': 'supported', 'hwtimer.stop': 'supported',
|
|
425
|
+
},
|
|
403
426
|
},
|
|
404
427
|
capacitive: {
|
|
405
428
|
// FT6336U capacitive touch is handled via the strategy-owned touch adapter
|
|
@@ -466,6 +489,9 @@ export default defineFrameworkManifest({
|
|
|
466
489
|
polyfills: {
|
|
467
490
|
emitted: [
|
|
468
491
|
{ id: 'cuttlefish_halt', domain: 'standard', notes: 'Mapped to a k_msleep halt loop (exceptions disabled)' },
|
|
492
|
+
{ id: 'wiring_compat', domain: 'standard', notes: 'HIGH/LOW/digitalRead/etc. macros routing Wiring tokens (referenced unconditionally by the UI runtime header) to the __tc_gpio_* helpers' },
|
|
493
|
+
{ id: 'string_methods', domain: 'embedded', notes: 'STL-free __tc_* string helpers (const char*, inline ASCII case conv, <cstring> only)' },
|
|
494
|
+
{ id: 'static_array', domain: 'embedded', notes: 'STL-free __tc_StaticArray<T,N> wrapper for no-<vector> mutated/struct array literals' },
|
|
469
495
|
{ id: 'timer_methods', domain: 'embedded', notes: 'k_timer + k_work pool (system workqueue); callbacks run in thread context' },
|
|
470
496
|
{ id: 'async_runtime', domain: 'embedded', notes: 'Heap-free static Promise/microtask runtime (generateStaticAsyncRuntime), pumped in loop()' },
|
|
471
497
|
],
|
|
@@ -473,7 +499,7 @@ export default defineFrameworkManifest({
|
|
|
473
499
|
},
|
|
474
500
|
toolchain: {
|
|
475
501
|
backend: 'west',
|
|
476
|
-
operations: { prepare: true, compile: true, upload: true, monitor: true },
|
|
502
|
+
operations: { prepare: true, compile: true, upload: true, monitor: true, debug: true },
|
|
477
503
|
},
|
|
478
504
|
libraryResolution: {
|
|
479
505
|
isFrameworkLibraryImport: false,
|
|
@@ -517,9 +543,9 @@ export default defineFrameworkManifest({
|
|
|
517
543
|
// pure string-snapshot tests (no hardware); they are the safety net that
|
|
518
544
|
// catches regressions like silent pull-resistor / interrupt no-ops.
|
|
519
545
|
halResolutionTests: [
|
|
520
|
-
'adc', 'ble', 'board', 'dac', '
|
|
521
|
-
'
|
|
522
|
-
'uart', 'wdt', 'worker',
|
|
546
|
+
'adc', 'ble', 'board', 'dac', 'fs', 'gpio', 'http', 'hwtimer', 'i2c',
|
|
547
|
+
'interrupts', 'mqtt', 'power', 'preferences', 'pulse', 'pwm', 'random',
|
|
548
|
+
'spi', 'timing', 'tone', 'uart', 'wdt', 'worker',
|
|
523
549
|
],
|
|
524
550
|
},
|
|
525
551
|
// Declared compatibility range for the installed Zephyr RTOS. The framework's
|
|
@@ -533,4 +559,7 @@ export default defineFrameworkManifest({
|
|
|
533
559
|
// `cuttlefish doctor` prints the detected Zephyr version + compat result and
|
|
534
560
|
// previews how the configured board target resolves for that version.
|
|
535
561
|
doctor: { available: true },
|
|
562
|
+
// `cuttlefish licenses` enumerates the Zephyr kernel + west manifest projects
|
|
563
|
+
// and resolves each one's SPDX license (mirrors framework-arduino).
|
|
564
|
+
licenses: { available: true },
|
|
536
565
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -2,5 +2,6 @@ export { ZephyrStrategy as FrameworkStrategy } from './strategy.js';
|
|
|
2
2
|
export { ZephyrStrategy } from './strategy.js';
|
|
3
3
|
export { Toolchain } from './toolchain/index.js';
|
|
4
4
|
export { runDoctor as doctor } from './doctor.js';
|
|
5
|
+
export { runLicensesPresenter as licenses } from './licenses.js';
|
|
5
6
|
export { chipForTarget, setActiveChip, getActiveChip, XIAO_BLE, } from './chips/index.js';
|
|
6
7
|
export type { ZephyrChipDescriptor, ZephyrGpioDtSpec, } from './chips/types.js';
|
package/dist/index.js
CHANGED
|
@@ -13,5 +13,10 @@ export { Toolchain } from './toolchain/index.js';
|
|
|
13
13
|
// under the dispatcher-facing alias `doctor` so the loader picks it up as
|
|
14
14
|
// mod.doctor (see framework-package.ts).
|
|
15
15
|
export { runDoctor as doctor } from './doctor.js';
|
|
16
|
+
// `cuttlefish licenses` — enumerate the Zephyr kernel + west manifest projects
|
|
17
|
+
// and resolve each one's SPDX license. Re-exported under the dispatcher-facing
|
|
18
|
+
// alias `licenses` so the loader picks it up as mod.licenses (see
|
|
19
|
+
// framework-package.ts). Mirrors framework-arduino's presenter.
|
|
20
|
+
export { runLicensesPresenter as licenses } from './licenses.js';
|
|
16
21
|
// Chip descriptor registry (for downstream tooling / additional boards).
|
|
17
22
|
export { chipForTarget, setActiveChip, getActiveChip, XIAO_BLE, } from './chips/index.js';
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type LibraryLicenseEntry, type ReadFile, type ReadDir } from '@typecad/cuttlefish/api/shared';
|
|
2
|
+
export type ZephyrLicensesOutcome = {
|
|
3
|
+
ok: true;
|
|
4
|
+
entries: LibraryLicenseEntry[];
|
|
5
|
+
needsBuild?: boolean;
|
|
6
|
+
} | {
|
|
7
|
+
ok: false;
|
|
8
|
+
reason: 'west-not-found' | 'no-workspace' | 'no-dependencies';
|
|
9
|
+
message: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Injected enumeration seams so scanZephyrLicenses is unit-testable without
|
|
13
|
+
* spawning west or touching disk. The production runner (defaultRunner) wires
|
|
14
|
+
* these to discoverWest + westSpawn + fs.
|
|
15
|
+
*/
|
|
16
|
+
export interface ZephyrLicensesRunner {
|
|
17
|
+
/** Absolute workspace topdir, or null if cwd is not inside a west workspace. */
|
|
18
|
+
topdir: () => string | null;
|
|
19
|
+
/** west manifest projects `[{ name, abspath }]`, or null if `west list` failed. */
|
|
20
|
+
listModules: () => {
|
|
21
|
+
name: string;
|
|
22
|
+
abspath: string;
|
|
23
|
+
}[] | null;
|
|
24
|
+
/** Discovered $ZEPHYR_BASE (absolute), or undefined. */
|
|
25
|
+
zephyrBase?: string;
|
|
26
|
+
/**
|
|
27
|
+
* The build's compile_commands.json text, or null when no build exists. The
|
|
28
|
+
* project scope uses it to determine which modules the firmware actually
|
|
29
|
+
* links (a module is "linked" iff one of its sources was compiled).
|
|
30
|
+
*/
|
|
31
|
+
compileCommands?: () => string | null;
|
|
32
|
+
readFile: ReadFile;
|
|
33
|
+
readdir: ReadDir;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the Zephyr kernel + west manifest projects to license entries.
|
|
37
|
+
*
|
|
38
|
+
* The Zephyr kernel is always included (when ZEPHYR_BASE is known) — a
|
|
39
|
+
* cuttlefish app always links it via `find_package(Zephyr)`.
|
|
40
|
+
*
|
|
41
|
+
* `all === false` (default, project scope) filters the west modules to those
|
|
42
|
+
* whose sources were compiled in the last build (via `compileCommands`). When
|
|
43
|
+
* no build is available, only the kernel is returned and `needsBuild` is set so
|
|
44
|
+
* the presenter can hint the user to build first. Never throws.
|
|
45
|
+
*/
|
|
46
|
+
export declare function scanZephyrLicenses(runner: ZephyrLicensesRunner, all?: boolean): ZephyrLicensesOutcome;
|
|
47
|
+
/** @internal Test-only override of the default runner. Pass null to simulate west-not-found. */
|
|
48
|
+
export declare function __setLicensesRunnerForTest(runner: ZephyrLicensesRunner | null | undefined): void;
|
|
49
|
+
/**
|
|
50
|
+
* `cuttlefish licenses` (Zephyr) presenter. Enumerates the Zephyr kernel +
|
|
51
|
+
* west manifest projects, resolves each one's license, classifies copyleft
|
|
52
|
+
* risk, and renders a sorted table. Warns on unknown licenses; sets
|
|
53
|
+
* process.exitCode under --strict when any strong-copyleft dependency is
|
|
54
|
+
* present. Never calls process.exit().
|
|
55
|
+
*
|
|
56
|
+
* Default scope = only the modules the firmware actually links (from the last
|
|
57
|
+
* `cuttlefish build`); `--all` lists every west manifest module.
|
|
58
|
+
*/
|
|
59
|
+
export declare function runLicensesPresenter(strict: boolean, all: boolean): void;
|
package/dist/licenses.js
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// @typecad/framework-zephyr — Zephyr license scanner
|
|
3
|
+
//
|
|
4
|
+
// Zephyr-specific enumeration + presenter for the `cuttlefish licenses`
|
|
5
|
+
// subcommand. The framework-agnostic SPDX detection engine lives in the shared
|
|
6
|
+
// cuttlefish core (`@typecad/cuttlefish/api/shared`); this module owns only the
|
|
7
|
+
// Zephyr pieces: discovering the west workspace, enumerating the Zephyr kernel
|
|
8
|
+
// + west manifest projects (`west list`), and resolving each one's LICENSE.
|
|
9
|
+
//
|
|
10
|
+
// Scoping mirrors framework-arduino's `--all` distinction:
|
|
11
|
+
// - default (project scope): only the dependencies the firmware ACTUALLY
|
|
12
|
+
// links, derived from the build's `compile_commands.json` (a module is
|
|
13
|
+
// listed iff its sources were compiled). A west manifest carries every
|
|
14
|
+
// vendor HAL/library; almost none are linked by a single project, so the
|
|
15
|
+
// default filters them out. Requires a prior `cuttlefish build` — without
|
|
16
|
+
// one, only the kernel is reported with a hint to build first.
|
|
17
|
+
// - `--all`: every west manifest project (the whole workspace).
|
|
18
|
+
//
|
|
19
|
+
// Mirrors framework-arduino's presenter shape (runLicensesPresenter(strict,
|
|
20
|
+
// all); never calls process.exit(); sets process.exitCode under --strict).
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
import { spawnSync } from 'node:child_process';
|
|
23
|
+
import { readFileSync, readdirSync } from 'node:fs';
|
|
24
|
+
import * as path from 'node:path';
|
|
25
|
+
import * as ui from '@typecad/cuttlefish/utils/ui';
|
|
26
|
+
import { loadCuttlefishConfig } from '@typecad/cuttlefish/config-loader';
|
|
27
|
+
import { resolveLibraryLicense, RISK_RANK, riskBracket, statusMark, countByRisk, } from '@typecad/cuttlefish/api/shared';
|
|
28
|
+
import { discoverWest } from './toolchain/west-discover.js';
|
|
29
|
+
import { westSpawn } from './toolchain/west-spawn.js';
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Linked-module filtering (project scope)
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
/** Normalize a path for case-insensitive, separator-agnostic comparison. */
|
|
34
|
+
function norm(p) {
|
|
35
|
+
return p.split('\\').join('/').toLowerCase();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Filter the west manifest to the modules whose sources were compiled in the
|
|
39
|
+
* last build. A module is "linked" iff some compiled translation unit lives
|
|
40
|
+
* under its abspath. If the build data is unparseable, no filtering is applied
|
|
41
|
+
* (degrade to listing all modules rather than reporting nothing).
|
|
42
|
+
*/
|
|
43
|
+
function filterLinkedModules(ccText, modules) {
|
|
44
|
+
let arr;
|
|
45
|
+
try {
|
|
46
|
+
arr = JSON.parse(ccText);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return modules;
|
|
50
|
+
}
|
|
51
|
+
if (!Array.isArray(arr))
|
|
52
|
+
return modules;
|
|
53
|
+
// One normalized blob of every compiled source path; membership is then a
|
|
54
|
+
// substring check per module (O(modules) after an O(TUs) join).
|
|
55
|
+
const blob = arr
|
|
56
|
+
.map((e) => norm(typeof e?.file === 'string' ? e.file : ''))
|
|
57
|
+
.join('\n');
|
|
58
|
+
return modules.filter((m) => blob.includes(norm(m.abspath) + '/'));
|
|
59
|
+
}
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// scanZephyrLicenses — enumerate + resolve the workspace dependency set
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
/**
|
|
64
|
+
* Resolve the Zephyr kernel + west manifest projects to license entries.
|
|
65
|
+
*
|
|
66
|
+
* The Zephyr kernel is always included (when ZEPHYR_BASE is known) — a
|
|
67
|
+
* cuttlefish app always links it via `find_package(Zephyr)`.
|
|
68
|
+
*
|
|
69
|
+
* `all === false` (default, project scope) filters the west modules to those
|
|
70
|
+
* whose sources were compiled in the last build (via `compileCommands`). When
|
|
71
|
+
* no build is available, only the kernel is returned and `needsBuild` is set so
|
|
72
|
+
* the presenter can hint the user to build first. Never throws.
|
|
73
|
+
*/
|
|
74
|
+
export function scanZephyrLicenses(runner, all = false) {
|
|
75
|
+
const { readFile, readdir } = runner;
|
|
76
|
+
const entries = [];
|
|
77
|
+
// 1. The Zephyr kernel ($ZEPHYR_BASE). Apache-2.0; resolved from its LICENSE.
|
|
78
|
+
if (runner.zephyrBase) {
|
|
79
|
+
entries.push(resolveLibraryLicense({ name: 'zephyr (kernel)', installDir: runner.zephyrBase }, readFile, readdir,
|
|
80
|
+
// Zephyr's top-level LICENSE is the authoritative source; no manifest
|
|
81
|
+
// license field to consult, and scanning its source headers is noise.
|
|
82
|
+
{ subdirs: [] }));
|
|
83
|
+
}
|
|
84
|
+
// 2. west manifest projects.
|
|
85
|
+
const modules = runner.listModules();
|
|
86
|
+
if (modules === null) {
|
|
87
|
+
if (entries.length > 0)
|
|
88
|
+
return { ok: true, entries };
|
|
89
|
+
return {
|
|
90
|
+
ok: false,
|
|
91
|
+
reason: 'no-workspace',
|
|
92
|
+
message: '`west list` did not return a project list (not inside a west workspace?).',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// Project scope: keep only the modules the build actually linked.
|
|
96
|
+
let projectModules = modules;
|
|
97
|
+
let needsBuild = false;
|
|
98
|
+
if (!all) {
|
|
99
|
+
const cc = runner.compileCommands ? runner.compileCommands() : null;
|
|
100
|
+
if (cc === null) {
|
|
101
|
+
// No build — can't determine the linked set. Report the kernel only and
|
|
102
|
+
// flag it so the presenter prints a "build first" hint.
|
|
103
|
+
projectModules = [];
|
|
104
|
+
needsBuild = true;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
projectModules = filterLinkedModules(cc, modules);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
for (const m of projectModules) {
|
|
111
|
+
// Skip the Zephyr kernel itself (already added above by name) to avoid a
|
|
112
|
+
// duplicate row when it is also a manifest project.
|
|
113
|
+
if (runner.zephyrBase && path.resolve(m.abspath) === path.resolve(runner.zephyrBase)) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
entries.push(resolveLibraryLicense({ name: m.name, installDir: m.abspath }, readFile, readdir, {
|
|
117
|
+
// Zephyr HAL modules commonly keep their LICENSE under a `zephyr/` or
|
|
118
|
+
// `src/` subdir (e.g. hal_nordic ships zephyr/LICENSE.txt). Check both
|
|
119
|
+
// alongside the root. (Source-header scanning is bounded — it only runs
|
|
120
|
+
// when no LICENSE file is found, capped at 6 files / 120 lines.)
|
|
121
|
+
subdirs: ['zephyr', 'src'],
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
if (entries.length === 0) {
|
|
125
|
+
return {
|
|
126
|
+
ok: false,
|
|
127
|
+
reason: 'no-dependencies',
|
|
128
|
+
message: 'No Zephyr dependencies found to scan.',
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
entries.sort((a, b) => {
|
|
132
|
+
const r = RISK_RANK[a.risk] - RISK_RANK[b.risk];
|
|
133
|
+
if (r !== 0)
|
|
134
|
+
return r;
|
|
135
|
+
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
|
136
|
+
});
|
|
137
|
+
return { ok: true, entries, needsBuild };
|
|
138
|
+
}
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
// Default runner — wires seams to discoverWest + westSpawn + fs
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
/**
|
|
143
|
+
* Find the build's compile_commands.json under cwd. Cuttlefish's Zephyr project
|
|
144
|
+
* root is the transpile output dir (e.g. `<cwd>/src/out`), so the build dir is
|
|
145
|
+
* `<root>/build` — not necessarily `<cwd>/build`. Check the common layouts,
|
|
146
|
+
* then fall back to a bounded search (skipping node_modules/.git/dist).
|
|
147
|
+
*/
|
|
148
|
+
function findCompileCommandsText(readFile, readdir) {
|
|
149
|
+
const direct = [
|
|
150
|
+
path.join(process.cwd(), 'build', 'compile_commands.json'),
|
|
151
|
+
path.join(process.cwd(), 'out', 'build', 'compile_commands.json'),
|
|
152
|
+
path.join(process.cwd(), 'src', 'out', 'build', 'compile_commands.json'),
|
|
153
|
+
];
|
|
154
|
+
for (const c of direct) {
|
|
155
|
+
const text = readFile(c);
|
|
156
|
+
if (text)
|
|
157
|
+
return text;
|
|
158
|
+
}
|
|
159
|
+
// Bounded recursive search for a build/compile_commands.json.
|
|
160
|
+
const isCc = (p) => {
|
|
161
|
+
const n = norm(p);
|
|
162
|
+
return n.endsWith('/build/compile_commands.json') || n.endsWith('\\build\\compile_commands.json');
|
|
163
|
+
};
|
|
164
|
+
let found;
|
|
165
|
+
const seen = new Set();
|
|
166
|
+
const walk = (dir, depth) => {
|
|
167
|
+
if (found || depth > 4 || seen.has(dir))
|
|
168
|
+
return;
|
|
169
|
+
seen.add(dir);
|
|
170
|
+
let entries;
|
|
171
|
+
try {
|
|
172
|
+
entries = readdir(dir);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
for (const e of entries) {
|
|
178
|
+
const full = path.join(dir, e);
|
|
179
|
+
if (e === 'compile_commands.json' && isCc(full)) {
|
|
180
|
+
found = full;
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
for (const e of entries) {
|
|
185
|
+
if (found)
|
|
186
|
+
return;
|
|
187
|
+
if (e === 'node_modules' || e === '.git' || e === 'dist' || e.startsWith('.'))
|
|
188
|
+
continue;
|
|
189
|
+
walk(path.join(dir, e), depth + 1);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
walk(process.cwd(), 0);
|
|
193
|
+
return found ? (readFile(found) ?? null) : null;
|
|
194
|
+
}
|
|
195
|
+
function defaultRunner() {
|
|
196
|
+
const install = discoverWest();
|
|
197
|
+
if (!install)
|
|
198
|
+
return null;
|
|
199
|
+
const zephyrBase = install.zephyrBase;
|
|
200
|
+
// `west topdir` prints the workspace root (one path line). Returns null when
|
|
201
|
+
// cwd is not inside a west workspace (west exits non-zero).
|
|
202
|
+
const topdir = () => {
|
|
203
|
+
try {
|
|
204
|
+
const inv = westSpawn(['topdir'], { encoding: 'utf8', timeout: 15_000 });
|
|
205
|
+
const r = spawnSync(inv.command, inv.args, inv.options);
|
|
206
|
+
const out = typeof r.stdout === 'string' ? r.stdout.trim() : '';
|
|
207
|
+
return r.status === 0 && out ? out : null;
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
// `west list --format '{name}\t{abspath}'` → one project per line. Returns
|
|
214
|
+
// null on any spawn failure.
|
|
215
|
+
const listModules = () => {
|
|
216
|
+
try {
|
|
217
|
+
const inv = westSpawn(['list', '--format', '{name}\t{abspath}'], {
|
|
218
|
+
encoding: 'utf8',
|
|
219
|
+
timeout: 30_000,
|
|
220
|
+
});
|
|
221
|
+
const r = spawnSync(inv.command, inv.args, inv.options);
|
|
222
|
+
if (r.status !== 0)
|
|
223
|
+
return null;
|
|
224
|
+
const out = typeof r.stdout === 'string' ? r.stdout : '';
|
|
225
|
+
const mods = [];
|
|
226
|
+
for (const line of out.split(/\r?\n/)) {
|
|
227
|
+
const trimmed = line.trim();
|
|
228
|
+
if (!trimmed)
|
|
229
|
+
continue;
|
|
230
|
+
const [name, abspath] = trimmed.split('\t');
|
|
231
|
+
if (name && abspath)
|
|
232
|
+
mods.push({ name, abspath });
|
|
233
|
+
}
|
|
234
|
+
return mods;
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
const readFile = (p) => {
|
|
241
|
+
try {
|
|
242
|
+
return readFileSync(p, 'utf8');
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
const readdir = (d) => {
|
|
249
|
+
try {
|
|
250
|
+
return readdirSync(d);
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
return [];
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
return {
|
|
257
|
+
topdir,
|
|
258
|
+
listModules,
|
|
259
|
+
zephyrBase,
|
|
260
|
+
compileCommands: () => findCompileCommandsText(readFile, readdir),
|
|
261
|
+
readFile,
|
|
262
|
+
readdir,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
// ---------------------------------------------------------------------------
|
|
266
|
+
// CLI presenter
|
|
267
|
+
// ---------------------------------------------------------------------------
|
|
268
|
+
let testRunner;
|
|
269
|
+
/** @internal Test-only override of the default runner. Pass null to simulate west-not-found. */
|
|
270
|
+
export function __setLicensesRunnerForTest(runner) {
|
|
271
|
+
testRunner = runner;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* `cuttlefish licenses` (Zephyr) presenter. Enumerates the Zephyr kernel +
|
|
275
|
+
* west manifest projects, resolves each one's license, classifies copyleft
|
|
276
|
+
* risk, and renders a sorted table. Warns on unknown licenses; sets
|
|
277
|
+
* process.exitCode under --strict when any strong-copyleft dependency is
|
|
278
|
+
* present. Never calls process.exit().
|
|
279
|
+
*
|
|
280
|
+
* Default scope = only the modules the firmware actually links (from the last
|
|
281
|
+
* `cuttlefish build`); `--all` lists every west manifest module.
|
|
282
|
+
*/
|
|
283
|
+
export function runLicensesPresenter(strict, all) {
|
|
284
|
+
ui.printHeader();
|
|
285
|
+
ui.printStep(all
|
|
286
|
+
? 'Checking licenses for every west manifest module'
|
|
287
|
+
: 'Checking licenses for this Zephyr project (linked dependencies only)');
|
|
288
|
+
// Surface the config (informational). Best-effort: a missing/malformed config
|
|
289
|
+
// never blocks the license scan.
|
|
290
|
+
let buildTarget;
|
|
291
|
+
try {
|
|
292
|
+
buildTarget = loadCuttlefishConfig(process.cwd())?.buildTarget;
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
/* best-effort */
|
|
296
|
+
}
|
|
297
|
+
if (buildTarget) {
|
|
298
|
+
ui.printInfo(`Board target ... ${buildTarget}`);
|
|
299
|
+
}
|
|
300
|
+
const runner = testRunner !== undefined ? testRunner : defaultRunner();
|
|
301
|
+
if (!runner) {
|
|
302
|
+
ui.printError('west ............. NOT FOUND');
|
|
303
|
+
ui.printInfo(" → install west (pip install west) or run the typeCAD Zephyr installer.");
|
|
304
|
+
process.exitCode = 1;
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const result = scanZephyrLicenses(runner, all);
|
|
308
|
+
if (!result.ok) {
|
|
309
|
+
if (result.reason === 'no-workspace' || result.reason === 'no-dependencies') {
|
|
310
|
+
ui.printInfo(`(${result.message})`);
|
|
311
|
+
}
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
// Project scope with no build: only the kernel is reported. Hint the user to
|
|
315
|
+
// build (which records which modules actually link) or use --all.
|
|
316
|
+
if (result.needsBuild) {
|
|
317
|
+
ui.printInfo('(no build found — only the Zephyr kernel is shown. Run `cuttlefish build` to scope ' +
|
|
318
|
+
'this report to the modules your firmware actually links, or use `cuttlefish licenses --all` ' +
|
|
319
|
+
'for every west module.)');
|
|
320
|
+
}
|
|
321
|
+
const counts = countByRisk(result.entries);
|
|
322
|
+
for (const e of result.entries) {
|
|
323
|
+
if (e.risk === 'unknown') {
|
|
324
|
+
ui.printWarning(`${e.name} .................. UNKNOWN`);
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
ui.printInfo(`${e.name} .................. ${e.spdx ?? 'UNKNOWN'}${riskBracket(e.risk)}${statusMark(e.risk)}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
ui.printSuccess(`${counts.permissive} permissive, ${counts['weak-copyleft']} weak copyleft, ` +
|
|
331
|
+
`${counts['strong-copyleft']} strong copyleft, ${counts.unknown} unknown`);
|
|
332
|
+
const unknowns = result.entries.filter((e) => e.risk === 'unknown');
|
|
333
|
+
if (unknowns.length > 0) {
|
|
334
|
+
ui.printWarning(`License could not be determined for ${unknowns.length} ${unknowns.length === 1 ? 'dependency' : 'dependencies'}:`);
|
|
335
|
+
for (const u of unknowns) {
|
|
336
|
+
ui.printInfo(` ${u.name} (check LICENSE in ${u.path})`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const strongCopyleft = result.entries.filter((e) => e.risk === 'strong-copyleft');
|
|
340
|
+
if (strongCopyleft.length > 0) {
|
|
341
|
+
ui.printWarning(`${strongCopyleft.length} ${strongCopyleft.length === 1 ? 'dependency carries' : 'dependencies carry'} strong-copyleft terms — review before shipping.`);
|
|
342
|
+
}
|
|
343
|
+
// --strict fails the build on any unknown OR strong-copyleft dependency.
|
|
344
|
+
if ((unknowns.length > 0 || strongCopyleft.length > 0) && strict) {
|
|
345
|
+
process.exitCode = 1;
|
|
346
|
+
}
|
|
347
|
+
}
|
package/dist/lowering/ble.js
CHANGED
|
@@ -388,7 +388,9 @@ export function lowerBle(op) {
|
|
|
388
388
|
case 'ble.on_read':
|
|
389
389
|
// Store the typed read handler as void*; __tc_ble_attr_read casts it back
|
|
390
390
|
// to the right signature based on the char's type field. reinterpret_cast
|
|
391
|
-
// (not a C-style cast)
|
|
391
|
+
// (not a C-style cast) avoids M5-0-7, but M5-0-10 still flags it — the
|
|
392
|
+
// whole type-erased table is covered by a knownPatterns deviation on
|
|
393
|
+
// that rule (see rules.ts, "BLE type-erased callback table").
|
|
392
394
|
return { code: `__tc_ble.on_read[__tc_ble.current_char] = reinterpret_cast<void*>(${s(o.handler)});` };
|
|
393
395
|
case 'ble.on_write':
|
|
394
396
|
return { code: `__tc_ble.on_write[__tc_ble.current_char] = (${s(o.handler)});` };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
|
|
2
|
+
import type { ZephyrChipDescriptor } from '../chips/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Emit the DAC device handle. Called from shimLines when the program uses the
|
|
5
|
+
* DAC (the chip must declare a `dac` entry, or nothing is emitted).
|
|
6
|
+
*/
|
|
7
|
+
export declare function dacInitLines(chip: ZephyrChipDescriptor): string[];
|
|
8
|
+
/**
|
|
9
|
+
* Resolve a HAL dac.* op to Zephyr C++.
|
|
10
|
+
* Returns `{ code }` for statement ops.
|
|
11
|
+
*/
|
|
12
|
+
export declare function lowerDac(op: HALOpIR, chip: ZephyrChipDescriptor): {
|
|
13
|
+
code?: string;
|
|
14
|
+
expression?: string;
|
|
15
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// DAC lowering — Zephyr DAC driver via dac_channel_setup / dac_write_value
|
|
3
|
+
//
|
|
4
|
+
// Targets with a DAC (ESP32: 2× 8-bit channels on GPIO25/26) declare a `dac`
|
|
5
|
+
// entry in the chip descriptor: the DT device node label + the pin→channel map.
|
|
6
|
+
// The lowering emits a `dac_channel_setup` (lazy, on first write) + a
|
|
7
|
+
// `dac_write_value` against DEVICE_DT_GET(DT_NODELABEL(<device>)).
|
|
8
|
+
//
|
|
9
|
+
// Targets without a DAC (nRF52840, ESP32-S3) omit `dac`; usage there lowers to a
|
|
10
|
+
// comment and profileDiagnostics flags it (mirror of the ADC pin-validity gate).
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
/** Look up a DAC channel by HAL pin number. */
|
|
13
|
+
function findDacChannel(chip, pin) {
|
|
14
|
+
return chip.dac?.channels.find((c) => c.pin === pin);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Emit the DAC device handle. Called from shimLines when the program uses the
|
|
18
|
+
* DAC (the chip must declare a `dac` entry, or nothing is emitted).
|
|
19
|
+
*/
|
|
20
|
+
export function dacInitLines(chip) {
|
|
21
|
+
if (!chip.dac)
|
|
22
|
+
return [];
|
|
23
|
+
return [
|
|
24
|
+
'// CUTTLEFISH_DAC_BEGIN',
|
|
25
|
+
`static const struct device* __tc_dac_dev = DEVICE_DT_GET(DT_NODELABEL(${chip.dac.device}));`,
|
|
26
|
+
'// CUTTLEFISH_DAC_END',
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Resolve a HAL dac.* op to Zephyr C++.
|
|
31
|
+
* Returns `{ code }` for statement ops.
|
|
32
|
+
*/
|
|
33
|
+
export function lowerDac(op, chip) {
|
|
34
|
+
const o = op;
|
|
35
|
+
// No DAC on this target (e.g. nRF52840). Return a comment so the resolver
|
|
36
|
+
// reports non-undefined (the manifest validator's probe sends pin:25 on the
|
|
37
|
+
// default chip, which has no DAC). profileDiagnostics flags real misuse.
|
|
38
|
+
if (!chip.dac) {
|
|
39
|
+
return { code: `/* dac on pin ${o.pin}: no DAC on ${chip.id} */` };
|
|
40
|
+
}
|
|
41
|
+
const ch = findDacChannel(chip, o.pin);
|
|
42
|
+
if (!ch) {
|
|
43
|
+
const valid = [...chip.dac.channels].map((c) => c.pin).sort((a, b) => a - b).join(', ');
|
|
44
|
+
return {
|
|
45
|
+
code: `/* dac on pin ${o.pin}: not a DAC pin on ${chip.id} (valid: ${valid}) */`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
switch (op.operation) {
|
|
49
|
+
case 'dac.write': {
|
|
50
|
+
// Lazy one-time channel setup on first write (the HAL DAC surface has no
|
|
51
|
+
// begin()), then output the value. Arduino analogWrite is 0–255 against
|
|
52
|
+
// the channel's resolution; dac_write_value takes the raw code.
|
|
53
|
+
return {
|
|
54
|
+
code: [
|
|
55
|
+
`{ static bool __tc_dac_done = false;`,
|
|
56
|
+
` if (!__tc_dac_done) {`,
|
|
57
|
+
` static const struct dac_channel_cfg __tc_dac_cfg = { .channel_id = ${ch.channel}, .resolution = ${ch.resolution} };`,
|
|
58
|
+
` (void)dac_channel_setup(__tc_dac_dev, &__tc_dac_cfg);`,
|
|
59
|
+
` __tc_dac_done = true;`,
|
|
60
|
+
` }`,
|
|
61
|
+
` (void)dac_write_value(__tc_dac_dev, ${ch.channel}, ${o.value}); }`,
|
|
62
|
+
].join(' '),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
default:
|
|
66
|
+
throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
|
|
67
|
+
`Open an issue or use rawCpp() to emit it manually.`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
|
|
2
|
+
/**
|
|
3
|
+
* Emit the littlefs mount + the typed helper API the lowering calls into.
|
|
4
|
+
* Called from shimLines when the program uses the filesystem. The helpers are
|
|
5
|
+
* `static` so multiple TUs that carry the shim (guarded by the include guard)
|
|
6
|
+
* do not collide.
|
|
7
|
+
*/
|
|
8
|
+
export declare function fsInitLines(): string[];
|
|
9
|
+
/**
|
|
10
|
+
* Resolve a HAL fs.* op to Zephyr C++.
|
|
11
|
+
* Returns `{ code }` — each op's code is the body of the HAL FS class method.
|
|
12
|
+
*/
|
|
13
|
+
export declare function lowerFs(op: HALOpIR): {
|
|
14
|
+
code?: string;
|
|
15
|
+
expression?: string;
|
|
16
|
+
};
|