From 5fd2be27ed2413d7ae3556afed303ea56f9be569 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 20 Sep 2026 22:14:10 -0500 Subject: [PATCH] Preserve glibc 2.36 runtime compatibility - Pin GNU runtime packs to glibc 2.36 Zig targets and reject incompatible imports. - Provision verified Zig builds and exercise published artifacts on Debian Bookworm. Fixes #349 Co-authored-by: Cruel <383999+Cruel@users.noreply.github.com> --- .github/workflows/ci.yml | 12 +++- .github/workflows/release.yml | 2 + AGENTS.md | 7 +- Dockerfile.sandbox | 5 ++ .../runtime-linux-arm64-gnu/scripts/build.mjs | 13 +++- .../runtime-linux-x64-gnu/scripts/build.mjs | 13 +++- .../scripts/artifact-policy.mjs | 32 +++++++++ .../runtime-pack-common/scripts/build.mjs | 9 ++- .../scripts/glibc-toolchain.mjs | 23 ++++++ .../test/artifact-policy.test.ts | 31 ++++++++ .../test/glibc-toolchain.test.ts | 24 +++++++ scripts/install-zig.sh | 72 +++++++++++++++++++ scripts/sandbox-bootstrap.sh | 7 +- scripts/sandbox-test.mjs | 23 +++++- 14 files changed, 254 insertions(+), 19 deletions(-) create mode 100644 packages/runtime-pack-common/scripts/artifact-policy.mjs create mode 100644 packages/runtime-pack-common/scripts/glibc-toolchain.mjs create mode 100644 packages/runtime-pack-common/test/artifact-policy.test.ts create mode 100644 packages/runtime-pack-common/test/glibc-toolchain.test.ts create mode 100644 scripts/install-zig.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fbe4d461b..32a155330 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,7 +114,7 @@ jobs: llvm_artifacts_linux_x64: name: test (Linux x64 LLVM artifacts, no clang) runs-on: ubuntu-24.04 - timeout-minutes: 25 + timeout-minutes: 30 steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 @@ -135,10 +135,10 @@ jobs: echo "$RUNNER_TEMP/llvm-22.1.8/bin" >> "$GITHUB_PATH" - run: pnpm install --frozen-lockfile - run: pnpm --filter @scriptc/llvm-linux-x64-gnu build:native - - run: CC=clang AR=llvm-ar pnpm --filter @scriptc/runtime-linux-x64-gnu build:native - uses: vercel-labs/setup-zig@v1 with: version: 0.16.0 + - run: CC=zig AR=zig pnpm --filter @scriptc/runtime-linux-x64-gnu build:native - name: Build and verify Linux x64 musl release packages run: | pnpm --filter @scriptc/llvm-linux-x64-musl build:native @@ -173,6 +173,14 @@ jobs: run: | node packages/cli/dist/main.js build tests/corpus/001-hello.ts -o "$RUNNER_TEMP/hello" test "$("$RUNNER_TEMP/hello")" = 'hello world' + - name: Debian 12 glibc 2.36 runtime-pack smoke + run: | + docker run --rm --volume "$GITHUB_WORKSPACE:/work:ro" --workdir /work node:24-bookworm-slim sh -ec ' + apt-get update + apt-get install --yes --no-install-recommends clang + SCRIPTC_NO_CACHE=1 SCRIPTC_LEGACY_C_PIPELINE=0 node packages/cli/dist/main.js build tests/corpus/001-hello.ts -o /tmp/hello + test "$(/tmp/hello)" = "hello world" + ' - name: WASI helper object/runtime-pack smoke run: pnpm test packages/cli/test/wasi-runtime-pack.test.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d4e4619df..629d4ed50 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -70,11 +70,13 @@ jobs: helper: llvm-linux-x64-gnu runtime: runtime-linux-x64-gnu llvm_asset: LLVM-22.1.8-Linux-X64 + use_zig: true - platform: linux-arm64 runner: ubuntu-24.04-arm helper: llvm-linux-arm64-gnu runtime: runtime-linux-arm64-gnu llvm_asset: LLVM-22.1.8-Linux-ARM64 + use_zig: true - platform: windows-x64 runner: windows-2022 helper: llvm-win32-x64-msvc diff --git a/AGENTS.md b/AGENTS.md index d6cfa48da..e99348b3f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,12 +13,7 @@ pnpm install && pnpm -r build # build the workspace pnpm test:sandbox # full gate: ~4m custom image, ~9m cold managed fallback ``` -The ordinary workspace build does not rebuild packaged native artifacts. -When changing native assembly/object emission or runtime-pack selection, -install CMake, Ninja, and the pinned LLVM 22 development package, then run the -matching `@scriptc/llvm-` and `@scriptc/runtime-` -`build:native` scripts explicitly. The macOS full test suite also needs its -generated artifacts. +The ordinary workspace build does not rebuild packaged native artifacts. When changing native assembly/object emission or runtime-pack selection, install CMake, Ninja, the pinned LLVM 22 development package, and Zig 0.16.0, then run the matching `@scriptc/llvm-` and `@scriptc/runtime-` `build:native` scripts explicitly. The macOS full test suite also needs its generated artifacts. Use focused local tests while iterating, then use `pnpm test:sandbox` whenever a full validation gate is required. It loads Sandbox configuration from the diff --git a/Dockerfile.sandbox b/Dockerfile.sandbox index 63745404d..6192f52d4 100644 --- a/Dockerfile.sandbox +++ b/Dockerfile.sandbox @@ -3,6 +3,7 @@ FROM ubuntu:24.04 ARG NODE_VERSION ARG PNPM_VERSION=11.1.3 +ARG ZIG_VERSION=0.16.0 ENV DEBIAN_FRONTEND=noninteractive @@ -45,6 +46,10 @@ RUN curl --fail --silent --show-error --location \ && tar -xJf /tmp/node.tar.xz --directory /usr/local --strip-components=1 \ && rm /tmp/node.tar.xz /tmp/SHASUMS256.txt +COPY scripts/install-zig.sh /tmp/install-zig.sh +RUN sh /tmp/install-zig.sh "$ZIG_VERSION" \ + && rm /tmp/install-zig.sh + RUN npm install --global "pnpm@${PNPM_VERSION}" WORKDIR /workspace diff --git a/packages/runtime-linux-arm64-gnu/scripts/build.mjs b/packages/runtime-linux-arm64-gnu/scripts/build.mjs index 32278d0a3..7ee089c8d 100644 --- a/packages/runtime-linux-arm64-gnu/scripts/build.mjs +++ b/packages/runtime-linux-arm64-gnu/scripts/build.mjs @@ -1,5 +1,12 @@ -import { dirname } from "node:path"; import { fileURLToPath } from "node:url"; -if (process.platform !== "linux" || process.arch !== "arm64") { process.stdout.write("@scriptc/runtime-linux-arm64-gnu: skipped on this host\n"); process.exit(0); } +import { dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import { glibcRuntimeToolchain } from "../../runtime-pack-common/scripts/glibc-toolchain.mjs"; + +if (process.platform !== "linux" || process.arch !== "arm64") { + process.stdout.write("@scriptc/runtime-linux-arm64-gnu: skipped on this host\n"); + process.exit(0); +} process.env.SCRIPTC_RUNTIME_PACK_ROOT = dirname(dirname(fileURLToPath(import.meta.url))); -process.env.SCRIPTC_RUNTIME_PACK_CONFIG = JSON.stringify({ platform: "linux", runtimeDefines: ["_GNU_SOURCE"], threadArgs: ["-pthread"], target: { name: "linux-arm64-gnu", llvm_triple: "aarch64-unknown-linux-gnu", architecture: "arm64", object_format: "elf", minimum_os: "glibc 2.36" }, targetArgs: ["-target", "aarch64-unknown-linux-gnu"], compileFlags: ["-ffunction-sections", "-fdata-sections"], systemLibraries: [{ name: "m", predicate: true }] }); +const { minimumOs, ...toolchain } = glibcRuntimeToolchain("arm64"); +process.env.SCRIPTC_RUNTIME_PACK_CONFIG = JSON.stringify({ platform: "linux", runtimeDefines: ["_GNU_SOURCE"], threadArgs: ["-pthread"], target: { name: "linux-arm64-gnu", llvm_triple: "aarch64-unknown-linux-gnu", architecture: "arm64", object_format: "elf", minimum_os: minimumOs }, ...toolchain, compileFlags: ["-ffunction-sections", "-fdata-sections"], systemLibraries: [{ name: "m", predicate: true }] }); await import("../../runtime-pack-common/scripts/build.mjs"); diff --git a/packages/runtime-linux-x64-gnu/scripts/build.mjs b/packages/runtime-linux-x64-gnu/scripts/build.mjs index ea5477334..7d226dabd 100644 --- a/packages/runtime-linux-x64-gnu/scripts/build.mjs +++ b/packages/runtime-linux-x64-gnu/scripts/build.mjs @@ -1,5 +1,12 @@ -import { dirname } from "node:path"; import { fileURLToPath } from "node:url"; -if (process.platform !== "linux" || process.arch !== "x64") { process.stdout.write("@scriptc/runtime-linux-x64-gnu: skipped on this host\n"); process.exit(0); } +import { dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import { glibcRuntimeToolchain } from "../../runtime-pack-common/scripts/glibc-toolchain.mjs"; + +if (process.platform !== "linux" || process.arch !== "x64") { + process.stdout.write("@scriptc/runtime-linux-x64-gnu: skipped on this host\n"); + process.exit(0); +} process.env.SCRIPTC_RUNTIME_PACK_ROOT = dirname(dirname(fileURLToPath(import.meta.url))); -process.env.SCRIPTC_RUNTIME_PACK_CONFIG = JSON.stringify({ platform: "linux", runtimeDefines: ["_GNU_SOURCE"], threadArgs: ["-pthread"], target: { name: "linux-x64-gnu", llvm_triple: "x86_64-unknown-linux-gnu", architecture: "x64", object_format: "elf", minimum_os: "glibc 2.36" }, targetArgs: ["-target", "x86_64-unknown-linux-gnu"], compileFlags: ["-ffunction-sections", "-fdata-sections"], systemLibraries: [{ name: "m", predicate: true }] }); +const { minimumOs, ...toolchain } = glibcRuntimeToolchain("x64"); +process.env.SCRIPTC_RUNTIME_PACK_CONFIG = JSON.stringify({ platform: "linux", runtimeDefines: ["_GNU_SOURCE"], threadArgs: ["-pthread"], target: { name: "linux-x64-gnu", llvm_triple: "x86_64-unknown-linux-gnu", architecture: "x64", object_format: "elf", minimum_os: minimumOs }, ...toolchain, compileFlags: ["-ffunction-sections", "-fdata-sections"], systemLibraries: [{ name: "m", predicate: true }] }); await import("../../runtime-pack-common/scripts/build.mjs"); diff --git a/packages/runtime-pack-common/scripts/artifact-policy.mjs b/packages/runtime-pack-common/scripts/artifact-policy.mjs new file mode 100644 index 000000000..a34c3cd54 --- /dev/null +++ b/packages/runtime-pack-common/scripts/artifact-policy.mjs @@ -0,0 +1,32 @@ +import { readFile, readdir } from "node:fs/promises"; +import { join, relative, sep } from "node:path"; + +export async function assertArtifactsExcludeStrings(root, forbiddenStrings) { + if (!Array.isArray(forbiddenStrings)) { + throw new Error("runtime-pack forbidden artifact strings must be an array"); + } + if (forbiddenStrings.length === 0) return; + if (forbiddenStrings.some((value) => typeof value !== "string" || value.length === 0)) { + throw new Error("runtime-pack forbidden artifact strings must be non-empty strings"); + } + const forbidden = forbiddenStrings.map((value) => ({ value, bytes: Buffer.from(value) })); + const visit = async (directory) => { + for (const entry of await readdir(directory, { withFileTypes: true })) { + const path = join(directory, entry.name); + if (entry.isDirectory()) { + await visit(path); + continue; + } + if (!entry.isFile()) continue; + const bytes = await readFile(path); + const match = forbidden.find((candidate) => bytes.includes(candidate.bytes)); + if (match !== undefined) { + const artifact = relative(root, path).split(sep).join("/"); + throw new Error( + `runtime-pack artifact ${artifact} contains forbidden symbol family ${match.value}`, + ); + } + } + }; + await visit(root); +} diff --git a/packages/runtime-pack-common/scripts/build.mjs b/packages/runtime-pack-common/scripts/build.mjs index bef41b2de..ebab80d1e 100644 --- a/packages/runtime-pack-common/scripts/build.mjs +++ b/packages/runtime-pack-common/scripts/build.mjs @@ -3,10 +3,11 @@ import { execFile } from "node:child_process"; import { createHash } from "node:crypto"; import { availableParallelism, tmpdir } from "node:os"; import { copyFile, mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from "node:fs/promises"; -import { basename, dirname, join, relative, sep } from "node:path"; +import { dirname, join, relative, sep } from "node:path"; import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; import { createRuntimePackMatrix } from "../runtime-pack-matrix.mjs"; +import { assertArtifactsExcludeStrings } from "./artifact-policy.mjs"; import { createDeterministicArchive } from "./archive.mjs"; import { installRuntimePack, withBuildLock } from "./build-state.mjs"; @@ -35,7 +36,7 @@ async function build() { const archiver = process.env.AR ?? config.archiver ?? "ar"; const archiverArgs = config.archiverArgs ?? []; const commonFlags = [ - ...config.targetArgs, "-std=c11", ...(config.threadArgs ?? []), "-fno-math-errno", "-fno-strict-aliasing", + ...config.targetArgs, ...(config.compilerFlags ?? []), "-std=c11", ...(config.threadArgs ?? []), "-fno-math-errno", "-fno-strict-aliasing", ...matrix.executable_section_elimination.compile_flags, "-Wno-deprecated-declarations", "-I", runtimeSrc, ...(config.runtimeDefines ?? []).map((define) => `-D${define}`), ]; @@ -55,6 +56,7 @@ async function build() { for (let i = 0; i < items.length; i += width) await Promise.all(items.slice(i, i + width).map(task)); }; const archive = async (id, sources, sourceRoot, flags) => { + process.stdout.write(`building ${packageManifest.name} ${id} archive\n`); const root = join(stagedOutputRoot, "vendor", id); const objectRoot = join(root, "objects"); await parallel(sources, async (source) => compile(join(sourceRoot, source), join(objectRoot, source.replace(/\.c$/, ".o")), flags)); @@ -80,6 +82,7 @@ async function build() { } const flavors = {}; for (const [flavor, flavorSpec] of Object.entries(matrix.flavors)) { + process.stdout.write(`building ${packageManifest.name} ${flavor} runtime\n`); const units = []; for (const unit of matrix.runtime_units) { const variants = []; @@ -103,6 +106,7 @@ async function build() { // selected, so vendor sources inherit the target's runtime defines too. const vendorTarget = [ ...config.targetArgs, + ...(config.compilerFlags ?? []), ...(config.runtimeDefines ?? []).map((define) => `-D${define}`), ]; const requestedArchives = new Set(matrix.archives.map((entry) => entry.id)); @@ -112,6 +116,7 @@ async function build() { ...(requestedArchives.has("zlib") ? [await archive("zlib", zlibSources, zlib, [...vendorTarget, "-std=c11", "-Os", "-I", zlib])] : []), ...(requestedArchives.has("mbedtls") ? [await archive("mbedtls", mbedtlsSources, join(mbedtls, "library"), [...vendorTarget, "-std=c11", "-Os", "-I", join(mbedtls, "include"), "-I", join(mbedtls, "library")])] : []), ]; + await assertArtifactsExcludeStrings(stagedOutputRoot, config.forbiddenArtifactStrings ?? []); const archiveSpecs = new Map(matrix.archives.map((entry) => [entry.id, entry])); const licensed = [[join(runtimeRoot, "LICENSE"), "artifacts/licenses/scriptc-runtime.txt", "Apache-2.0"], [join(quickjs, "LICENSE"), "artifacts/licenses/quickjs-ng.txt", "MIT"], [join(vendorRoot, "ryu", "LICENSE-Boost"), "artifacts/licenses/ryu.txt", "BSL-1.0"], [join(zlib, "LICENSE"), "artifacts/licenses/zlib.txt", "Zlib"], [join(mbedtls, "LICENSE"), "artifacts/licenses/mbedtls.txt", "Apache-2.0"]]; await Promise.all(licensed.map(async ([source, destination]) => { const output = join(buildRoot, destination); await mkdir(dirname(output), { recursive: true }); await copyFile(source, output); })); diff --git a/packages/runtime-pack-common/scripts/glibc-toolchain.mjs b/packages/runtime-pack-common/scripts/glibc-toolchain.mjs new file mode 100644 index 000000000..fac61e750 --- /dev/null +++ b/packages/runtime-pack-common/scripts/glibc-toolchain.mjs @@ -0,0 +1,23 @@ +const GLIBC_RUNTIME_FLOOR = "2.36"; + +const GNU_ARCHITECTURES = { + x64: "x86_64", + arm64: "aarch64", +}; + +export function glibcRuntimeToolchain(architecture) { + const targetArchitecture = GNU_ARCHITECTURES[architecture]; + if (targetArchitecture === undefined) { + throw new Error(`unsupported GNU runtime architecture: ${architecture}`); + } + return { + minimumOs: `glibc ${GLIBC_RUNTIME_FLOOR}`, + compiler: "zig", + compilerArgs: ["cc"], + archiver: "zig", + archiverArgs: ["ar"], + targetArgs: ["-target", `${targetArchitecture}-linux-gnu.${GLIBC_RUNTIME_FLOOR}`], + compilerFlags: ["-fno-sanitize=undefined"], + forbiddenArtifactStrings: ["__isoc23_", "__ubsan_"], + }; +} diff --git a/packages/runtime-pack-common/test/artifact-policy.test.ts b/packages/runtime-pack-common/test/artifact-policy.test.ts new file mode 100644 index 000000000..8cbf7b3ee --- /dev/null +++ b/packages/runtime-pack-common/test/artifact-policy.test.ts @@ -0,0 +1,31 @@ +import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { expect, test } from "vitest"; +import { assertArtifactsExcludeStrings } from "../scripts/artifact-policy.mjs"; + +test("accepts artifacts whose imports stay within the declared libc floor", async () => { + const root = await mkdtemp(join(tmpdir(), "scriptc-artifact-policy-")); + try { + await writeFile(join(root, "runtime.o"), Buffer.from("strtol\0__isoc99_sscanf\0")); + await expect(assertArtifactsExcludeStrings(root, ["__isoc23_", "__ubsan_"])).resolves.toBeUndefined(); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test.each([ + ["__isoc23_sscanf", "__isoc23_"], + ["__ubsan_handle_add_overflow", "__ubsan_"], +])("rejects the %s import in nested runtime artifacts", async (symbol, family) => { + const root = await mkdtemp(join(tmpdir(), "scriptc-artifact-policy-")); + try { + await mkdir(join(root, "release", "runtime"), { recursive: true }); + await writeFile(join(root, "release", "runtime", "scr_lib.o"), Buffer.from(`${symbol}\0`)); + await expect(assertArtifactsExcludeStrings(root, ["__isoc23_", "__ubsan_"])).rejects.toThrow( + `runtime-pack artifact release/runtime/scr_lib.o contains forbidden symbol family ${family}`, + ); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); diff --git a/packages/runtime-pack-common/test/glibc-toolchain.test.ts b/packages/runtime-pack-common/test/glibc-toolchain.test.ts new file mode 100644 index 000000000..e520cbc03 --- /dev/null +++ b/packages/runtime-pack-common/test/glibc-toolchain.test.ts @@ -0,0 +1,24 @@ +import { expect, test } from "vitest"; +import { glibcRuntimeToolchain } from "../scripts/glibc-toolchain.mjs"; + +test.each([ + ["x64", "x86_64-linux-gnu.2.36"], + ["arm64", "aarch64-linux-gnu.2.36"], +])("pins the %s GNU runtime to its glibc floor", (architecture, target) => { + expect(glibcRuntimeToolchain(architecture)).toEqual({ + minimumOs: "glibc 2.36", + compiler: "zig", + compilerArgs: ["cc"], + archiver: "zig", + archiverArgs: ["ar"], + targetArgs: ["-target", target], + compilerFlags: ["-fno-sanitize=undefined"], + forbiddenArtifactStrings: ["__isoc23_", "__ubsan_"], + }); +}); + +test("rejects unknown GNU runtime architectures", () => { + expect(() => glibcRuntimeToolchain("riscv64")).toThrow( + "unsupported GNU runtime architecture: riscv64", + ); +}); diff --git a/scripts/install-zig.sh b/scripts/install-zig.sh new file mode 100644 index 000000000..81cabbe97 --- /dev/null +++ b/scripts/install-zig.sh @@ -0,0 +1,72 @@ +#!/bin/sh +set -eu + +version=${1:?usage: install-zig.sh } +case "$version" in + *[!0-9A-Za-z.+-]*) echo "invalid Zig version: $version" >&2; exit 1 ;; +esac + +case "$(uname -s)-$(uname -m)" in + Linux-x86_64) target=x86_64-linux ;; + Linux-aarch64 | Linux-arm64) target=aarch64-linux ;; + *) echo "unsupported Zig host: $(uname -s)/$(uname -m)" >&2; exit 1 ;; +esac + +if command -v zig >/dev/null 2>&1 && [ "$(zig version)" = "$version" ]; then + exit 0 +fi + +temporary=$(mktemp -d) +trap 'rm -rf "$temporary"' EXIT HUP INT TERM + +curl --fail --silent --show-error --location \ + https://ziglang.org/download/index.json \ + --output "$temporary/index.json" +index_size=$(wc -c <"$temporary/index.json" | tr -d ' ') +if [ "$index_size" -gt 2097152 ]; then + echo "Zig download index exceeds 2 MiB" >&2 + exit 1 +fi + +node --input-type=module - "$version" "$target" "$temporary/index.json" >"$temporary/metadata" <<'NODE' +import { readFile } from "node:fs/promises"; + +const [version, target, indexPath] = process.argv.slice(2); +const release = JSON.parse(await readFile(indexPath, "utf8"))[version]; +const artifact = release?.[target]; +if (!artifact?.tarball || !artifact?.shasum || !artifact?.size) { + throw new Error(`Zig ${version} does not publish ${target}`); +} +const url = new URL(artifact.tarball); +if (url.protocol !== "https:" || !/^[0-9a-f]{64}$/.test(artifact.shasum)) { + throw new Error(`Zig ${version} returned invalid ${target} metadata`); +} +process.stdout.write(`${url.href}\n${artifact.shasum}\n${artifact.size}\n`); +NODE + +url=$(sed -n '1p' "$temporary/metadata") +sha256=$(sed -n '2p' "$temporary/metadata") +expected_size=$(sed -n '3p' "$temporary/metadata") +archive="$temporary/zig.tar.xz" +curl --fail --silent --show-error --location "$url" --output "$archive" +printf '%s %s\n' "$sha256" "$archive" | sha256sum --check --strict - +actual_size=$(wc -c <"$archive" | tr -d ' ') +if [ "$actual_size" != "$expected_size" ]; then + echo "Zig archive size mismatch: expected $expected_size, got $actual_size" >&2 + exit 1 +fi + +as_root() { + if [ "$(id -u)" -eq 0 ]; then + "$@" + else + sudo "$@" + fi +} + +destination="/opt/zig-$version" +as_root rm -rf "$destination" +as_root mkdir -p "$destination" +as_root tar -xJf "$archive" --directory "$destination" --strip-components=1 +as_root ln -sf "$destination/zig" /usr/local/bin/zig +test "$(zig version)" = "$version" diff --git a/scripts/sandbox-bootstrap.sh b/scripts/sandbox-bootstrap.sh index fe8186ac5..54c06c8b1 100644 --- a/scripts/sandbox-bootstrap.sh +++ b/scripts/sandbox-bootstrap.sh @@ -3,8 +3,9 @@ set -eu node_version=$(tr -d '\r\n' < .node-version) pnpm_version=$(sed -n 's/^ARG PNPM_VERSION=//p' Dockerfile.sandbox) -if [ -z "$node_version" ] || [ -z "$pnpm_version" ]; then - echo "could not read the pinned Node or pnpm version" >&2 +zig_version=$(sed -n 's/^ARG ZIG_VERSION=//p' Dockerfile.sandbox) +if [ -z "$node_version" ] || [ -z "$pnpm_version" ] || [ -z "$zig_version" ]; then + echo "could not read the pinned Node, pnpm, or Zig version" >&2 exit 1 fi @@ -50,6 +51,7 @@ grep " ${node_archive}$" /tmp/node-SHASUMS256.txt \ | sed "s# ${node_archive}# /tmp/${node_archive}#" \ | sha256sum --check --strict - sudo tar -xJf "/tmp/${node_archive}" --directory /usr/local --strip-components=1 +sh scripts/install-zig.sh "$zig_version" sudo npm install --global "pnpm@${pnpm_version}" rm -f "/tmp/${node_archive}" /tmp/node-SHASUMS256.txt /tmp/llvm-snapshot.gpg.key /tmp/llvm-snapshot.gpg @@ -57,4 +59,5 @@ sudo rm -rf /var/lib/apt/lists/* node --version pnpm --version +zig version clang --version | sed -n '1p' diff --git a/scripts/sandbox-test.mjs b/scripts/sandbox-test.mjs index d7b624265..8964b488e 100644 --- a/scripts/sandbox-test.mjs +++ b/scripts/sandbox-test.mjs @@ -725,7 +725,28 @@ try { // Workspace builds deliberately do not rebuild packaged native artifacts. // Every remote lane needs the Linux helper and runtime from this worktree. await execIn(worker, "pnpm", ["--filter", "@scriptc/llvm-linux-x64-gnu", "build:native"], {}, "LLVM helper", 5 * 60_000); - await execIn(worker, "pnpm", ["--filter", "@scriptc/runtime-linux-x64-gnu", "build:native"], { CC: "clang-22", AR: "llvm-ar-22" }, "runtime pack", 5 * 60_000); + await execIn( + worker, + "pnpm", + ["--filter", "@scriptc/runtime-linux-x64-gnu", "build:native"], + { CC: "zig", AR: "zig" }, + "runtime pack", + 5 * 60_000, + "/workspace", + 3 * 60_000, + ); + // Zig is a build-only dependency in this lane. Cross-target suites own + // the conditional Zig tests; exposing it here would silently expand the + // native-cache shard while that shard deliberately disables stable + // toolchain caching. + await execIn( + worker, + "sudo", + ["rm", "-f", "/usr/local/bin/zig"], + {}, + "runtime toolchain cleanup", + 60_000, + ); }, imageConfig.custom ? workers.length : 8); await allWorkers("Testing", async (worker) => {