mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-30 07:14:39 +08:00
fix(runtime): preserve executable link identity
This commit is contained in:
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user