mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-02 00:25:34 +08:00
* Expand LLVM native output targets - Add owned helper and runtime-pack contracts for macOS x64, Linux glibc/musl, Windows x64, and WASI. - Generalize LLVM target selection, package validation, linker plans, and release/CI matrices. - Document linker boundaries and add focused native-output and runtime-pack coverage. * Pin Windows LLVM helper CI to VS 2022 The official LLVM archive needs the Visual Studio 2022 CMake generator, which is not guaranteed by windows-latest. * Align bootstrap cache with LLVM runtime-pack builds Use the same target-specific helper and linker identity before and after compiler loading so routed executable cache hits remain lightweight. * Install Windows LLVM CI tools with Chocolatey windows-2022 includes Visual Studio but not winget; use its available Chocolatey bootstrap for CMake and Ninja. * Prevent Zig version probes from leaving runtime-pack objects Run runtime-pack compiler version probes in a private temporary directory and remove the accidental tracked WASI a.o file. * Use 7-Zip for Windows LLVM setup Extract the official LLVM development archive with two-stage 7-Zip instead of Windows tar.exe, which timed out while materializing the toolchain tree. * Define ssize_t for the MSVC runtime pack Clang's MSVC target does not expose POSIX ssize_t through sys/types.h; define the pointer-sized runtime type without affecting MinGW. * Build the Windows runtime pack with Zig Use the MinGW-compatible Windows sysroot required by the runtime while retaining COFF helper output and installing Zig in the Windows native CI lane. * Give macOS LLVM differential shard time to finish Shard 1 passed its setup and focused contracts but was cancelled during its cold LLVM differential slice at the 20-minute job limit.
104 lines
5.2 KiB
JavaScript
104 lines
5.2 KiB
JavaScript
#!/usr/bin/env node
|
|
import { execFileSync } from "node:child_process";
|
|
import { accessSync, constants, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { validateLlvmHelperExports } from "./llvm-package-symbols.mjs";
|
|
|
|
const tarball = process.argv[2];
|
|
if (tarball === undefined) throw new Error("usage: verify-llvm-package.mjs <tarball>");
|
|
// Homebrew's arm64 LLVM 22 bottles are not byte-identical across supported
|
|
// macOS runner images: clean builds have produced stripped helpers ranging
|
|
// from roughly 26 MiB to 53 MiB despite identical protocol, LLVM version,
|
|
// target set, and dynamic dependencies. Keep a hard package-size regression
|
|
// fence with enough room for both observed bottle layouts.
|
|
const installedBudget = 64 * 1024 * 1024;
|
|
const packedBudget = 32 * 1024 * 1024;
|
|
const work = mkdtempSync(join(tmpdir(), "scriptc-llvm-pack-"));
|
|
try {
|
|
execFileSync("tar", ["-xzf", tarball, "-C", work]);
|
|
const root = join(work, "package");
|
|
const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
for (const notice of ["LICENSE", "SCRIPTC_LICENSE", "THIRD_PARTY_NOTICES"]) {
|
|
if (statSync(join(root, notice)).size === 0) throw new Error(`${notice} is missing or empty`);
|
|
}
|
|
if (manifest.os?.join(",") !== "darwin" || manifest.cpu?.join(",") !== "arm64") {
|
|
throw new Error("helper manifest must constrain os=darwin and cpu=arm64");
|
|
}
|
|
if (manifest.bin?.["scriptc-llvm-codegen"] !== "bin/scriptc-llvm-codegen") {
|
|
throw new Error("helper manifest must expose the executable as an npm bin");
|
|
}
|
|
const binary = join(root, "bin", "scriptc-llvm-codegen");
|
|
accessSync(binary, constants.X_OK);
|
|
const installedSize = statSync(binary).size;
|
|
const packedSize = statSync(tarball).size;
|
|
const sizeFailures = [
|
|
...(installedSize > installedBudget
|
|
? [`stripped helper is ${installedSize} bytes, exceeding the ${installedBudget}-byte installed-size budget`]
|
|
: []),
|
|
...(packedSize > packedBudget
|
|
? [`helper tarball is ${packedSize} bytes, exceeding the ${packedBudget}-byte compressed-size budget`]
|
|
: []),
|
|
];
|
|
if (sizeFailures.length > 0) throw new Error(sizeFailures.join("; "));
|
|
const version = JSON.parse(execFileSync(binary, ["version", "--format=json"], {
|
|
encoding: "utf8",
|
|
}));
|
|
if (
|
|
version.protocol_version !== "1" ||
|
|
version.scriptc_package_version !== manifest.version ||
|
|
version.llvm_version !== "22.1.8" ||
|
|
version.default_target !== "arm64-apple-macosx14.0.0" ||
|
|
!Array.isArray(version.supported_targets) ||
|
|
!version.supported_targets.includes(version.default_target)
|
|
) throw new Error(`packed helper identity mismatch: ${JSON.stringify(version)}`);
|
|
const dependencies = execFileSync("otool", ["-L", binary], { encoding: "utf8" });
|
|
const nonSystemDependencies = dependencies.trim().split("\n").slice(1)
|
|
.map((line) => line.trim().split(" (compatibility version", 1)[0])
|
|
.filter((path) => path !== undefined &&
|
|
!path.startsWith("/usr/lib/") && !path.startsWith("/System/Library/"));
|
|
if (nonSystemDependencies.length > 0) {
|
|
throw new Error(
|
|
`packed helper has non-system runtime dependencies: ${nonSystemDependencies.join(", ")}\n` +
|
|
dependencies,
|
|
);
|
|
}
|
|
const exportedSymbols = execFileSync("nm", ["-gU", binary], { encoding: "utf8" })
|
|
.trim().split("\n").filter(Boolean)
|
|
.map((line) => line.trim().split(/\s+/).at(-1));
|
|
const exportValidation = validateLlvmHelperExports(exportedSymbols);
|
|
if (!exportValidation.hasMain || exportValidation.unexpected.length > 0) {
|
|
throw new Error(
|
|
`packed helper must export _main without LLVM globals, found: ${exportedSymbols.join(", ")}`,
|
|
);
|
|
}
|
|
const loadCommands = execFileSync("otool", ["-l", binary], { encoding: "utf8" });
|
|
if (!/LC_BUILD_VERSION[\s\S]*?platform 1[\s\S]*?minos 15\.0(?:\s|$)/.test(loadCommands)) {
|
|
throw new Error("packed helper must declare its macOS 15.0 minimum host version");
|
|
}
|
|
const probeInput = join(work, "probe.ll");
|
|
const probeObject = join(work, "probe.o");
|
|
writeFileSync(probeInput, "define i32 @answer() { ret i32 42 }\n");
|
|
execFileSync(binary, [
|
|
"emit", "--input", probeInput, "--output", probeObject,
|
|
"--filetype", "obj", "--target", version.default_target,
|
|
"--opt-level", "2", "--relocation-model", "pic",
|
|
"--diagnostic-format", "json", "--source-path", "/src/probe.ts",
|
|
]);
|
|
const objectLoadCommands = execFileSync("otool", ["-l", probeObject], { encoding: "utf8" });
|
|
if (!/LC_BUILD_VERSION[\s\S]*?platform 1[\s\S]*?minos 14\.0(?:\s|$)/.test(objectLoadCommands)) {
|
|
throw new Error("packed helper must emit objects with the macOS 14.0 deployment target");
|
|
}
|
|
const attributes = execFileSync("xattr", [binary], { encoding: "utf8" });
|
|
if (attributes.split("\n").includes("com.apple.quarantine")) {
|
|
throw new Error("packed helper carries a quarantine attribute");
|
|
}
|
|
execFileSync("codesign", ["--verify", "--strict", binary], { stdio: "inherit" });
|
|
process.stdout.write(
|
|
`verified ${manifest.name}@${manifest.version}: ${installedSize} bytes installed, ` +
|
|
`${packedSize} bytes packed\n`,
|
|
);
|
|
} finally {
|
|
rmSync(work, { recursive: true, force: true });
|
|
}
|