@kvell007/embed-labs-cli 0.1.0-alpha.60 → 0.1.0-alpha.62

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.
@@ -184,6 +184,7 @@ export function defaultLocalReleaseRoot() {
184
184
  export async function latestLocalToolchain(options = {}) {
185
185
  const boardId = options.boardId ?? DEFAULT_BOARD_ID;
186
186
  const channelName = options.channel ?? DEFAULT_CHANNEL;
187
+ const host = localToolchainHostId();
187
188
  const { channel, manifests, metadataRoot } = await loadLocalToolchainMetadata(options.metadataRoot, channelName);
188
189
  const boardPackageId = boardPackageIdFor(boardId);
189
190
  const board = manifests.get(boardPackageId);
@@ -198,20 +199,20 @@ export async function latestLocalToolchain(options = {}) {
198
199
  download = await resolveLocalToolchainDownloadPlan({
199
200
  boardId: canonicalBoardId,
200
201
  channel: channelName,
201
- host: hostId(),
202
+ host,
202
203
  toolchain: "llvm"
203
204
  });
204
205
  }
205
206
  catch (error) {
206
207
  downloadError = error instanceof Error ? error.message : String(error);
207
208
  }
208
- if (!download && isNativeWindowsTaishanPiHost(canonicalBoardId, hostId())) {
209
- downloadError = taishanPiWindowsRequirementMessage(hostId());
209
+ if (!download && isNativeWindowsTaishanPiHost(canonicalBoardId, host)) {
210
+ downloadError = taishanPiWindowsRequirementMessage(host);
210
211
  }
211
212
  return {
212
213
  board_id: canonicalBoardId,
213
- channel: channel.channel,
214
- host: hostId(),
214
+ channel: channelName,
215
+ host,
215
216
  version: download?.version ?? board.version,
216
217
  metadata_root: metadataRoot,
217
218
  packages,
@@ -324,7 +325,7 @@ async function discoverInstalledLocalToolchains(installRoot, current) {
324
325
  }
325
326
  export async function listLocalToolchainEnvironments(options = {}) {
326
327
  const channelName = options.channel ?? DEFAULT_CHANNEL;
327
- const host = hostId();
328
+ const host = localToolchainHostId();
328
329
  const installRoot = resolveInstallRoot(options.installRoot);
329
330
  const registryPath = localToolchainRegistryPath(installRoot);
330
331
  const { channel, manifests, metadataRoot } = await loadLocalToolchainMetadata(options.metadataRoot, channelName);
@@ -412,7 +413,7 @@ export async function listLocalToolchainEnvironments(options = {}) {
412
413
  display_name: board.display_name || [board.board, board.variant].filter(Boolean).join(" ") || boardId,
413
414
  family: board.family,
414
415
  variant: board.variant,
415
- channel: channel.channel,
416
+ channel: channelName,
416
417
  host,
417
418
  status,
418
419
  supported_host: effectiveHostSupport.supported,
@@ -430,9 +431,9 @@ export async function listLocalToolchainEnvironments(options = {}) {
430
431
  packages,
431
432
  components: download?.components?.map(localToolchainEnvironmentComponent),
432
433
  execution,
433
- install_command: nativeWindowsTaishanPi ? "embedlabs local wsl status" : `embedlabs local toolchain install --board ${boardId} --mode ${mode}`,
434
- update_command: nativeWindowsTaishanPi ? "embedlabs local wsl status" : `embedlabs local toolchain install --board ${boardId} --mode ${mode} --force`,
435
- notes: environmentNotes({ boardId, status, downloadError, unsupportedPackages: effectiveHostSupport.unsupportedPackages })
434
+ install_command: nativeWindowsTaishanPi ? "embedlabs local wsl status" : `embedlabs local toolchain install --board ${boardId}${localToolchainChannelFlag(channelName)} --mode ${mode}`,
435
+ update_command: nativeWindowsTaishanPi ? "embedlabs local wsl status" : `embedlabs local toolchain install --board ${boardId}${localToolchainChannelFlag(channelName)} --mode ${mode} --force`,
436
+ notes: environmentNotes({ boardId, host, status, downloadError, unsupportedPackages: effectiveHostSupport.unsupportedPackages })
436
437
  });
437
438
  }
438
439
  const filteredEnvironments = options.installedOnly
@@ -440,7 +441,7 @@ export async function listLocalToolchainEnvironments(options = {}) {
440
441
  : environments;
441
442
  return {
442
443
  host,
443
- channel: channel.channel,
444
+ channel: channelName,
444
445
  metadata_source: metadataRoot ? "local_override" : "built_in",
445
446
  metadata_root: metadataRoot,
446
447
  install_root: installRoot,
@@ -1070,6 +1071,9 @@ function rp2350ArmToolchainDirName() {
1070
1071
  }
1071
1072
  export async function compileTaishanPiSingleFile(options) {
1072
1073
  assertAuthenticated(options.auth);
1074
+ if (platform() === "win32" && normalizeBoardId(options.boardId) === DEFAULT_BOARD_ID) {
1075
+ return await compileTaishanPiSingleFileWindowsWsl(options);
1076
+ }
1073
1077
  const releaseRoot = await resolveLocalReleaseRoot(options.releaseRoot);
1074
1078
  const sourcePath = resolve(options.sourcePath);
1075
1079
  const outputPath = resolve(options.outputPath);
@@ -1095,6 +1099,44 @@ export async function compileTaishanPiSingleFile(options) {
1095
1099
  commands: [buildResult]
1096
1100
  });
1097
1101
  }
1102
+ async function compileTaishanPiSingleFileWindowsWsl(options) {
1103
+ const route = taishanPiWindowsExecutionRoute(hostId(), await windowsWslStatus());
1104
+ if (route.actual_host !== "linux-x86_64" || route.status === "wsl_missing" || route.status === "wsl_not_configured" || route.status === "architecture_mismatch") {
1105
+ throw new Error(`${route.reason} Run: embedlabs local wsl status`);
1106
+ }
1107
+ const sourcePath = resolve(options.sourcePath);
1108
+ const outputPath = resolve(options.outputPath);
1109
+ await access(sourcePath, constants.R_OK);
1110
+ await mkdir(dirname(outputPath), { recursive: true });
1111
+ const releaseRoot = options.releaseRoot?.trim()
1112
+ ? normalizeWindowsWslReleaseRoot(options.releaseRoot)
1113
+ : await resolveWindowsWslTaishanPiReleaseRoot();
1114
+ const sourceWslPath = windowsPathToWslPath(sourcePath, "source");
1115
+ const outputWslPath = windowsPathToWslPath(outputPath, "output");
1116
+ const compiler = wslCompilerForSource(releaseRoot, sourcePath);
1117
+ const sysroot = `${trimTrailingSlash(releaseRoot)}/toolchain/host/aarch64-buildroot-linux-gnu/sysroot`;
1118
+ const commandScript = [
1119
+ "set -euo pipefail",
1120
+ `test -x ${sh(compiler)}`,
1121
+ `test -r ${sh(sysroot)}`,
1122
+ `mkdir -p ${sh(posixDirname(outputWslPath))}`,
1123
+ `${sh(compiler)} --sysroot=${sh(sysroot)} -O2 ${sh(sourceWslPath)} -o ${sh(outputWslPath)}`
1124
+ ].join("\n");
1125
+ const buildResult = await runCommand(windowsWslBashCommand(commandScript), homedir());
1126
+ if (buildResult.exit_code !== 0) {
1127
+ throw new Error(`Windows WSL TaishanPi compile failed with exit code ${buildResult.exit_code}: ${buildResult.stderr_tail.join("\n")}`);
1128
+ }
1129
+ return await localCompileResult({
1130
+ boardId: options.boardId,
1131
+ operation: "local.compile.single_file",
1132
+ releaseRoot,
1133
+ accountId: options.accountId,
1134
+ auth: options.auth,
1135
+ sourcePath,
1136
+ artifactPath: outputPath,
1137
+ commands: [buildResult]
1138
+ });
1139
+ }
1098
1140
  export async function buildTaishanPiQtSmoke(options) {
1099
1141
  assertAuthenticated(options.auth);
1100
1142
  const releaseRoot = await resolveLocalReleaseRoot(options.releaseRoot);
@@ -1444,8 +1486,8 @@ function environmentNotes(input) {
1444
1486
  if (isRp2350MonitorBoardId(input.boardId)) {
1445
1487
  notes.push("RP2350 Monitor is an optional hardware-control firmware image; the board environment itself is the Pico 2 W or ColorEasyPICO2 C/C++ SDK environment.");
1446
1488
  }
1447
- if (isNativeWindowsTaishanPiHost(input.boardId, hostId())) {
1448
- notes.push(taishanPiWindowsRequirementMessage(hostId()));
1489
+ if (isNativeWindowsTaishanPiHost(input.boardId, input.host)) {
1490
+ notes.push(taishanPiWindowsRequirementMessage(input.host));
1449
1491
  notes.push("Check this computer with: embedlabs local wsl status");
1450
1492
  }
1451
1493
  if (input.status === "available") {
@@ -1476,6 +1518,9 @@ function localToolchainInstallModesForDownload(download) {
1476
1518
  ? [...modes].filter((mode) => LOCAL_TOOLCHAIN_INSTALL_MODES.includes(mode))
1477
1519
  : [...LOCAL_TOOLCHAIN_INSTALL_MODES];
1478
1520
  }
1521
+ function localToolchainChannelFlag(channelName) {
1522
+ return channelName === DEFAULT_CHANNEL ? "" : ` --channel ${channelName}`;
1523
+ }
1479
1524
  function localToolchainEnvironmentComponent(component) {
1480
1525
  return {
1481
1526
  id: component.id,
@@ -1496,6 +1541,23 @@ function hostId() {
1496
1541
  }
1497
1542
  return `${platform()}-${arch()}`;
1498
1543
  }
1544
+ const LOCAL_TOOLCHAIN_HOST_IDS = new Set([
1545
+ "darwin-arm64",
1546
+ "linux-x86_64",
1547
+ "linux-arm64",
1548
+ "win32-x64",
1549
+ "win32-arm64"
1550
+ ]);
1551
+ function localToolchainHostId() {
1552
+ const override = process.env.EMBEDLABS_TEST_LOCAL_TOOLCHAIN_HOST?.trim();
1553
+ if (override) {
1554
+ if (!LOCAL_TOOLCHAIN_HOST_IDS.has(override)) {
1555
+ throw new Error(`Unsupported EMBEDLABS_TEST_LOCAL_TOOLCHAIN_HOST value: ${override}`);
1556
+ }
1557
+ return override;
1558
+ }
1559
+ return hostId();
1560
+ }
1499
1561
  function isNativeWindowsTaishanPiHost(boardId, host) {
1500
1562
  return normalizeBoardId(boardId) === DEFAULT_BOARD_ID && host.startsWith("win32-");
1501
1563
  }
@@ -2017,6 +2079,84 @@ function compilerForSource(releaseRoot, sourcePath) {
2017
2079
  }
2018
2080
  throw new Error(`Unsupported source extension ${extension || "<none>"}; use .c, .cc, .cpp, or .cxx.`);
2019
2081
  }
2082
+ function wslCompilerForSource(releaseRoot, sourcePath) {
2083
+ const extension = extname(sourcePath).toLowerCase();
2084
+ const binDir = `${trimTrailingSlash(releaseRoot)}/toolchain/llvm-cross/bin`;
2085
+ if (extension === ".c") {
2086
+ return `${binDir}/aarch64-linux-gnu-gcc`;
2087
+ }
2088
+ if ([".cc", ".cpp", ".cxx"].includes(extension)) {
2089
+ return `${binDir}/aarch64-linux-gnu-g++`;
2090
+ }
2091
+ throw new Error(`Unsupported source extension ${extension || "<none>"}; use .c, .cc, .cpp, or .cxx.`);
2092
+ }
2093
+ async function resolveWindowsWslTaishanPiReleaseRoot() {
2094
+ const explicit = process.env.EMBEDLABS_WINDOWS_WSL_TAISHANPI_RELEASE_ROOT?.trim()
2095
+ || process.env.EMBEDLABS_WSL_TAISHANPI_RELEASE_ROOT?.trim();
2096
+ if (explicit) {
2097
+ return normalizeWindowsWslReleaseRoot(explicit);
2098
+ }
2099
+ const script = `
2100
+ set -euo pipefail
2101
+ REGISTRY="\${EMBEDLABS_WSL_INSTALL_ROOT:-$HOME/.embedlabs}/registry/local-toolchains.json"
2102
+ if [ ! -f "$REGISTRY" ]; then
2103
+ echo "Missing WSL TaishanPi registry at $REGISTRY. Run inside WSL first: npx -y embedlabs@latest local toolchain install --board taishanpi-1m-rk3566 --channel windows-test --mode compile" >&2
2104
+ exit 2
2105
+ fi
2106
+ node - "$REGISTRY" <<'NODE'
2107
+ const fs = require("fs");
2108
+ const registry = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
2109
+ const env = registry.environments && registry.environments["taishanpi-1m-rk3566"] ? registry.environments["taishanpi-1m-rk3566"] : registry;
2110
+ if (!env.release_root) {
2111
+ console.error("WSL TaishanPi registry does not contain release_root. Reinstall with: npx -y embedlabs@latest local toolchain install --board taishanpi-1m-rk3566 --channel windows-test --mode compile");
2112
+ process.exit(2);
2113
+ }
2114
+ console.log(env.release_root);
2115
+ NODE
2116
+ `;
2117
+ const result = await runCommand(windowsWslBashCommand(script), homedir());
2118
+ if (result.exit_code !== 0 || result.stdout_tail.length < 1) {
2119
+ throw new Error(`Unable to resolve TaishanPi WSL release root: ${result.stderr_tail.join("\n") || result.stdout_tail.join("\n")}`);
2120
+ }
2121
+ return normalizeWindowsWslReleaseRoot(result.stdout_tail[result.stdout_tail.length - 1]);
2122
+ }
2123
+ function normalizeWindowsWslReleaseRoot(value) {
2124
+ const trimmed = value.trim();
2125
+ if (!trimmed) {
2126
+ throw new Error("WSL release root cannot be empty.");
2127
+ }
2128
+ if (/^[A-Za-z]:[\\/]/.test(trimmed)) {
2129
+ return windowsPathToWslPath(resolve(trimmed), "release-root");
2130
+ }
2131
+ if (trimmed.startsWith("/")) {
2132
+ return trimTrailingSlash(trimmed);
2133
+ }
2134
+ throw new Error(`WSL release root must be a Linux absolute path or Windows drive path, got ${value}.`);
2135
+ }
2136
+ function windowsPathToWslPath(value, label) {
2137
+ const normalized = value.replaceAll("\\", "/");
2138
+ const match = normalized.match(/^([A-Za-z]):\/(.*)$/);
2139
+ if (!match) {
2140
+ throw new Error(`Windows WSL ${label} path must be an absolute drive path, got ${value}.`);
2141
+ }
2142
+ return `/mnt/${match[1].toLowerCase()}/${match[2]}`;
2143
+ }
2144
+ function windowsWslBashCommand(script) {
2145
+ const command = ["wsl.exe"];
2146
+ const distro = process.env.EMBEDLABS_WINDOWS_WSL_DISTRO?.trim();
2147
+ if (distro) {
2148
+ command.push("-d", distro);
2149
+ }
2150
+ command.push("--", "bash", "-lc", script);
2151
+ return command;
2152
+ }
2153
+ function posixDirname(value) {
2154
+ const index = value.lastIndexOf("/");
2155
+ return index > 0 ? value.slice(0, index) : "/";
2156
+ }
2157
+ function sh(value) {
2158
+ return `'${value.replaceAll("'", "'\\''")}'`;
2159
+ }
2020
2160
  async function localCompileResult(input) {
2021
2161
  await access(input.artifactPath, constants.R_OK);
2022
2162
  const artifactInfo = await stat(input.artifactPath);