@scriptc/compiler 0.0.32 → 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/dist/backend/cc-cache.test.js +308 -1
- package/dist/backend/cc-cache.test.js.map +1 -1
- package/dist/backend/cc.d.ts +6 -0
- package/dist/backend/cc.js +707 -68
- 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.test.js +4 -4
- 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. */
|
|
@@ -974,13 +974,14 @@ async function ensureTlsArchive(sanitize, driver, buildIdentity, cacheRoot = ven
|
|
|
974
974
|
/** The library base: the executable lane's unconditional sources minus the
|
|
975
975
|
* fiber/loop and child-process units, plus the library-mode TU. */
|
|
976
976
|
const LIB_RUNTIME_SOURCES = [
|
|
977
|
-
...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"),
|
|
978
978
|
"scr_library.c",
|
|
979
979
|
];
|
|
980
980
|
export async function compileLibArchive(opts) {
|
|
981
981
|
const rtDir = runtimeSrcDir();
|
|
982
982
|
const driver = resolveCc();
|
|
983
983
|
const sanitize = opts.sanitize ?? false;
|
|
984
|
+
const optimization = opts.optimization ?? "release";
|
|
984
985
|
const regex = opts.regex ?? false;
|
|
985
986
|
const sources = [
|
|
986
987
|
...LIB_RUNTIME_SOURCES,
|
|
@@ -1008,7 +1009,9 @@ export async function compileLibArchive(opts) {
|
|
|
1008
1009
|
const cflags = [
|
|
1009
1010
|
"-std=c11",
|
|
1010
1011
|
...driver.targetArgs,
|
|
1011
|
-
...(sanitize
|
|
1012
|
+
...(sanitize
|
|
1013
|
+
? ["-O1", "-fsanitize=address", "-DSCR_RC_AUDIT"]
|
|
1014
|
+
: [optimization === "dev" ? "-O0" : "-O2"]),
|
|
1012
1015
|
"-fno-math-errno",
|
|
1013
1016
|
"-fno-strict-aliasing", // the emitted object model type-puns — see compileC's buildArgs
|
|
1014
1017
|
"-Wno-deprecated-declarations",
|
|
@@ -1602,6 +1605,15 @@ async function resolvedToolIdentity(command, env = process.env) {
|
|
|
1602
1605
|
return (await resolvedTool(command, env))?.cacheIdentity ?? null;
|
|
1603
1606
|
}
|
|
1604
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
|
+
}
|
|
1605
1617
|
/** `/usr/bin/clang` on Darwin is Apple's immutable driver shim: its `-###`
|
|
1606
1618
|
* trace names the selected Xcode/CommandLineTools clang rather than the shim
|
|
1607
1619
|
* itself. The ordinary implicit-toolchain fingerprint below captures that
|
|
@@ -1623,12 +1635,7 @@ async function compilerDriverSupportsPersistentCache(driver, environmentFingerpr
|
|
|
1623
1635
|
// wrapper. This is deliberately undocumented and test-scoped.
|
|
1624
1636
|
if (process.env["SCRIPTC_TEST_TRUST_COMPILER_WRAPPER"] === "1")
|
|
1625
1637
|
return true;
|
|
1626
|
-
const driverKey =
|
|
1627
|
-
environmentFingerprint,
|
|
1628
|
-
driver.argv.join("\x1f"),
|
|
1629
|
-
driver.target ?? "<native>",
|
|
1630
|
-
driver.targetArgs.join("\x1f"),
|
|
1631
|
-
].join("\0");
|
|
1638
|
+
const driverKey = compilerDriverProbeKey(driver, environmentFingerprint);
|
|
1632
1639
|
const compiler = driver.argv[0] ?? "clang";
|
|
1633
1640
|
const resolvedDriver = await resolvedTool(compiler);
|
|
1634
1641
|
// A prior dependency list cannot prove that name resolution is unchanged:
|
|
@@ -1669,6 +1676,8 @@ async function compilerDriverSupportsPersistentCache(driver, environmentFingerpr
|
|
|
1669
1676
|
direct = effectiveCompiler !== null &&
|
|
1670
1677
|
(effectiveCompiler.fileIdentity === resolvedDriver.fileIdentity ||
|
|
1671
1678
|
isAppleSystemClangHandoff(resolvedDriver, effectiveCompiler));
|
|
1679
|
+
if (direct)
|
|
1680
|
+
directCompilerSelections.set(driverKey, effectiveCompiler);
|
|
1672
1681
|
}
|
|
1673
1682
|
}
|
|
1674
1683
|
catch {
|
|
@@ -1678,6 +1687,8 @@ async function compilerDriverSupportsPersistentCache(driver, environmentFingerpr
|
|
|
1678
1687
|
await rm(probeDir, { recursive: true, force: true }).catch(() => undefined);
|
|
1679
1688
|
}
|
|
1680
1689
|
directCompilerDriverMemos.set(probeKey, direct);
|
|
1690
|
+
if (!direct)
|
|
1691
|
+
directCompilerSelections.delete(driverKey);
|
|
1681
1692
|
return direct;
|
|
1682
1693
|
}
|
|
1683
1694
|
/** Complete library archives may skip `ar`, so the selected archiver must be
|
|
@@ -1758,6 +1769,29 @@ async function toolVersionOnce(argv, environmentFingerprint = toolchainEnvironme
|
|
|
1758
1769
|
toolVersionFallbacks.set(spellingKey, memo);
|
|
1759
1770
|
return memo;
|
|
1760
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
|
+
}
|
|
1761
1795
|
function parseMakeDependencies(output, cwd = process.cwd()) {
|
|
1762
1796
|
const flattened = output.replace(/\\\r?\n/g, " ");
|
|
1763
1797
|
const separator = flattened.indexOf(": ");
|
|
@@ -1839,13 +1873,14 @@ async function effectiveCompilerInvocationFingerprintFresh(driver, environmentFi
|
|
|
1839
1873
|
invocation: normalizedProbeInvocation(invocation, probeDir),
|
|
1840
1874
|
dependencies,
|
|
1841
1875
|
dependencyFingerprint: await fingerprintDependencyFiles(dependencies),
|
|
1876
|
+
invocationPaths: await existingDriverTracePaths(`${invocation.stdout}\n${invocation.stderr}`, probeDir, probeDir),
|
|
1842
1877
|
};
|
|
1843
1878
|
}
|
|
1844
1879
|
finally {
|
|
1845
1880
|
await rm(probeDir, { recursive: true, force: true }).catch(() => undefined);
|
|
1846
1881
|
}
|
|
1847
1882
|
}
|
|
1848
|
-
|
|
1883
|
+
const fingerprint = createHash("sha256")
|
|
1849
1884
|
.update("effective-compiler-invocation-v2\0")
|
|
1850
1885
|
.update(environmentFingerprint)
|
|
1851
1886
|
.update("\0")
|
|
@@ -1863,6 +1898,7 @@ async function effectiveCompilerInvocationFingerprintFresh(driver, environmentFi
|
|
|
1863
1898
|
.update("\0")
|
|
1864
1899
|
.update(probe.dependencyFingerprint)
|
|
1865
1900
|
.digest("hex");
|
|
1901
|
+
return rememberFingerprintDependencies(fingerprint, [...probe.dependencies, ...probe.invocationPaths], probe.dependencies, probe.dependencyFingerprint);
|
|
1866
1902
|
}
|
|
1867
1903
|
const stableEffectiveCompilerInvocationMemos = new Map();
|
|
1868
1904
|
function effectiveCompilerInvocationFingerprint(driver, environmentFingerprint, compileArgs, sourceExtension = ".c") {
|
|
@@ -1876,17 +1912,30 @@ function effectiveCompilerInvocationFingerprint(driver, environmentFingerprint,
|
|
|
1876
1912
|
}
|
|
1877
1913
|
async function nativeSourceFiles(directory, recursive, include = (name) => name.endsWith(".c") || name.endsWith(".h")) {
|
|
1878
1914
|
const files = [];
|
|
1879
|
-
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);
|
|
1880
1920
|
const entries = await readdir(current, { withFileTypes: true }).catch(() => []);
|
|
1881
1921
|
for (const entry of entries) {
|
|
1882
1922
|
const path = join(current, entry.name);
|
|
1883
|
-
if (entry.isDirectory() && recursive)
|
|
1884
|
-
await walk(path);
|
|
1885
|
-
|
|
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)) {
|
|
1886
1934
|
files.push(path);
|
|
1935
|
+
}
|
|
1887
1936
|
}
|
|
1888
1937
|
};
|
|
1889
|
-
await walk(directory);
|
|
1938
|
+
await walk(directory, new Set());
|
|
1890
1939
|
return files.sort();
|
|
1891
1940
|
}
|
|
1892
1941
|
/** Every system-header spelling consumed by a source tree scriptc owns. The
|
|
@@ -1938,6 +1987,18 @@ async function fingerprintDependencyFiles(paths) {
|
|
|
1938
1987
|
}
|
|
1939
1988
|
return hash.digest("hex");
|
|
1940
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
|
+
}
|
|
1941
2002
|
/** Exact headers selected while preprocessing the caller's translation unit.
|
|
1942
2003
|
* The shared toolchain probe covers the runtime/vendor trees, but compileC is
|
|
1943
2004
|
* also a public API: an opted-in caller can include a system or header-only SDK
|
|
@@ -1946,7 +2007,7 @@ async function fingerprintDependencyFiles(paths) {
|
|
|
1946
2007
|
* quote-include directory and compiler-visible source spelling. */
|
|
1947
2008
|
async function translationUnitDependencyFingerprintFresh(driver, cflags, sourcePath, sourceBytes, environmentFingerprint) {
|
|
1948
2009
|
if (sourcePath.endsWith(".ll")) {
|
|
1949
|
-
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"), []);
|
|
1950
2011
|
}
|
|
1951
2012
|
const compiler = driver.argv[0] ?? "clang";
|
|
1952
2013
|
const compilerIdentity = await resolvedToolIdentity(compiler);
|
|
@@ -1983,7 +2044,7 @@ async function translationUnitDependencyFingerprintFresh(driver, cflags, sourceP
|
|
|
1983
2044
|
await rm(probeDir, { recursive: true, force: true }).catch(() => undefined);
|
|
1984
2045
|
}
|
|
1985
2046
|
}
|
|
1986
|
-
|
|
2047
|
+
const fingerprint = createHash("sha256")
|
|
1987
2048
|
.update("translation-unit-dependencies-v1\0")
|
|
1988
2049
|
.update(environmentFingerprint)
|
|
1989
2050
|
.update("\0")
|
|
@@ -1993,6 +2054,7 @@ async function translationUnitDependencyFingerprintFresh(driver, cflags, sourceP
|
|
|
1993
2054
|
.update("\0")
|
|
1994
2055
|
.update(probe.dependencyFingerprint)
|
|
1995
2056
|
.digest("hex");
|
|
2057
|
+
return rememberFingerprintDependencies(fingerprint, probe.dependencies, probe.dependencies, probe.dependencyFingerprint);
|
|
1996
2058
|
}
|
|
1997
2059
|
const stableTranslationUnitDependencyMemos = new Map();
|
|
1998
2060
|
function translationUnitDependencyFingerprint(driver, cflags, sourcePath, sourceBytes, environmentFingerprint) {
|
|
@@ -2086,10 +2148,15 @@ async function implicitToolchainFingerprintFresh(driver, environmentFingerprint)
|
|
|
2086
2148
|
compilerInvocation: normalizedProbeInvocation(compilerInvocation, probeDir),
|
|
2087
2149
|
dependencies: dependencyPaths,
|
|
2088
2150
|
dependencyFingerprint: await fingerprintDependencyFiles(dependencyPaths),
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
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
|
+
})),
|
|
2093
2160
|
};
|
|
2094
2161
|
}
|
|
2095
2162
|
finally {
|
|
@@ -2120,7 +2187,11 @@ async function implicitToolchainFingerprintFresh(driver, environmentFingerprint)
|
|
|
2120
2187
|
.update(currentIdentity ?? tool.identity ?? "<unresolved>")
|
|
2121
2188
|
.update("\0");
|
|
2122
2189
|
}
|
|
2123
|
-
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);
|
|
2124
2195
|
}
|
|
2125
2196
|
const stableImplicitToolchainMemos = new Map();
|
|
2126
2197
|
function implicitToolchainFingerprint(driver, environmentFingerprint) {
|
|
@@ -2156,6 +2227,51 @@ function driverTraceCandidates(line) {
|
|
|
2156
2227
|
}
|
|
2157
2228
|
return candidates;
|
|
2158
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
|
+
}
|
|
2159
2275
|
export async function parseLinkTraceFiles(output, cwd, excludedRoot, driverDryRun = false) {
|
|
2160
2276
|
const files = new Set();
|
|
2161
2277
|
for (const line of output.split(/\r?\n/)) {
|
|
@@ -2241,9 +2357,15 @@ async function implicitLinkerFingerprintFresh(driver, environmentFingerprint, li
|
|
|
2241
2357
|
linkerInvocation: normalizedProbeInvocation(driverInvocation, probeDir),
|
|
2242
2358
|
dependencies,
|
|
2243
2359
|
dependencyFingerprint: await fingerprintDependencyFiles(dependencies),
|
|
2360
|
+
invocationPaths: await existingDriverTracePaths(`${driverInvocation.stdout}\n${driverInvocation.stderr}`, probeDir, probeDir),
|
|
2244
2361
|
linker: {
|
|
2245
2362
|
spelling: linkerSpelling,
|
|
2246
|
-
identity: linkerSpelling === ""
|
|
2363
|
+
identity: linkerSpelling === ""
|
|
2364
|
+
? null
|
|
2365
|
+
: (await resolvedTool(linkerSpelling))?.cacheIdentity ?? null,
|
|
2366
|
+
path: linkerSpelling === ""
|
|
2367
|
+
? null
|
|
2368
|
+
: (await resolvedTool(linkerSpelling))?.canonicalPath ?? null,
|
|
2247
2369
|
},
|
|
2248
2370
|
};
|
|
2249
2371
|
}
|
|
@@ -2254,7 +2376,7 @@ async function implicitLinkerFingerprintFresh(driver, environmentFingerprint, li
|
|
|
2254
2376
|
const linkerIdentity = probe.linker.spelling === ""
|
|
2255
2377
|
? null
|
|
2256
2378
|
: await resolvedToolIdentity(probe.linker.spelling);
|
|
2257
|
-
|
|
2379
|
+
const fingerprint = createHash("sha256")
|
|
2258
2380
|
.update("implicit-linker-v3\0")
|
|
2259
2381
|
.update(environmentFingerprint)
|
|
2260
2382
|
.update("\0")
|
|
@@ -2280,6 +2402,11 @@ async function implicitLinkerFingerprintFresh(driver, environmentFingerprint, li
|
|
|
2280
2402
|
.update("\0")
|
|
2281
2403
|
.update(linkerIdentity ?? probe.linker.identity ?? "<unresolved>")
|
|
2282
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);
|
|
2283
2410
|
}
|
|
2284
2411
|
const stableImplicitLinkerMemos = new Map();
|
|
2285
2412
|
function implicitLinkerFingerprint(driver, environmentFingerprint, linkArgs, effectiveInvocationArgs, traceInvocationArgs) {
|
|
@@ -2301,21 +2428,18 @@ function ccacheAvailable() {
|
|
|
2301
2428
|
ccacheMemo ??= execFileAsync("ccache", ["--version"]).then(() => true, () => false);
|
|
2302
2429
|
return ccacheMemo;
|
|
2303
2430
|
}
|
|
2304
|
-
/** 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
|
|
2305
2432
|
* textually included by scr_number.c, and the separately-built vendor pins —
|
|
2306
2433
|
* everything a binary links that the emitted C bytes don't already cover
|
|
2307
2434
|
* (npm-embedded C rides inside the emitted C; the engine and standalone
|
|
2308
2435
|
* vendor archives are pinned by their version constants). The small source
|
|
2309
|
-
* tree is hashed on every identity calculation:
|
|
2310
|
-
*
|
|
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. */
|
|
2311
2441
|
async function runtimeFingerprintFresh(rtDir) {
|
|
2312
|
-
const groups = await
|
|
2313
|
-
{ label: "runtime", dir: rtDir },
|
|
2314
|
-
{ label: "ryu", dir: join(rtDir, "..", "vendor", "ryu") },
|
|
2315
|
-
].map(async (group) => {
|
|
2316
|
-
const names = (await readdir(group.dir)).filter((n) => n.endsWith(".c") || n.endsWith(".h")).sort();
|
|
2317
|
-
return { ...group, names };
|
|
2318
|
-
}));
|
|
2442
|
+
const groups = await runtimeFingerprintInputGroups(rtDir);
|
|
2319
2443
|
const h = createHash("sha256").update(QJS_COMMIT).update(MBEDTLS_VERSION).update(ZLIB_VERSION);
|
|
2320
2444
|
for (const group of groups) {
|
|
2321
2445
|
for (const n of group.names) {
|
|
@@ -2324,6 +2448,20 @@ async function runtimeFingerprintFresh(rtDir) {
|
|
|
2324
2448
|
}
|
|
2325
2449
|
return h.digest("hex");
|
|
2326
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
|
+
}
|
|
2327
2465
|
const stableRuntimeFingerprintMemos = new Map();
|
|
2328
2466
|
export function runtimeFingerprint(rtDir) {
|
|
2329
2467
|
const key = resolve(rtDir);
|
|
@@ -2412,6 +2550,279 @@ async function publishCachedFile(source, destination) {
|
|
|
2412
2550
|
]);
|
|
2413
2551
|
}
|
|
2414
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
|
+
}
|
|
2415
2826
|
/** The cached .o set for one flag flavor, compiled on first need. Concurrent
|
|
2416
2827
|
* first builds (parallel test workers on a cold cache) may duplicate work;
|
|
2417
2828
|
* per-file atomic renames make every winner equivalent. Publication is held
|
|
@@ -2445,7 +2856,22 @@ async function ensureRuntimeObjects(root, ccArgv, cflags, sources, keyPrefix, ve
|
|
|
2445
2856
|
await Promise.all(missing.slice(i, i + width).map(async (src) => {
|
|
2446
2857
|
const tmpObj = join(tmpDir, `${basename(src, ".c")}.o`);
|
|
2447
2858
|
const argv = [...(useCcache ? ["ccache"] : []), ...ccArgv, ...cflags, "-c", src, "-o", tmpObj];
|
|
2448
|
-
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);
|
|
2449
2875
|
compiled.set(src, { object: tmpObj, digest: await fileDigest(tmpObj) });
|
|
2450
2876
|
}));
|
|
2451
2877
|
}
|
|
@@ -2657,10 +3083,88 @@ export async function compileC(opts) {
|
|
|
2657
3083
|
root = null;
|
|
2658
3084
|
}
|
|
2659
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
|
+
}
|
|
2660
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
|
+
]);
|
|
2661
3145
|
if (persistentDriverCache) {
|
|
2662
3146
|
try {
|
|
2663
|
-
|
|
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
|
+
}
|
|
2664
3168
|
}
|
|
2665
3169
|
catch {
|
|
2666
3170
|
// Cache discovery is best-effort. In particular, a compiler wrapper can
|
|
@@ -2817,6 +3321,7 @@ export async function compileC(opts) {
|
|
|
2817
3321
|
...(opts.http2 ?? false ? [rt(join(rtDir, "scr_http2.c"))] : []),
|
|
2818
3322
|
...(opts.dgram ? [rt(join(rtDir, "scr_dgram.c"))] : []),
|
|
2819
3323
|
...(opts.watch ? [rt(join(rtDir, "scr_watch.c"))] : []),
|
|
3324
|
+
...(opts.foreignFfi ? [rt(join(rtDir, "scr_ffi_queue.c"))] : []),
|
|
2820
3325
|
...(opts.nodeTest ? [rt(join(rtDir, "scr_test.c"))] : []),
|
|
2821
3326
|
// The CA-store unit rides its own gate OR the tls one: scr_tls.c
|
|
2822
3327
|
// references its default-set override unconditionally.
|
|
@@ -2959,12 +3464,63 @@ export async function compileC(opts) {
|
|
|
2959
3464
|
};
|
|
2960
3465
|
let runtimeCompilerInvocation = null;
|
|
2961
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
|
+
]);
|
|
2962
3480
|
if (root !== null) {
|
|
2963
3481
|
try {
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
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
|
+
}
|
|
2968
3524
|
}
|
|
2969
3525
|
catch {
|
|
2970
3526
|
// Preserve the uncached build for wrappers that compile successfully but
|
|
@@ -2989,11 +3545,11 @@ export async function compileC(opts) {
|
|
|
2989
3545
|
let fingerprint;
|
|
2990
3546
|
let cBytes;
|
|
2991
3547
|
try {
|
|
2992
|
-
[cv, fingerprint, cBytes] = await Promise.all([
|
|
3548
|
+
[cv, fingerprint, cBytes] = await (payloadMetadata ?? Promise.all([
|
|
2993
3549
|
ccVersionOnce(driver.argv, toolchainEnv, true),
|
|
2994
3550
|
runtimeFingerprint(rtDir),
|
|
2995
3551
|
readFile(opts.cPath),
|
|
2996
|
-
]);
|
|
3552
|
+
]));
|
|
2997
3553
|
}
|
|
2998
3554
|
catch {
|
|
2999
3555
|
// A version/fingerprint probe is an optimization boundary. If the compiler
|
|
@@ -3012,16 +3568,6 @@ export async function compileC(opts) {
|
|
|
3012
3568
|
(opts.linkInputs?.length ?? 0) === 0 &&
|
|
3013
3569
|
(opts.systemLibraries?.length ?? 0) === 0;
|
|
3014
3570
|
let programDependencies = null;
|
|
3015
|
-
if (cacheCompleteArtifact) {
|
|
3016
|
-
try {
|
|
3017
|
-
programDependencies = await translationUnitDependencyFingerprint(driver, cflags, opts.cPath, cBytes, toolchainEnv);
|
|
3018
|
-
}
|
|
3019
|
-
catch {
|
|
3020
|
-
// Program-header discovery is needed only for a complete hit. Runtime
|
|
3021
|
-
// objects remain safe because they never contain the caller's TU.
|
|
3022
|
-
cacheCompleteArtifact = false;
|
|
3023
|
-
}
|
|
3024
|
-
}
|
|
3025
3571
|
const linkProbeArgs = [
|
|
3026
3572
|
...(sanitize ? ["-fsanitize=address"] : []),
|
|
3027
3573
|
...threadArgs,
|
|
@@ -3062,15 +3608,89 @@ export async function compileC(opts) {
|
|
|
3062
3608
|
? effectiveLinkInvocationArgs
|
|
3063
3609
|
: effectiveLinkInvocationArgs.filter((arg) => arg !== `-L${curlStubDir}`);
|
|
3064
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
|
+
]);
|
|
3065
3627
|
if (cacheCompleteArtifact) {
|
|
3066
3628
|
try {
|
|
3067
|
-
|
|
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
|
+
}
|
|
3068
3686
|
}
|
|
3069
3687
|
catch {
|
|
3070
|
-
//
|
|
3071
|
-
// objects remain safely cacheable
|
|
3072
|
-
//
|
|
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.
|
|
3073
3691
|
cacheCompleteArtifact = false;
|
|
3692
|
+
preBuildDependencies = null;
|
|
3693
|
+
localArtifactDependencyPaths = null;
|
|
3074
3694
|
}
|
|
3075
3695
|
}
|
|
3076
3696
|
const binDir = join(root, "bin");
|
|
@@ -3123,6 +3743,9 @@ export async function compileC(opts) {
|
|
|
3123
3743
|
// cache entry populated by a less restrictive shell must not widen access.
|
|
3124
3744
|
await chmod(tmpOut, 0o777 & ~process.umask());
|
|
3125
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
|
+
}
|
|
3126
3749
|
return; // hit: the program/runtime payload compile and link were skipped
|
|
3127
3750
|
}
|
|
3128
3751
|
catch {
|
|
@@ -3158,18 +3781,25 @@ export async function compileC(opts) {
|
|
|
3158
3781
|
await writeFile(programPath, cBytes);
|
|
3159
3782
|
let objects = null;
|
|
3160
3783
|
let cacheInputsStable = true;
|
|
3161
|
-
let
|
|
3784
|
+
let strictObjectVerification = null;
|
|
3162
3785
|
const objectImplicitToolchainStillMatches = () => {
|
|
3163
|
-
|
|
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),
|
|
3164
3794
|
implicitToolchainFingerprint(driver, toolchainEnv),
|
|
3165
3795
|
effectiveCompilerInvocationFingerprint(driver, toolchainEnv, cflags),
|
|
3166
|
-
]).then(([currentImplicit, currentInvocation]) =>
|
|
3796
|
+
]).then(([currentRuntime, currentImplicit, currentInvocation]) => currentRuntime === fingerprint &&
|
|
3797
|
+
currentImplicit === implicitToolchain &&
|
|
3167
3798
|
currentInvocation === runtimeCompilerInvocation, () => false);
|
|
3168
|
-
return
|
|
3799
|
+
return strictObjectVerification;
|
|
3169
3800
|
};
|
|
3170
3801
|
try {
|
|
3171
|
-
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 () =>
|
|
3172
|
-
(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());
|
|
3173
3803
|
objects = await stageRuntimeObjects(cached, join(buildDir, "runtime-objects"));
|
|
3174
3804
|
}
|
|
3175
3805
|
catch (err) {
|
|
@@ -3180,10 +3810,11 @@ export async function compileC(opts) {
|
|
|
3180
3810
|
await runClang(buildArgs((p) => objects?.get(p) ?? p, { programPath, outPath: privateOut, compilerVisibleSource: opts.cPath }));
|
|
3181
3811
|
await installArtifact(privateOut, opts.outPath);
|
|
3182
3812
|
if (cachedBin !== null && keyHex !== null) {
|
|
3183
|
-
//
|
|
3184
|
-
//
|
|
3185
|
-
//
|
|
3186
|
-
//
|
|
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.
|
|
3187
3818
|
const [currentRuntime, currentImplicit, currentRuntimeInvocation, currentProgramInvocation, currentProgramDependencies, currentLinker, currentCompiler] = await Promise.all([
|
|
3188
3819
|
runtimeFingerprint(rtDir).catch(() => null),
|
|
3189
3820
|
implicitToolchainFingerprint(driver, toolchainEnv).catch(() => null),
|
|
@@ -3195,6 +3826,8 @@ export async function compileC(opts) {
|
|
|
3195
3826
|
]);
|
|
3196
3827
|
cacheInputsStable =
|
|
3197
3828
|
cacheInputsStable &&
|
|
3829
|
+
preBuildDependencies !== null &&
|
|
3830
|
+
await localArtifactDependenciesStillMatch(preBuildDependencies) &&
|
|
3198
3831
|
currentRuntime === fingerprint &&
|
|
3199
3832
|
currentImplicit === implicitToolchain &&
|
|
3200
3833
|
currentRuntimeInvocation === runtimeCompilerInvocation &&
|
|
@@ -3214,6 +3847,12 @@ export async function compileC(opts) {
|
|
|
3214
3847
|
/* publishing is best-effort */
|
|
3215
3848
|
}
|
|
3216
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
|
+
}
|
|
3217
3856
|
}
|
|
3218
3857
|
finally {
|
|
3219
3858
|
await rm(buildDir, { recursive: true, force: true }).catch(() => undefined);
|