@scriptc/compiler 0.0.31 → 0.0.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ambient/scriptc-node-fallback.d.ts +1 -1
- package/dist/backend/cc-cache.test.js +308 -1
- package/dist/backend/cc-cache.test.js.map +1 -1
- package/dist/backend/cc.d.ts +17 -5
- package/dist/backend/cc.js +745 -75
- package/dist/backend/cc.js.map +1 -1
- package/dist/backend/emission/emit-exprs.js +53 -2
- package/dist/backend/emission/emit-exprs.js.map +1 -1
- package/dist/backend/emission/emitter.d.ts +12 -5
- package/dist/backend/emission/emitter.js +160 -22
- package/dist/backend/emission/emitter.js.map +1 -1
- package/dist/backend/emission/may-throw.js +7 -4
- package/dist/backend/emission/may-throw.js.map +1 -1
- package/dist/backend/ffi-callbacks.d.ts +38 -1
- package/dist/backend/ffi-callbacks.js +76 -3
- package/dist/backend/ffi-callbacks.js.map +1 -1
- package/dist/backend/llvm/emitter.js +243 -12
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/diagnostics/diagnostic.js +2 -0
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/ffi/profile.d.ts +17 -5
- package/dist/ffi/profile.js +169 -22
- package/dist/ffi/profile.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +1 -0
- package/dist/frontend/lowering/lower-calls.js +89 -23
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.js +65 -19
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +12 -0
- package/dist/frontend/lowering/lowerer.js +20 -1
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/ts7/checker.d.ts +2 -0
- package/dist/frontend/ts7/checker.js +20 -3
- package/dist/frontend/ts7/checker.js.map +1 -1
- package/dist/frontend/types.d.ts +8 -0
- package/dist/frontend/types.js +33 -5
- package/dist/frontend/types.js.map +1 -1
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -1
- package/dist/ir/nodes.d.ts +22 -9
- package/dist/ir/nodes.js +7 -1
- package/dist/ir/nodes.js.map +1 -1
- package/dist/ir/serialize.d.ts +1 -1
- package/dist/ir/serialize.js +1 -1
- package/dist/ir/validate.js +74 -1
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/int-infer.js +7 -1
- package/dist/library/int-infer.js.map +1 -1
- package/dist/library/int-infer.test.js +73 -4
- package/dist/library/int-infer.test.js.map +1 -1
- package/dist/library/profile.d.ts +3 -0
- package/dist/library/profile.js +9 -1
- package/dist/library/profile.js.map +1 -1
- package/package.json +2 -2
- package/surface-manifest.json +1 -1
package/dist/backend/cc.js
CHANGED
|
@@ -2,9 +2,9 @@ import { execFile, spawnSync } from "node:child_process";
|
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { constants as fsConstants, existsSync, readdirSync } from "node:fs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
|
-
import { access, chmod, copyFile, link, mkdir, mkdtemp, readdir, readFile, realpath, rename, rm, stat, unlink, utimes, writeFile } from "node:fs/promises";
|
|
5
|
+
import { access, chmod, copyFile, link, lstat, mkdir, mkdtemp, readdir, readFile, realpath, rename, rm, stat, unlink, utimes, writeFile } from "node:fs/promises";
|
|
6
6
|
import { availableParallelism, homedir, tmpdir } from "node:os";
|
|
7
|
-
import { basename, delimiter, dirname, extname, isAbsolute, join, resolve } from "node:path";
|
|
7
|
+
import { basename, delimiter, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
8
8
|
import { promisify } from "node:util";
|
|
9
9
|
import { localizeElfObject, mergeAndLocalizeCoffObjects } from "./object-localize.js";
|
|
10
10
|
const execFileAsync = promisify(execFile);
|
|
@@ -32,7 +32,7 @@ function stableTestMemo(cache, key, probe) {
|
|
|
32
32
|
});
|
|
33
33
|
return pending;
|
|
34
34
|
}
|
|
35
|
-
const RUNTIME_SOURCES = ["scr_number.c", "scr_string.c", "scr_array.c", "scr_bytes.c", "scr_bytes_io.c", "scr_map.c", "scr_closure.c", "scr_object.c", "scr_union.c", "scr_exception.c", "scr_error.c", "scr_console.c", "scr_lib.c", "scr_path.c", "scr_url.c", "scr_json.c", "scr_async.c", "scr_child.c", "scr_cycle.c"];
|
|
35
|
+
const RUNTIME_SOURCES = ["scr_number.c", "scr_string.c", "scr_array.c", "scr_bytes.c", "scr_bytes_io.c", "scr_map.c", "scr_closure.c", "scr_ffi.c", "scr_object.c", "scr_union.c", "scr_exception.c", "scr_error.c", "scr_console.c", "scr_lib.c", "scr_path.c", "scr_url.c", "scr_json.c", "scr_async.c", "scr_child.c", "scr_cycle.c"];
|
|
36
36
|
/** The pinned quickjs-ng snapshot under packages/runtime/vendor/quickjs-ng
|
|
37
37
|
* (see vendor/README.md — update both together). Keys the archive cache so
|
|
38
38
|
* a vendor bump can never reuse a stale engine build. */
|
|
@@ -169,6 +169,30 @@ export class CcCompileError extends Error {
|
|
|
169
169
|
this.name = "CcCompileError";
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
|
+
/** Preserve the useful output from a failed compiler/tool invocation. Node's
|
|
173
|
+
* execFile error exposes stderr and stdout independently, but either stream
|
|
174
|
+
* may be present as an empty string. Prefer compiler diagnostics, retain a
|
|
175
|
+
* non-standard stdout diagnostic when that is all the tool emitted, and only
|
|
176
|
+
* then fall back to the process error itself. */
|
|
177
|
+
export function subprocessFailureDetail(err) {
|
|
178
|
+
const failure = err;
|
|
179
|
+
const output = (value) => {
|
|
180
|
+
const text = Buffer.isBuffer(value) ? value.toString("utf8") : value ?? "";
|
|
181
|
+
return text.trim().length > 0 ? text.trimEnd() : "";
|
|
182
|
+
};
|
|
183
|
+
const stderr = output(failure.stderr);
|
|
184
|
+
const stdout = output(failure.stdout);
|
|
185
|
+
if (stderr !== "" && stdout !== "")
|
|
186
|
+
return `${stderr}\n\ncompiler stdout:\n${stdout}`;
|
|
187
|
+
if (stderr !== "")
|
|
188
|
+
return stderr;
|
|
189
|
+
if (stdout !== "")
|
|
190
|
+
return `compiler stdout:\n${stdout}`;
|
|
191
|
+
if (typeof failure.message === "string" && failure.message.trim() !== "") {
|
|
192
|
+
return failure.message;
|
|
193
|
+
}
|
|
194
|
+
return String(err);
|
|
195
|
+
}
|
|
172
196
|
export function runtimeSrcDir() {
|
|
173
197
|
const testRoot = process.env["SCRIPTC_TEST_RUNTIME_SRC_DIR"];
|
|
174
198
|
if (testRoot !== undefined)
|
|
@@ -611,7 +635,7 @@ async function ensureEngineArchive(sanitize, driver, buildIdentity, cacheRoot =
|
|
|
611
635
|
"Install cmake (e.g. `brew install cmake`) and retry. Static builds do not need it.");
|
|
612
636
|
}
|
|
613
637
|
throw new Error(`cmake failed configuring the embedded engine (${vendor}).\n\n` +
|
|
614
|
-
|
|
638
|
+
subprocessFailureDetail(err));
|
|
615
639
|
}
|
|
616
640
|
await execFileAsync("cmake", [
|
|
617
641
|
"--build", buildDir,
|
|
@@ -950,13 +974,14 @@ async function ensureTlsArchive(sanitize, driver, buildIdentity, cacheRoot = ven
|
|
|
950
974
|
/** The library base: the executable lane's unconditional sources minus the
|
|
951
975
|
* fiber/loop and child-process units, plus the library-mode TU. */
|
|
952
976
|
const LIB_RUNTIME_SOURCES = [
|
|
953
|
-
...RUNTIME_SOURCES.filter((f) => f !== "scr_async.c" && f !== "scr_child.c"),
|
|
977
|
+
...RUNTIME_SOURCES.filter((f) => f !== "scr_async.c" && f !== "scr_child.c" && f !== "scr_ffi.c"),
|
|
954
978
|
"scr_library.c",
|
|
955
979
|
];
|
|
956
980
|
export async function compileLibArchive(opts) {
|
|
957
981
|
const rtDir = runtimeSrcDir();
|
|
958
982
|
const driver = resolveCc();
|
|
959
983
|
const sanitize = opts.sanitize ?? false;
|
|
984
|
+
const optimization = opts.optimization ?? "release";
|
|
960
985
|
const regex = opts.regex ?? false;
|
|
961
986
|
const sources = [
|
|
962
987
|
...LIB_RUNTIME_SOURCES,
|
|
@@ -984,7 +1009,9 @@ export async function compileLibArchive(opts) {
|
|
|
984
1009
|
const cflags = [
|
|
985
1010
|
"-std=c11",
|
|
986
1011
|
...driver.targetArgs,
|
|
987
|
-
...(sanitize
|
|
1012
|
+
...(sanitize
|
|
1013
|
+
? ["-O1", "-fsanitize=address", "-DSCR_RC_AUDIT"]
|
|
1014
|
+
: [optimization === "dev" ? "-O0" : "-O2"]),
|
|
988
1015
|
"-fno-math-errno",
|
|
989
1016
|
"-fno-strict-aliasing", // the emitted object model type-puns — see compileC's buildArgs
|
|
990
1017
|
"-Wno-deprecated-declarations",
|
|
@@ -1161,7 +1188,7 @@ export async function compileLibArchive(opts) {
|
|
|
1161
1188
|
await execFileAsync(driver.argv[0] ?? "clang", args);
|
|
1162
1189
|
}
|
|
1163
1190
|
catch (err) {
|
|
1164
|
-
const stderr =
|
|
1191
|
+
const stderr = subprocessFailureDetail(err);
|
|
1165
1192
|
throw new Error(`${driver.argv.join(" ")} failed compiling ${src} for the library archive.\n` +
|
|
1166
1193
|
`This is a scriptc bug (generated/runtime C should always compile) unless the compiler itself is missing/broken.\n\n${stderr}`);
|
|
1167
1194
|
}
|
|
@@ -1343,7 +1370,7 @@ async function localizeLibraryObjects(driver, arArgv, buildDir, programObject, s
|
|
|
1343
1370
|
await execFileAsync(argv[0], [...argv.slice(1)]);
|
|
1344
1371
|
}
|
|
1345
1372
|
catch (err) {
|
|
1346
|
-
const stderr =
|
|
1373
|
+
const stderr = subprocessFailureDetail(err);
|
|
1347
1374
|
throw new Error(`${argv[0]} failed while localizing the library archive's runtime symbols (abi.localize_runtime).\n` +
|
|
1348
1375
|
`Runtime symbol localization needs the ${platform === "darwin" ? "host toolchain's ld" : driver.target === null ? "host toolchain's ld and objcopy" : "cross driver's relocatable link"} beside the C compiler.\n\n${stderr}`);
|
|
1349
1376
|
}
|
|
@@ -1578,6 +1605,15 @@ async function resolvedToolIdentity(command, env = process.env) {
|
|
|
1578
1605
|
return (await resolvedTool(command, env))?.cacheIdentity ?? null;
|
|
1579
1606
|
}
|
|
1580
1607
|
const directCompilerDriverMemos = new Map();
|
|
1608
|
+
const directCompilerSelections = new Map();
|
|
1609
|
+
function compilerDriverProbeKey(driver, environmentFingerprint) {
|
|
1610
|
+
return [
|
|
1611
|
+
environmentFingerprint,
|
|
1612
|
+
driver.argv.join("\x1f"),
|
|
1613
|
+
driver.target ?? "<native>",
|
|
1614
|
+
driver.targetArgs.join("\x1f"),
|
|
1615
|
+
].join("\0");
|
|
1616
|
+
}
|
|
1581
1617
|
/** `/usr/bin/clang` on Darwin is Apple's immutable driver shim: its `-###`
|
|
1582
1618
|
* trace names the selected Xcode/CommandLineTools clang rather than the shim
|
|
1583
1619
|
* itself. The ordinary implicit-toolchain fingerprint below captures that
|
|
@@ -1599,12 +1635,7 @@ async function compilerDriverSupportsPersistentCache(driver, environmentFingerpr
|
|
|
1599
1635
|
// wrapper. This is deliberately undocumented and test-scoped.
|
|
1600
1636
|
if (process.env["SCRIPTC_TEST_TRUST_COMPILER_WRAPPER"] === "1")
|
|
1601
1637
|
return true;
|
|
1602
|
-
const driverKey =
|
|
1603
|
-
environmentFingerprint,
|
|
1604
|
-
driver.argv.join("\x1f"),
|
|
1605
|
-
driver.target ?? "<native>",
|
|
1606
|
-
driver.targetArgs.join("\x1f"),
|
|
1607
|
-
].join("\0");
|
|
1638
|
+
const driverKey = compilerDriverProbeKey(driver, environmentFingerprint);
|
|
1608
1639
|
const compiler = driver.argv[0] ?? "clang";
|
|
1609
1640
|
const resolvedDriver = await resolvedTool(compiler);
|
|
1610
1641
|
// A prior dependency list cannot prove that name resolution is unchanged:
|
|
@@ -1645,6 +1676,8 @@ async function compilerDriverSupportsPersistentCache(driver, environmentFingerpr
|
|
|
1645
1676
|
direct = effectiveCompiler !== null &&
|
|
1646
1677
|
(effectiveCompiler.fileIdentity === resolvedDriver.fileIdentity ||
|
|
1647
1678
|
isAppleSystemClangHandoff(resolvedDriver, effectiveCompiler));
|
|
1679
|
+
if (direct)
|
|
1680
|
+
directCompilerSelections.set(driverKey, effectiveCompiler);
|
|
1648
1681
|
}
|
|
1649
1682
|
}
|
|
1650
1683
|
catch {
|
|
@@ -1654,6 +1687,8 @@ async function compilerDriverSupportsPersistentCache(driver, environmentFingerpr
|
|
|
1654
1687
|
await rm(probeDir, { recursive: true, force: true }).catch(() => undefined);
|
|
1655
1688
|
}
|
|
1656
1689
|
directCompilerDriverMemos.set(probeKey, direct);
|
|
1690
|
+
if (!direct)
|
|
1691
|
+
directCompilerSelections.delete(driverKey);
|
|
1657
1692
|
return direct;
|
|
1658
1693
|
}
|
|
1659
1694
|
/** Complete library archives may skip `ar`, so the selected archiver must be
|
|
@@ -1734,6 +1769,29 @@ async function toolVersionOnce(argv, environmentFingerprint = toolchainEnvironme
|
|
|
1734
1769
|
toolVersionFallbacks.set(spellingKey, memo);
|
|
1735
1770
|
return memo;
|
|
1736
1771
|
}
|
|
1772
|
+
/** Dependency paths discovered while computing one strict fingerprint. The
|
|
1773
|
+
* output-local cache stamp snapshots these exact files/directories after a
|
|
1774
|
+
* validated build, then can prove a later same-output no-op without spawning
|
|
1775
|
+
* clang again. The content identity closes the gap between hashing and that
|
|
1776
|
+
* metadata snapshot: a file changed in the gap cannot make new bytes ride an
|
|
1777
|
+
* old key. Keep the map bounded for long-lived corpus/test processes. */
|
|
1778
|
+
const fingerprintDependencies = new Map();
|
|
1779
|
+
function rememberFingerprintDependencies(fingerprint, paths, contentPaths = [], contentFingerprint = null) {
|
|
1780
|
+
fingerprintDependencies.set(fingerprint, {
|
|
1781
|
+
paths: [...new Set(paths)].sort(),
|
|
1782
|
+
contentPaths: [...new Set(contentPaths)].sort(),
|
|
1783
|
+
contentFingerprint,
|
|
1784
|
+
});
|
|
1785
|
+
if (fingerprintDependencies.size > 256) {
|
|
1786
|
+
const oldest = fingerprintDependencies.keys().next().value;
|
|
1787
|
+
if (oldest !== undefined)
|
|
1788
|
+
fingerprintDependencies.delete(oldest);
|
|
1789
|
+
}
|
|
1790
|
+
return fingerprint;
|
|
1791
|
+
}
|
|
1792
|
+
function fingerprintDependencyPaths(fingerprint) {
|
|
1793
|
+
return fingerprintDependencies.get(fingerprint)?.paths ?? [];
|
|
1794
|
+
}
|
|
1737
1795
|
function parseMakeDependencies(output, cwd = process.cwd()) {
|
|
1738
1796
|
const flattened = output.replace(/\\\r?\n/g, " ");
|
|
1739
1797
|
const separator = flattened.indexOf(": ");
|
|
@@ -1815,13 +1873,14 @@ async function effectiveCompilerInvocationFingerprintFresh(driver, environmentFi
|
|
|
1815
1873
|
invocation: normalizedProbeInvocation(invocation, probeDir),
|
|
1816
1874
|
dependencies,
|
|
1817
1875
|
dependencyFingerprint: await fingerprintDependencyFiles(dependencies),
|
|
1876
|
+
invocationPaths: await existingDriverTracePaths(`${invocation.stdout}\n${invocation.stderr}`, probeDir, probeDir),
|
|
1818
1877
|
};
|
|
1819
1878
|
}
|
|
1820
1879
|
finally {
|
|
1821
1880
|
await rm(probeDir, { recursive: true, force: true }).catch(() => undefined);
|
|
1822
1881
|
}
|
|
1823
1882
|
}
|
|
1824
|
-
|
|
1883
|
+
const fingerprint = createHash("sha256")
|
|
1825
1884
|
.update("effective-compiler-invocation-v2\0")
|
|
1826
1885
|
.update(environmentFingerprint)
|
|
1827
1886
|
.update("\0")
|
|
@@ -1839,6 +1898,7 @@ async function effectiveCompilerInvocationFingerprintFresh(driver, environmentFi
|
|
|
1839
1898
|
.update("\0")
|
|
1840
1899
|
.update(probe.dependencyFingerprint)
|
|
1841
1900
|
.digest("hex");
|
|
1901
|
+
return rememberFingerprintDependencies(fingerprint, [...probe.dependencies, ...probe.invocationPaths], probe.dependencies, probe.dependencyFingerprint);
|
|
1842
1902
|
}
|
|
1843
1903
|
const stableEffectiveCompilerInvocationMemos = new Map();
|
|
1844
1904
|
function effectiveCompilerInvocationFingerprint(driver, environmentFingerprint, compileArgs, sourceExtension = ".c") {
|
|
@@ -1852,17 +1912,30 @@ function effectiveCompilerInvocationFingerprint(driver, environmentFingerprint,
|
|
|
1852
1912
|
}
|
|
1853
1913
|
async function nativeSourceFiles(directory, recursive, include = (name) => name.endsWith(".c") || name.endsWith(".h")) {
|
|
1854
1914
|
const files = [];
|
|
1855
|
-
const walk = async (current) => {
|
|
1915
|
+
const walk = async (current, ancestors) => {
|
|
1916
|
+
const canonical = await realpath(current).catch(() => resolve(current));
|
|
1917
|
+
if (ancestors.has(canonical))
|
|
1918
|
+
return;
|
|
1919
|
+
const nestedAncestors = new Set(ancestors).add(canonical);
|
|
1856
1920
|
const entries = await readdir(current, { withFileTypes: true }).catch(() => []);
|
|
1857
1921
|
for (const entry of entries) {
|
|
1858
1922
|
const path = join(current, entry.name);
|
|
1859
|
-
if (entry.isDirectory() && recursive)
|
|
1860
|
-
await walk(path);
|
|
1861
|
-
|
|
1923
|
+
if (entry.isDirectory() && recursive) {
|
|
1924
|
+
await walk(path, nestedAncestors);
|
|
1925
|
+
}
|
|
1926
|
+
else if (entry.isSymbolicLink()) {
|
|
1927
|
+
const target = await stat(path).catch(() => null);
|
|
1928
|
+
if (target?.isDirectory() && recursive)
|
|
1929
|
+
await walk(path, nestedAncestors);
|
|
1930
|
+
else if (target?.isFile() && include(entry.name))
|
|
1931
|
+
files.push(path);
|
|
1932
|
+
}
|
|
1933
|
+
else if (entry.isFile() && include(entry.name)) {
|
|
1862
1934
|
files.push(path);
|
|
1935
|
+
}
|
|
1863
1936
|
}
|
|
1864
1937
|
};
|
|
1865
|
-
await walk(directory);
|
|
1938
|
+
await walk(directory, new Set());
|
|
1866
1939
|
return files.sort();
|
|
1867
1940
|
}
|
|
1868
1941
|
/** Every system-header spelling consumed by a source tree scriptc owns. The
|
|
@@ -1914,6 +1987,18 @@ async function fingerprintDependencyFiles(paths) {
|
|
|
1914
1987
|
}
|
|
1915
1988
|
return hash.digest("hex");
|
|
1916
1989
|
}
|
|
1990
|
+
async function fingerprintDependenciesStillMatch(fingerprints) {
|
|
1991
|
+
const distinct = [...new Set(fingerprints)];
|
|
1992
|
+
const identities = distinct
|
|
1993
|
+
.map((fingerprint) => fingerprintDependencies.get(fingerprint))
|
|
1994
|
+
// A restored native-metadata stamp carries and revalidates the dependency
|
|
1995
|
+
// snapshot from the process that minted this fingerprint. Only identities
|
|
1996
|
+
// computed in this process have an additional content hash to recheck here.
|
|
1997
|
+
.filter((identity) => identity !== undefined);
|
|
1998
|
+
return (await Promise.all(identities.map(async (identity) => identity.contentFingerprint === null ||
|
|
1999
|
+
await fingerprintDependencyFiles(identity.contentPaths).catch(() => null) ===
|
|
2000
|
+
identity.contentFingerprint))).every(Boolean);
|
|
2001
|
+
}
|
|
1917
2002
|
/** Exact headers selected while preprocessing the caller's translation unit.
|
|
1918
2003
|
* The shared toolchain probe covers the runtime/vendor trees, but compileC is
|
|
1919
2004
|
* also a public API: an opted-in caller can include a system or header-only SDK
|
|
@@ -1922,7 +2007,7 @@ async function fingerprintDependencyFiles(paths) {
|
|
|
1922
2007
|
* quote-include directory and compiler-visible source spelling. */
|
|
1923
2008
|
async function translationUnitDependencyFingerprintFresh(driver, cflags, sourcePath, sourceBytes, environmentFingerprint) {
|
|
1924
2009
|
if (sourcePath.endsWith(".ll")) {
|
|
1925
|
-
return createHash("sha256").update("translation-unit-dependencies-v1\0llvm-ir").digest("hex");
|
|
2010
|
+
return rememberFingerprintDependencies(createHash("sha256").update("translation-unit-dependencies-v1\0llvm-ir").digest("hex"), []);
|
|
1926
2011
|
}
|
|
1927
2012
|
const compiler = driver.argv[0] ?? "clang";
|
|
1928
2013
|
const compilerIdentity = await resolvedToolIdentity(compiler);
|
|
@@ -1959,7 +2044,7 @@ async function translationUnitDependencyFingerprintFresh(driver, cflags, sourceP
|
|
|
1959
2044
|
await rm(probeDir, { recursive: true, force: true }).catch(() => undefined);
|
|
1960
2045
|
}
|
|
1961
2046
|
}
|
|
1962
|
-
|
|
2047
|
+
const fingerprint = createHash("sha256")
|
|
1963
2048
|
.update("translation-unit-dependencies-v1\0")
|
|
1964
2049
|
.update(environmentFingerprint)
|
|
1965
2050
|
.update("\0")
|
|
@@ -1969,6 +2054,7 @@ async function translationUnitDependencyFingerprintFresh(driver, cflags, sourceP
|
|
|
1969
2054
|
.update("\0")
|
|
1970
2055
|
.update(probe.dependencyFingerprint)
|
|
1971
2056
|
.digest("hex");
|
|
2057
|
+
return rememberFingerprintDependencies(fingerprint, probe.dependencies, probe.dependencies, probe.dependencyFingerprint);
|
|
1972
2058
|
}
|
|
1973
2059
|
const stableTranslationUnitDependencyMemos = new Map();
|
|
1974
2060
|
function translationUnitDependencyFingerprint(driver, cflags, sourcePath, sourceBytes, environmentFingerprint) {
|
|
@@ -2062,10 +2148,15 @@ async function implicitToolchainFingerprintFresh(driver, environmentFingerprint)
|
|
|
2062
2148
|
compilerInvocation: normalizedProbeInvocation(compilerInvocation, probeDir),
|
|
2063
2149
|
dependencies: dependencyPaths,
|
|
2064
2150
|
dependencyFingerprint: await fingerprintDependencyFiles(dependencyPaths),
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2151
|
+
invocationPaths: await existingDriverTracePaths(`${compilerInvocation.stdout}\n${compilerInvocation.stderr}`, probeDir, probeDir),
|
|
2152
|
+
tools: await Promise.all(toolSpellings.map(async (spelling) => {
|
|
2153
|
+
const resolved = await resolvedTool(spelling);
|
|
2154
|
+
return {
|
|
2155
|
+
spelling,
|
|
2156
|
+
identity: resolved?.cacheIdentity ?? null,
|
|
2157
|
+
path: resolved?.canonicalPath ?? null,
|
|
2158
|
+
};
|
|
2159
|
+
})),
|
|
2069
2160
|
};
|
|
2070
2161
|
}
|
|
2071
2162
|
finally {
|
|
@@ -2096,7 +2187,11 @@ async function implicitToolchainFingerprintFresh(driver, environmentFingerprint)
|
|
|
2096
2187
|
.update(currentIdentity ?? tool.identity ?? "<unresolved>")
|
|
2097
2188
|
.update("\0");
|
|
2098
2189
|
}
|
|
2099
|
-
return hash.digest("hex")
|
|
2190
|
+
return rememberFingerprintDependencies(hash.digest("hex"), [
|
|
2191
|
+
...probe.dependencies,
|
|
2192
|
+
...probe.invocationPaths,
|
|
2193
|
+
...probe.tools.flatMap((tool) => tool.path === null ? [] : [tool.path]),
|
|
2194
|
+
], probe.dependencies, probe.dependencyFingerprint);
|
|
2100
2195
|
}
|
|
2101
2196
|
const stableImplicitToolchainMemos = new Map();
|
|
2102
2197
|
function implicitToolchainFingerprint(driver, environmentFingerprint) {
|
|
@@ -2132,6 +2227,51 @@ function driverTraceCandidates(line) {
|
|
|
2132
2227
|
}
|
|
2133
2228
|
return candidates;
|
|
2134
2229
|
}
|
|
2230
|
+
async function existingDriverTracePaths(output, cwd, excludedRoot) {
|
|
2231
|
+
const candidates = new Set();
|
|
2232
|
+
for (const line of output.split(/\r?\n/)) {
|
|
2233
|
+
const tokens = driverTraceCandidates(line);
|
|
2234
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
2235
|
+
const token = tokens[index];
|
|
2236
|
+
const joinedPathOption = ["-I", "-L", "-F"].find((option) => token.startsWith(option) && token.length > option.length);
|
|
2237
|
+
const equalsPathOption = ["--sysroot=", "-resource-dir="].find((option) => token.startsWith(option));
|
|
2238
|
+
const separatePathOptions = [
|
|
2239
|
+
"-I",
|
|
2240
|
+
"-L",
|
|
2241
|
+
"-F",
|
|
2242
|
+
"--sysroot",
|
|
2243
|
+
"-isysroot",
|
|
2244
|
+
"-resource-dir",
|
|
2245
|
+
"-isystem",
|
|
2246
|
+
"-iquote",
|
|
2247
|
+
"-internal-isystem",
|
|
2248
|
+
"-internal-externc-isystem",
|
|
2249
|
+
"-internal-iframework",
|
|
2250
|
+
];
|
|
2251
|
+
const optionPath = joinedPathOption !== undefined
|
|
2252
|
+
? token.slice(joinedPathOption.length)
|
|
2253
|
+
: equalsPathOption !== undefined
|
|
2254
|
+
? token.slice(equalsPathOption.length)
|
|
2255
|
+
: separatePathOptions.includes(token)
|
|
2256
|
+
? tokens[index + 1] ?? ""
|
|
2257
|
+
: token;
|
|
2258
|
+
if (!isAbsolute(optionPath))
|
|
2259
|
+
continue;
|
|
2260
|
+
const path = resolve(cwd, optionPath);
|
|
2261
|
+
if (path === excludedRoot ||
|
|
2262
|
+
path.startsWith(`${excludedRoot}/`) ||
|
|
2263
|
+
path.startsWith(`${excludedRoot}\\`)) {
|
|
2264
|
+
continue;
|
|
2265
|
+
}
|
|
2266
|
+
candidates.add(path);
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
const existing = await Promise.all([...candidates].map(async (path) => [path, await lstat(path).catch(() => null)]));
|
|
2270
|
+
return existing
|
|
2271
|
+
.filter((entry) => entry[1] !== null)
|
|
2272
|
+
.map(([path]) => path)
|
|
2273
|
+
.sort();
|
|
2274
|
+
}
|
|
2135
2275
|
export async function parseLinkTraceFiles(output, cwd, excludedRoot, driverDryRun = false) {
|
|
2136
2276
|
const files = new Set();
|
|
2137
2277
|
for (const line of output.split(/\r?\n/)) {
|
|
@@ -2217,9 +2357,15 @@ async function implicitLinkerFingerprintFresh(driver, environmentFingerprint, li
|
|
|
2217
2357
|
linkerInvocation: normalizedProbeInvocation(driverInvocation, probeDir),
|
|
2218
2358
|
dependencies,
|
|
2219
2359
|
dependencyFingerprint: await fingerprintDependencyFiles(dependencies),
|
|
2360
|
+
invocationPaths: await existingDriverTracePaths(`${driverInvocation.stdout}\n${driverInvocation.stderr}`, probeDir, probeDir),
|
|
2220
2361
|
linker: {
|
|
2221
2362
|
spelling: linkerSpelling,
|
|
2222
|
-
identity: linkerSpelling === ""
|
|
2363
|
+
identity: linkerSpelling === ""
|
|
2364
|
+
? null
|
|
2365
|
+
: (await resolvedTool(linkerSpelling))?.cacheIdentity ?? null,
|
|
2366
|
+
path: linkerSpelling === ""
|
|
2367
|
+
? null
|
|
2368
|
+
: (await resolvedTool(linkerSpelling))?.canonicalPath ?? null,
|
|
2223
2369
|
},
|
|
2224
2370
|
};
|
|
2225
2371
|
}
|
|
@@ -2230,7 +2376,7 @@ async function implicitLinkerFingerprintFresh(driver, environmentFingerprint, li
|
|
|
2230
2376
|
const linkerIdentity = probe.linker.spelling === ""
|
|
2231
2377
|
? null
|
|
2232
2378
|
: await resolvedToolIdentity(probe.linker.spelling);
|
|
2233
|
-
|
|
2379
|
+
const fingerprint = createHash("sha256")
|
|
2234
2380
|
.update("implicit-linker-v3\0")
|
|
2235
2381
|
.update(environmentFingerprint)
|
|
2236
2382
|
.update("\0")
|
|
@@ -2256,6 +2402,11 @@ async function implicitLinkerFingerprintFresh(driver, environmentFingerprint, li
|
|
|
2256
2402
|
.update("\0")
|
|
2257
2403
|
.update(linkerIdentity ?? probe.linker.identity ?? "<unresolved>")
|
|
2258
2404
|
.digest("hex");
|
|
2405
|
+
return rememberFingerprintDependencies(fingerprint, [
|
|
2406
|
+
...probe.dependencies,
|
|
2407
|
+
...probe.invocationPaths,
|
|
2408
|
+
...(probe.linker.path === null ? [] : [probe.linker.path]),
|
|
2409
|
+
], probe.dependencies, probe.dependencyFingerprint);
|
|
2259
2410
|
}
|
|
2260
2411
|
const stableImplicitLinkerMemos = new Map();
|
|
2261
2412
|
function implicitLinkerFingerprint(driver, environmentFingerprint, linkArgs, effectiveInvocationArgs, traceInvocationArgs) {
|
|
@@ -2277,21 +2428,18 @@ function ccacheAvailable() {
|
|
|
2277
2428
|
ccacheMemo ??= execFileAsync("ccache", ["--version"]).then(() => true, () => false);
|
|
2278
2429
|
return ccacheMemo;
|
|
2279
2430
|
}
|
|
2280
|
-
/** Content hash of every .c/.h in the runtime src
|
|
2431
|
+
/** Content hash of every .c/.h in the runtime src tree, the Ryū sources
|
|
2281
2432
|
* textually included by scr_number.c, and the separately-built vendor pins —
|
|
2282
2433
|
* everything a binary links that the emitted C bytes don't already cover
|
|
2283
2434
|
* (npm-embedded C rides inside the emitted C; the engine and standalone
|
|
2284
2435
|
* vendor archives are pinned by their version constants). The small source
|
|
2285
|
-
* tree is hashed on every identity calculation:
|
|
2286
|
-
*
|
|
2436
|
+
* tree is hashed on every identity calculation: recursive enumeration is
|
|
2437
|
+
* required because a newly added nested header can begin shadowing a system
|
|
2438
|
+
* include without changing any previously selected dependency, while content
|
|
2439
|
+
* hashing catches same-size edits whose timestamp was preserved by a copy/sync
|
|
2440
|
+
* tool. */
|
|
2287
2441
|
async function runtimeFingerprintFresh(rtDir) {
|
|
2288
|
-
const groups = await
|
|
2289
|
-
{ label: "runtime", dir: rtDir },
|
|
2290
|
-
{ label: "ryu", dir: join(rtDir, "..", "vendor", "ryu") },
|
|
2291
|
-
].map(async (group) => {
|
|
2292
|
-
const names = (await readdir(group.dir)).filter((n) => n.endsWith(".c") || n.endsWith(".h")).sort();
|
|
2293
|
-
return { ...group, names };
|
|
2294
|
-
}));
|
|
2442
|
+
const groups = await runtimeFingerprintInputGroups(rtDir);
|
|
2295
2443
|
const h = createHash("sha256").update(QJS_COMMIT).update(MBEDTLS_VERSION).update(ZLIB_VERSION);
|
|
2296
2444
|
for (const group of groups) {
|
|
2297
2445
|
for (const n of group.names) {
|
|
@@ -2300,6 +2448,20 @@ async function runtimeFingerprintFresh(rtDir) {
|
|
|
2300
2448
|
}
|
|
2301
2449
|
return h.digest("hex");
|
|
2302
2450
|
}
|
|
2451
|
+
async function runtimeFingerprintInputGroups(rtDir) {
|
|
2452
|
+
return Promise.all([
|
|
2453
|
+
{ label: "runtime", dir: rtDir },
|
|
2454
|
+
{ label: "ryu", dir: join(rtDir, "..", "vendor", "ryu") },
|
|
2455
|
+
].map(async (group) => {
|
|
2456
|
+
const names = (await nativeSourceFiles(group.dir, true))
|
|
2457
|
+
.map((path) => relative(group.dir, path))
|
|
2458
|
+
.sort();
|
|
2459
|
+
return { ...group, names };
|
|
2460
|
+
}));
|
|
2461
|
+
}
|
|
2462
|
+
async function runtimeFingerprintInputPaths(rtDir) {
|
|
2463
|
+
return (await runtimeFingerprintInputGroups(rtDir)).flatMap((group) => group.names.map((name) => join(group.dir, name)));
|
|
2464
|
+
}
|
|
2303
2465
|
const stableRuntimeFingerprintMemos = new Map();
|
|
2304
2466
|
export function runtimeFingerprint(rtDir) {
|
|
2305
2467
|
const key = resolve(rtDir);
|
|
@@ -2388,6 +2550,279 @@ async function publishCachedFile(source, destination) {
|
|
|
2388
2550
|
]);
|
|
2389
2551
|
}
|
|
2390
2552
|
}
|
|
2553
|
+
async function directoryTreeDigest(root, excludedPaths = []) {
|
|
2554
|
+
const hash = createHash("sha256").update("native-dependency-tree-v1\0");
|
|
2555
|
+
const visited = new Set();
|
|
2556
|
+
const excluded = excludedPaths.map((path) => resolve(path));
|
|
2557
|
+
const walk = async (directory, relative) => {
|
|
2558
|
+
const canonical = await realpath(directory);
|
|
2559
|
+
if (visited.has(canonical)) {
|
|
2560
|
+
hash.update(relative).update("\0cycle\0").update(canonical).update("\0");
|
|
2561
|
+
return;
|
|
2562
|
+
}
|
|
2563
|
+
visited.add(canonical);
|
|
2564
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
2565
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
2566
|
+
for (const entry of entries) {
|
|
2567
|
+
const path = join(directory, entry.name);
|
|
2568
|
+
const absolute = resolve(path);
|
|
2569
|
+
if (excluded.some((candidate) => absolute === candidate ||
|
|
2570
|
+
absolute.startsWith(`${candidate}/`) ||
|
|
2571
|
+
absolute.startsWith(`${candidate}\\`)))
|
|
2572
|
+
continue;
|
|
2573
|
+
const child = relative === "" ? entry.name : `${relative}/${entry.name}`;
|
|
2574
|
+
const info = await lstat(path);
|
|
2575
|
+
const kind = info.isDirectory()
|
|
2576
|
+
? "directory"
|
|
2577
|
+
: info.isFile()
|
|
2578
|
+
? "file"
|
|
2579
|
+
: info.isSymbolicLink()
|
|
2580
|
+
? "symlink"
|
|
2581
|
+
: "other";
|
|
2582
|
+
hash.update(child).update("\0").update(kind).update("\0");
|
|
2583
|
+
if (kind === "symlink") {
|
|
2584
|
+
const target = await realpath(path).catch(() => "<missing>");
|
|
2585
|
+
hash.update(target).update("\0");
|
|
2586
|
+
const targetInfo = await stat(path).catch(() => null);
|
|
2587
|
+
if (targetInfo?.isDirectory())
|
|
2588
|
+
await walk(path, child);
|
|
2589
|
+
}
|
|
2590
|
+
if (kind === "directory")
|
|
2591
|
+
await walk(path, child);
|
|
2592
|
+
}
|
|
2593
|
+
visited.delete(canonical);
|
|
2594
|
+
};
|
|
2595
|
+
await walk(root, "");
|
|
2596
|
+
return hash.digest("hex");
|
|
2597
|
+
}
|
|
2598
|
+
function localDependencyKind(info) {
|
|
2599
|
+
return info.isFile()
|
|
2600
|
+
? "file"
|
|
2601
|
+
: info.isDirectory()
|
|
2602
|
+
? "directory"
|
|
2603
|
+
: info.isSymbolicLink()
|
|
2604
|
+
? "symlink"
|
|
2605
|
+
: null;
|
|
2606
|
+
}
|
|
2607
|
+
async function snapshotLocalArtifactDependency(path, treeExclusions = null) {
|
|
2608
|
+
const info = await lstat(path);
|
|
2609
|
+
const kind = localDependencyKind(info);
|
|
2610
|
+
if (kind === null)
|
|
2611
|
+
throw new Error(`unsupported local artifact dependency: ${path}`);
|
|
2612
|
+
const dependency = {
|
|
2613
|
+
path,
|
|
2614
|
+
kind,
|
|
2615
|
+
dev: info.dev,
|
|
2616
|
+
ino: info.ino,
|
|
2617
|
+
size: info.size,
|
|
2618
|
+
mtimeMs: info.mtimeMs,
|
|
2619
|
+
ctimeMs: info.ctimeMs,
|
|
2620
|
+
};
|
|
2621
|
+
if (kind === "directory" && treeExclusions !== null) {
|
|
2622
|
+
dependency.treeExclusions = [...new Set(treeExclusions.map((entry) => resolve(entry)))].sort();
|
|
2623
|
+
dependency.treeDigest = await directoryTreeDigest(path, dependency.treeExclusions);
|
|
2624
|
+
}
|
|
2625
|
+
if (kind === "symlink") {
|
|
2626
|
+
const targetPath = await realpath(path);
|
|
2627
|
+
const target = await stat(path);
|
|
2628
|
+
const targetKind = target.isFile() ? "file" : target.isDirectory() ? "directory" : null;
|
|
2629
|
+
if (targetKind === null)
|
|
2630
|
+
throw new Error(`unsupported symlink target dependency: ${path}`);
|
|
2631
|
+
dependency.targetPath = targetPath;
|
|
2632
|
+
dependency.targetKind = targetKind;
|
|
2633
|
+
dependency.targetDev = target.dev;
|
|
2634
|
+
dependency.targetIno = target.ino;
|
|
2635
|
+
dependency.targetSize = target.size;
|
|
2636
|
+
dependency.targetMtimeMs = target.mtimeMs;
|
|
2637
|
+
dependency.targetCtimeMs = target.ctimeMs;
|
|
2638
|
+
if (targetKind === "directory" && treeExclusions !== null) {
|
|
2639
|
+
dependency.treeExclusions = [...new Set(treeExclusions.map((entry) => resolve(entry)))].sort();
|
|
2640
|
+
dependency.treeDigest = await directoryTreeDigest(path, dependency.treeExclusions);
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
return dependency;
|
|
2644
|
+
}
|
|
2645
|
+
async function snapshotLocalArtifactDependencies(dependencyPaths, recursiveDirectories = [], recursiveExclusions = []) {
|
|
2646
|
+
const recursive = new Set(recursiveDirectories.map((path) => resolve(path)));
|
|
2647
|
+
return Promise.all([...new Set(dependencyPaths)].sort().map((path) => snapshotLocalArtifactDependency(path, recursive.has(resolve(path)) ? recursiveExclusions : null)));
|
|
2648
|
+
}
|
|
2649
|
+
async function localArtifactDependenciesStillMatch(dependencies) {
|
|
2650
|
+
return (await Promise.all(dependencies.map(async (dependency) => {
|
|
2651
|
+
const current = await snapshotLocalArtifactDependency(dependency.path, dependency.treeDigest === undefined ? null : dependency.treeExclusions ?? []).catch(() => null);
|
|
2652
|
+
return current !== null && JSON.stringify(current) === JSON.stringify(dependency);
|
|
2653
|
+
}))).every(Boolean);
|
|
2654
|
+
}
|
|
2655
|
+
function nativeMetadataStampPath(root, key) {
|
|
2656
|
+
return join(root, "meta", createHash("sha256").update(key).digest("hex"));
|
|
2657
|
+
}
|
|
2658
|
+
function nativeMetadataStampIntegrity(stamp) {
|
|
2659
|
+
return createHash("sha256")
|
|
2660
|
+
.update("native-metadata-stamp-v2\0")
|
|
2661
|
+
.update(JSON.stringify(stamp))
|
|
2662
|
+
.digest("hex");
|
|
2663
|
+
}
|
|
2664
|
+
async function readNativeMetadataStamp(root, key) {
|
|
2665
|
+
try {
|
|
2666
|
+
const stamp = JSON.parse(await readFile(nativeMetadataStampPath(root, key), "utf8"));
|
|
2667
|
+
if (stamp.version !== 2 ||
|
|
2668
|
+
stamp.key !== key ||
|
|
2669
|
+
stamp.values === null ||
|
|
2670
|
+
typeof stamp.values !== "object" ||
|
|
2671
|
+
!Array.isArray(stamp.dependencies) ||
|
|
2672
|
+
!/^[0-9a-f]{64}$/.test(stamp.integrity) ||
|
|
2673
|
+
nativeMetadataStampIntegrity({
|
|
2674
|
+
version: stamp.version,
|
|
2675
|
+
key: stamp.key,
|
|
2676
|
+
values: stamp.values,
|
|
2677
|
+
dependencies: stamp.dependencies,
|
|
2678
|
+
}) !== stamp.integrity ||
|
|
2679
|
+
!(await localArtifactDependenciesStillMatch(stamp.dependencies))) {
|
|
2680
|
+
return null;
|
|
2681
|
+
}
|
|
2682
|
+
return stamp;
|
|
2683
|
+
}
|
|
2684
|
+
catch {
|
|
2685
|
+
return null;
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
async function publishNativeMetadataStamp(root, key, values, dependencyPaths, fingerprints = []) {
|
|
2689
|
+
const destination = nativeMetadataStampPath(root, key);
|
|
2690
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
2691
|
+
const dependencies = await snapshotLocalArtifactDependencies(dependencyPaths);
|
|
2692
|
+
if (!(await fingerprintDependenciesStillMatch(fingerprints))) {
|
|
2693
|
+
throw new CacheInputsChangedError();
|
|
2694
|
+
}
|
|
2695
|
+
const unsigned = { version: 2, key, values, dependencies };
|
|
2696
|
+
const stamp = {
|
|
2697
|
+
...unsigned,
|
|
2698
|
+
integrity: nativeMetadataStampIntegrity(unsigned),
|
|
2699
|
+
};
|
|
2700
|
+
const tmp = `${destination}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
2701
|
+
try {
|
|
2702
|
+
await writeFile(tmp, `${JSON.stringify(stamp)}\n`, { mode: 0o600 });
|
|
2703
|
+
await rename(tmp, destination);
|
|
2704
|
+
}
|
|
2705
|
+
finally {
|
|
2706
|
+
await rm(tmp, { force: true }).catch(() => undefined);
|
|
2707
|
+
}
|
|
2708
|
+
return stamp;
|
|
2709
|
+
}
|
|
2710
|
+
function nativeMetadataKey(kind, parts) {
|
|
2711
|
+
const hash = createHash("sha256").update(`native-metadata-${kind}-v2\0`);
|
|
2712
|
+
for (const part of parts) {
|
|
2713
|
+
hash.update(typeof part === "string" ? part : part.join("\x1f")).update("\0");
|
|
2714
|
+
}
|
|
2715
|
+
return `${kind}-${hash.digest("hex")}`;
|
|
2716
|
+
}
|
|
2717
|
+
/** The caller-visible output is itself the cheapest safe cache tier. Once a
|
|
2718
|
+
* generated TU has produced this exact binary, an unchanged rebuild need not
|
|
2719
|
+
* rediscover every SDK header and linker input merely to copy equivalent bytes
|
|
2720
|
+
* back onto the same path. This tier is deliberately narrower than the CAS:
|
|
2721
|
+
* only frontend-generated programs with no caller-owned native inputs opt in.
|
|
2722
|
+
* The generated TU bytes, every scriptc runtime source, the selected direct
|
|
2723
|
+
* compiler inode, target/options/environment, and the output path all join the
|
|
2724
|
+
* key. A digest rejects a modified/truncated output before the no-op hit. */
|
|
2725
|
+
function localArtifactIdentity(opts, driver, environmentFingerprint, compilerIdentity, runtimeHash, programBytes) {
|
|
2726
|
+
const normalizedOptions = Object.fromEntries(Object.entries(opts)
|
|
2727
|
+
.filter(([, value]) => value !== undefined)
|
|
2728
|
+
.sort(([a], [b]) => a.localeCompare(b)));
|
|
2729
|
+
return createHash("sha256")
|
|
2730
|
+
.update("local-artifact-v1\0")
|
|
2731
|
+
.update(cacheTargetIdentity(driver)).update("\0")
|
|
2732
|
+
.update(environmentFingerprint).update("\0")
|
|
2733
|
+
.update(compilerIdentity).update("\0")
|
|
2734
|
+
.update(runtimeHash).update("\0")
|
|
2735
|
+
.update(driver.argv.join("\x1f")).update("\0")
|
|
2736
|
+
.update(driver.targetArgs.join("\x1f")).update("\0")
|
|
2737
|
+
.update(driver.linkArgs.join("\x1f")).update("\0")
|
|
2738
|
+
.update(process.env["SCRIPTC_FETCH_CURL"] === "1" ? "fetch-curl" : "fetch-native").update("\0")
|
|
2739
|
+
.update(JSON.stringify(normalizedOptions)).update("\0")
|
|
2740
|
+
.update(resolve(opts.cPath)).update("\0")
|
|
2741
|
+
.update(resolve(opts.outPath)).update("\0")
|
|
2742
|
+
.update(programBytes)
|
|
2743
|
+
.digest("hex");
|
|
2744
|
+
}
|
|
2745
|
+
function localArtifactStampPath(root, outPath) {
|
|
2746
|
+
const outputKey = createHash("sha256").update(resolve(outPath)).digest("hex");
|
|
2747
|
+
return join(root, "local", outputKey);
|
|
2748
|
+
}
|
|
2749
|
+
function localArtifactStampIntegrity(stamp) {
|
|
2750
|
+
return createHash("sha256")
|
|
2751
|
+
.update("local-artifact-stamp-v2\0")
|
|
2752
|
+
.update(JSON.stringify(stamp))
|
|
2753
|
+
.digest("hex");
|
|
2754
|
+
}
|
|
2755
|
+
async function localArtifactHit(stampPath, outPath, key) {
|
|
2756
|
+
try {
|
|
2757
|
+
const stamp = JSON.parse(await readFile(stampPath, "utf8"));
|
|
2758
|
+
const output = await lstat(outPath);
|
|
2759
|
+
const expectedMode = 0o777 & ~process.umask();
|
|
2760
|
+
if (stamp.version !== 2 ||
|
|
2761
|
+
stamp.key !== key ||
|
|
2762
|
+
!/^[0-9a-f]{64}$/.test(stamp.digest ?? "") ||
|
|
2763
|
+
!Array.isArray(stamp.dependencies) ||
|
|
2764
|
+
!/^[0-9a-f]{64}$/.test(stamp.integrity ?? "") ||
|
|
2765
|
+
localArtifactStampIntegrity({
|
|
2766
|
+
version: stamp.version,
|
|
2767
|
+
key: stamp.key,
|
|
2768
|
+
digest: stamp.digest,
|
|
2769
|
+
dependencies: stamp.dependencies,
|
|
2770
|
+
}) !== stamp.integrity ||
|
|
2771
|
+
!output.isFile() ||
|
|
2772
|
+
(output.mode & 0o777) !== expectedMode ||
|
|
2773
|
+
stamp.dependencies.some((dependency) => dependency === null ||
|
|
2774
|
+
typeof dependency !== "object" ||
|
|
2775
|
+
typeof dependency.path !== "string" ||
|
|
2776
|
+
dependency.kind !== "file" &&
|
|
2777
|
+
dependency.kind !== "directory" &&
|
|
2778
|
+
dependency.kind !== "symlink" ||
|
|
2779
|
+
typeof dependency.dev !== "number" ||
|
|
2780
|
+
typeof dependency.ino !== "number" ||
|
|
2781
|
+
typeof dependency.size !== "number" ||
|
|
2782
|
+
typeof dependency.mtimeMs !== "number" ||
|
|
2783
|
+
typeof dependency.ctimeMs !== "number" ||
|
|
2784
|
+
dependency.treeDigest !== undefined && typeof dependency.treeDigest !== "string" ||
|
|
2785
|
+
dependency.kind === "symlink" && (typeof dependency.targetPath !== "string" ||
|
|
2786
|
+
dependency.targetKind !== "file" && dependency.targetKind !== "directory" ||
|
|
2787
|
+
typeof dependency.targetDev !== "number" ||
|
|
2788
|
+
typeof dependency.targetIno !== "number" ||
|
|
2789
|
+
typeof dependency.targetSize !== "number" ||
|
|
2790
|
+
typeof dependency.targetMtimeMs !== "number" ||
|
|
2791
|
+
typeof dependency.targetCtimeMs !== "number")) ||
|
|
2792
|
+
!(await localArtifactDependenciesStillMatch(stamp.dependencies)) ||
|
|
2793
|
+
await fileDigest(outPath) !== stamp.digest) {
|
|
2794
|
+
return false;
|
|
2795
|
+
}
|
|
2796
|
+
const now = new Date();
|
|
2797
|
+
await utimes(stampPath, now, now).catch(() => undefined);
|
|
2798
|
+
return true;
|
|
2799
|
+
}
|
|
2800
|
+
catch {
|
|
2801
|
+
return false;
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
async function publishLocalArtifactStamp(stampPath, outPath, key, dependencyPaths, recursiveDirectories = [], recursiveExclusions = []) {
|
|
2805
|
+
await mkdir(dirname(stampPath), { recursive: true });
|
|
2806
|
+
const tmp = `${stampPath}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
2807
|
+
try {
|
|
2808
|
+
const dependencies = await snapshotLocalArtifactDependencies(dependencyPaths, recursiveDirectories, recursiveExclusions);
|
|
2809
|
+
const unsigned = {
|
|
2810
|
+
version: 2,
|
|
2811
|
+
key,
|
|
2812
|
+
digest: await fileDigest(outPath),
|
|
2813
|
+
dependencies,
|
|
2814
|
+
};
|
|
2815
|
+
const stamp = {
|
|
2816
|
+
...unsigned,
|
|
2817
|
+
integrity: localArtifactStampIntegrity(unsigned),
|
|
2818
|
+
};
|
|
2819
|
+
await writeFile(tmp, `${JSON.stringify(stamp)}\n`, { mode: 0o600 });
|
|
2820
|
+
await rename(tmp, stampPath);
|
|
2821
|
+
}
|
|
2822
|
+
finally {
|
|
2823
|
+
await rm(tmp, { force: true }).catch(() => undefined);
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2391
2826
|
/** The cached .o set for one flag flavor, compiled on first need. Concurrent
|
|
2392
2827
|
* first builds (parallel test workers on a cold cache) may duplicate work;
|
|
2393
2828
|
* per-file atomic renames make every winner equivalent. Publication is held
|
|
@@ -2421,7 +2856,22 @@ async function ensureRuntimeObjects(root, ccArgv, cflags, sources, keyPrefix, ve
|
|
|
2421
2856
|
await Promise.all(missing.slice(i, i + width).map(async (src) => {
|
|
2422
2857
|
const tmpObj = join(tmpDir, `${basename(src, ".c")}.o`);
|
|
2423
2858
|
const argv = [...(useCcache ? ["ccache"] : []), ...ccArgv, ...cflags, "-c", src, "-o", tmpObj];
|
|
2424
|
-
await execFileAsync(argv[0] ?? "clang", argv.slice(1)
|
|
2859
|
+
await execFileAsync(argv[0] ?? "clang", argv.slice(1), useCcache
|
|
2860
|
+
? {
|
|
2861
|
+
// ccache direct mode remembers only the headers selected by
|
|
2862
|
+
// its previous manifest and can miss a newly created,
|
|
2863
|
+
// higher-priority header. The scriptc object-set key already
|
|
2864
|
+
// includes the recursive runtime namespace fingerprint, so
|
|
2865
|
+
// carry it into ccache's own keyspace as well.
|
|
2866
|
+
env: {
|
|
2867
|
+
...process.env,
|
|
2868
|
+
CCACHE_NAMESPACE: [
|
|
2869
|
+
process.env["CCACHE_NAMESPACE"],
|
|
2870
|
+
`scriptc-${setKey}`,
|
|
2871
|
+
].filter((value) => value !== undefined && value !== "").join(":"),
|
|
2872
|
+
},
|
|
2873
|
+
}
|
|
2874
|
+
: undefined);
|
|
2425
2875
|
compiled.set(src, { object: tmpObj, digest: await fileDigest(tmpObj) });
|
|
2426
2876
|
}));
|
|
2427
2877
|
}
|
|
@@ -2562,6 +3012,7 @@ export async function compileC(opts) {
|
|
|
2562
3012
|
const net = (opts.net ?? false) || nativeFetch || netIsland;
|
|
2563
3013
|
const http = (opts.http ?? false) || nativeFetch || netIsland;
|
|
2564
3014
|
const tls = (opts.tls ?? false) || nativeFetch || netIsland;
|
|
3015
|
+
const tlsCa = (opts.tlsCa ?? false) || tls;
|
|
2565
3016
|
const driver = resolveCc();
|
|
2566
3017
|
// Mobile triples produce library archives, never standalone executables:
|
|
2567
3018
|
// the executable-lane runtime (event loop, sockets, child processes) is
|
|
@@ -2632,10 +3083,88 @@ export async function compileC(opts) {
|
|
|
2632
3083
|
root = null;
|
|
2633
3084
|
}
|
|
2634
3085
|
}
|
|
3086
|
+
let localArtifact = null;
|
|
3087
|
+
// Generated executable TUs are closed over scriptc's own runtime tree. A
|
|
3088
|
+
// same-output rebuild can therefore check those bytes directly before the
|
|
3089
|
+
// broader cross-output CAS performs its compiler/SDK/linker rediscovery.
|
|
3090
|
+
// FFI/native-input builds and the public arbitrary-C cache API stay on the
|
|
3091
|
+
// strict path because their dependency graphs are caller-owned.
|
|
3092
|
+
if (root !== null &&
|
|
3093
|
+
cachePolicy.completeArtifacts &&
|
|
3094
|
+
opts.cacheIdentity === "scriptc-generated-v1" &&
|
|
3095
|
+
(opts.linkInputs?.length ?? 0) === 0 &&
|
|
3096
|
+
(opts.systemLibraries?.length ?? 0) === 0 &&
|
|
3097
|
+
process.env["SCRIPTC_TEST_TRUST_COMPILER_WRAPPER"] !== "1") {
|
|
3098
|
+
try {
|
|
3099
|
+
const [compiler, runtimeHash, programBytes] = await Promise.all([
|
|
3100
|
+
resolvedTool(driver.argv[0] ?? "clang"),
|
|
3101
|
+
runtimeFingerprint(rtDir),
|
|
3102
|
+
readFile(opts.cPath),
|
|
3103
|
+
]);
|
|
3104
|
+
if (compiler !== null) {
|
|
3105
|
+
const effectiveCompiler = directCompilerSelections.get(compilerDriverProbeKey(driver, toolchainEnv)) ?? compiler;
|
|
3106
|
+
const key = localArtifactIdentity(opts, driver, toolchainEnv, `${compiler.cacheIdentity}\0${effectiveCompiler.cacheIdentity}`, runtimeHash, programBytes);
|
|
3107
|
+
const stampPath = localArtifactStampPath(root, opts.outPath);
|
|
3108
|
+
localArtifact = {
|
|
3109
|
+
stampPath,
|
|
3110
|
+
key,
|
|
3111
|
+
runtimeHash,
|
|
3112
|
+
programBytes,
|
|
3113
|
+
compilerPath: effectiveCompiler.canonicalPath,
|
|
3114
|
+
};
|
|
3115
|
+
if (await localArtifactHit(stampPath, opts.outPath, key))
|
|
3116
|
+
return;
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
catch {
|
|
3120
|
+
// The output-local tier is only an optimization; the fully validated
|
|
3121
|
+
// CAS below remains the source of truth on any metadata trouble.
|
|
3122
|
+
localArtifact = null;
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
2635
3125
|
let implicitToolchain = null;
|
|
3126
|
+
let toolchainMetadataStamp = null;
|
|
3127
|
+
const metadataCompiler = root === null
|
|
3128
|
+
? null
|
|
3129
|
+
: await resolvedTool(driver.argv[0] ?? "clang");
|
|
3130
|
+
const metadataEffectiveCompiler = directCompilerSelections.get(compilerDriverProbeKey(driver, toolchainEnv)) ?? metadataCompiler;
|
|
3131
|
+
const toolchainMetadataKey = root === null ||
|
|
3132
|
+
metadataCompiler === null ||
|
|
3133
|
+
metadataEffectiveCompiler === null ||
|
|
3134
|
+
process.env["SCRIPTC_TEST_TRUST_COMPILER_WRAPPER"] === "1"
|
|
3135
|
+
? null
|
|
3136
|
+
: nativeMetadataKey("toolchain", [
|
|
3137
|
+
cacheTargetIdentity(driver),
|
|
3138
|
+
toolchainEnv,
|
|
3139
|
+
driver.argv,
|
|
3140
|
+
driver.targetArgs,
|
|
3141
|
+
metadataCompiler.cacheIdentity,
|
|
3142
|
+
metadataEffectiveCompiler.cacheIdentity,
|
|
3143
|
+
rtDir,
|
|
3144
|
+
]);
|
|
2636
3145
|
if (persistentDriverCache) {
|
|
2637
3146
|
try {
|
|
2638
|
-
|
|
3147
|
+
toolchainMetadataStamp =
|
|
3148
|
+
root === null || toolchainMetadataKey === null
|
|
3149
|
+
? null
|
|
3150
|
+
: await readNativeMetadataStamp(root, toolchainMetadataKey);
|
|
3151
|
+
implicitToolchain = toolchainMetadataStamp?.values["implicitToolchain"] ??
|
|
3152
|
+
await implicitToolchainFingerprint(driver, toolchainEnv);
|
|
3153
|
+
let compilerVersion = toolchainMetadataStamp?.values["compilerVersion"];
|
|
3154
|
+
if (compilerVersion === undefined) {
|
|
3155
|
+
compilerVersion = await ccVersionOnce(driver.argv, toolchainEnv, true);
|
|
3156
|
+
}
|
|
3157
|
+
if (toolchainMetadataStamp === null &&
|
|
3158
|
+
root !== null &&
|
|
3159
|
+
toolchainMetadataKey !== null) {
|
|
3160
|
+
if (metadataCompiler !== null && metadataEffectiveCompiler !== null) {
|
|
3161
|
+
toolchainMetadataStamp = await publishNativeMetadataStamp(root, toolchainMetadataKey, { implicitToolchain, compilerVersion }, [
|
|
3162
|
+
metadataCompiler.canonicalPath,
|
|
3163
|
+
metadataEffectiveCompiler.canonicalPath,
|
|
3164
|
+
...fingerprintDependencyPaths(implicitToolchain),
|
|
3165
|
+
], [implicitToolchain]);
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
2639
3168
|
}
|
|
2640
3169
|
catch {
|
|
2641
3170
|
// Cache discovery is best-effort. In particular, a compiler wrapper can
|
|
@@ -2792,22 +3321,27 @@ export async function compileC(opts) {
|
|
|
2792
3321
|
...(opts.http2 ?? false ? [rt(join(rtDir, "scr_http2.c"))] : []),
|
|
2793
3322
|
...(opts.dgram ? [rt(join(rtDir, "scr_dgram.c"))] : []),
|
|
2794
3323
|
...(opts.watch ? [rt(join(rtDir, "scr_watch.c"))] : []),
|
|
3324
|
+
...(opts.foreignFfi ? [rt(join(rtDir, "scr_ffi_queue.c"))] : []),
|
|
2795
3325
|
...(opts.nodeTest ? [rt(join(rtDir, "scr_test.c"))] : []),
|
|
2796
3326
|
// The CA-store unit rides its own gate OR the tls one: scr_tls.c
|
|
2797
3327
|
// references its default-set override unconditionally.
|
|
2798
|
-
...(
|
|
3328
|
+
...(tlsCa ? [rt(join(rtDir, "scr_tls_ca.c"))] : []),
|
|
2799
3329
|
...(tlsArchive
|
|
2800
3330
|
? [
|
|
2801
3331
|
"-I", join(vendorTlsDir(), "include"),
|
|
2802
3332
|
rt(join(rtDir, "scr_tls.c")),
|
|
2803
3333
|
tlsArchive,
|
|
2804
|
-
// mbedTLS's win32 entropy poll is BCryptGenRandom (bcrypt.h)
|
|
2805
|
-
//
|
|
3334
|
+
// mbedTLS's win32 entropy poll is BCryptGenRandom (bcrypt.h).
|
|
3335
|
+
// The unconditional win32 libs above do not carry it.
|
|
2806
3336
|
// Never present on the default path, so the historical TLS
|
|
2807
3337
|
// link line cannot change.
|
|
2808
3338
|
...(targetPlatform(driver) === "win32" ? ["-lbcrypt"] : []),
|
|
2809
3339
|
]
|
|
2810
3340
|
: []),
|
|
3341
|
+
// scr_tls_ca.c enumerates and PEM-encodes Windows system-store entries.
|
|
3342
|
+
// This is independent of the mbedTLS archive: getCACertificates-only
|
|
3343
|
+
// programs need crypt32 too, while TLS programs imply the CA unit.
|
|
3344
|
+
...(tlsCa && targetPlatform(driver) === "win32" ? ["-lcrypt32"] : []),
|
|
2811
3345
|
// Static fetch is the engine-free half of scr_fetch.c. Dynamic builds
|
|
2812
3346
|
// compile the same source beside scr_island.c below, where its
|
|
2813
3347
|
// SCR_DYNAMIC half installs the full web surface.
|
|
@@ -2917,7 +3451,7 @@ export async function compileC(opts) {
|
|
|
2917
3451
|
await execFileAsync(driver.argv[0] ?? "clang", [...driver.argv.slice(1), ...args]);
|
|
2918
3452
|
}
|
|
2919
3453
|
catch (err) {
|
|
2920
|
-
const stderr =
|
|
3454
|
+
const stderr = subprocessFailureDetail(err);
|
|
2921
3455
|
const guidance = (opts.linkInputs?.length ?? 0) > 0 ||
|
|
2922
3456
|
(opts.systemLibraries?.length ?? 0) > 0
|
|
2923
3457
|
? "This build includes native FFI link inputs. Check that every symbol and system library exists, " +
|
|
@@ -2930,12 +3464,63 @@ export async function compileC(opts) {
|
|
|
2930
3464
|
};
|
|
2931
3465
|
let runtimeCompilerInvocation = null;
|
|
2932
3466
|
let programCompilerInvocation = null;
|
|
3467
|
+
let payloadMetadata = null;
|
|
3468
|
+
let compileMetadataStamp = null;
|
|
3469
|
+
const compileMetadataKey = root === null || process.env["SCRIPTC_TEST_TRUST_COMPILER_WRAPPER"] === "1"
|
|
3470
|
+
? null
|
|
3471
|
+
: nativeMetadataKey("compile", [
|
|
3472
|
+
cacheTargetIdentity(driver),
|
|
3473
|
+
toolchainEnv,
|
|
3474
|
+
implicitToolchain ?? "<uncached>",
|
|
3475
|
+
driver.argv,
|
|
3476
|
+
cflags,
|
|
3477
|
+
programCompilerArgs,
|
|
3478
|
+
programSourceExtension,
|
|
3479
|
+
]);
|
|
2933
3480
|
if (root !== null) {
|
|
2934
3481
|
try {
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
3482
|
+
// These probes inspect disjoint inputs. Start the payload reads here as
|
|
3483
|
+
// well so runtime hashing and clang's dry-run traces overlap instead of
|
|
3484
|
+
// forming a serial prelude before every cache lookup.
|
|
3485
|
+
payloadMetadata = Promise.all([
|
|
3486
|
+
Promise.resolve(toolchainMetadataStamp?.values["compilerVersion"] ??
|
|
3487
|
+
ccVersionOnce(driver.argv, toolchainEnv, true)),
|
|
3488
|
+
localArtifact === null
|
|
3489
|
+
? runtimeFingerprint(rtDir)
|
|
3490
|
+
: Promise.resolve(localArtifact.runtimeHash),
|
|
3491
|
+
localArtifact === null
|
|
3492
|
+
? readFile(opts.cPath)
|
|
3493
|
+
: Promise.resolve(localArtifact.programBytes),
|
|
3494
|
+
]);
|
|
3495
|
+
compileMetadataStamp = compileMetadataKey === null
|
|
3496
|
+
? null
|
|
3497
|
+
: await readNativeMetadataStamp(root, compileMetadataKey);
|
|
3498
|
+
if (compileMetadataStamp !== null) {
|
|
3499
|
+
runtimeCompilerInvocation = compileMetadataStamp.values["runtimeInvocation"] ?? null;
|
|
3500
|
+
programCompilerInvocation = compileMetadataStamp.values["programInvocation"] ?? null;
|
|
3501
|
+
if (runtimeCompilerInvocation === null || programCompilerInvocation === null) {
|
|
3502
|
+
compileMetadataStamp = null;
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
if (compileMetadataStamp === null) {
|
|
3506
|
+
const [runtimeInvocation, programInvocation] = await Promise.all([
|
|
3507
|
+
effectiveCompilerInvocationFingerprint(driver, toolchainEnv, cflags),
|
|
3508
|
+
programSourceExtension === ".ll"
|
|
3509
|
+
? effectiveCompilerInvocationFingerprint(driver, toolchainEnv, programCompilerArgs, programSourceExtension)
|
|
3510
|
+
: Promise.resolve(null),
|
|
3511
|
+
]);
|
|
3512
|
+
runtimeCompilerInvocation = runtimeInvocation;
|
|
3513
|
+
programCompilerInvocation = programInvocation ?? runtimeInvocation;
|
|
3514
|
+
if (compileMetadataKey !== null) {
|
|
3515
|
+
compileMetadataStamp = await publishNativeMetadataStamp(root, compileMetadataKey, {
|
|
3516
|
+
runtimeInvocation: runtimeCompilerInvocation,
|
|
3517
|
+
programInvocation: programCompilerInvocation,
|
|
3518
|
+
}, [
|
|
3519
|
+
...fingerprintDependencyPaths(runtimeCompilerInvocation),
|
|
3520
|
+
...fingerprintDependencyPaths(programCompilerInvocation),
|
|
3521
|
+
], [runtimeCompilerInvocation, programCompilerInvocation]);
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
2939
3524
|
}
|
|
2940
3525
|
catch {
|
|
2941
3526
|
// Preserve the uncached build for wrappers that compile successfully but
|
|
@@ -2960,11 +3545,11 @@ export async function compileC(opts) {
|
|
|
2960
3545
|
let fingerprint;
|
|
2961
3546
|
let cBytes;
|
|
2962
3547
|
try {
|
|
2963
|
-
[cv, fingerprint, cBytes] = await Promise.all([
|
|
3548
|
+
[cv, fingerprint, cBytes] = await (payloadMetadata ?? Promise.all([
|
|
2964
3549
|
ccVersionOnce(driver.argv, toolchainEnv, true),
|
|
2965
3550
|
runtimeFingerprint(rtDir),
|
|
2966
3551
|
readFile(opts.cPath),
|
|
2967
|
-
]);
|
|
3552
|
+
]));
|
|
2968
3553
|
}
|
|
2969
3554
|
catch {
|
|
2970
3555
|
// A version/fingerprint probe is an optimization boundary. If the compiler
|
|
@@ -2983,16 +3568,6 @@ export async function compileC(opts) {
|
|
|
2983
3568
|
(opts.linkInputs?.length ?? 0) === 0 &&
|
|
2984
3569
|
(opts.systemLibraries?.length ?? 0) === 0;
|
|
2985
3570
|
let programDependencies = null;
|
|
2986
|
-
if (cacheCompleteArtifact) {
|
|
2987
|
-
try {
|
|
2988
|
-
programDependencies = await translationUnitDependencyFingerprint(driver, cflags, opts.cPath, cBytes, toolchainEnv);
|
|
2989
|
-
}
|
|
2990
|
-
catch {
|
|
2991
|
-
// Program-header discovery is needed only for a complete hit. Runtime
|
|
2992
|
-
// objects remain safe because they never contain the caller's TU.
|
|
2993
|
-
cacheCompleteArtifact = false;
|
|
2994
|
-
}
|
|
2995
|
-
}
|
|
2996
3571
|
const linkProbeArgs = [
|
|
2997
3572
|
...(sanitize ? ["-fsanitize=address"] : []),
|
|
2998
3573
|
...threadArgs,
|
|
@@ -3000,6 +3575,7 @@ export async function compileC(opts) {
|
|
|
3000
3575
|
? ["-ladvapi32", "-liphlpapi", "-lws2_32"]
|
|
3001
3576
|
: []),
|
|
3002
3577
|
...(tls && targetPlatform(driver) === "win32" ? ["-lbcrypt"] : []),
|
|
3578
|
+
...(tlsCa && targetPlatform(driver) === "win32" ? ["-lcrypt32"] : []),
|
|
3003
3579
|
...(curlFetch && driver.target === null ? ["-lcurl"] : []),
|
|
3004
3580
|
...(dynamic && !driver.linkArgs.includes("-lm") ? ["-lm"] : []),
|
|
3005
3581
|
...(((opts.zlib ?? false) || nativeFetch) && driver.target === null ? ["-lz"] : []),
|
|
@@ -3014,6 +3590,7 @@ export async function compileC(opts) {
|
|
|
3014
3590
|
? ["-ladvapi32", "-liphlpapi", "-lws2_32"]
|
|
3015
3591
|
: []),
|
|
3016
3592
|
...(tls && targetPlatform(driver) === "win32" ? ["-lbcrypt"] : []),
|
|
3593
|
+
...(tlsCa && targetPlatform(driver) === "win32" ? ["-lcrypt32"] : []),
|
|
3017
3594
|
...(curlStubDir !== null ? [`-L${curlStubDir}`] : []),
|
|
3018
3595
|
...(curlFetch && driver.target === null ? ["-lcurl"] : []),
|
|
3019
3596
|
...(dynamic && !driver.linkArgs.includes("-lm") ? ["-lm"] : []),
|
|
@@ -3031,15 +3608,89 @@ export async function compileC(opts) {
|
|
|
3031
3608
|
? effectiveLinkInvocationArgs
|
|
3032
3609
|
: effectiveLinkInvocationArgs.filter((arg) => arg !== `-L${curlStubDir}`);
|
|
3033
3610
|
let implicitLinker = null;
|
|
3611
|
+
let preBuildDependencies = null;
|
|
3612
|
+
let localArtifactDependencyPaths = null;
|
|
3613
|
+
let linkMetadataStamp = null;
|
|
3614
|
+
const linkMetadataKey = root === null || process.env["SCRIPTC_TEST_TRUST_COMPILER_WRAPPER"] === "1"
|
|
3615
|
+
? null
|
|
3616
|
+
: nativeMetadataKey("link", [
|
|
3617
|
+
cacheTargetIdentity(driver),
|
|
3618
|
+
toolchainEnv,
|
|
3619
|
+
implicitToolchain ?? "<uncached>",
|
|
3620
|
+
runtimeCompilerInvocation ?? "<uncached>",
|
|
3621
|
+
programCompilerInvocation ?? "<uncached>",
|
|
3622
|
+
driver.argv,
|
|
3623
|
+
linkProbeArgs,
|
|
3624
|
+
effectiveLinkInvocationArgs,
|
|
3625
|
+
linkTraceInvocationArgs,
|
|
3626
|
+
]);
|
|
3034
3627
|
if (cacheCompleteArtifact) {
|
|
3035
3628
|
try {
|
|
3036
|
-
|
|
3629
|
+
// Header discovery and linker tracing are independent subprocess trees.
|
|
3630
|
+
// Running them together removes one complete probe round-trip from both
|
|
3631
|
+
// cache hits and ordinary edit/build misses without changing either key.
|
|
3632
|
+
linkMetadataStamp = linkMetadataKey === null
|
|
3633
|
+
? null
|
|
3634
|
+
: await readNativeMetadataStamp(root, linkMetadataKey);
|
|
3635
|
+
if (linkMetadataStamp !== null) {
|
|
3636
|
+
implicitLinker = linkMetadataStamp.values["implicitLinker"] ?? null;
|
|
3637
|
+
if (implicitLinker === null)
|
|
3638
|
+
linkMetadataStamp = null;
|
|
3639
|
+
}
|
|
3640
|
+
[programDependencies, implicitLinker] = await Promise.all([
|
|
3641
|
+
translationUnitDependencyFingerprint(driver, cflags, opts.cPath, cBytes, toolchainEnv),
|
|
3642
|
+
linkMetadataStamp === null
|
|
3643
|
+
? implicitLinkerFingerprint(driver, toolchainEnv, linkProbeArgs, effectiveLinkInvocationArgs, linkTraceInvocationArgs)
|
|
3644
|
+
: Promise.resolve(implicitLinker),
|
|
3645
|
+
]);
|
|
3646
|
+
if (linkMetadataStamp === null && linkMetadataKey !== null) {
|
|
3647
|
+
linkMetadataStamp = await publishNativeMetadataStamp(root, linkMetadataKey, { implicitLinker }, fingerprintDependencyPaths(implicitLinker), [implicitLinker]);
|
|
3648
|
+
}
|
|
3649
|
+
preBuildDependencies = await snapshotLocalArtifactDependencies([
|
|
3650
|
+
...(await runtimeFingerprintInputPaths(rtDir)),
|
|
3651
|
+
...(toolchainMetadataStamp?.dependencies.map((dependency) => dependency.path) ??
|
|
3652
|
+
fingerprintDependencyPaths(implicitToolchain)),
|
|
3653
|
+
...(compileMetadataStamp?.dependencies.map((dependency) => dependency.path) ?? []),
|
|
3654
|
+
...(linkMetadataStamp?.dependencies.map((dependency) => dependency.path) ??
|
|
3655
|
+
fingerprintDependencyPaths(implicitLinker)),
|
|
3656
|
+
...fingerprintDependencyPaths(programDependencies),
|
|
3657
|
+
]);
|
|
3658
|
+
// Every content-bearing fingerprint was computed before this metadata
|
|
3659
|
+
// snapshot. Re-read those exact files now so the snapshot cannot certify
|
|
3660
|
+
// bytes that changed after hashing but before the final compile starts.
|
|
3661
|
+
if (!(await fingerprintDependenciesStillMatch([
|
|
3662
|
+
implicitToolchain,
|
|
3663
|
+
runtimeCompilerInvocation,
|
|
3664
|
+
programCompilerInvocation,
|
|
3665
|
+
implicitLinker,
|
|
3666
|
+
programDependencies,
|
|
3667
|
+
])) ||
|
|
3668
|
+
await runtimeFingerprint(rtDir).catch(() => null) !== fingerprint ||
|
|
3669
|
+
!(await Promise.all([toolchainMetadataStamp, compileMetadataStamp, linkMetadataStamp]
|
|
3670
|
+
.filter((stamp) => stamp !== null)
|
|
3671
|
+
.map((stamp) => localArtifactDependenciesStillMatch(stamp.dependencies)))).every(Boolean)) {
|
|
3672
|
+
throw new CacheInputsChangedError();
|
|
3673
|
+
}
|
|
3674
|
+
if (localArtifact !== null) {
|
|
3675
|
+
// Native metadata stamps persist their dependency paths across CLI
|
|
3676
|
+
// processes; fingerprintDependencyPaths deliberately does not. Build
|
|
3677
|
+
// the output-local stamp from this complete validated snapshot so a
|
|
3678
|
+
// source edit in a fresh process cannot replace it with only the two
|
|
3679
|
+
// process-local fallback paths.
|
|
3680
|
+
localArtifactDependencyPaths = [
|
|
3681
|
+
localArtifact.compilerPath,
|
|
3682
|
+
dirname(resolve(opts.cPath)),
|
|
3683
|
+
...preBuildDependencies.map((dependency) => dependency.path),
|
|
3684
|
+
];
|
|
3685
|
+
}
|
|
3037
3686
|
}
|
|
3038
3687
|
catch {
|
|
3039
|
-
//
|
|
3040
|
-
// objects remain safely cacheable
|
|
3041
|
-
//
|
|
3688
|
+
// Program-header discovery and linker tracing are both required for a
|
|
3689
|
+
// complete hit. Runtime objects remain safely cacheable if either probe
|
|
3690
|
+
// is unavailable.
|
|
3042
3691
|
cacheCompleteArtifact = false;
|
|
3692
|
+
preBuildDependencies = null;
|
|
3693
|
+
localArtifactDependencyPaths = null;
|
|
3043
3694
|
}
|
|
3044
3695
|
}
|
|
3045
3696
|
const binDir = join(root, "bin");
|
|
@@ -3092,6 +3743,9 @@ export async function compileC(opts) {
|
|
|
3092
3743
|
// cache entry populated by a less restrictive shell must not widen access.
|
|
3093
3744
|
await chmod(tmpOut, 0o777 & ~process.umask());
|
|
3094
3745
|
await rename(tmpOut, opts.outPath);
|
|
3746
|
+
if (localArtifact !== null && localArtifactDependencyPaths !== null) {
|
|
3747
|
+
await publishLocalArtifactStamp(localArtifact.stampPath, opts.outPath, localArtifact.key, localArtifactDependencyPaths, [dirname(resolve(opts.cPath))], [root]).catch(() => undefined);
|
|
3748
|
+
}
|
|
3095
3749
|
return; // hit: the program/runtime payload compile and link were skipped
|
|
3096
3750
|
}
|
|
3097
3751
|
catch {
|
|
@@ -3127,18 +3781,25 @@ export async function compileC(opts) {
|
|
|
3127
3781
|
await writeFile(programPath, cBytes);
|
|
3128
3782
|
let objects = null;
|
|
3129
3783
|
let cacheInputsStable = true;
|
|
3130
|
-
let
|
|
3784
|
+
let strictObjectVerification = null;
|
|
3131
3785
|
const objectImplicitToolchainStillMatches = () => {
|
|
3132
|
-
|
|
3786
|
+
if (preBuildDependencies !== null) {
|
|
3787
|
+
return localArtifactDependenciesStillMatch(preBuildDependencies);
|
|
3788
|
+
}
|
|
3789
|
+
// Complete-artifact caching can be disabled by caller-owned native
|
|
3790
|
+
// inputs while the safe runtime-object tier remains active. Preserve its
|
|
3791
|
+
// strict discovery fallback in that posture.
|
|
3792
|
+
strictObjectVerification ??= Promise.all([
|
|
3793
|
+
runtimeFingerprint(rtDir),
|
|
3133
3794
|
implicitToolchainFingerprint(driver, toolchainEnv),
|
|
3134
3795
|
effectiveCompilerInvocationFingerprint(driver, toolchainEnv, cflags),
|
|
3135
|
-
]).then(([currentImplicit, currentInvocation]) =>
|
|
3796
|
+
]).then(([currentRuntime, currentImplicit, currentInvocation]) => currentRuntime === fingerprint &&
|
|
3797
|
+
currentImplicit === implicitToolchain &&
|
|
3136
3798
|
currentInvocation === runtimeCompilerInvocation, () => false);
|
|
3137
|
-
return
|
|
3799
|
+
return strictObjectVerification;
|
|
3138
3800
|
};
|
|
3139
3801
|
try {
|
|
3140
|
-
const cached = await ensureRuntimeObjects(root, driver.argv, cflags, rtInputs, `obj-v5\0${cacheTargetIdentity(driver)}\0${toolchainEnv}\0${implicitToolchain}\0${runtimeCompilerInvocation}\0${ccName}\0${cv}\0${fingerprint}\0`, async () =>
|
|
3141
|
-
(await objectImplicitToolchainStillMatches()));
|
|
3802
|
+
const cached = await ensureRuntimeObjects(root, driver.argv, cflags, rtInputs, `obj-v5\0${cacheTargetIdentity(driver)}\0${toolchainEnv}\0${implicitToolchain}\0${runtimeCompilerInvocation}\0${ccName}\0${cv}\0${fingerprint}\0`, async () => await objectImplicitToolchainStillMatches());
|
|
3142
3803
|
objects = await stageRuntimeObjects(cached, join(buildDir, "runtime-objects"));
|
|
3143
3804
|
}
|
|
3144
3805
|
catch (err) {
|
|
@@ -3149,10 +3810,11 @@ export async function compileC(opts) {
|
|
|
3149
3810
|
await runClang(buildArgs((p) => objects?.get(p) ?? p, { programPath, outPath: privateOut, compilerVisibleSource: opts.cPath }));
|
|
3150
3811
|
await installArtifact(privateOut, opts.outPath);
|
|
3151
3812
|
if (cachedBin !== null && keyHex !== null) {
|
|
3152
|
-
//
|
|
3153
|
-
//
|
|
3154
|
-
//
|
|
3155
|
-
//
|
|
3813
|
+
// Metadata comparison catches ordinary changes cheaply, but cannot by
|
|
3814
|
+
// itself prove the snapshot was taken from the same bytes hashed into
|
|
3815
|
+
// the key. Recompute every content-bearing identity after the final link
|
|
3816
|
+
// so a header/SDK/compiler change in either pre-build gap cannot publish
|
|
3817
|
+
// new output under an old key.
|
|
3156
3818
|
const [currentRuntime, currentImplicit, currentRuntimeInvocation, currentProgramInvocation, currentProgramDependencies, currentLinker, currentCompiler] = await Promise.all([
|
|
3157
3819
|
runtimeFingerprint(rtDir).catch(() => null),
|
|
3158
3820
|
implicitToolchainFingerprint(driver, toolchainEnv).catch(() => null),
|
|
@@ -3164,6 +3826,8 @@ export async function compileC(opts) {
|
|
|
3164
3826
|
]);
|
|
3165
3827
|
cacheInputsStable =
|
|
3166
3828
|
cacheInputsStable &&
|
|
3829
|
+
preBuildDependencies !== null &&
|
|
3830
|
+
await localArtifactDependenciesStillMatch(preBuildDependencies) &&
|
|
3167
3831
|
currentRuntime === fingerprint &&
|
|
3168
3832
|
currentImplicit === implicitToolchain &&
|
|
3169
3833
|
currentRuntimeInvocation === runtimeCompilerInvocation &&
|
|
@@ -3183,6 +3847,12 @@ export async function compileC(opts) {
|
|
|
3183
3847
|
/* publishing is best-effort */
|
|
3184
3848
|
}
|
|
3185
3849
|
}
|
|
3850
|
+
if (localArtifact !== null &&
|
|
3851
|
+
localArtifactDependencyPaths !== null &&
|
|
3852
|
+
cacheCompleteArtifact &&
|
|
3853
|
+
cacheInputsStable) {
|
|
3854
|
+
await publishLocalArtifactStamp(localArtifact.stampPath, opts.outPath, localArtifact.key, localArtifactDependencyPaths, [dirname(resolve(opts.cPath))], [root]).catch(() => undefined);
|
|
3855
|
+
}
|
|
3186
3856
|
}
|
|
3187
3857
|
finally {
|
|
3188
3858
|
await rm(buildDir, { recursive: true, force: true }).catch(() => undefined);
|