ttsc 0.31.1 → 0.31.3
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/lib/compiler/internal/build/BuildExecution.d.ts +6 -3
- package/lib/compiler/internal/build/BuildExecution.js +40 -12
- package/lib/compiler/internal/build/BuildExecution.js.map +1 -1
- package/lib/compiler/internal/build/RunBuildOptions.d.ts +2 -1
- package/lib/compiler/internal/startResidentTransform.js +5 -4
- package/lib/compiler/internal/startResidentTransform.js.map +1 -1
- package/lib/compiler/internal/transformProjectInMemory.js +9 -7
- package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
- package/lib/internal/javascriptRuntimeCapabilities.d.ts +13 -4
- package/lib/internal/javascriptRuntimeCapabilities.js +56 -6
- package/lib/internal/javascriptRuntimeCapabilities.js.map +1 -1
- package/lib/internal/pluginSource.d.ts +1 -0
- package/lib/internal/pluginSource.js +1 -0
- package/lib/internal/pluginSource.js.map +1 -1
- package/lib/internal/resolveNodeBinary.d.ts +6 -1
- package/lib/internal/resolveNodeBinary.js +6 -2
- package/lib/internal/resolveNodeBinary.js.map +1 -1
- package/lib/internal/runtimeExecutableIdentity.d.ts +22 -13
- package/lib/internal/runtimeExecutableIdentity.js +192 -102
- package/lib/internal/runtimeExecutableIdentity.js.map +1 -1
- package/lib/launcher/internal/runTtsc.js +2 -2
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runtime/installRuntimeHooks.js +58 -4
- package/lib/launcher/internal/runtime/installRuntimeHooks.js.map +1 -1
- package/lib/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.d.ts +5 -1
- package/lib/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.js +7 -2
- package/lib/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.js.map +1 -1
- package/lib/launcher/internal/ttscserver/runTtscserver.js +8 -1
- package/lib/launcher/internal/ttscserver/runTtscserver.js.map +1 -1
- package/lib/launcher/internal/watch/WatchTopology.d.ts +20 -3
- package/lib/launcher/internal/watch/WatchTopology.js +31 -9
- package/lib/launcher/internal/watch/WatchTopology.js.map +1 -1
- package/lib/plugin/internal/CapabilityResolutionFormat.d.ts +21 -62
- package/lib/plugin/internal/CapabilityResolutionFormat.js +15 -106
- package/lib/plugin/internal/CapabilityResolutionFormat.js.map +1 -1
- package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.d.ts +6 -0
- package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js +4 -4
- package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js.map +1 -1
- package/lib/plugin/internal/load/loadProjectPlugins.d.ts +17 -3
- package/lib/plugin/internal/load/loadProjectPlugins.js +86 -23
- package/lib/plugin/internal/load/loadProjectPlugins.js.map +1 -1
- package/lib/plugin/internal/readCapabilityResolution.d.ts +6 -0
- package/lib/plugin/internal/readCapabilityResolution.js +8 -5
- package/lib/plugin/internal/readCapabilityResolution.js.map +1 -1
- package/lib/plugin/internal/source/GoEnvironmentReading.d.ts +9 -3
- package/lib/plugin/internal/source/GoEnvironmentReading.js +53 -7
- package/lib/plugin/internal/source/GoEnvironmentReading.js.map +1 -1
- package/lib/plugin/internal/source/NativeSourcePackages.d.ts +44 -6
- package/lib/plugin/internal/source/NativeSourcePackages.js +150 -8
- package/lib/plugin/internal/source/NativeSourcePackages.js.map +1 -1
- package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.d.ts +21 -3
- package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js +32 -7
- package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js.map +1 -1
- package/lib/plugin/internal/source/PluginContentIdentities.d.ts +265 -0
- package/lib/plugin/internal/source/PluginContentIdentities.js +452 -0
- package/lib/plugin/internal/source/PluginContentIdentities.js.map +1 -0
- package/lib/plugin/internal/source/PluginLoadAnswers.d.ts +63 -0
- package/lib/plugin/internal/source/PluginLoadAnswers.js +128 -0
- package/lib/plugin/internal/source/PluginLoadAnswers.js.map +1 -0
- package/lib/plugin/internal/source/SourceBuildCacheLayout.d.ts +10 -0
- package/lib/plugin/internal/source/SourceBuildCacheLayout.js +12 -0
- package/lib/plugin/internal/source/SourceBuildCacheLayout.js.map +1 -1
- package/lib/plugin/internal/source/SourcePluginWorkspace.d.ts +13 -2
- package/lib/plugin/internal/source/SourcePluginWorkspace.js +140 -4
- package/lib/plugin/internal/source/SourcePluginWorkspace.js.map +1 -1
- package/lib/plugin/internal/source/buildSourcePlugin.d.ts +15 -2
- package/lib/plugin/internal/source/buildSourcePlugin.js +27 -4
- package/lib/plugin/internal/source/buildSourcePlugin.js.map +1 -1
- package/lib/plugin/internal/source/computeCacheKey.d.ts +21 -5
- package/lib/plugin/internal/source/computeCacheKey.js +71 -29
- package/lib/plugin/internal/source/computeCacheKey.js.map +1 -1
- package/lib/plugin/internal/source/hashPluginBuildEnvironment.d.ts +7 -3
- package/lib/plugin/internal/source/hashPluginBuildEnvironment.js +74 -35
- package/lib/plugin/internal/source/hashPluginBuildEnvironment.js.map +1 -1
- package/lib/plugin/internal/source/pluginBuildEnvironment.d.ts +8 -3
- package/lib/plugin/internal/source/pluginBuildEnvironment.js +8 -4
- package/lib/plugin/internal/source/pluginBuildEnvironment.js.map +1 -1
- package/lib/plugin/internal/source/pluginBuildEnvironmentWorker.js +12 -2
- package/lib/plugin/internal/source/pluginBuildEnvironmentWorker.js.map +1 -1
- package/lib/plugin/internal/source/pluginBuildVersions.d.ts +2 -2
- package/lib/plugin/internal/source/pluginBuildVersions.js +4 -17
- package/lib/plugin/internal/source/pluginBuildVersions.js.map +1 -1
- package/lib/plugin/internal/source/pluginSourceState.d.ts +6 -2
- package/lib/plugin/internal/source/pluginSourceState.js +20 -8
- package/lib/plugin/internal/source/pluginSourceState.js.map +1 -1
- package/lib/plugin/internal/source/pluginSourceStateHolds.d.ts +7 -2
- package/lib/plugin/internal/source/pluginSourceStateHolds.js +12 -7
- package/lib/plugin/internal/source/pluginSourceStateHolds.js.map +1 -1
- package/lib/plugin/internal/source/processPluginBuildEnvironment.d.ts +7 -2
- package/lib/plugin/internal/source/processPluginBuildEnvironment.js +8 -3
- package/lib/plugin/internal/source/processPluginBuildEnvironment.js.map +1 -1
- package/lib/plugin/internal/source/provenPluginSourceDigest.d.ts +28 -0
- package/lib/plugin/internal/source/provenPluginSourceDigest.js +41 -0
- package/lib/plugin/internal/source/provenPluginSourceDigest.js.map +1 -0
- package/lib/plugin/internal/source/pruneCacheFileRoot.d.ts +3 -2
- package/lib/plugin/internal/source/pruneCacheFileRoot.js +3 -2
- package/lib/plugin/internal/source/pruneCacheFileRoot.js.map +1 -1
- package/lib/plugin/internal/source/ttscPackageVersion.d.ts +26 -0
- package/lib/plugin/internal/source/ttscPackageVersion.js +47 -0
- package/lib/plugin/internal/source/ttscPackageVersion.js.map +1 -0
- package/lib/plugin/internal/writeCapabilityResolution.d.ts +6 -0
- package/lib/plugin/internal/writeCapabilityResolution.js +11 -7
- package/lib/plugin/internal/writeCapabilityResolution.js.map +1 -1
- package/lib/plugin/resolveCapabilityPlugins.js +67 -15
- package/lib/plugin/resolveCapabilityPlugins.js.map +1 -1
- package/lib/structures/internal/TtscSingleFileEmitOptions.d.ts +5 -1
- package/package.json +8 -8
- package/src/compiler/internal/build/BuildExecution.ts +48 -14
- package/src/compiler/internal/build/RunBuildOptions.ts +2 -1
- package/src/compiler/internal/startResidentTransform.ts +12 -3
- package/src/compiler/internal/transformProjectInMemory.ts +10 -2
- package/src/internal/javascriptRuntimeCapabilities.ts +71 -5
- package/src/internal/pluginSource.ts +1 -0
- package/src/internal/resolveNodeBinary.ts +12 -1
- package/src/internal/runtimeExecutableIdentity.ts +227 -141
- package/src/launcher/internal/runTtsc.ts +6 -2
- package/src/launcher/internal/runtime/installRuntimeHooks.ts +68 -3
- package/src/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.ts +11 -2
- package/src/launcher/internal/ttscserver/runTtscserver.ts +18 -1
- package/src/launcher/internal/watch/WatchTopology.ts +38 -9
- package/src/plugin/internal/CapabilityResolutionFormat.ts +23 -107
- package/src/plugin/internal/load/PluginDescriptorEvaluationCache.ts +14 -4
- package/src/plugin/internal/load/loadProjectPlugins.ts +226 -98
- package/src/plugin/internal/readCapabilityResolution.ts +17 -6
- package/src/plugin/internal/source/GoEnvironmentReading.ts +74 -5
- package/src/plugin/internal/source/NativeSourcePackages.ts +262 -5
- package/src/plugin/internal/source/PluginBuildEnvironmentReadings.ts +41 -5
- package/src/plugin/internal/source/PluginContentIdentities.ts +563 -0
- package/src/plugin/internal/source/PluginLoadAnswers.ts +151 -0
- package/src/plugin/internal/source/SourceBuildCacheLayout.ts +14 -0
- package/src/plugin/internal/source/SourcePluginWorkspace.ts +176 -2
- package/src/plugin/internal/source/buildSourcePlugin.ts +43 -3
- package/src/plugin/internal/source/computeCacheKey.ts +103 -47
- package/src/plugin/internal/source/hashPluginBuildEnvironment.ts +144 -29
- package/src/plugin/internal/source/pluginBuildEnvironment.ts +9 -2
- package/src/plugin/internal/source/pluginBuildEnvironmentWorker.ts +18 -2
- package/src/plugin/internal/source/pluginBuildVersions.ts +5 -26
- package/src/plugin/internal/source/pluginSourceState.ts +28 -12
- package/src/plugin/internal/source/pluginSourceStateHolds.ts +15 -8
- package/src/plugin/internal/source/processPluginBuildEnvironment.ts +8 -2
- package/src/plugin/internal/source/provenPluginSourceDigest.ts +45 -0
- package/src/plugin/internal/source/pruneCacheFileRoot.ts +3 -2
- package/src/plugin/internal/source/ttscPackageVersion.ts +45 -0
- package/src/plugin/internal/writeCapabilityResolution.ts +29 -8
- package/src/plugin/resolveCapabilityPlugins.ts +87 -13
- package/src/structures/internal/TtscSingleFileEmitOptions.ts +8 -1
|
@@ -5,11 +5,11 @@ import path from "node:path";
|
|
|
5
5
|
import { GoToolResolution } from "./GoToolResolution";
|
|
6
6
|
import type { ITtscBuildContributor } from "./ITtscBuildContributor";
|
|
7
7
|
import type { PluginBuildEnvironmentWitness } from "./PluginBuildEnvironmentWitness";
|
|
8
|
+
import { PluginContentIdentities } from "./PluginContentIdentities";
|
|
8
9
|
import type { SourceBuildFilesystemOperations } from "./SourceBuildFilesystemOperations";
|
|
9
10
|
import type { SourcePluginWorkspace } from "./SourcePluginWorkspace";
|
|
10
11
|
import { hashPluginBuildEnvironment } from "./hashPluginBuildEnvironment";
|
|
11
12
|
import { pluginModuleReplaceDirectories } from "./pluginModuleReplaceDirectories";
|
|
12
|
-
import { pluginSourceDigest } from "./pluginSourceDigest";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Compute a 32-character lowercase key from a plugin build's SHA-256 digest.
|
|
@@ -30,10 +30,12 @@ import { pluginSourceDigest } from "./pluginSourceDigest";
|
|
|
30
30
|
* values are trusted producer readings, not revalidated content observations.
|
|
31
31
|
* `sourceDigests` carries the digests one load already took: every build of the
|
|
32
32
|
* load keys on one reading of each directory, and the load reports those
|
|
33
|
-
* readings. The environment enters
|
|
34
|
-
* rule that reported state takes it from too,
|
|
35
|
-
* the digest of each build directory's, so the
|
|
36
|
-
* `go env` run.
|
|
33
|
+
* readings. The environment enters as the digest of
|
|
34
|
+
* `hashPluginBuildEnvironment`, the rule that reported state takes it from too,
|
|
35
|
+
* and `environmentDigests` carries the digest of each build directory's, so the
|
|
36
|
+
* load reports it without a second `go env` run. With `environmentWitnesses`, a
|
|
37
|
+
* reading the load already took for the same directory, such as package
|
|
38
|
+
* selection's, keys the build too, and its witness joins the build's.
|
|
37
39
|
*
|
|
38
40
|
* The `ttsc cache` CLI and plugin build pipeline share this key computation.
|
|
39
41
|
*
|
|
@@ -43,7 +45,7 @@ import { pluginSourceDigest } from "./pluginSourceDigest";
|
|
|
43
45
|
* @evidence contracts/common.md#meaningful-documentation Native paragraphs explain input coverage and the provenance of reported digests; documented optional output maps have blank separation between members.
|
|
44
46
|
* @evidence contracts/portability.md#os-neutral-implementation Native roots are resolved with Node path APIs and executable resolution; platform/architecture intentionally distinguish incompatible binary artifacts.
|
|
45
47
|
* @evidence contracts/performance.md#efficient-algorithms Source digest misses enumerate/sort paths and read individual files in full, not bounded streaming chunks. Overlay/contributor ordering compares path/name text; replacement discovery can read a manifest/run Go and native containment queries. Toolchain/env hashing delegates native metadata/probes/full bytes and memo checks without an unmeasured dominant-cost ranking; JSON framing processes all version/entry/label/digest text.
|
|
46
|
-
* @evidence contracts/performance.md#reuse-equivalent-work Caller-owned sourceDigests share supplied absolute-directory readings across roles without independently verifying their provenance/currentness. EnvironmentDigests receives this call's selected environment identity so reporting can avoid another probe; it
|
|
48
|
+
* @evidence contracts/performance.md#reuse-equivalent-work Caller-owned sourceDigests share supplied absolute-directory readings across roles without independently verifying their provenance/currentness. A supplied record store lets a new process reuse a directory, SDK or executable digest only while its separable metadata signature matches the recorded one (#1722), so unchanged inputs are not re-read per process. EnvironmentDigests receives this call's selected environment identity so reporting can avoid another probe. A same-directory reading of the same load, with its witness, replaces a second environment probe; the witness then joins this build's, which the build compares after it ran, and a non-default byte adapter never shares. Reuse inherits the selected population and metadata/producer premises.
|
|
47
49
|
*
|
|
48
50
|
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources Output maps belong to the enclosing load; this operation retains no handles or process-wide records itself.
|
|
49
51
|
*/
|
|
@@ -66,6 +68,14 @@ export function computeCacheKey(inputs: {
|
|
|
66
68
|
*/
|
|
67
69
|
environmentDigests?: Map<string, string>;
|
|
68
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Witness of each reading in `environmentDigests` this load took, by
|
|
73
|
+
* directory. A digest with its witness stands for this call's own reading:
|
|
74
|
+
* the key takes it instead of running Go again and copies the witness into
|
|
75
|
+
* `environmentWitness`. Filled with this call's reading when it takes one.
|
|
76
|
+
*/
|
|
77
|
+
environmentWitnesses?: Map<string, PluginBuildEnvironmentWitness.Record>;
|
|
78
|
+
|
|
69
79
|
/**
|
|
70
80
|
* Receives selected native toolchain dependencies from the environment reader
|
|
71
81
|
* for later comparison under its metadata-distinguishability premise; it is
|
|
@@ -77,6 +87,14 @@ export function computeCacheKey(inputs: {
|
|
|
77
87
|
filesystem?: Partial<SourceBuildFilesystemOperations>;
|
|
78
88
|
goBinary?: string;
|
|
79
89
|
goModReader?: SourcePluginWorkspace.GoModReader;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Record store that proves unchanged sources, overlays, contributors, GOROOT
|
|
93
|
+
* and executables from metadata in a new process (#1722). Without it every
|
|
94
|
+
* process reads their bytes.
|
|
95
|
+
*/
|
|
96
|
+
identities?: PluginContentIdentities.Store;
|
|
97
|
+
|
|
80
98
|
overlayDirs?: readonly string[];
|
|
81
99
|
|
|
82
100
|
/**
|
|
@@ -128,61 +146,96 @@ export function computeCacheKey(inputs: {
|
|
|
128
146
|
// binaries produced by the former absolute-ancestry layout out of this
|
|
129
147
|
// layout's cache admissions, including callers with no SDK overlays.
|
|
130
148
|
hash.update(JSON.stringify(["external-source-layout", 1]));
|
|
131
|
-
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
hash.update(data);
|
|
138
|
-
environment.update(data);
|
|
139
|
-
},
|
|
140
|
-
},
|
|
141
|
-
goBinary,
|
|
142
|
-
inputs.dir,
|
|
143
|
-
env,
|
|
144
|
-
filesystem,
|
|
145
|
-
inputs.environmentWitness,
|
|
146
|
-
);
|
|
147
|
-
inputs.environmentDigests?.set(
|
|
148
|
-
path.resolve(inputs.dir),
|
|
149
|
-
environment.digest("hex"),
|
|
149
|
+
const overlays = [...(inputs.overlayDirs ?? [])].sort();
|
|
150
|
+
// Hash contributors in sorted-by-name order so two consumers with the
|
|
151
|
+
// same logical set produce the same key regardless of declaration order
|
|
152
|
+
// in the host's plugin descriptor.
|
|
153
|
+
const sortedContributors = [...(inputs.contributors ?? [])].sort((a, b) =>
|
|
154
|
+
a.name === b.name ? 0 : a.name < b.name ? -1 : 1,
|
|
150
155
|
);
|
|
151
|
-
|
|
156
|
+
// A store whose root lies inside a directory this key hashes would write
|
|
157
|
+
// records into that source, so such a store proves nothing here. The build
|
|
158
|
+
// rejects that cache placement (`SourcePluginAdmission`) once every source
|
|
159
|
+
// is known; replacement targets narrow the store below, before their own
|
|
160
|
+
// digests, since only Go's reading of the manifest names them.
|
|
161
|
+
let identities = PluginContentIdentities.outside(inputs.identities, [
|
|
162
|
+
inputs.dir,
|
|
163
|
+
...overlays,
|
|
164
|
+
...sortedContributors.map((contributor) => contributor.source),
|
|
165
|
+
]);
|
|
166
|
+
// The key frames the environment digest pluginBuildEnvironment reports for
|
|
167
|
+
// this directory. WARNING (#1721): package selection reads that environment
|
|
168
|
+
// first in the same load; reading it again here doubled `go env` and
|
|
169
|
+
// `go version` per load. A reading taken through another byte adapter is
|
|
170
|
+
// not this one's, so only the default adapter shares it.
|
|
171
|
+
const environmentDirectory = path.resolve(inputs.dir);
|
|
172
|
+
const sharedDigest =
|
|
173
|
+
inputs.filesystem === undefined
|
|
174
|
+
? inputs.environmentDigests?.get(environmentDirectory)
|
|
175
|
+
: undefined;
|
|
176
|
+
const sharedWitness =
|
|
177
|
+
sharedDigest === undefined
|
|
178
|
+
? undefined
|
|
179
|
+
: inputs.environmentWitnesses?.get(environmentDirectory);
|
|
180
|
+
let environment: string;
|
|
181
|
+
if (sharedDigest !== undefined && sharedWitness !== undefined) {
|
|
182
|
+
// The build compares this witness after it ran, exactly as it would its
|
|
183
|
+
// own reading's, so a toolchain change since the reading still refuses.
|
|
184
|
+
for (const [file, state] of sharedWitness)
|
|
185
|
+
if (!inputs.environmentWitness?.has(file))
|
|
186
|
+
inputs.environmentWitness?.set(file, state);
|
|
187
|
+
environment = sharedDigest;
|
|
188
|
+
} else {
|
|
189
|
+
const witness =
|
|
190
|
+
inputs.environmentWitness ??
|
|
191
|
+
(inputs.environmentWitnesses === undefined ? undefined : new Map());
|
|
192
|
+
const reading = crypto.createHash("sha256");
|
|
193
|
+
hashPluginBuildEnvironment(
|
|
194
|
+
reading,
|
|
195
|
+
goBinary,
|
|
196
|
+
inputs.dir,
|
|
197
|
+
env,
|
|
198
|
+
filesystem,
|
|
199
|
+
witness,
|
|
200
|
+
identities,
|
|
201
|
+
);
|
|
202
|
+
environment = reading.digest("hex");
|
|
203
|
+
inputs.environmentDigests?.set(environmentDirectory, environment);
|
|
204
|
+
if (inputs.filesystem === undefined && witness !== undefined)
|
|
205
|
+
inputs.environmentWitnesses?.set(environmentDirectory, new Map(witness));
|
|
206
|
+
}
|
|
207
|
+
hash.update(JSON.stringify(["environment", environment]));
|
|
208
|
+
const directory = (label: string, root: string): void =>
|
|
209
|
+
hashSourceDirectory(hash, label, root, inputs.sourceDigests, identities);
|
|
210
|
+
directory("plugin", inputs.dir);
|
|
152
211
|
// Local replacement targets outside the module supply separately keyed
|
|
153
212
|
// sources. buildSourcePlugin snapshots and proves their copies before
|
|
154
|
-
// redirecting the Go replacement directives to the scratch tree.
|
|
155
|
-
|
|
213
|
+
// redirecting the Go replacement directives to the scratch tree. Go reads the
|
|
214
|
+
// manifest after the plugin's digest, so an edit made meanwhile lands between
|
|
215
|
+
// that digest and the build's copy, which `requireKeyedSource` refuses.
|
|
216
|
+
const replacements = pluginModuleReplaceDirectories(
|
|
156
217
|
inputs.dir,
|
|
157
218
|
env,
|
|
158
219
|
goBinary,
|
|
159
220
|
inputs.goModReader,
|
|
160
|
-
)
|
|
161
|
-
|
|
162
|
-
|
|
221
|
+
);
|
|
222
|
+
identities = PluginContentIdentities.outside(
|
|
223
|
+
identities,
|
|
224
|
+
replacements.map((replacement) => replacement.directory),
|
|
225
|
+
);
|
|
226
|
+
for (const replacement of replacements) {
|
|
227
|
+
directory(
|
|
163
228
|
`replace:${replacement.modulePath}${
|
|
164
229
|
replacement.version === undefined ? "" : `@${replacement.version}`
|
|
165
230
|
}`,
|
|
166
231
|
replacement.directory,
|
|
167
|
-
inputs.sourceDigests,
|
|
168
232
|
);
|
|
169
233
|
}
|
|
170
|
-
for (const [index, dir] of
|
|
171
|
-
|
|
234
|
+
for (const [index, dir] of overlays.entries()) {
|
|
235
|
+
directory(`overlay:${index}`, dir);
|
|
172
236
|
}
|
|
173
|
-
// Hash contributors in sorted-by-name order so two consumers with the
|
|
174
|
-
// same logical set produce the same key regardless of declaration order
|
|
175
|
-
// in the host's plugin descriptor.
|
|
176
|
-
const sortedContributors = [...(inputs.contributors ?? [])].sort((a, b) =>
|
|
177
|
-
a.name === b.name ? 0 : a.name < b.name ? -1 : 1,
|
|
178
|
-
);
|
|
179
237
|
for (const contributor of sortedContributors) {
|
|
180
|
-
|
|
181
|
-
hash,
|
|
182
|
-
`contributor:${contributor.name}`,
|
|
183
|
-
contributor.source,
|
|
184
|
-
inputs.sourceDigests,
|
|
185
|
-
);
|
|
238
|
+
directory(`contributor:${contributor.name}`, contributor.source);
|
|
186
239
|
}
|
|
187
240
|
return hash.digest("hex").slice(0, 32);
|
|
188
241
|
}
|
|
@@ -197,11 +250,14 @@ function hashSourceDirectory(
|
|
|
197
250
|
label: string,
|
|
198
251
|
root: string,
|
|
199
252
|
digests: Map<string, string> | undefined,
|
|
253
|
+
identities: PluginContentIdentities.Store | undefined,
|
|
200
254
|
): void {
|
|
201
255
|
const directory = path.resolve(root);
|
|
202
256
|
let digest = digests?.get(directory);
|
|
203
257
|
if (digest === undefined) {
|
|
204
|
-
|
|
258
|
+
// WARNING (#1722): the load-scoped map above ends with the process. Only the
|
|
259
|
+
// record store spares a new process from reading every source byte again.
|
|
260
|
+
digest = PluginContentIdentities.sourceDirectory(identities, directory);
|
|
205
261
|
digests?.set(directory, digest);
|
|
206
262
|
}
|
|
207
263
|
hash.update(JSON.stringify(["dir", label, digest]));
|
|
@@ -6,6 +6,7 @@ import { GoEnvironmentReading } from "./GoEnvironmentReading";
|
|
|
6
6
|
import { GoSourceInputs } from "./GoSourceInputs";
|
|
7
7
|
import { GoToolResolution } from "./GoToolResolution";
|
|
8
8
|
import { PluginBuildEnvironmentWitness } from "./PluginBuildEnvironmentWitness";
|
|
9
|
+
import { PluginContentIdentities } from "./PluginContentIdentities";
|
|
9
10
|
import type { SourceBuildFilesystemOperations } from "./SourceBuildFilesystemOperations";
|
|
10
11
|
import { spawnGoTool } from "./spawnGoTool";
|
|
11
12
|
|
|
@@ -42,14 +43,17 @@ import { spawnGoTool } from "./spawnGoTool";
|
|
|
42
43
|
* variable carries: the Go tool, the Go environment file `go env -w` writes,
|
|
43
44
|
* the executables the C toolchain commands name, and GOROOT. A consumer that
|
|
44
45
|
* keeps the reading compares their metadata before reusing it.
|
|
46
|
+
* @param identities Record store that lets a new process prove the GOROOT
|
|
47
|
+
* corpus and executable bytes from their metadata (`PluginContentIdentities`,
|
|
48
|
+
* #1722); without it every process reads them.
|
|
45
49
|
* @evidence contracts/common.md#principled-implementation Fixed artifact flags, observed compiler bytes/version, selected reported-or-fallback build values, named command executables and selected SDK files enter the same framed identity used by source-state reporting. Fallback values and metadata-qualified reuse are explicit premises, not complete observation of arbitrary toolchain inputs.
|
|
46
50
|
* @evidence contracts/common.md#clear-and-simple-design Private helpers separate compiler identity, Go-reported settings, external variables and SDK content while sharing one hash sink and optional pre-read witness.
|
|
47
51
|
* @evidence contracts/common.md#prohibited-implementation-shortcuts Cache signatures include real identity/change metadata and effective invocation context; failed SDK witnessing refuses reuse rather than accepting a VERSION-only proxy.
|
|
48
52
|
* @evidence contracts/common.md#meaningful-documentation Native documentation names the input classes and witness purpose; private comments explain context-sensitive compiler memoization and SDK exclusions without treating a passing check as proof.
|
|
49
53
|
* @evidence contracts/portability.md#os-neutral-implementation Node path/stat/process APIs resolve native tool identities; platform-specific executable suffixes and environment lookup are isolated in GoToolResolution, and Go supplies its own effective build settings.
|
|
50
54
|
* @evidence contracts/performance.md#efficient-algorithms SDK traversal sorts reached entry names and selected file paths, performs native stat/realpath/link queries and serializes topology/metadata/path text. Matching manifests share aggregate hashes; changes read full selected files via the caller adapter, retaining file buffers with population/topology data. Compiler misses capture at most three complete version/byte readings when native metadata moves during a reading; only a stable attempt enters the memo. Go env can run up to three times for its file witness, and command tokens can each perform executable searches/full reads. Environment sorting/framing and command substring parsing also process name/value/command bytes; E/D/F/B alone do not capture native lookup or text comparison costs.
|
|
51
|
-
* @evidence contracts/performance.md#reuse-equivalent-work Compiler memo identity includes selected path/file metadata and invocation cwd/environment; SDK aggregate reuse requires matching complete ordered metadata/topology.
|
|
52
|
-
* @evidence contracts/performance.md#bound-retention-and-release-resources Compiler, Go-environment-path and SDK maps retain historical distinct tool/context/root keys without eviction. SDK population/topology and full individual file reads contribute transient bytes; synchronous probes delegate process/capture lifetime to their owner and do not certify arbitrary descendants are gone. No independent entry/record/file/output byte ceiling is imposed here.
|
|
55
|
+
* @evidence contracts/performance.md#reuse-equivalent-work Compiler memo identity includes selected path/file metadata and invocation cwd/environment; SDK aggregate reuse requires matching complete ordered metadata/topology. With a record store, a new process reuses the SDK aggregate and executable digests only while their signatures match the recorded ones and every stamp is separable from a freshly minted reference (#1722); `go version` and `go env` stay live because they report toolchain selection no file metadata shows. All of this relies on native metadata distinguishability and sequential observation premises. Physical directories expand once per selection policy while aliases retain edges; optional caller witnesses receive reached selected dependencies.
|
|
56
|
+
* @evidence contracts/performance.md#bound-retention-and-release-resources Compiler, Go-environment-path and SDK maps retain historical distinct tool/context/root keys without eviction; persisted records belong to the single-file collector. SDK population/topology and full individual file reads contribute transient bytes; synchronous probes delegate process/capture lifetime to their owner and do not certify arbitrary descendants are gone. No independent entry/record/file/output byte ceiling is imposed here.
|
|
53
57
|
*/
|
|
54
58
|
export function hashPluginBuildEnvironment(
|
|
55
59
|
hash: { update(data: string): unknown },
|
|
@@ -58,6 +62,7 @@ export function hashPluginBuildEnvironment(
|
|
|
58
62
|
env: NodeJS.ProcessEnv,
|
|
59
63
|
filesystem: SourceBuildFilesystemOperations,
|
|
60
64
|
witness?: PluginBuildEnvironmentWitness.Record,
|
|
65
|
+
identities?: PluginContentIdentities.Store,
|
|
61
66
|
): void {
|
|
62
67
|
// Fixed artifact policy must invalidate binaries and reported source states
|
|
63
68
|
// together, including binaries cached before this policy was introduced.
|
|
@@ -68,11 +73,25 @@ export function hashPluginBuildEnvironment(
|
|
|
68
73
|
hash.update(
|
|
69
74
|
JSON.stringify([
|
|
70
75
|
"go",
|
|
71
|
-
resolveGoCompilerIdentity(
|
|
76
|
+
resolveGoCompilerIdentity(
|
|
77
|
+
goBinary,
|
|
78
|
+
env,
|
|
79
|
+
directory,
|
|
80
|
+
witness,
|
|
81
|
+
identities,
|
|
82
|
+
),
|
|
72
83
|
]),
|
|
73
84
|
);
|
|
74
85
|
}
|
|
75
|
-
hashGoBuildEnvironment(
|
|
86
|
+
hashGoBuildEnvironment(
|
|
87
|
+
hash,
|
|
88
|
+
goBinary,
|
|
89
|
+
directory,
|
|
90
|
+
env,
|
|
91
|
+
filesystem,
|
|
92
|
+
witness,
|
|
93
|
+
identities,
|
|
94
|
+
);
|
|
76
95
|
hashExternalGoBuildEnvironment(hash, env);
|
|
77
96
|
}
|
|
78
97
|
|
|
@@ -173,6 +192,7 @@ function resolveGoCompilerIdentity(
|
|
|
173
192
|
env: NodeJS.ProcessEnv = process.env,
|
|
174
193
|
cwd: string = process.cwd(),
|
|
175
194
|
witness?: PluginBuildEnvironmentWitness.Record,
|
|
195
|
+
identities?: PluginContentIdentities.Store,
|
|
176
196
|
): string {
|
|
177
197
|
const selected = GoToolResolution.resolveGoToolForBuild(
|
|
178
198
|
goBinary,
|
|
@@ -217,7 +237,13 @@ function resolveGoCompilerIdentity(
|
|
|
217
237
|
compilerEnv,
|
|
218
238
|
cwd,
|
|
219
239
|
);
|
|
220
|
-
identity = computeGoCompilerIdentity(
|
|
240
|
+
identity = computeGoCompilerIdentity(
|
|
241
|
+
selected,
|
|
242
|
+
resolved,
|
|
243
|
+
compilerEnv,
|
|
244
|
+
cwd,
|
|
245
|
+
identities,
|
|
246
|
+
);
|
|
221
247
|
if (!PluginBuildEnvironmentWitness.holds(observed)) continue;
|
|
222
248
|
for (const [file, signature] of observed)
|
|
223
249
|
if (witness !== undefined && !witness.has(file))
|
|
@@ -269,10 +295,13 @@ function computeGoCompilerIdentity(
|
|
|
269
295
|
resolved: string,
|
|
270
296
|
env: NodeJS.ProcessEnv,
|
|
271
297
|
cwd: string,
|
|
298
|
+
identities: PluginContentIdentities.Store | undefined,
|
|
272
299
|
): string {
|
|
273
300
|
if (!fs.existsSync(resolved)) {
|
|
274
301
|
return "missing";
|
|
275
302
|
}
|
|
303
|
+
// `go version` stays a live probe: it answers which toolchain the effective
|
|
304
|
+
// environment selects (GOTOOLCHAIN, go.mod), which no file metadata shows.
|
|
276
305
|
const version = spawnGoTool(goBinary, ["version"], {
|
|
277
306
|
cwd,
|
|
278
307
|
encoding: "utf8",
|
|
@@ -283,10 +312,27 @@ function computeGoCompilerIdentity(
|
|
|
283
312
|
version.error !== undefined
|
|
284
313
|
? ((version.error as NodeJS.ErrnoException).code ?? version.error.message)
|
|
285
314
|
: `${version.status ?? 0}:${version.stdout}${version.stderr}`;
|
|
286
|
-
const binaryHash =
|
|
315
|
+
const binaryHash = hashExecutable(resolved, identities);
|
|
287
316
|
return `sha256:${binaryHash}:${versionText}`;
|
|
288
317
|
}
|
|
289
318
|
|
|
319
|
+
/**
|
|
320
|
+
* The content digest of an executable, proven from its metadata in a process
|
|
321
|
+
* that has the record store (#1722) and read in full otherwise.
|
|
322
|
+
*/
|
|
323
|
+
function hashExecutable(
|
|
324
|
+
file: string,
|
|
325
|
+
identities: PluginContentIdentities.Store | undefined,
|
|
326
|
+
): string {
|
|
327
|
+
return PluginContentIdentities.digest(
|
|
328
|
+
identities,
|
|
329
|
+
"executable",
|
|
330
|
+
file,
|
|
331
|
+
(references) => PluginContentIdentities.fileObservation(references, file),
|
|
332
|
+
() => hashFile(file),
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
290
336
|
function resolveExecutableIdentityPath(
|
|
291
337
|
binary: string,
|
|
292
338
|
env: NodeJS.ProcessEnv = process.env,
|
|
@@ -352,7 +398,8 @@ function hashGoBuildEnvironment(
|
|
|
352
398
|
cwd: string,
|
|
353
399
|
env: NodeJS.ProcessEnv,
|
|
354
400
|
filesystem: SourceBuildFilesystemOperations,
|
|
355
|
-
witness
|
|
401
|
+
witness: PluginBuildEnvironmentWitness.Record | undefined,
|
|
402
|
+
identities: PluginContentIdentities.Store | undefined,
|
|
356
403
|
): void {
|
|
357
404
|
const values = resolveGoBuildEnvironment(
|
|
358
405
|
goBinary,
|
|
@@ -360,6 +407,7 @@ function hashGoBuildEnvironment(
|
|
|
360
407
|
env,
|
|
361
408
|
filesystem,
|
|
362
409
|
witness,
|
|
410
|
+
identities,
|
|
363
411
|
);
|
|
364
412
|
for (const key of GO_BUILD_ENV_KEYS) {
|
|
365
413
|
const value = values.get(key);
|
|
@@ -374,7 +422,8 @@ function resolveGoBuildEnvironment(
|
|
|
374
422
|
cwd: string,
|
|
375
423
|
env: NodeJS.ProcessEnv,
|
|
376
424
|
filesystem: SourceBuildFilesystemOperations,
|
|
377
|
-
witness
|
|
425
|
+
witness: PluginBuildEnvironmentWitness.Record | undefined,
|
|
426
|
+
identities: PluginContentIdentities.Store | undefined,
|
|
378
427
|
): Map<string, string> {
|
|
379
428
|
const values = new Map<string, string>();
|
|
380
429
|
if (goBinary !== undefined) {
|
|
@@ -384,6 +433,7 @@ function resolveGoBuildEnvironment(
|
|
|
384
433
|
env,
|
|
385
434
|
GO_BUILD_ENV_KEYS,
|
|
386
435
|
witness,
|
|
436
|
+
identities,
|
|
387
437
|
);
|
|
388
438
|
if (parsed !== undefined) {
|
|
389
439
|
try {
|
|
@@ -392,7 +442,14 @@ function resolveGoBuildEnvironment(
|
|
|
392
442
|
if (typeof raw === "string" && raw !== "") {
|
|
393
443
|
values.set(
|
|
394
444
|
key,
|
|
395
|
-
normalizeGoBuildEnvValue(
|
|
445
|
+
normalizeGoBuildEnvValue(
|
|
446
|
+
key,
|
|
447
|
+
raw,
|
|
448
|
+
env,
|
|
449
|
+
filesystem,
|
|
450
|
+
witness,
|
|
451
|
+
identities,
|
|
452
|
+
),
|
|
396
453
|
);
|
|
397
454
|
}
|
|
398
455
|
}
|
|
@@ -408,7 +465,14 @@ function resolveGoBuildEnvironment(
|
|
|
408
465
|
if (value !== undefined && value !== "") {
|
|
409
466
|
values.set(
|
|
410
467
|
key,
|
|
411
|
-
normalizeGoBuildEnvValue(
|
|
468
|
+
normalizeGoBuildEnvValue(
|
|
469
|
+
key,
|
|
470
|
+
value,
|
|
471
|
+
env,
|
|
472
|
+
filesystem,
|
|
473
|
+
witness,
|
|
474
|
+
identities,
|
|
475
|
+
),
|
|
412
476
|
);
|
|
413
477
|
}
|
|
414
478
|
}
|
|
@@ -420,7 +484,8 @@ function normalizeGoBuildEnvValue(
|
|
|
420
484
|
value: string,
|
|
421
485
|
env: NodeJS.ProcessEnv,
|
|
422
486
|
filesystem: SourceBuildFilesystemOperations,
|
|
423
|
-
witness
|
|
487
|
+
witness: PluginBuildEnvironmentWitness.Record | undefined,
|
|
488
|
+
identities: PluginContentIdentities.Store | undefined,
|
|
424
489
|
): string {
|
|
425
490
|
if (key === "GOROOT") {
|
|
426
491
|
// Root/version observations also cover a missing SDK. The complete SDK
|
|
@@ -428,10 +493,15 @@ function normalizeGoBuildEnvValue(
|
|
|
428
493
|
// nested content changes that do not move the root or VERSION metadata.
|
|
429
494
|
PluginBuildEnvironmentWitness.add(witness, value);
|
|
430
495
|
PluginBuildEnvironmentWitness.add(witness, path.join(value, "VERSION"));
|
|
431
|
-
return resolveGoRootCacheIdentity(value, filesystem, witness);
|
|
496
|
+
return resolveGoRootCacheIdentity(value, filesystem, witness, identities);
|
|
432
497
|
}
|
|
433
498
|
if (GO_BUILD_COMMAND_ENV_KEYS.has(key)) {
|
|
434
|
-
return `${value}\0${resolveCommandCacheIdentity(
|
|
499
|
+
return `${value}\0${resolveCommandCacheIdentity(
|
|
500
|
+
value,
|
|
501
|
+
env,
|
|
502
|
+
witness,
|
|
503
|
+
identities,
|
|
504
|
+
)}`;
|
|
435
505
|
}
|
|
436
506
|
return value;
|
|
437
507
|
}
|
|
@@ -451,7 +521,8 @@ function normalizeGoBuildEnvValue(
|
|
|
451
521
|
function resolveCommandCacheIdentity(
|
|
452
522
|
command: string,
|
|
453
523
|
env: NodeJS.ProcessEnv,
|
|
454
|
-
witness
|
|
524
|
+
witness: PluginBuildEnvironmentWitness.Record | undefined,
|
|
525
|
+
identities: PluginContentIdentities.Store | undefined,
|
|
455
526
|
): string {
|
|
456
527
|
const tokens = splitGoCommand(command);
|
|
457
528
|
if (tokens === null) {
|
|
@@ -473,7 +544,7 @@ function resolveCommandCacheIdentity(
|
|
|
473
544
|
}
|
|
474
545
|
let identity: string;
|
|
475
546
|
try {
|
|
476
|
-
identity = `command:sha256:${
|
|
547
|
+
identity = `command:sha256:${hashExecutable(resolved, identities)}`;
|
|
477
548
|
} catch {
|
|
478
549
|
return `command:unreadable:${resolved}`;
|
|
479
550
|
}
|
|
@@ -487,7 +558,7 @@ function resolveCommandCacheIdentity(
|
|
|
487
558
|
if (!GoToolResolution.isExecutableFile(operand)) return;
|
|
488
559
|
PluginBuildEnvironmentWitness.add(witness, operand);
|
|
489
560
|
try {
|
|
490
|
-
identity += `;${index + 1}:sha256:${
|
|
561
|
+
identity += `;${index + 1}:sha256:${hashExecutable(operand, identities)}`;
|
|
491
562
|
} catch {
|
|
492
563
|
identity += `;${index + 1}:unreadable:${operand}`;
|
|
493
564
|
}
|
|
@@ -544,6 +615,9 @@ interface GoRootIdentitySnapshot {
|
|
|
544
615
|
files: string[];
|
|
545
616
|
topology: string[];
|
|
546
617
|
signature: string;
|
|
618
|
+
|
|
619
|
+
/** Whether every file stamp precedes its device's minted reference. */
|
|
620
|
+
separable: boolean;
|
|
547
621
|
}
|
|
548
622
|
|
|
549
623
|
interface GoRootIdentityCacheEntry {
|
|
@@ -554,19 +628,31 @@ interface GoRootIdentityCacheEntry {
|
|
|
554
628
|
// GOROOT is usually stable but its selected source/tool payloads contribute to
|
|
555
629
|
// every plugin key. Retain only the final aggregate
|
|
556
630
|
// identity, guarded by a fresh metadata/topology manifest on every call. A
|
|
557
|
-
// changed or incomplete manifest falls through to
|
|
631
|
+
// changed or incomplete manifest falls through to the record store, then to a
|
|
632
|
+
// full content read.
|
|
633
|
+
//
|
|
634
|
+
// WARNING (#1186, #1722): this map lives only as long as the process. #1186
|
|
635
|
+
// stopped at it, so every CLI launch, worker and bundler restart still read the
|
|
636
|
+
// whole SDK (3,723 files, 135 MB for the bundled toolchain). The record store
|
|
637
|
+
// below is what carries the identity across processes; never drop it in favor
|
|
638
|
+
// of this memo alone.
|
|
558
639
|
const goRootIdentityCache = new Map<string, GoRootIdentityCacheEntry>();
|
|
559
640
|
|
|
560
641
|
function resolveGoRootCacheIdentity(
|
|
561
642
|
goRoot: string,
|
|
562
643
|
filesystem: SourceBuildFilesystemOperations,
|
|
563
|
-
witness
|
|
644
|
+
witness: PluginBuildEnvironmentWitness.Record | undefined,
|
|
645
|
+
identities: PluginContentIdentities.Store | undefined,
|
|
564
646
|
): string {
|
|
565
647
|
const resolved = resolveRealPath(goRoot);
|
|
566
648
|
if (!fs.existsSync(resolved)) {
|
|
567
649
|
return `missing:${goRoot}`;
|
|
568
650
|
}
|
|
569
|
-
const snapshot = collectGoRootIdentitySnapshot(
|
|
651
|
+
const snapshot = collectGoRootIdentitySnapshot(
|
|
652
|
+
resolved,
|
|
653
|
+
witness,
|
|
654
|
+
identities?.references,
|
|
655
|
+
);
|
|
570
656
|
if (!snapshot.complete)
|
|
571
657
|
PluginBuildEnvironmentWitness.refuse(witness, resolved);
|
|
572
658
|
if (snapshot.complete) {
|
|
@@ -575,15 +661,37 @@ function resolveGoRootCacheIdentity(
|
|
|
575
661
|
return cached.identity;
|
|
576
662
|
}
|
|
577
663
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
664
|
+
// The first observation is the snapshot the witness just recorded; a second
|
|
665
|
+
// one, after the content read, brackets that read for publication.
|
|
666
|
+
let observed = 0;
|
|
667
|
+
const identity = PluginContentIdentities.digest(
|
|
668
|
+
snapshot.complete ? identities : undefined,
|
|
669
|
+
"goroot",
|
|
670
|
+
resolved,
|
|
671
|
+
(references) => {
|
|
672
|
+
const current =
|
|
673
|
+
observed++ === 0
|
|
674
|
+
? snapshot
|
|
675
|
+
: collectGoRootIdentitySnapshot(resolved, undefined, references);
|
|
676
|
+
return current.complete
|
|
677
|
+
? { separable: current.separable, signature: current.signature }
|
|
678
|
+
: undefined;
|
|
679
|
+
},
|
|
680
|
+
() => {
|
|
681
|
+
const hash = crypto.createHash("sha256");
|
|
682
|
+
hash.update(JSON.stringify(snapshot.topology));
|
|
683
|
+
for (const file of snapshot.files) {
|
|
684
|
+
const relative = path
|
|
685
|
+
.relative(resolved, file)
|
|
686
|
+
.split(path.sep)
|
|
687
|
+
.join("/");
|
|
688
|
+
const contents = filesystem.readFile(file);
|
|
689
|
+
hash.update(JSON.stringify([relative, contents.length]));
|
|
690
|
+
hash.update(contents);
|
|
691
|
+
}
|
|
692
|
+
return `sha256:${hash.digest("hex")}`;
|
|
693
|
+
},
|
|
694
|
+
);
|
|
587
695
|
if (snapshot.complete) {
|
|
588
696
|
goRootIdentityCache.set(resolved, {
|
|
589
697
|
identity,
|
|
@@ -596,9 +704,10 @@ function resolveGoRootCacheIdentity(
|
|
|
596
704
|
function collectGoRootIdentitySnapshot(
|
|
597
705
|
root: string,
|
|
598
706
|
witness?: PluginBuildEnvironmentWitness.Record,
|
|
707
|
+
references?: ReadonlyMap<bigint, bigint>,
|
|
599
708
|
): GoRootIdentitySnapshot {
|
|
600
709
|
const out: string[] = [];
|
|
601
|
-
const state = { complete: true };
|
|
710
|
+
const state = { complete: true, separable: references !== undefined };
|
|
602
711
|
const topology: string[] = [];
|
|
603
712
|
walkGoRootIdentity(root, out, topology, state, witness);
|
|
604
713
|
out.sort();
|
|
@@ -613,6 +722,11 @@ function collectGoRootIdentitySnapshot(
|
|
|
613
722
|
state.complete = false;
|
|
614
723
|
continue;
|
|
615
724
|
}
|
|
725
|
+
if (
|
|
726
|
+
references === undefined ||
|
|
727
|
+
!PluginContentIdentities.separable(references, stats)
|
|
728
|
+
)
|
|
729
|
+
state.separable = false;
|
|
616
730
|
signature.update(
|
|
617
731
|
[
|
|
618
732
|
relative,
|
|
@@ -634,6 +748,7 @@ function collectGoRootIdentitySnapshot(
|
|
|
634
748
|
files: out,
|
|
635
749
|
topology,
|
|
636
750
|
signature: signature.digest("hex"),
|
|
751
|
+
separable: state.separable,
|
|
637
752
|
};
|
|
638
753
|
}
|
|
639
754
|
|
|
@@ -3,6 +3,7 @@ import fs from "node:fs";
|
|
|
3
3
|
|
|
4
4
|
import { GoToolResolution } from "./GoToolResolution";
|
|
5
5
|
import type { PluginBuildEnvironmentWitness } from "./PluginBuildEnvironmentWitness";
|
|
6
|
+
import type { PluginContentIdentities } from "./PluginContentIdentities";
|
|
6
7
|
import { hashPluginBuildEnvironment } from "./hashPluginBuildEnvironment";
|
|
7
8
|
import { resolveGoCompiler } from "./resolveGoCompiler";
|
|
8
9
|
|
|
@@ -19,12 +20,16 @@ import { resolveGoCompiler } from "./resolveGoCompiler";
|
|
|
19
20
|
* GOROOT metadata. Witnessing a newly selected environment file can repeat the
|
|
20
21
|
* probe to observe it before reading. Compiler identity reuses a
|
|
21
22
|
* metadata-and-context memo; a changed SDK manifest rehashes all contributing
|
|
22
|
-
* content rather than only edited files.
|
|
23
|
+
* content rather than only edited files. A supplied record store lets a new
|
|
24
|
+
* process take the SDK and executable digests from records instead of their
|
|
25
|
+
* bytes, while `go env` and `go version` still run.
|
|
23
26
|
*
|
|
24
27
|
* @param directory The directory a build runs `go` in.
|
|
25
28
|
* @param env The effective environment, `process.env` by default.
|
|
26
29
|
* @param witness Receives the paths the reading depends on and no variable
|
|
27
30
|
* carries (`hashPluginBuildEnvironment`).
|
|
31
|
+
* @param identities Record store that lets a new process prove the SDK and
|
|
32
|
+
* executables from metadata (#1722).
|
|
28
33
|
* @evidence contracts/common.md#principled-implementation The digest uses the same resolved compiler and environment serializer as the binary key, so reported toolchain state denotes the inputs actually used by the build.
|
|
29
34
|
* @evidence contracts/common.md#clear-and-simple-design Compiler selection, executable resolution and hashing each stay with their owning helper; this function only composes one environment reading.
|
|
30
35
|
* @evidence contracts/common.md#prohibited-implementation-shortcuts The result comes from real Go/environment inputs, not a fixed compiler version or a caller-specific substitute.
|
|
@@ -32,13 +37,14 @@ import { resolveGoCompiler } from "./resolveGoCompiler";
|
|
|
32
37
|
* @evidence contracts/portability.md#os-neutral-implementation Executable identity is resolved before probing, and Node filesystem/process boundaries implement native spelling rather than assuming Windows or POSIX paths are interchangeable.
|
|
33
38
|
* @evidence contracts/performance.md#efficient-algorithms The wrapper resolves the compiler/tool under the supplied environment before delegating full environment hashing. Native resolution/probes, SDK metadata/name/path sorting and changed-manifest full content reads contribute cost; the final hash output is fixed-width but streamed identity/input text and full-file buffers are not. No independent duplicate source-directory digest scan is added here.
|
|
34
39
|
*
|
|
35
|
-
* @
|
|
40
|
+
* @evidence contracts/performance.md#reuse-equivalent-work This is the fresh-reading boundary required by builds: the Go probes run on every call. Content digests of the SDK and executables are shared across processes only through the supplied record store's separable-signature proof; processPluginBuildEnvironment owns reuse of the whole reading when a consumer permits it.
|
|
36
41
|
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources Its digest and filesystem adapter are call-local; retained compiler/content memo ownership belongs to the hasher.
|
|
37
42
|
*/
|
|
38
43
|
export function pluginBuildEnvironment(
|
|
39
44
|
directory: string,
|
|
40
45
|
env: NodeJS.ProcessEnv = process.env,
|
|
41
46
|
witness?: PluginBuildEnvironmentWitness.Record,
|
|
47
|
+
identities?: PluginContentIdentities.Store,
|
|
42
48
|
): string {
|
|
43
49
|
const goBinary = GoToolResolution.resolveGoToolForBuild(
|
|
44
50
|
resolveGoCompiler(env).binary,
|
|
@@ -54,6 +60,7 @@ export function pluginBuildEnvironment(
|
|
|
54
60
|
env,
|
|
55
61
|
{ readFile: (location) => fs.readFileSync(location) },
|
|
56
62
|
witness,
|
|
63
|
+
identities,
|
|
57
64
|
);
|
|
58
65
|
return hash.digest("hex");
|
|
59
66
|
}
|