fix(process): preserve selected Darwin helper environment through execution

This commit is contained in:
morluto committed 2026-10-26 22:42:48 +08:00
1 parent 921142efbc
commit f02df45a46
3 files changed
+90 -5

No files matched your search

+9 -3
View File
@@ -19,6 +19,7 @@ import type {
import { analysisErrorWithCleanupFailure } from "../domain/analysisErrorCleanup.js";
import { safeParseJson } from "../domain/safeJson.js";
import { boundedUtf8Text } from "./boundedUtf8Text.js";
import { snapshotEnvironment } from "./snapshotEnvironment.js";
import {
execFileOutput,
execFileOutputFailure,
@@ -407,7 +408,7 @@ export const createDarwinProcessRunTokenReader = (
readonly compiler?: DarwinProcessReaderCompiler;
/** Deadline for helper compilation, independent of provider execution. */
readonly compileTimeoutMs?: number;
/** Environment consulted when `compileTimeoutMs` is omitted. */
/** Captured compiler/helper environment and default compile deadline. */
readonly environment?: Readonly<Record<string, string | undefined>>;
/**
* Caller-owned directory holding the compiler's module cache. Unset keeps
@@ -421,7 +422,11 @@ export const createDarwinProcessRunTokenReader = (
kind: "xcrun",
executable: "/usr/bin/xcrun",
};
const compilerTimeoutMs = resolveSwiftcCompileTimeoutMs(options);
const environment = snapshotEnvironment(options.environment ?? process.env);
const compilerTimeoutMs = resolveSwiftcCompileTimeoutMs({
...options,
environment,
});
const moduleCachePath = options.moduleCachePath ?? "";
let root: ProcessReaderRoot | undefined;
let executable: string | undefined;
@@ -496,7 +501,7 @@ export const createDarwinProcessRunTokenReader = (
const compilerTemporary = join(operationRoot.path, "tmp");
await mkdir(compilerTemporary);
const compilerEnvironment: NodeJS.ProcessEnv = {
...process.env,
...environment,
TMPDIR: compilerTemporary,
};
const compilerArguments =
@@ -640,6 +645,7 @@ export const createDarwinProcessRunTokenReader = (
const pending = (async () => {
const binary = await compile(signal);
const { stdout } = await execFileOutput(binary, arguments_, {
env: environment,
timeout: 15_000,
stopSignal: "SIGTERM",
...(signal === undefined ? {} : { signal }),
+2 -1
View File
@@ -981,12 +981,13 @@ export const createSystemProcessOwnershipHost = (
const darwinTokens =
platform === "darwin"
? createDarwinProcessRunTokenReader({
environment: hostEnvironment,
...(darwinCompiler === undefined ? {} : { compiler: darwinCompiler }),
...(darwinModuleCachePath === undefined
? {}
: { moduleCachePath: darwinModuleCachePath }),
...(options.compileTimeoutMs === undefined
? { environment: hostEnvironment }
? {}
: { compileTimeoutMs: options.compileTimeoutMs }),
})
: undefined;
@@ -15,7 +15,7 @@ import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
import { _electron as electron } from "playwright-core";
import { expect, it, vi } from "vitest";
import { expect, it, onTestFinished, vi } from "vitest";
import { z } from "zod";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
@@ -457,6 +457,84 @@ const existsPath = async (target: string) =>
() => false,
);
it
.skipIf(process.platform === "win32")
.each([
"selected environment",
"selected environment and deadline",
"default environment",
] as const)(
"captures the compiler and helper environment from %s",
async (selection) => {
const directory = await createTestTempDirectory(
"rea-process-token-selected-environment-",
);
const developerDirectory = join(directory, "selected Xcode.app");
const compileState = join(directory, "compile-environment.json");
const executable = join(directory, "fixture-xcrun");
const helper = `#!${process.execPath}
console.log(JSON.stringify({ results: process.argv.slice(2).map(pid => ({
pid: Number(pid), state: "readable", run_id: process.env.DEVELOPER_DIR,
})) }));
`;
await writeFile(
executable,
`#!${process.execPath}
const fs = process.getBuiltinModule("node:fs");
fs.writeFileSync(${JSON.stringify(compileState)}, JSON.stringify({
developerDirectory: process.env.DEVELOPER_DIR ?? null,
}));
const output = process.argv[process.argv.indexOf("-o") + 1];
fs.writeFileSync(output, ${JSON.stringify(helper)});
fs.chmodSync(output, 0o755);
`,
{ mode: 0o755 },
);
onTestFinished(() => {
vi.unstubAllEnvs();
});
vi.stubEnv("DEVELOPER_DIR", developerDirectory);
const environment = { ...process.env };
const compiler = { kind: "xcrun" as const, executable };
const reader =
selection === "default environment"
? createDarwinProcessRunTokenReader({ compiler })
: createSystemProcessOwnershipHost("darwin", environment, {
darwinCompiler: compiler,
...(selection === "selected environment and deadline"
? { compileTimeoutMs: 15_000 }
: {}),
});
environment.DEVELOPER_DIR = join(directory, "later caller selection");
vi.stubEnv("DEVELOPER_DIR", join(directory, "later ambient selection"));
try {
await reader.prepare?.(AbortSignal.timeout(15_000));
expect(JSON.parse(await readFile(compileState, "utf8"))).toEqual({
developerDirectory,
});
const processes = [
{
pid: 123,
parentPid: 1,
processGroupId: 123,
state: "S",
command: "fixture",
},
];
const observations =
"read" in reader
? await reader.read(processes)
: await reader.runTokens?.(processes);
expect(observations?.get(123)).toEqual({
state: "readable",
runId: developerDirectory,
});
} finally {
await reader.close?.();
}
},
);
it.skipIf(process.platform === "win32")(
"forwards a caller-owned module cache and keeps it after the helper root is removed",
async () => {