fix(runtime): preserve executable link identity

This commit is contained in:
Chris Tate
2026-08-28 07:22:09 -05:00
parent a3a0587651
commit 214bb31e9a
3 changed files with 60 additions and 10 deletions
+16 -6
View File
@@ -2,7 +2,7 @@ import { execFile } from "node:child_process";
import { createRequire } from "node:module";
import { chmod, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { release as osRelease, tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { basename, dirname, join } from "node:path";
import { promisify } from "node:util";
import { afterEach, describe, expect, test } from "vitest";
@@ -35,13 +35,17 @@ describe.runIf(supported)("precompiled runtime executable builds", () => {
"",
].join("\n"));
await chmod(wrapper, 0o755);
await execFileAsync(process.execPath, [
const cliArgs = [
"--import", tsxLoader, cliEntry, "build", entry, "-o", output,
], {
];
const env = {
...process.env,
SCRIPTC_NO_CACHE: "1",
SCRIPTC_LINKER: wrapper,
};
await execFileAsync(process.execPath, cliArgs, {
env: {
...process.env,
SCRIPTC_NO_CACHE: "1",
SCRIPTC_LINKER: wrapper,
...env,
},
});
const args = JSON.parse(await readFile(log, "utf8")) as string[];
@@ -53,5 +57,11 @@ describe.runIf(supported)("precompiled runtime executable builds", () => {
expect(args.some((arg) => arg.includes("runtime-darwin-arm64/artifacts"))).toBe(false);
await expect(execFileAsync(output, [], { encoding: "utf8" }))
.resolves.toMatchObject({ stdout: "precompiled runtime\n" });
const firstExecutable = await readFile(output);
const signature = await execFileAsync("codesign", ["-dvvv", output], { encoding: "utf8" });
expect(signature.stderr).toContain(`Identifier=${basename(output)}`);
await execFileAsync(process.execPath, cliArgs, { env });
expect(await readFile(output)).toEqual(firstExecutable);
});
});
@@ -1,7 +1,7 @@
import { createHash } from "node:crypto";
import { chmod, mkdtemp, mkdir, readFile, stat, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { basename, dirname, join } from "node:path";
import { describe, expect, test } from "vitest";
import { compilerReleaseVersion } from "../library/sidecar.js";
import { snapshotNativeArtifactDependencies } from "./native-toolchain.js";
@@ -242,6 +242,41 @@ describe("runtime pack manifests", () => {
expect(await readFile(runtimeObject, "utf8")).toBe("tampered");
});
test("preserves the requested basename in the private linker output path", async () => {
const { root, packagePath } = await fixture();
const programObject = join(root, "program.o");
const output = join(root, "requested-name");
const linker = join(root, "linker.mjs");
await Promise.all([
writeFile(programObject, "program object"),
writeFile(linker, [
"#!/usr/bin/env node",
'import { writeFileSync } from "node:fs";',
'const outputIndex = process.argv.indexOf("-o");',
'const output = process.argv[outputIndex + 1];',
'writeFileSync(output, JSON.stringify(output));',
"",
].join("\n")),
]);
await chmod(linker, 0o755);
const plan = await createRuntimeLinkPlan({
target: MACOS_ARM64_TARGET,
programObject,
outPath: output,
features: BASE,
ffi: null,
optimization: "release",
resolver: () => packagePath,
});
await linkRuntimePackExecutable(plan, { linker });
const privateOutput = JSON.parse(await readFile(output, "utf8")) as string;
expect(privateOutput).not.toBe(output);
expect(basename(privateOutput)).toBe(basename(output));
expect(dirname(dirname(privateOutput))).toBe(dirname(output));
});
test("does not publish a cache proof from a stale program-object dependency snapshot", async () => {
const { root, packagePath } = await fixture();
const programObject = join(root, "program.o");
@@ -7,7 +7,6 @@ import { basename, dirname, join, resolve } from "node:path";
import { promisify } from "node:util";
import type { FfiProfile } from "../ffi/ffi-manifest.js";
import { compilerReleaseVersion } from "../library/sidecar.js";
import { privateSiblingPath } from "./build-cache.js";
import type { NativeLinkFeatures } from "./native-link-info.js";
import {
CcCompileError,
@@ -521,7 +520,13 @@ export async function linkRuntimePackExecutable(
): Promise<void> {
const linker = options.linker ?? process.env["SCRIPTC_LINKER"] ?? "clang";
const linkerPath = await resolveExecutable(linker);
const privateOut = privateSiblingPath(plan.outputPath, "runtime-pack-link");
// ld64 derives an ad-hoc signature identifier from the output basename.
// Keep that basename caller-visible while a private sibling directory gives
// the link its own inode and preserves an atomic same-filesystem install.
const privateOutRoot = await mkdtemp(
join(dirname(plan.outputPath), ".scriptc-runtime-pack-link-"),
);
const privateOut = join(privateOutRoot, basename(plan.outputPath));
let stagedRoot: string | null = null;
try {
const staged = await stageRuntimePackArtifacts(plan.runtimePack);
@@ -583,7 +588,7 @@ export async function linkRuntimePackExecutable(
);
} finally {
await Promise.all([
rm(privateOut, { force: true }).catch(() => undefined),
rm(privateOutRoot, { recursive: true, force: true }).catch(() => undefined),
stagedRoot === null
? Promise.resolve()
: rm(stagedRoot, { recursive: true, force: true }).catch(() => undefined),