@kvell007/embed-labs-cli 0.1.0-alpha.11 → 0.1.0-alpha.13
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/index.js +317 -23
- package/dist/index.js.map +1 -1
- package/dist/local-toolchain.d.ts +40 -5
- package/dist/local-toolchain.js +244 -59
- package/dist/local-toolchain.js.map +1 -1
- package/package.json +3 -3
|
@@ -6,6 +6,7 @@ export interface LocalToolchainAuthContext {
|
|
|
6
6
|
}
|
|
7
7
|
export interface LocalToolchainValidationResult {
|
|
8
8
|
ok: boolean;
|
|
9
|
+
mode: string;
|
|
9
10
|
host: {
|
|
10
11
|
platform: string;
|
|
11
12
|
arch: string;
|
|
@@ -107,6 +108,7 @@ export interface LocalToolchainInstallOptions extends LocalToolchainLatestOption
|
|
|
107
108
|
sourceReleaseRoot?: string;
|
|
108
109
|
sourceUrl?: string;
|
|
109
110
|
installRoot?: string;
|
|
111
|
+
mode?: string;
|
|
110
112
|
force?: boolean;
|
|
111
113
|
}
|
|
112
114
|
export interface LocalToolchainInstallResult {
|
|
@@ -114,16 +116,18 @@ export interface LocalToolchainInstallResult {
|
|
|
114
116
|
version: string;
|
|
115
117
|
channel: string;
|
|
116
118
|
host: string;
|
|
119
|
+
mode: string;
|
|
117
120
|
install_root: string;
|
|
118
121
|
release_root: string;
|
|
119
122
|
registry_path: string;
|
|
120
123
|
source: {
|
|
121
|
-
kind: "directory" | "url";
|
|
124
|
+
kind: "directory" | "url" | "components";
|
|
122
125
|
value: string;
|
|
123
126
|
downloaded_path?: string;
|
|
124
127
|
mirror_kind?: string;
|
|
125
128
|
sha256?: string;
|
|
126
129
|
size_bytes?: number;
|
|
130
|
+
components?: LocalToolchainInstalledComponent[];
|
|
127
131
|
};
|
|
128
132
|
installed_paths: string[];
|
|
129
133
|
packages: LocalToolchainPackageRef[];
|
|
@@ -133,6 +137,7 @@ export interface LocalToolchainCurrentResult {
|
|
|
133
137
|
installed: boolean;
|
|
134
138
|
board_id: string;
|
|
135
139
|
version?: string;
|
|
140
|
+
mode?: string;
|
|
136
141
|
release_root?: string;
|
|
137
142
|
registry_path: string;
|
|
138
143
|
install_root: string;
|
|
@@ -147,15 +152,17 @@ export interface LocalToolchainDownloadPlan {
|
|
|
147
152
|
board_id: string;
|
|
148
153
|
host: string;
|
|
149
154
|
toolchain: string;
|
|
150
|
-
source_url
|
|
151
|
-
mirror_kind
|
|
152
|
-
archive
|
|
155
|
+
source_url?: string;
|
|
156
|
+
mirror_kind?: string;
|
|
157
|
+
archive?: {
|
|
153
158
|
file: string;
|
|
154
159
|
size_bytes: number;
|
|
155
160
|
sha256: string;
|
|
156
161
|
content_type?: string;
|
|
157
162
|
};
|
|
158
163
|
mirrors: LocalToolchainDownloadMirror[];
|
|
164
|
+
components?: LocalToolchainDownloadComponent[];
|
|
165
|
+
default_mode?: string;
|
|
159
166
|
}
|
|
160
167
|
export interface LocalToolchainDownloadMirror {
|
|
161
168
|
kind: string;
|
|
@@ -164,10 +171,38 @@ export interface LocalToolchainDownloadMirror {
|
|
|
164
171
|
sha256?: string;
|
|
165
172
|
size_bytes?: number;
|
|
166
173
|
}
|
|
174
|
+
export interface LocalToolchainDownloadComponent {
|
|
175
|
+
id: string;
|
|
176
|
+
version: string;
|
|
177
|
+
role?: string;
|
|
178
|
+
install_modes?: string[];
|
|
179
|
+
archive: {
|
|
180
|
+
file: string;
|
|
181
|
+
size_bytes: number;
|
|
182
|
+
sha256: string;
|
|
183
|
+
content_type?: string;
|
|
184
|
+
};
|
|
185
|
+
source_url?: string;
|
|
186
|
+
mirror_kind?: string;
|
|
187
|
+
mirrors: LocalToolchainDownloadMirror[];
|
|
188
|
+
}
|
|
189
|
+
export interface LocalToolchainInstalledComponent {
|
|
190
|
+
id: string;
|
|
191
|
+
version: string;
|
|
192
|
+
role?: string;
|
|
193
|
+
archive_file: string;
|
|
194
|
+
mirror_kind?: string;
|
|
195
|
+
downloaded_path: string;
|
|
196
|
+
size_bytes: number;
|
|
197
|
+
sha256: string;
|
|
198
|
+
}
|
|
167
199
|
export declare function defaultLocalReleaseRoot(): string;
|
|
168
200
|
export declare function latestLocalToolchain(options?: LocalToolchainLatestOptions): Promise<LocalToolchainLatestResult>;
|
|
169
201
|
export declare function currentLocalToolchain(installRoot?: string, boardId?: string): Promise<LocalToolchainCurrentResult>;
|
|
170
202
|
export declare function installLocalToolchain(options?: LocalToolchainInstallOptions): Promise<LocalToolchainInstallResult>;
|
|
171
|
-
export declare function validateLocalToolchain(
|
|
203
|
+
export declare function validateLocalToolchain(input?: string | {
|
|
204
|
+
releaseRoot?: string;
|
|
205
|
+
mode?: string;
|
|
206
|
+
}): Promise<LocalToolchainValidationResult>;
|
|
172
207
|
export declare function compileTaishanPiSingleFile(options: LocalCompileOptions): Promise<LocalCompileResult>;
|
|
173
208
|
export declare function buildTaishanPiQtSmoke(options: LocalQtSmokeBuildOptions): Promise<LocalCompileResult>;
|
package/dist/local-toolchain.js
CHANGED
|
@@ -103,15 +103,23 @@ const INSTALL_COPY_PATHS = [
|
|
|
103
103
|
"toolchain/llvm-cross",
|
|
104
104
|
"toolchain/host",
|
|
105
105
|
"toolchain/host-tools",
|
|
106
|
+
"toolchain/qt6-rk3566-llvm-toolchain.cmake",
|
|
106
107
|
"qt-target/qt6-rk3566-llvm-6.8.3",
|
|
108
|
+
"qt-host/qt6-host-macos-6.8.3",
|
|
107
109
|
"tools/mac",
|
|
108
110
|
"toolkit-runtime/qtquick-live-preview",
|
|
109
111
|
"toolkit-runtime/rp2350-monitor",
|
|
110
112
|
"toolkit-runtime/RP2350-Monitor",
|
|
111
113
|
"images/current",
|
|
112
114
|
"userdata/rootfs",
|
|
113
|
-
"boot-workspace
|
|
115
|
+
"boot-workspace",
|
|
116
|
+
"README.md",
|
|
117
|
+
"meta",
|
|
118
|
+
"scripts",
|
|
119
|
+
"support",
|
|
120
|
+
"third_party"
|
|
114
121
|
];
|
|
122
|
+
const LOCAL_TOOLCHAIN_INSTALL_MODES = ["minimal", "compile", "qt", "full", "images"];
|
|
115
123
|
export function defaultLocalReleaseRoot() {
|
|
116
124
|
return process.env.EMBEDLABS_LOCAL_RELEASE_ROOT?.trim()
|
|
117
125
|
|| process.env.EMBEDLABS_RELEASE_ROOT?.trim()
|
|
@@ -144,7 +152,7 @@ export async function latestLocalToolchain(options = {}) {
|
|
|
144
152
|
board_id: boardId,
|
|
145
153
|
channel: channel.channel,
|
|
146
154
|
host: hostId(),
|
|
147
|
-
version: board.version,
|
|
155
|
+
version: download?.version ?? board.version,
|
|
148
156
|
metadata_root: metadataRoot,
|
|
149
157
|
packages,
|
|
150
158
|
download,
|
|
@@ -161,6 +169,7 @@ export async function currentLocalToolchain(installRoot, boardId = DEFAULT_BOARD
|
|
|
161
169
|
installed: !!releaseRoot,
|
|
162
170
|
board_id: typeof registry.board_id === "string" ? registry.board_id : boardId,
|
|
163
171
|
version: typeof registry.version === "string" ? registry.version : undefined,
|
|
172
|
+
mode: typeof registry.mode === "string" ? registry.mode : undefined,
|
|
164
173
|
release_root: releaseRoot,
|
|
165
174
|
registry_path: registryPath,
|
|
166
175
|
install_root: root,
|
|
@@ -181,30 +190,42 @@ export async function installLocalToolchain(options = {}) {
|
|
|
181
190
|
const latest = await latestLocalToolchain(options);
|
|
182
191
|
const installRoot = resolveInstallRoot(options.installRoot);
|
|
183
192
|
const releaseRoot = resolve(installRoot, "toolchains", latest.board_id, latest.version);
|
|
193
|
+
const installMode = normalizeLocalToolchainInstallMode(options.mode ?? latest.download?.default_mode);
|
|
184
194
|
if (await pathExists(releaseRoot) && !options.force) {
|
|
185
|
-
const validation = await validateLocalToolchain(releaseRoot);
|
|
195
|
+
const validation = await validateLocalToolchain({ releaseRoot, mode: installMode });
|
|
186
196
|
if (!validation.ok) {
|
|
187
|
-
|
|
197
|
+
if (latest.download?.components?.length) {
|
|
198
|
+
// Component installs can upgrade an existing lower-mode install by overlaying
|
|
199
|
+
// only the newly selected components instead of deleting the whole tree.
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
throw new Error(`Existing local toolchain is incomplete at ${releaseRoot}; rerun with --force.`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
await writeCurrentRegistry(installRoot, latest, releaseRoot, installMode);
|
|
207
|
+
return {
|
|
208
|
+
board_id: latest.board_id,
|
|
209
|
+
version: latest.version,
|
|
210
|
+
channel: latest.channel,
|
|
211
|
+
host: latest.host,
|
|
212
|
+
mode: installMode,
|
|
213
|
+
install_root: installRoot,
|
|
214
|
+
release_root: releaseRoot,
|
|
215
|
+
registry_path: localToolchainRegistryPath(installRoot),
|
|
216
|
+
source: { kind: "directory", value: releaseRoot },
|
|
217
|
+
installed_paths: [],
|
|
218
|
+
packages: latest.packages,
|
|
219
|
+
validation
|
|
220
|
+
};
|
|
188
221
|
}
|
|
189
|
-
await writeCurrentRegistry(installRoot, latest, releaseRoot);
|
|
190
|
-
return {
|
|
191
|
-
board_id: latest.board_id,
|
|
192
|
-
version: latest.version,
|
|
193
|
-
channel: latest.channel,
|
|
194
|
-
host: latest.host,
|
|
195
|
-
install_root: installRoot,
|
|
196
|
-
release_root: releaseRoot,
|
|
197
|
-
registry_path: localToolchainRegistryPath(installRoot),
|
|
198
|
-
source: { kind: "directory", value: releaseRoot },
|
|
199
|
-
installed_paths: [],
|
|
200
|
-
packages: latest.packages,
|
|
201
|
-
validation
|
|
202
|
-
};
|
|
203
222
|
}
|
|
204
223
|
const tempDir = await mkdtemp(join(tmpdir(), "embedlabs-local-toolchain-install-"));
|
|
205
224
|
try {
|
|
206
225
|
const sourceRoot = await sourceReleaseRootForInstall(options, latest, installRoot, tempDir);
|
|
207
|
-
await
|
|
226
|
+
if (options.force || !await pathExists(releaseRoot) || sourceRoot.source.kind !== "components") {
|
|
227
|
+
await rm(releaseRoot, { recursive: true, force: true });
|
|
228
|
+
}
|
|
208
229
|
await mkdir(releaseRoot, { recursive: true });
|
|
209
230
|
const installedPaths = [];
|
|
210
231
|
for (const relativePath of INSTALL_COPY_PATHS) {
|
|
@@ -222,8 +243,8 @@ export async function installLocalToolchain(options = {}) {
|
|
|
222
243
|
});
|
|
223
244
|
installedPaths.push(relativePath);
|
|
224
245
|
}
|
|
225
|
-
await writeCurrentRegistry(installRoot, latest, releaseRoot);
|
|
226
|
-
const validation = await validateLocalToolchain(releaseRoot);
|
|
246
|
+
await writeCurrentRegistry(installRoot, latest, releaseRoot, installMode, sourceRoot.source);
|
|
247
|
+
const validation = await validateLocalToolchain({ releaseRoot, mode: installMode });
|
|
227
248
|
if (!validation.ok) {
|
|
228
249
|
throw new Error(`Installed local toolchain is incomplete: ${validation.missing_paths.join(", ")}`);
|
|
229
250
|
}
|
|
@@ -232,6 +253,7 @@ export async function installLocalToolchain(options = {}) {
|
|
|
232
253
|
version: latest.version,
|
|
233
254
|
channel: latest.channel,
|
|
234
255
|
host: latest.host,
|
|
256
|
+
mode: installMode,
|
|
235
257
|
install_root: installRoot,
|
|
236
258
|
release_root: releaseRoot,
|
|
237
259
|
registry_path: localToolchainRegistryPath(installRoot),
|
|
@@ -245,26 +267,11 @@ export async function installLocalToolchain(options = {}) {
|
|
|
245
267
|
await rm(tempDir, { recursive: true, force: true });
|
|
246
268
|
}
|
|
247
269
|
}
|
|
248
|
-
export async function validateLocalToolchain(
|
|
270
|
+
export async function validateLocalToolchain(input) {
|
|
271
|
+
const releaseRoot = typeof input === "string" ? input : input?.releaseRoot;
|
|
272
|
+
const mode = normalizeLocalToolchainInstallMode(typeof input === "string" ? undefined : input?.mode);
|
|
249
273
|
const resolvedRoot = await resolveLocalReleaseRoot(releaseRoot);
|
|
250
|
-
const required =
|
|
251
|
-
["release root", "."],
|
|
252
|
-
["C compiler", "toolchain/llvm-cross/bin/aarch64-linux-gnu-gcc"],
|
|
253
|
-
["C++ compiler", "toolchain/llvm-cross/bin/aarch64-linux-gnu-g++"],
|
|
254
|
-
["readelf", "toolchain/llvm-cross/bin/aarch64-linux-gnu-readelf"],
|
|
255
|
-
["host clang wrapper", "toolchain/host-tools/bin/clang-aarch64-linux-gnu"],
|
|
256
|
-
["host GCC libraries", "toolchain/host/lib/gcc"],
|
|
257
|
-
["Qt CMake", "qt-target/qt6-rk3566-llvm-6.8.3/bin/qt-cmake"],
|
|
258
|
-
["target sysroot", "toolchain/host/aarch64-buildroot-linux-gnu/sysroot"],
|
|
259
|
-
["Qt target libraries", "qt-target/qt6-rk3566-llvm-6.8.3/lib"],
|
|
260
|
-
["Rockchip mkimage", "tools/mac/mkimage"],
|
|
261
|
-
["Rockchip dumpimage", "tools/mac/dumpimage"],
|
|
262
|
-
["Rockchip resource_tool", "tools/mac/resource_tool"],
|
|
263
|
-
["Rockchip rkdeveloptool", "tools/mac/rkdeveloptool"],
|
|
264
|
-
["QtQuick live preview", "toolkit-runtime/qtquick-live-preview/bin/embed-qml-live-preview"],
|
|
265
|
-
["RP2350 Monitor CLI", "toolkit-runtime/rp2350-monitor/tools/rpmon_cli.py"],
|
|
266
|
-
["RP2350 Monitor logic analyzer", "toolkit-runtime/rp2350-monitor/ui/bin/embed-labs-logic-analyzer"]
|
|
267
|
-
];
|
|
274
|
+
const required = requiredLocalToolchainChecks(mode);
|
|
268
275
|
const checked_paths = [];
|
|
269
276
|
for (const [label, relativePath] of required) {
|
|
270
277
|
const absolutePath = resolve(resolvedRoot, relativePath);
|
|
@@ -277,6 +284,7 @@ export async function validateLocalToolchain(releaseRoot) {
|
|
|
277
284
|
const missing_paths = checked_paths.filter((item) => !item.exists).map((item) => item.path);
|
|
278
285
|
return {
|
|
279
286
|
ok: missing_paths.length === 0,
|
|
287
|
+
mode,
|
|
280
288
|
host: {
|
|
281
289
|
platform: platform(),
|
|
282
290
|
arch: arch()
|
|
@@ -287,10 +295,71 @@ export async function validateLocalToolchain(releaseRoot) {
|
|
|
287
295
|
missing_paths,
|
|
288
296
|
notes: [
|
|
289
297
|
"Local build commands require an Embed Labs auth token so local resource use remains account attributable.",
|
|
290
|
-
|
|
298
|
+
`This validator checks the TaishanPi LLVM local support layout for install mode ${mode}.`
|
|
291
299
|
]
|
|
292
300
|
};
|
|
293
301
|
}
|
|
302
|
+
function normalizeLocalToolchainInstallMode(mode) {
|
|
303
|
+
const normalized = mode?.trim();
|
|
304
|
+
if (!normalized) {
|
|
305
|
+
return "qt";
|
|
306
|
+
}
|
|
307
|
+
if (LOCAL_TOOLCHAIN_INSTALL_MODES.includes(normalized)) {
|
|
308
|
+
return normalized;
|
|
309
|
+
}
|
|
310
|
+
throw new Error(`Unsupported local toolchain install mode ${normalized}; expected ${LOCAL_TOOLCHAIN_INSTALL_MODES.join(", ")}.`);
|
|
311
|
+
}
|
|
312
|
+
function requiredLocalToolchainChecks(mode) {
|
|
313
|
+
const base = [
|
|
314
|
+
["release root", "."],
|
|
315
|
+
["Rockchip mkimage", "tools/mac/mkimage"],
|
|
316
|
+
["Rockchip dumpimage", "tools/mac/dumpimage"],
|
|
317
|
+
["Rockchip resource_tool", "tools/mac/resource_tool"],
|
|
318
|
+
["Rockchip rkdeveloptool", "tools/mac/rkdeveloptool"],
|
|
319
|
+
["boot resource image", "boot-workspace/out/resource.img"],
|
|
320
|
+
["boot image", "boot-workspace/out/boot.img"],
|
|
321
|
+
["boot DTB", "boot-workspace/out/tspi-rk3566-user-v10-linux.dtb"],
|
|
322
|
+
["RP2350 Monitor CLI", "toolkit-runtime/rp2350-monitor/tools/rpmon_cli.py"],
|
|
323
|
+
["RP2350 Monitor logic analyzer", "toolkit-runtime/rp2350-monitor/ui/bin/embed-labs-logic-analyzer"],
|
|
324
|
+
["package metadata", "meta"]
|
|
325
|
+
];
|
|
326
|
+
const compile = [
|
|
327
|
+
["C compiler", "toolchain/llvm-cross/bin/aarch64-linux-gnu-gcc"],
|
|
328
|
+
["C++ compiler", "toolchain/llvm-cross/bin/aarch64-linux-gnu-g++"],
|
|
329
|
+
["readelf", "toolchain/llvm-cross/bin/aarch64-linux-gnu-readelf"],
|
|
330
|
+
["host clang wrapper", "toolchain/host-tools/bin/clang-aarch64-linux-gnu"],
|
|
331
|
+
["host GCC libraries", "toolchain/host/lib/gcc"],
|
|
332
|
+
["target sysroot", "toolchain/host/aarch64-buildroot-linux-gnu/sysroot"],
|
|
333
|
+
["target include directory", "toolchain/host/aarch64-buildroot-linux-gnu/include"]
|
|
334
|
+
];
|
|
335
|
+
const qt = [
|
|
336
|
+
["Qt CMake", "qt-target/qt6-rk3566-llvm-6.8.3/bin/qt-cmake"],
|
|
337
|
+
["Qt target libraries", "qt-target/qt6-rk3566-llvm-6.8.3/lib"],
|
|
338
|
+
["Qt host tools", "qt-host/qt6-host-macos-6.8.3"],
|
|
339
|
+
["QtQuick live preview", "toolkit-runtime/qtquick-live-preview/bin/embed-qml-live-preview"]
|
|
340
|
+
];
|
|
341
|
+
const images = [
|
|
342
|
+
["base boot image", "images/current/boot.img"],
|
|
343
|
+
["base rootfs image", "images/current/rootfs.img"],
|
|
344
|
+
["base image parameter", "images/current/parameter.txt"]
|
|
345
|
+
];
|
|
346
|
+
const full = [
|
|
347
|
+
["rootfs overlay", "userdata/rootfs"]
|
|
348
|
+
];
|
|
349
|
+
if (mode === "minimal") {
|
|
350
|
+
return base;
|
|
351
|
+
}
|
|
352
|
+
if (mode === "compile") {
|
|
353
|
+
return [...base, ...compile];
|
|
354
|
+
}
|
|
355
|
+
if (mode === "qt") {
|
|
356
|
+
return [...base, ...compile, ...qt];
|
|
357
|
+
}
|
|
358
|
+
if (mode === "images") {
|
|
359
|
+
return [...base, ...images];
|
|
360
|
+
}
|
|
361
|
+
return [...base, ...compile, ...qt, ...images, ...full];
|
|
362
|
+
}
|
|
294
363
|
export async function compileTaishanPiSingleFile(options) {
|
|
295
364
|
assertAuthenticated(options.auth);
|
|
296
365
|
const releaseRoot = await resolveLocalReleaseRoot(options.releaseRoot);
|
|
@@ -400,7 +469,7 @@ async function resolveLocalToolchainDownloadPlan(input) {
|
|
|
400
469
|
return item.board_id === input.boardId
|
|
401
470
|
&& item.host === input.host
|
|
402
471
|
&& item.toolchain === input.toolchain
|
|
403
|
-
&& (item.kind === undefined || item.kind === "toolchain-archive");
|
|
472
|
+
&& (item.kind === undefined || item.kind === "toolchain-archive" || item.kind === "board-support-archive");
|
|
404
473
|
});
|
|
405
474
|
if (!entry?.manifest) {
|
|
406
475
|
return undefined;
|
|
@@ -413,20 +482,24 @@ async function resolveLocalToolchainDownloadPlan(input) {
|
|
|
413
482
|
if (manifest.board_id !== input.boardId || manifest.host !== input.host || manifest.toolchain !== input.toolchain) {
|
|
414
483
|
throw new Error(`Download manifest does not match requested ${input.boardId}/${input.host}/${input.toolchain}.`);
|
|
415
484
|
}
|
|
416
|
-
if (!manifest.archive?.file || !manifest.archive.sha256 || !Number.isFinite(manifest.archive.size_bytes))
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
485
|
+
if ((!manifest.archive?.file || !manifest.archive.sha256 || !Number.isFinite(manifest.archive.size_bytes))
|
|
486
|
+
&& (!Array.isArray(manifest.components) || manifest.components.length === 0)) {
|
|
487
|
+
throw new Error(`Download manifest ${manifest.id}@${manifest.version} is missing archive/components metadata.`);
|
|
488
|
+
}
|
|
489
|
+
const mirrors = manifest.archive
|
|
490
|
+
? orderDownloadMirrors((manifest.mirrors ?? [])
|
|
491
|
+
.filter((mirror) => mirror.enabled !== false && typeof mirror.url === "string" && mirror.url.length > 0)
|
|
492
|
+
.map((mirror) => ({
|
|
493
|
+
kind: mirror.kind || "unknown",
|
|
494
|
+
enabled: mirror.enabled !== false,
|
|
495
|
+
url: mirror.url,
|
|
496
|
+
sha256: typeof mirror.sha256 === "string" ? mirror.sha256 : manifest.archive?.sha256,
|
|
497
|
+
size_bytes: Number.isFinite(mirror.size_bytes) ? mirror.size_bytes : manifest.archive?.size_bytes
|
|
498
|
+
})), manifest.download_policy?.preferred_order)
|
|
499
|
+
: [];
|
|
500
|
+
const components = (manifest.components ?? []).map((component) => normalizeDownloadComponent(component, manifest, manifestUrl));
|
|
428
501
|
const first = mirrors[0];
|
|
429
|
-
if (!first) {
|
|
502
|
+
if (!first && components.length === 0) {
|
|
430
503
|
return undefined;
|
|
431
504
|
}
|
|
432
505
|
return {
|
|
@@ -437,14 +510,52 @@ async function resolveLocalToolchainDownloadPlan(input) {
|
|
|
437
510
|
board_id: input.boardId,
|
|
438
511
|
host: input.host,
|
|
439
512
|
toolchain: input.toolchain,
|
|
440
|
-
source_url: first
|
|
441
|
-
mirror_kind: first
|
|
442
|
-
archive: {
|
|
513
|
+
source_url: first?.url,
|
|
514
|
+
mirror_kind: first?.kind,
|
|
515
|
+
archive: manifest.archive ? {
|
|
443
516
|
file: manifest.archive.file,
|
|
444
517
|
size_bytes: manifest.archive.size_bytes,
|
|
445
518
|
sha256: manifest.archive.sha256,
|
|
446
519
|
content_type: manifest.archive.content_type
|
|
520
|
+
} : undefined,
|
|
521
|
+
mirrors,
|
|
522
|
+
components: components.length > 0 ? components : undefined,
|
|
523
|
+
default_mode: manifest.download_policy?.default_mode
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
function normalizeDownloadComponent(component, manifest, manifestUrl) {
|
|
527
|
+
if (!component.id || !component.version) {
|
|
528
|
+
throw new Error(`Download manifest ${manifest.id}@${manifest.version} contains a component without id/version.`);
|
|
529
|
+
}
|
|
530
|
+
if (!component.archive?.file || !component.archive.sha256 || !Number.isFinite(component.archive.size_bytes)) {
|
|
531
|
+
throw new Error(`Download manifest ${manifest.id}@${manifest.version} component ${component.id} is missing archive metadata.`);
|
|
532
|
+
}
|
|
533
|
+
const mirrors = orderDownloadMirrors((component.mirrors ?? [])
|
|
534
|
+
.filter((mirror) => mirror.enabled !== false && typeof mirror.url === "string" && mirror.url.length > 0)
|
|
535
|
+
.map((mirror) => ({
|
|
536
|
+
kind: mirror.kind || "unknown",
|
|
537
|
+
enabled: mirror.enabled !== false,
|
|
538
|
+
url: new URL(mirror.url, manifestUrl).toString(),
|
|
539
|
+
sha256: typeof mirror.sha256 === "string" ? mirror.sha256 : component.archive?.sha256,
|
|
540
|
+
size_bytes: Number.isFinite(mirror.size_bytes) ? mirror.size_bytes : component.archive?.size_bytes
|
|
541
|
+
})), manifest.download_policy?.preferred_order);
|
|
542
|
+
const first = mirrors[0];
|
|
543
|
+
if (!first) {
|
|
544
|
+
throw new Error(`Download manifest ${manifest.id}@${manifest.version} component ${component.id} has no enabled mirrors.`);
|
|
545
|
+
}
|
|
546
|
+
return {
|
|
547
|
+
id: component.id,
|
|
548
|
+
version: component.version,
|
|
549
|
+
role: component.role,
|
|
550
|
+
install_modes: Array.isArray(component.install_modes) ? component.install_modes : undefined,
|
|
551
|
+
archive: {
|
|
552
|
+
file: component.archive.file,
|
|
553
|
+
size_bytes: component.archive.size_bytes,
|
|
554
|
+
sha256: component.archive.sha256,
|
|
555
|
+
content_type: component.archive.content_type
|
|
447
556
|
},
|
|
557
|
+
source_url: first.url,
|
|
558
|
+
mirror_kind: first.kind,
|
|
448
559
|
mirrors
|
|
449
560
|
};
|
|
450
561
|
}
|
|
@@ -528,7 +639,7 @@ function resolveInstallRoot(installRoot) {
|
|
|
528
639
|
function localToolchainRegistryPath(installRoot) {
|
|
529
640
|
return join(installRoot, "registry", "local-toolchains.json");
|
|
530
641
|
}
|
|
531
|
-
async function writeCurrentRegistry(installRoot, latest, releaseRoot) {
|
|
642
|
+
async function writeCurrentRegistry(installRoot, latest, releaseRoot, mode, source) {
|
|
532
643
|
const registryPath = localToolchainRegistryPath(installRoot);
|
|
533
644
|
await mkdir(dirname(registryPath), { recursive: true });
|
|
534
645
|
await writeFile(registryPath, `${JSON.stringify({
|
|
@@ -537,8 +648,11 @@ async function writeCurrentRegistry(installRoot, latest, releaseRoot) {
|
|
|
537
648
|
version: latest.version,
|
|
538
649
|
channel: latest.channel,
|
|
539
650
|
host: latest.host,
|
|
651
|
+
mode,
|
|
540
652
|
release_root: releaseRoot,
|
|
541
653
|
packages: latest.packages,
|
|
654
|
+
source,
|
|
655
|
+
installed_components: source?.components,
|
|
542
656
|
updated_at: new Date().toISOString()
|
|
543
657
|
}, null, 2)}\n`, "utf8");
|
|
544
658
|
}
|
|
@@ -576,12 +690,18 @@ async function sourceReleaseRootForInstall(options, latest, installRoot, tempDir
|
|
|
576
690
|
source: { kind: "url", value: options.sourceUrl, downloaded_path: downloadedPath }
|
|
577
691
|
};
|
|
578
692
|
}
|
|
693
|
+
if (latest.download?.components?.length) {
|
|
694
|
+
return await componentSourceRootForInstall(options, latest.download, installRoot, tempDir);
|
|
695
|
+
}
|
|
579
696
|
if (latest.download) {
|
|
580
697
|
const failures = [];
|
|
581
698
|
for (const mirror of latest.download.mirrors) {
|
|
582
699
|
if (!mirror.enabled) {
|
|
583
700
|
continue;
|
|
584
701
|
}
|
|
702
|
+
if (!latest.download.archive) {
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
585
705
|
const extractRoot = join(tempDir, `extract-${safeFileToken(mirror.kind)}`);
|
|
586
706
|
try {
|
|
587
707
|
const downloadedPath = await downloadToolchainArchive(mirror.url, installRoot, {
|
|
@@ -622,6 +742,71 @@ async function sourceReleaseRootForInstall(options, latest, installRoot, tempDir
|
|
|
622
742
|
}
|
|
623
743
|
throw new Error("Local toolchain install could not resolve a source. Pass --source-url <tar.gz>, --source-release-root <path>, or configure EMBED_DOWNLOAD_BASE_URL.");
|
|
624
744
|
}
|
|
745
|
+
async function componentSourceRootForInstall(options, download, installRoot, tempDir) {
|
|
746
|
+
const mode = normalizeLocalToolchainInstallMode(options.mode ?? download.default_mode);
|
|
747
|
+
const components = selectedDownloadComponents(download.components ?? [], mode);
|
|
748
|
+
if (components.length === 0) {
|
|
749
|
+
throw new Error(`No local toolchain components selected for mode ${mode}.`);
|
|
750
|
+
}
|
|
751
|
+
const extractRoot = join(tempDir, "extract-components");
|
|
752
|
+
await mkdir(extractRoot, { recursive: true });
|
|
753
|
+
const installedComponents = [];
|
|
754
|
+
const failures = [];
|
|
755
|
+
for (const component of components) {
|
|
756
|
+
let installed = false;
|
|
757
|
+
for (const mirror of component.mirrors) {
|
|
758
|
+
if (!mirror.enabled) {
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
761
|
+
try {
|
|
762
|
+
const downloadedPath = await downloadToolchainArchive(mirror.url, installRoot, {
|
|
763
|
+
sha256: mirror.sha256 ?? component.archive.sha256,
|
|
764
|
+
size_bytes: mirror.size_bytes ?? component.archive.size_bytes
|
|
765
|
+
});
|
|
766
|
+
const extracted = await runCommand(["tar", "-xzf", downloadedPath, "-C", extractRoot], tempDir);
|
|
767
|
+
if (extracted.exit_code !== 0) {
|
|
768
|
+
throw new Error(`Could not extract component ${component.id}: ${extracted.stderr_tail.join("\n")}`);
|
|
769
|
+
}
|
|
770
|
+
installedComponents.push({
|
|
771
|
+
id: component.id,
|
|
772
|
+
version: component.version,
|
|
773
|
+
role: component.role,
|
|
774
|
+
archive_file: component.archive.file,
|
|
775
|
+
mirror_kind: mirror.kind,
|
|
776
|
+
downloaded_path: downloadedPath,
|
|
777
|
+
size_bytes: mirror.size_bytes ?? component.archive.size_bytes,
|
|
778
|
+
sha256: mirror.sha256 ?? component.archive.sha256
|
|
779
|
+
});
|
|
780
|
+
installed = true;
|
|
781
|
+
break;
|
|
782
|
+
}
|
|
783
|
+
catch (error) {
|
|
784
|
+
failures.push(`${component.id}@${component.version}/${mirror.kind}: ${error instanceof Error ? error.message : String(error)}`);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
if (!installed) {
|
|
788
|
+
throw new Error(`Could not install component ${component.id}@${component.version}: ${failures.join("; ")}`);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
return {
|
|
792
|
+
path: extractRoot,
|
|
793
|
+
source: {
|
|
794
|
+
kind: "components",
|
|
795
|
+
value: download.manifest_url,
|
|
796
|
+
mirror_kind: "components",
|
|
797
|
+
size_bytes: installedComponents.reduce((total, component) => total + component.size_bytes, 0),
|
|
798
|
+
components: installedComponents
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
function selectedDownloadComponents(components, mode) {
|
|
803
|
+
return components.filter((component) => {
|
|
804
|
+
if (!component.install_modes?.length) {
|
|
805
|
+
return true;
|
|
806
|
+
}
|
|
807
|
+
return component.install_modes.includes(mode);
|
|
808
|
+
});
|
|
809
|
+
}
|
|
625
810
|
async function downloadToolchainArchive(sourceUrl, installRoot, expected) {
|
|
626
811
|
const downloadsDir = join(installRoot, "cache", "downloads");
|
|
627
812
|
await mkdir(downloadsDir, { recursive: true });
|