diff --git a/packages/compiler/src/native/prepare.test.ts b/packages/compiler/src/native/prepare.test.ts index 5d0f9faae..07698820e 100644 --- a/packages/compiler/src/native/prepare.test.ts +++ b/packages/compiler/src/native/prepare.test.ts @@ -1,7 +1,7 @@ import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { afterEach, expect, test } from "vitest"; +import { afterEach, expect, test, vi } from "vitest"; import { LINUX_ARM64_GNU_TARGET, nativeCodegenTarget, @@ -23,6 +23,7 @@ function text(input: ReturnType["prepare"]>) { const scratch: string[] = []; afterEach(() => { + vi.unstubAllEnvs(); for (const path of scratch.splice(0)) rmSync(path, { recursive: true, force: true }); }); @@ -197,6 +198,30 @@ test("structurally invalid cached features cause a rebuild even with a valid dig expect(f.loads()).toBe(2); }); +test("timing, cache placement and emission worker variables keep the frontend output", () => { + for (const name of [ + "SCRIPTC_CACHE_DIR", + "SCRIPTC_CACHE_MAX_MB", + "SCRIPTC_EMIT_WORKERS", + "SCRIPTC_EMIT_WORKER_FUNCTIONS", + "SCRIPTC_TIMING", + "SCRIPTC_TEST_FRONTEND_KEY", + ]) + vi.stubEnv(name, undefined); + const f = fixture(); + expect(f.prepare().ok).toBe(true); + vi.stubEnv("SCRIPTC_CACHE_DIR", f.cache.root); + vi.stubEnv("SCRIPTC_CACHE_MAX_MB", "8192"); + vi.stubEnv("SCRIPTC_EMIT_WORKERS", "1"); + vi.stubEnv("SCRIPTC_EMIT_WORKER_FUNCTIONS", "100"); + vi.stubEnv("SCRIPTC_TIMING", "0"); + expect(f.prepare().ok).toBe(true); + expect(f.loads()).toBe(1); + vi.stubEnv("SCRIPTC_TEST_FRONTEND_KEY", "1"); + expect(f.prepare().ok).toBe(true); + expect(f.loads()).toBe(2); +}); + test("serialized IR travels as bounded parts through the frontend cache", () => { const f = fixture(); const first = f.prepare({ emitIr: true }); diff --git a/packages/compiler/src/native/prepare.ts b/packages/compiler/src/native/prepare.ts index a10d16585..11b8f9dd1 100644 --- a/packages/compiler/src/native/prepare.ts +++ b/packages/compiler/src/native/prepare.ts @@ -211,6 +211,24 @@ function implementationIdentity(toolchain: NativeToolchain): string { ); } +/** SCRIPTC_* variables that report on a build, place its cache or set + * backend emission parallelism. The frontend's output does not depend on + * them, so they are left out of its cache key. */ +const FRONTEND_INDEPENDENT_VARIABLES = new Set([ + "SCRIPTC_CACHE_DIR", + "SCRIPTC_CACHE_MAX_MB", + "SCRIPTC_EMIT_WORKERS", + "SCRIPTC_EMIT_WORKER_FUNCTIONS", + "SCRIPTC_TIMING", +]); + +/** The SCRIPTC_* variables a frontend cache entry depends on, sorted by name. */ +function frontendEnvironment(): [string, string | undefined][] { + return Object.entries(process.env) + .filter(([name]) => name.startsWith("SCRIPTC_") && !FRONTEND_INDEPENDENT_VARIABLES.has(name)) + .sort(([a], [b]) => a.localeCompare(b)); +} + function validFeatures(value: unknown): value is NativeLinkFeatures { if (value === null || typeof value !== "object" || Array.isArray(value)) return false; const features = value as Record; @@ -363,9 +381,7 @@ export function prepareNativeExecutable( implementation: implementationIdentity(toolchain), target: toolchain.target.name, declarations: toolchain.declarationsRoot ?? null, - environment: Object.entries(process.env) - .filter(([name]) => name.startsWith("SCRIPTC_")) - .sort(([a], [b]) => a.localeCompare(b)), + environment: frontendEnvironment(), }), ); const cached = readFrontendCache(cache, key, exclusions); @@ -493,9 +509,7 @@ export function prepareNativeLibrary( implementation: implementationIdentity(toolchain), target: toolchain.target.name, declarations: toolchain.declarationsRoot ?? null, - environment: Object.entries(process.env) - .filter(([name]) => name.startsWith("SCRIPTC_")) - .sort(([a], [b]) => a.localeCompare(b)), + environment: frontendEnvironment(), }), ); const cached = readFrontendCache(cache, key, exclusions);