mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-23 06:10:02 +08:00
* feat(runtime): ship precompiled macOS artifacts - Add a versioned macOS arm64 runtime pack with deterministic feature selection and artifact verification. - Link LLVM helper objects against release-built runtime and vendor inputs without compiling user-machine C. - Wire package publishing, cache validation, documentation, and full-gate coverage for the new path. * fix(runtime): harden precompiled artifact handling * fix(runtime): make precompiled artifacts reproducible * fix(runtime): reject opaque linkers from executable cache * fix(runtime): normalize precompiled archive metadata * fix(runtime): harden precompiled artifact caching * fix(runtime): close executable cache link race * fix(runtime): stage verified pack artifacts * fix(runtime): bracket helper cache inputs * fix(runtime): preserve executable link identity * fix(runtime): trace selected linker dependencies
111 lines
4.6 KiB
TypeScript
111 lines
4.6 KiB
TypeScript
import { chmod, mkdir, mkdtemp, readFile, rm, stat, utimes, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { afterEach, describe, expect, test } from "vitest";
|
|
import { createDeterministicArchive } from "../scripts/archive.mjs";
|
|
import { installRuntimePack, withBuildLock } from "../scripts/build-state.mjs";
|
|
|
|
const dirs: string[] = [];
|
|
|
|
function memberMetadata(bytes: Buffer) {
|
|
const entries: { timestamp: string; uid: string; gid: string; mode: string }[] = [];
|
|
let offset = Buffer.byteLength("!<arch>\n");
|
|
while (offset < bytes.length) {
|
|
const size = Number(bytes.subarray(offset + 48, offset + 58).toString("ascii").trim());
|
|
entries.push({
|
|
timestamp: bytes.subarray(offset + 16, offset + 28).toString("ascii").trim(),
|
|
uid: bytes.subarray(offset + 28, offset + 34).toString("ascii").trim(),
|
|
gid: bytes.subarray(offset + 34, offset + 40).toString("ascii").trim(),
|
|
mode: bytes.subarray(offset + 40, offset + 48).toString("ascii").trim(),
|
|
});
|
|
offset += 60 + size + (size % 2);
|
|
}
|
|
return entries;
|
|
}
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(dirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
|
|
});
|
|
|
|
describe.runIf(process.platform === "darwin")("runtime-pack archives", () => {
|
|
test("serializes concurrent pack builders", async () => {
|
|
const dir = await mkdtemp(join(tmpdir(), "scriptc-runtime-build-lock-"));
|
|
dirs.push(dir);
|
|
const lock = join(dir, "build.lock");
|
|
const events: string[] = [];
|
|
let releaseFirst!: () => void;
|
|
const firstMayFinish = new Promise<void>((resolve) => { releaseFirst = resolve; });
|
|
let firstStarted!: () => void;
|
|
const firstDidStart = new Promise<void>((resolve) => { firstStarted = resolve; });
|
|
const first = withBuildLock(lock, async () => {
|
|
events.push("first:start");
|
|
firstStarted();
|
|
await firstMayFinish;
|
|
events.push("first:end");
|
|
}, { retryMilliseconds: 5 });
|
|
await firstDidStart;
|
|
const second = withBuildLock(lock, async () => {
|
|
events.push("second:start");
|
|
events.push("second:end");
|
|
}, { retryMilliseconds: 5 });
|
|
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
expect(events).toEqual(["first:start"]);
|
|
releaseFirst();
|
|
await Promise.all([first, second]);
|
|
expect(events).toEqual(["first:start", "first:end", "second:start", "second:end"]);
|
|
});
|
|
|
|
test("installs a staged pack over the previous complete pair", async () => {
|
|
const dir = await mkdtemp(join(tmpdir(), "scriptc-runtime-build-install-"));
|
|
dirs.push(dir);
|
|
const outputRoot = join(dir, "artifacts");
|
|
const manifestPath = join(dir, "runtime-pack.json");
|
|
const stagedOutputRoot = join(dir, "stage", "artifacts");
|
|
const stagedManifestPath = join(dir, "stage", "runtime-pack.json");
|
|
await Promise.all([mkdir(outputRoot), mkdir(stagedOutputRoot, { recursive: true })]);
|
|
await Promise.all([
|
|
writeFile(join(outputRoot, "runtime.o"), "old object"),
|
|
writeFile(manifestPath, "old manifest"),
|
|
writeFile(join(stagedOutputRoot, "runtime.o"), "new object"),
|
|
writeFile(stagedManifestPath, "new manifest"),
|
|
]);
|
|
|
|
await installRuntimePack({
|
|
outputRoot,
|
|
manifestPath,
|
|
stagedOutputRoot,
|
|
stagedManifestPath,
|
|
backupRoot: join(dir, "artifacts.backup"),
|
|
backupManifestPath: join(dir, "manifest.backup"),
|
|
});
|
|
|
|
expect(await readFile(join(outputRoot, "runtime.o"), "utf8")).toBe("new object");
|
|
expect(await readFile(manifestPath, "utf8")).toBe("new manifest");
|
|
expect(await stat(join(dir, "artifacts.backup")).then(() => true, () => false)).toBe(false);
|
|
});
|
|
|
|
test("normalize timestamps, ownership, and modes", async () => {
|
|
const dir = await mkdtemp(join(tmpdir(), "scriptc-runtime-archive-"));
|
|
dirs.push(dir);
|
|
const member = join(dir, "member.o");
|
|
const first = join(dir, "first.a");
|
|
const second = join(dir, "second.a");
|
|
await writeFile(member, "runtime-pack archive member\n");
|
|
|
|
await chmod(member, 0o600);
|
|
await utimes(member, new Date(1_000), new Date(1_000));
|
|
await createDeterministicArchive("ar", first, [member]);
|
|
await utimes(member, new Date(2_000), new Date(2_000));
|
|
await createDeterministicArchive("ar", second, [member]);
|
|
|
|
const firstBytes = await readFile(first);
|
|
expect(await readFile(second)).toEqual(firstBytes);
|
|
const metadata = memberMetadata(firstBytes);
|
|
expect(metadata.length).toBeGreaterThanOrEqual(1);
|
|
expect(metadata.every((entry) =>
|
|
entry.timestamp === "0" && entry.uid === "0" && entry.gid === "0" &&
|
|
entry.mode === "100644"
|
|
)).toBe(true);
|
|
});
|
|
});
|