Files
scriptc/scripts/verify-llvm-package.mjs
Chris Tate c08ab955ae Enforce linting and consistent source formatting
- Enforce oxlint and oxfmt for owned JavaScript and TypeScript with independent CI checks.
- Preserve compiler import boundaries and protected fixtures while cleaning up lint findings.
- Standardize source formatting and validate import, fixture, and embedded-source protections.
2026-10-05 17:51:24 -05:00

139 lines
5.3 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 });
}