mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(process): add an opt-in Swift module cache (#2252)
* fix(process): add an opt-in Swift module cache Signed-off-by: Charan Rathore <180254320+charan-rathore@users.noreply.github.com> * fix(process): preserve caller-owned Swift module caches Fold the complementary implementation from #2260 into the earlier #2252 proposal, retaining the original contribution history. Co-authored-by: NIU-123370 <191000457+NIU-123370@users.noreply.github.com> * fix(process): bypass xcrun lookup for explicit Swift toolchains --------- Signed-off-by: Charan Rathore <180254320+charan-rathore@users.noreply.github.com> Co-authored-by: morluto <76467478+morluto@users.noreply.github.com> Co-authored-by: NIU-123370 <191000457+NIU-123370@users.noreply.github.com>
This commit is contained in:
4 files changed
+392
-38
No files matched your search
@@ -416,3 +416,52 @@ while its token became unavailable after this change; npm changes its title
|
||||
as well. A newly started unreadable process can prevent verified cleanup even
|
||||
after the selected command exits. REA preserves this uncertainty and leaves
|
||||
that process untouched.
|
||||
|
||||
Reading those tokens requires compiling a small Swift helper with `xcrun swiftc`.
|
||||
By default that compilation reuses Swift's shared module cache, while the
|
||||
compiler's intermediate objects stay inside the per-run temporary root that REA
|
||||
removes afterwards. Set `REA_DARWIN_MODULE_CACHE_PATH` to a writable directory
|
||||
when a shared cache is unavailable, for example under an enforced write
|
||||
boundary. REA creates the directory, passes it to the compiler as
|
||||
`-module-cache-path`, and exports it as `CLANG_MODULE_CACHE_PATH` so the
|
||||
Objective-C header modules the helper builds land there too. REA never removes
|
||||
that directory: it outlives each run, so the first compilation into a new
|
||||
directory pays a cold-cache build and later runs reuse it. An absent or empty
|
||||
value keeps the shared default.
|
||||
|
||||
`xcrun` also maintains a separate tool-lookup cache. Its `--no-cache` option
|
||||
refreshes entries and does not guarantee a write-free lookup. To avoid that
|
||||
lookup while compiling REA's helper, set `REA_DARWIN_SWIFTC_PATH` to the absolute
|
||||
path of an installed `swiftc` and `SDKROOT` to the absolute path of a compatible
|
||||
macOS SDK. REA invokes that compiler directly with `-sdk` and the selected SDK;
|
||||
an unset or empty compiler setting keeps the normal `xcrun swiftc` path. A
|
||||
missing or relative SDK/compiler path fails with an actionable prerequisite
|
||||
error before compilation. An incompatible installation reports the compiler's
|
||||
bounded diagnostics; REA does not repair or replace the toolchain.
|
||||
|
||||
For example, select paths from your existing Xcode or Command Line Tools
|
||||
installation **before** entering an enforced write boundary:
|
||||
|
||||
```sh
|
||||
export REA_DARWIN_SWIFTC_PATH="$(xcrun --find swiftc)"
|
||||
export SDKROOT="$(xcrun --show-sdk-path)"
|
||||
export REA_DARWIN_MODULE_CACHE_PATH="$PWD/rea-cache/modules"
|
||||
export TMPDIR="$PWD/rea-cache/tmp"
|
||||
mkdir -p "$TMPDIR"
|
||||
rea analyze-javascript-application ./fixture --artifact-format directory
|
||||
```
|
||||
|
||||
Keep the selected compiler and SDK paired. Permit REA's evidence/output paths
|
||||
and its temporary directory as well as the caller-owned module cache. REA does
|
||||
not create a sandbox or claim that these settings contain every write made by
|
||||
arbitrary toolchains or analysis providers. They bypass REA's `xcrun` lookup
|
||||
and select the helper's Swift/Clang cache without disabling ownership checks.
|
||||
The caller-owned cache survives cancellation and cleanup; per-run compiler
|
||||
intermediates and the compiled helper remain REA-owned temporary files.
|
||||
|
||||
The boundary must also permit process-table inspection. On macOS, `/bin/ps`
|
||||
is setuid and a blanket sandbox may reject its launch with `spawn EPERM`.
|
||||
REA then preserves ownership uncertainty and can report `cleanup_incomplete`;
|
||||
it does not disable verification to continue. A sandbox that explicitly allows
|
||||
this read-only process inspector is a separate prerequisite from the compiler
|
||||
and cache settings.
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mkdir, mkdtemp, rm } from "node:fs/promises";
|
||||
import { rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { isAbsolute, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -163,14 +163,19 @@ const compilerOutcomeDescription = (
|
||||
const compilerInspectionError = (
|
||||
cause: unknown,
|
||||
timeoutMs: number,
|
||||
compiler: DarwinProcessReaderCompiler,
|
||||
): DarwinProcessOwnershipInspectionError => {
|
||||
const outcome = compilerOutcome(cause, timeoutMs);
|
||||
const code = outcome?.code ?? "unknown";
|
||||
const description = compilerOutcomeDescription(outcome, code);
|
||||
const launcher =
|
||||
compiler.kind === "xcrun"
|
||||
? "xcrun"
|
||||
: "the selected Swift compiler (REA_DARWIN_SWIFTC_PATH)";
|
||||
const prefix =
|
||||
code === "ENOENT"
|
||||
? "macOS process ownership inspection requires the Apple Swift compiler via xcrun"
|
||||
: "macOS process ownership helper compilation failed via xcrun";
|
||||
? `macOS process ownership inspection requires the Apple Swift compiler via ${launcher}`
|
||||
: `macOS process ownership helper compilation failed via ${launcher}`;
|
||||
const stderr = boundedUtf8Text(outcome?.stderr ?? "", STDERR_EXCERPT_LIMIT);
|
||||
const diagnostics = stderr.text.trim();
|
||||
const truncated =
|
||||
@@ -235,15 +240,35 @@ const waitForCompilation = async (
|
||||
});
|
||||
};
|
||||
|
||||
/** Explicit tool selection; direct Swift compilation also needs its matching SDK. */
|
||||
export type DarwinProcessReaderCompiler =
|
||||
| { readonly kind: "xcrun"; readonly executable: string }
|
||||
| {
|
||||
readonly kind: "swiftc";
|
||||
readonly executable: string;
|
||||
readonly sdkRoot: string;
|
||||
};
|
||||
|
||||
/** Compile and retain the narrow Darwin sysctl reader for this REA process. */
|
||||
export const createDarwinProcessRunTokenReader = (
|
||||
options: {
|
||||
readonly xcrun?: string;
|
||||
readonly compiler?: DarwinProcessReaderCompiler;
|
||||
/** Deadline for the helper compilation; the default keeps verification fast. */
|
||||
readonly compileTimeoutMs?: number;
|
||||
/**
|
||||
* Caller-owned directory holding the compiler's module cache. Unset keeps
|
||||
* Swift's reusable default cache; a supplied directory outlives this
|
||||
* reader because cleanup only removes the per-run helper root.
|
||||
*/
|
||||
readonly moduleCachePath?: string;
|
||||
} = {},
|
||||
) => {
|
||||
const compiler = options.compiler ?? {
|
||||
kind: "xcrun",
|
||||
executable: "/usr/bin/xcrun",
|
||||
};
|
||||
const compilerTimeoutMs = options.compileTimeoutMs ?? COMPILER_TIMEOUT_MS;
|
||||
const moduleCachePath = options.moduleCachePath ?? "";
|
||||
let root: string | undefined;
|
||||
let executable: string | undefined;
|
||||
let compilation: ProcessReaderCompilation | undefined;
|
||||
@@ -266,6 +291,14 @@ export const createDarwinProcessRunTokenReader = (
|
||||
const runCompilation = async (signal: AbortSignal): Promise<string> => {
|
||||
let operationRoot: string | undefined;
|
||||
try {
|
||||
if (
|
||||
compiler.kind === "swiftc" &&
|
||||
(!isAbsolute(compiler.executable) || !isAbsolute(compiler.sdkRoot))
|
||||
) {
|
||||
throw new DarwinProcessOwnershipInspectionError(
|
||||
"Direct Darwin helper compilation requires absolute REA_DARWIN_SWIFTC_PATH and SDKROOT paths selecting a compatible installed Swift compiler and macOS SDK.",
|
||||
);
|
||||
}
|
||||
operationRoot = await mkdtemp(join(tmpdir(), "rea-process-token-"));
|
||||
root = operationRoot;
|
||||
process.once("exit", cleanupAtExit);
|
||||
@@ -280,23 +313,30 @@ export const createDarwinProcessRunTokenReader = (
|
||||
...process.env,
|
||||
TMPDIR: compilerTemporary,
|
||||
};
|
||||
// Use Swift's reusable default cache for REA's internal helper compilation.
|
||||
delete compilerEnvironment.CLANG_MODULE_CACHE_PATH;
|
||||
const compilerArguments =
|
||||
compiler.kind === "xcrun" ? ["swiftc"] : ["-sdk", compiler.sdkRoot];
|
||||
if (compiler.kind === "swiftc")
|
||||
compilerEnvironment.SDKROOT = compiler.sdkRoot;
|
||||
if (moduleCachePath === "") {
|
||||
// Use Swift's reusable default cache for REA's internal helper compilation.
|
||||
delete compilerEnvironment.CLANG_MODULE_CACHE_PATH;
|
||||
} else {
|
||||
await mkdir(moduleCachePath, { recursive: true });
|
||||
compilerArguments.push("-module-cache-path", moduleCachePath);
|
||||
compilerEnvironment.CLANG_MODULE_CACHE_PATH = moduleCachePath;
|
||||
}
|
||||
compilerArguments.push(...sourceFiles, "-o", output);
|
||||
try {
|
||||
await execFileOutput(
|
||||
options.xcrun ?? "/usr/bin/xcrun",
|
||||
["swiftc", ...sourceFiles, "-o", output],
|
||||
{
|
||||
timeout: compilerTimeoutMs,
|
||||
maxBuffer: 1024 * 1024,
|
||||
signal,
|
||||
stopSignal: SWIFTC_INTERRUPT,
|
||||
env: compilerEnvironment,
|
||||
},
|
||||
);
|
||||
await execFileOutput(compiler.executable, compilerArguments, {
|
||||
timeout: compilerTimeoutMs,
|
||||
maxBuffer: 1024 * 1024,
|
||||
signal,
|
||||
stopSignal: SWIFTC_INTERRUPT,
|
||||
env: compilerEnvironment,
|
||||
});
|
||||
} catch (cause: unknown) {
|
||||
if (isExpectedAbort(cause, signal)) throw cause;
|
||||
throw compilerInspectionError(cause, compilerTimeoutMs);
|
||||
throw compilerInspectionError(cause, compilerTimeoutMs, compiler);
|
||||
}
|
||||
if (signal.aborted || closed) throw abortReason(signal);
|
||||
executable = output;
|
||||
|
||||
@@ -4,7 +4,10 @@ import { AnalysisCapabilityUnavailableError } from "../domain/analysisErrorCore.
|
||||
import { launcherIdentityFailure } from "./ProcessOwnershipIdentity.js";
|
||||
import { descendantsOf, liveProcesses } from "./ProcessOwnershipProcessTree.js";
|
||||
import { execFileOutput } from "./ExecFileOutput.js";
|
||||
import { createDarwinProcessRunTokenReader } from "./DarwinProcessRunTokenReader.js";
|
||||
import {
|
||||
createDarwinProcessRunTokenReader,
|
||||
type DarwinProcessReaderCompiler,
|
||||
} from "./DarwinProcessRunTokenReader.js";
|
||||
import { readProcessRunTokens } from "./ProcessRunTokenObservations.js";
|
||||
import type {
|
||||
OwnedProcessGroup,
|
||||
@@ -219,16 +222,32 @@ export const createSystemProcessOwnershipHost = (
|
||||
hostEnvironment: NodeJS.ProcessEnv = process.env,
|
||||
options: {
|
||||
/** Override the compiler executable used by the Darwin native reader. */
|
||||
readonly darwinXcrun?: string;
|
||||
readonly darwinCompiler?: DarwinProcessReaderCompiler;
|
||||
/** Override the caller-owned module cache used compiling that helper. */
|
||||
readonly darwinModuleCachePath?: string;
|
||||
} = {},
|
||||
): ProcessOwnershipHost => {
|
||||
const darwinModuleCachePath =
|
||||
options.darwinModuleCachePath ??
|
||||
hostEnvironment.REA_DARWIN_MODULE_CACHE_PATH;
|
||||
const swiftcPath = hostEnvironment.REA_DARWIN_SWIFTC_PATH;
|
||||
const darwinCompiler =
|
||||
options.darwinCompiler ??
|
||||
(swiftcPath === undefined || swiftcPath === ""
|
||||
? undefined
|
||||
: {
|
||||
kind: "swiftc" as const,
|
||||
executable: swiftcPath,
|
||||
sdkRoot: hostEnvironment.SDKROOT ?? "",
|
||||
});
|
||||
const darwinTokens =
|
||||
platform === "darwin"
|
||||
? createDarwinProcessRunTokenReader(
|
||||
options.darwinXcrun === undefined
|
||||
? createDarwinProcessRunTokenReader({
|
||||
...(darwinCompiler === undefined ? {} : { compiler: darwinCompiler }),
|
||||
...(darwinModuleCachePath === undefined
|
||||
? {}
|
||||
: { xcrun: options.darwinXcrun },
|
||||
)
|
||||
: { moduleCachePath: darwinModuleCachePath }),
|
||||
})
|
||||
: undefined;
|
||||
const listProcesses = async (signal?: AbortSignal) => {
|
||||
if (platform === "win32") return [];
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
mkdir,
|
||||
mkdtemp,
|
||||
readFile,
|
||||
readdir,
|
||||
rm,
|
||||
symlink,
|
||||
writeFile,
|
||||
@@ -156,7 +157,10 @@ type ElectronCaptureOutcome =
|
||||
|
||||
it("reports an actionable missing Swift compiler without installing it", async () => {
|
||||
const reader = createDarwinProcessRunTokenReader({
|
||||
xcrun: join(tmpdir(), "rea-no-such-xcrun"),
|
||||
compiler: {
|
||||
kind: "xcrun",
|
||||
executable: join(tmpdir(), "rea-no-such-xcrun"),
|
||||
},
|
||||
});
|
||||
try {
|
||||
await expect(
|
||||
@@ -198,7 +202,9 @@ it.skipIf(process.platform === "win32")(
|
||||
'#!/usr/bin/env node\nprocess.stderr.write("module cache is not writable\\n" + "x".repeat(12000)); process.exitCode = 1;\n',
|
||||
);
|
||||
await chmod(executable, 0o755);
|
||||
const reader = createDarwinProcessRunTokenReader({ xcrun: executable });
|
||||
const reader = createDarwinProcessRunTokenReader({
|
||||
compiler: { kind: "xcrun", executable: executable },
|
||||
});
|
||||
try {
|
||||
const failure = await preparationFailure(reader);
|
||||
expect(failure.compilerOutcome).toMatchObject({
|
||||
@@ -248,7 +254,7 @@ it.skipIf(process.platform === "win32")(
|
||||
);
|
||||
await chmod(executable, 0o755);
|
||||
const reader = createDarwinProcessRunTokenReader({
|
||||
xcrun: executable,
|
||||
compiler: { kind: "xcrun", executable: executable },
|
||||
// Headroom for the child interpreter's cold start under suite load.
|
||||
compileTimeoutMs: 3_000,
|
||||
});
|
||||
@@ -280,7 +286,9 @@ it.skipIf(process.platform === "win32")(
|
||||
'#!/usr/bin/env node\nprocess.stderr.write("fatal: segmentation fault during type checking\\n");\nprocess.kill(process.pid, "SIGSEGV");\n',
|
||||
);
|
||||
await chmod(executable, 0o755);
|
||||
const reader = createDarwinProcessRunTokenReader({ xcrun: executable });
|
||||
const reader = createDarwinProcessRunTokenReader({
|
||||
compiler: { kind: "xcrun", executable: executable },
|
||||
});
|
||||
try {
|
||||
await expect(preparationFailure(reader)).resolves.toMatchObject({
|
||||
message:
|
||||
@@ -310,7 +318,9 @@ it.skipIf(process.platform === "win32")(
|
||||
`#!/usr/bin/env node\nprocess.stderr.write(${JSON.stringify(`${diagnostic}\n`)}); process.exitCode = 1;\n`,
|
||||
);
|
||||
await chmod(executable, 0o755);
|
||||
const reader = createDarwinProcessRunTokenReader({ xcrun: executable });
|
||||
const reader = createDarwinProcessRunTokenReader({
|
||||
compiler: { kind: "xcrun", executable: executable },
|
||||
});
|
||||
try {
|
||||
const failure = await preparationFailure(reader);
|
||||
const outcome = failure.compilerOutcome;
|
||||
@@ -376,7 +386,7 @@ it.skipIf(process.platform === "win32")(
|
||||
);
|
||||
const fakeCompiler = await writeBlockingCompiler(directory);
|
||||
const reader = createDarwinProcessRunTokenReader({
|
||||
xcrun: fakeCompiler.executable,
|
||||
compiler: { kind: "xcrun", executable: fakeCompiler.executable },
|
||||
});
|
||||
const preparation = reader.prepare();
|
||||
try {
|
||||
@@ -390,6 +400,242 @@ it.skipIf(process.platform === "win32")(
|
||||
},
|
||||
);
|
||||
|
||||
const writeCacheRecordingCompiler = async (directory: string) => {
|
||||
const executable = join(directory, "fake-cache-xcrun");
|
||||
const statePath = join(directory, "cache-compile-state.json");
|
||||
const source = `#!/usr/bin/env node
|
||||
const fs = process.getBuiltinModule("node:fs");
|
||||
const path = process.getBuiltinModule("node:path");
|
||||
const flagIndex = process.argv.indexOf("-module-cache-path");
|
||||
const cache = flagIndex === -1 ? undefined : process.argv[flagIndex + 1];
|
||||
fs.writeFileSync(${JSON.stringify(statePath)}, JSON.stringify({
|
||||
arguments: process.argv.slice(2),
|
||||
moduleCacheEnvironment: process.env.CLANG_MODULE_CACHE_PATH ?? null,
|
||||
}));
|
||||
if (cache !== undefined) {
|
||||
fs.mkdirSync(cache, { recursive: true });
|
||||
fs.writeFileSync(path.join(cache, "written-into-cache"), "kept");
|
||||
}
|
||||
const output = process.argv[process.argv.indexOf("-o") + 1];
|
||||
fs.writeFileSync(output, "#!/bin/sh\\nexit 0\\n");
|
||||
fs.chmodSync(output, 0o755);
|
||||
`;
|
||||
await writeFile(executable, source);
|
||||
await chmod(executable, 0o755);
|
||||
return { executable, statePath };
|
||||
};
|
||||
|
||||
const readCacheCompileState = async (statePath: string) =>
|
||||
JSON.parse(await readFile(statePath, "utf8")) as {
|
||||
arguments: string[];
|
||||
moduleCacheEnvironment: string | null;
|
||||
};
|
||||
|
||||
const existsPath = async (target: string) =>
|
||||
access(target).then(
|
||||
() => true,
|
||||
() => false,
|
||||
);
|
||||
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"forwards a caller-owned module cache and keeps it after the helper root is removed",
|
||||
async () => {
|
||||
const directory = await createTestTempDirectory(
|
||||
"rea-process-token-caller-cache-test-",
|
||||
);
|
||||
const cachePath = join(directory, "caller", " modules ");
|
||||
const fakeCompiler = await writeCacheRecordingCompiler(directory);
|
||||
const reader = createDarwinProcessRunTokenReader({
|
||||
compiler: { kind: "xcrun", executable: fakeCompiler.executable },
|
||||
moduleCachePath: cachePath,
|
||||
});
|
||||
let executable: string;
|
||||
try {
|
||||
executable = await reader.prepare();
|
||||
} finally {
|
||||
await reader.close();
|
||||
}
|
||||
const state = await readCacheCompileState(fakeCompiler.statePath);
|
||||
const flagIndex = state.arguments.indexOf("-module-cache-path");
|
||||
expect(state.arguments[0]).toBe("swiftc");
|
||||
expect(flagIndex).toBe(1);
|
||||
expect(state.arguments[flagIndex + 1]).toBe(cachePath);
|
||||
expect(state.moduleCacheEnvironment).toBe(cachePath);
|
||||
expect(await existsPath(join(cachePath, "written-into-cache"))).toBe(true);
|
||||
expect(await existsPath(executable)).toBe(false);
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"selects the caller module cache through REA_DARWIN_MODULE_CACHE_PATH",
|
||||
async () => {
|
||||
const directory = await createTestTempDirectory(
|
||||
"rea-process-token-host-cache-test-",
|
||||
);
|
||||
const cachePath = join(directory, "from-environment", "modules");
|
||||
const fakeCompiler = await writeCacheRecordingCompiler(directory);
|
||||
const host = createSystemProcessOwnershipHost(
|
||||
"darwin",
|
||||
{ ...process.env, REA_DARWIN_MODULE_CACHE_PATH: cachePath },
|
||||
{
|
||||
darwinCompiler: { kind: "xcrun", executable: fakeCompiler.executable },
|
||||
},
|
||||
);
|
||||
try {
|
||||
await host.prepare?.(AbortSignal.timeout(15_000));
|
||||
const state = await readCacheCompileState(fakeCompiler.statePath);
|
||||
const flagIndex = state.arguments.indexOf("-module-cache-path");
|
||||
expect(state.arguments[flagIndex + 1]).toBe(cachePath);
|
||||
} finally {
|
||||
await host.close?.();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"keeps the shared module cache for the system host without the setting",
|
||||
async () => {
|
||||
const directory = await createTestTempDirectory(
|
||||
"rea-process-token-host-default-test-",
|
||||
);
|
||||
vi.stubEnv("CLANG_MODULE_CACHE_PATH", join(directory, "ambient-cache"));
|
||||
const hostEnvironment = { ...process.env };
|
||||
delete hostEnvironment.REA_DARWIN_MODULE_CACHE_PATH;
|
||||
const fakeCompiler = await writeCacheRecordingCompiler(directory);
|
||||
const host = createSystemProcessOwnershipHost("darwin", hostEnvironment, {
|
||||
darwinCompiler: { kind: "xcrun", executable: fakeCompiler.executable },
|
||||
});
|
||||
try {
|
||||
await host.prepare?.(AbortSignal.timeout(15_000));
|
||||
const state = await readCacheCompileState(fakeCompiler.statePath);
|
||||
expect(state.arguments).not.toContain("-module-cache-path");
|
||||
expect(state.moduleCacheEnvironment).toBeNull();
|
||||
} finally {
|
||||
await host.close?.();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"bypasses xcrun for an explicitly selected Swift compiler and SDK",
|
||||
async () => {
|
||||
const directory = await createTestTempDirectory(
|
||||
"rea-process-token-direct-compiler-",
|
||||
);
|
||||
const fakeCompiler = await writeCacheRecordingCompiler(directory);
|
||||
const sdkRoot = join(directory, "MacOSX.sdk");
|
||||
const cachePath = join(directory, "modules");
|
||||
const host = createSystemProcessOwnershipHost("darwin", {
|
||||
...process.env,
|
||||
REA_DARWIN_SWIFTC_PATH: fakeCompiler.executable,
|
||||
SDKROOT: sdkRoot,
|
||||
REA_DARWIN_MODULE_CACHE_PATH: cachePath,
|
||||
});
|
||||
try {
|
||||
await host.prepare?.(AbortSignal.timeout(15_000));
|
||||
const state = await readCacheCompileState(fakeCompiler.statePath);
|
||||
expect(state.arguments.slice(0, 2)).toEqual(["-sdk", sdkRoot]);
|
||||
expect(state.arguments).not.toContain("swiftc");
|
||||
expect(state.arguments).toContain(cachePath);
|
||||
} finally {
|
||||
await host.close?.();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"reports the missing SDK prerequisite before starting a direct compiler",
|
||||
async () => {
|
||||
const directory = await createTestTempDirectory(
|
||||
"rea-process-token-direct-no-sdk-",
|
||||
);
|
||||
const fakeCompiler = await writeCacheRecordingCompiler(directory);
|
||||
const host = createSystemProcessOwnershipHost("darwin", {
|
||||
...process.env,
|
||||
REA_DARWIN_SWIFTC_PATH: fakeCompiler.executable,
|
||||
SDKROOT: "",
|
||||
});
|
||||
try {
|
||||
await expect(
|
||||
host.prepare?.(AbortSignal.timeout(15_000)),
|
||||
).rejects.toMatchObject({
|
||||
name: "DarwinProcessOwnershipInspectionError",
|
||||
message: expect.stringContaining("REA_DARWIN_SWIFTC_PATH and SDKROOT"),
|
||||
});
|
||||
expect(await existsPath(fakeCompiler.statePath)).toBe(false);
|
||||
} finally {
|
||||
await host.close?.();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"identifies a missing selected Swift compiler rather than xcrun",
|
||||
async () => {
|
||||
const directory = await createTestTempDirectory(
|
||||
"rea-process-token-direct-missing-",
|
||||
);
|
||||
const host = createSystemProcessOwnershipHost("darwin", {
|
||||
...process.env,
|
||||
REA_DARWIN_SWIFTC_PATH: join(directory, "missing-swiftc"),
|
||||
SDKROOT: join(directory, "MacOSX.sdk"),
|
||||
});
|
||||
try {
|
||||
await expect(
|
||||
host.prepare?.(AbortSignal.timeout(15_000)),
|
||||
).rejects.toMatchObject({
|
||||
message: expect.stringContaining(
|
||||
"selected Swift compiler (REA_DARWIN_SWIFTC_PATH)",
|
||||
),
|
||||
compilerOutcome: expect.objectContaining({
|
||||
outcome: "spawn_failed",
|
||||
code: "ENOENT",
|
||||
}),
|
||||
});
|
||||
} finally {
|
||||
await host.close?.();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(!onDarwin)(
|
||||
"compiles the helper against the caller cache with the host compiler",
|
||||
async () => {
|
||||
const directory = await createTestTempDirectory(
|
||||
"rea-process-token-real-cache-test-",
|
||||
);
|
||||
const cachePath = join(directory, "caller", "modules");
|
||||
const { stdout: compilerPath } = await execFileAsync("/usr/bin/xcrun", [
|
||||
"--find",
|
||||
"swiftc",
|
||||
]);
|
||||
const { stdout: sdkRoot } = await execFileAsync("/usr/bin/xcrun", [
|
||||
"--show-sdk-path",
|
||||
]);
|
||||
const host = createSystemProcessOwnershipHost("darwin", {
|
||||
...process.env,
|
||||
REA_DARWIN_MODULE_CACHE_PATH: cachePath,
|
||||
REA_DARWIN_SWIFTC_PATH: compilerPath.trim(),
|
||||
SDKROOT: sdkRoot.trim(),
|
||||
});
|
||||
try {
|
||||
await host.prepare?.(AbortSignal.timeout(180_000));
|
||||
const entries = await readdir(cachePath);
|
||||
expect(
|
||||
entries.some(
|
||||
(name) =>
|
||||
name.endsWith(".swiftmodule") ||
|
||||
name.endsWith(".pcm") ||
|
||||
name === "Modules",
|
||||
),
|
||||
).toBe(true);
|
||||
} finally {
|
||||
await host.close?.();
|
||||
}
|
||||
expect(await existsPath(join(directory, "caller"))).toBe(true);
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"rethrows compile cancellation, removes its temporary root, and permits retry",
|
||||
async () => {
|
||||
@@ -398,7 +644,7 @@ it.skipIf(process.platform === "win32")(
|
||||
);
|
||||
const fakeCompiler = await writeBlockingCompiler(directory);
|
||||
const reader = createDarwinProcessRunTokenReader({
|
||||
xcrun: fakeCompiler.executable,
|
||||
compiler: { kind: "xcrun", executable: fakeCompiler.executable },
|
||||
});
|
||||
const controller = new AbortController();
|
||||
const compilation = reader.prepare(controller.signal).then(
|
||||
@@ -483,7 +729,7 @@ it.skipIf(process.platform === "win32")(
|
||||
);
|
||||
const compiler = await writeTemporaryObjectCompiler(directory);
|
||||
const reader = createDarwinProcessRunTokenReader({
|
||||
xcrun: compiler.executable,
|
||||
compiler: { kind: "xcrun", executable: compiler.executable },
|
||||
});
|
||||
const controller = new AbortController();
|
||||
const preparation = reader.prepare(controller.signal).then(
|
||||
@@ -532,7 +778,7 @@ it.skipIf(process.platform === "win32").each(["first", "second"] as const)(
|
||||
);
|
||||
const fakeCompiler = await writeBlockingCompiler(directory);
|
||||
const reader = createDarwinProcessRunTokenReader({
|
||||
xcrun: fakeCompiler.executable,
|
||||
compiler: { kind: "xcrun", executable: fakeCompiler.executable },
|
||||
});
|
||||
const firstController = new AbortController();
|
||||
const secondController = new AbortController();
|
||||
@@ -590,7 +836,7 @@ it.skipIf(process.platform === "win32" || process.getuid?.() === 0)(
|
||||
);
|
||||
const fakeCompiler = await writeBlockingCompiler(directory);
|
||||
const reader = createDarwinProcessRunTokenReader({
|
||||
xcrun: fakeCompiler.executable,
|
||||
compiler: { kind: "xcrun", executable: fakeCompiler.executable },
|
||||
});
|
||||
const controller = new AbortController();
|
||||
const preparation = reader.prepare(controller.signal).then(
|
||||
@@ -631,7 +877,7 @@ it.skipIf(process.platform === "win32")(
|
||||
);
|
||||
const fakeCompiler = await writeBlockingCompiler(directory);
|
||||
const reader = createDarwinProcessRunTokenReader({
|
||||
xcrun: fakeCompiler.executable,
|
||||
compiler: { kind: "xcrun", executable: fakeCompiler.executable },
|
||||
});
|
||||
const preparation = reader.prepare().then(
|
||||
() => ({ state: "fulfilled" as const }),
|
||||
@@ -664,7 +910,7 @@ it.skipIf(process.platform === "win32")(
|
||||
);
|
||||
const fakeCompiler = await writeBlockingCompiler(directory);
|
||||
const reader = createDarwinProcessRunTokenReader({
|
||||
xcrun: fakeCompiler.executable,
|
||||
compiler: { kind: "xcrun", executable: fakeCompiler.executable },
|
||||
});
|
||||
const prepareSpy = vi
|
||||
.spyOn(systemProcessOwnershipHost, "prepare")
|
||||
@@ -716,7 +962,7 @@ it.skipIf(process.platform === "win32").each(["baseline", "tokens"] as const)(
|
||||
);
|
||||
const fakeCompiler = await writeBlockingIdentityCompiler(directory);
|
||||
const host = createSystemProcessOwnershipHost("darwin", process.env, {
|
||||
darwinXcrun: fakeCompiler.executable,
|
||||
darwinCompiler: { kind: "xcrun", executable: fakeCompiler.executable },
|
||||
});
|
||||
const controller = new AbortController();
|
||||
let helperRoot: string | undefined;
|
||||
@@ -775,7 +1021,7 @@ it.skipIf(process.platform === "win32")(
|
||||
);
|
||||
const fakeCompiler = await writeBlockingIdentityCompiler(directory);
|
||||
const reader = createDarwinProcessRunTokenReader({
|
||||
xcrun: fakeCompiler.executable,
|
||||
compiler: { kind: "xcrun", executable: fakeCompiler.executable },
|
||||
});
|
||||
const prepareSpy = vi
|
||||
.spyOn(systemProcessOwnershipHost, "prepare")
|
||||
|
||||
Reference in new issue
Block a user