ttsc 0.31.0 → 0.31.1
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/cmd/platform/main.go +2 -2
- package/internal/sourceprocess/contain_windows.go +10 -13
- package/internal/sourceprocess/started_windows.go +41 -0
- package/internal/sourceprocess/windows_started_publication_distinguishes_pending_from_refusal_test.go +76 -0
- package/internal/watchhelper/run.go +30 -20
- package/internal/watchhelper/run_other.go +4 -4
- package/internal/watchhelper/run_windows.go +471 -0
- package/internal/watchhelper/watchhelper.go +8 -2
- package/internal/watchhelper/windows_notifications_preserve_mutations_without_access_noise_test.go +506 -0
- package/lib/internal/nativeBinary.d.ts +3 -3
- package/lib/internal/nativeBinary.js +3 -3
- package/lib/plugin/internal/load/loadProjectPlugins.d.ts +12 -13
- package/lib/plugin/internal/load/loadProjectPlugins.js +61 -36
- package/lib/plugin/internal/load/loadProjectPlugins.js.map +1 -1
- package/lib/plugin/internal/source/GoEnvironmentReading.d.ts +42 -0
- package/lib/plugin/internal/source/GoEnvironmentReading.js +110 -0
- package/lib/plugin/internal/source/GoEnvironmentReading.js.map +1 -0
- package/lib/plugin/internal/source/NativeSourcePackages.d.ts +7 -7
- package/lib/plugin/internal/source/NativeSourcePackages.js +33 -20
- package/lib/plugin/internal/source/NativeSourcePackages.js.map +1 -1
- package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.d.ts +21 -0
- package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js +36 -0
- package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js.map +1 -1
- package/lib/plugin/internal/source/PluginBuildEnvironmentWitness.d.ts +5 -3
- package/lib/plugin/internal/source/PluginBuildEnvironmentWitness.js +14 -8
- package/lib/plugin/internal/source/PluginBuildEnvironmentWitness.js.map +1 -1
- package/lib/plugin/internal/source/SourcePluginWorkspace.d.ts +50 -19
- package/lib/plugin/internal/source/SourcePluginWorkspace.js +109 -22
- package/lib/plugin/internal/source/SourcePluginWorkspace.js.map +1 -1
- package/lib/plugin/internal/source/buildSourcePlugin.d.ts +19 -16
- package/lib/plugin/internal/source/buildSourcePlugin.js +27 -22
- package/lib/plugin/internal/source/buildSourcePlugin.js.map +1 -1
- package/lib/plugin/internal/source/computeCacheKey.d.ts +4 -2
- package/lib/plugin/internal/source/computeCacheKey.js +15 -6
- package/lib/plugin/internal/source/computeCacheKey.js.map +1 -1
- package/lib/plugin/internal/source/hashPluginBuildEnvironment.js +2 -68
- package/lib/plugin/internal/source/hashPluginBuildEnvironment.js.map +1 -1
- package/lib/plugin/internal/source/pluginModuleReplaceDirectories.d.ts +6 -5
- package/lib/plugin/internal/source/pluginModuleReplaceDirectories.js +10 -17
- package/lib/plugin/internal/source/pluginModuleReplaceDirectories.js.map +1 -1
- package/package.json +8 -8
- package/src/internal/nativeBinary.ts +3 -3
- package/src/plugin/internal/load/loadProjectPlugins.ts +209 -117
- package/src/plugin/internal/source/GoEnvironmentReading.ts +109 -0
- package/src/plugin/internal/source/NativeSourcePackages.ts +103 -43
- package/src/plugin/internal/source/PluginBuildEnvironmentReadings.ts +42 -0
- package/src/plugin/internal/source/PluginBuildEnvironmentWitness.ts +18 -8
- package/src/plugin/internal/source/SourcePluginWorkspace.ts +143 -25
- package/src/plugin/internal/source/buildSourcePlugin.ts +62 -34
- package/src/plugin/internal/source/computeCacheKey.ts +26 -8
- package/src/plugin/internal/source/hashPluginBuildEnvironment.ts +8 -76
- package/src/plugin/internal/source/pluginModuleReplaceDirectories.ts +13 -25
|
@@ -8,13 +8,12 @@ import { OwnedSynchronousProcess } from "../../../internal/OwnedSynchronousProce
|
|
|
8
8
|
import { SourceNativeRetirement } from "../../../internal/SourceNativeRetirement";
|
|
9
9
|
import { createCanonicalTempDirectory } from "../../../internal/createCanonicalTempDirectory";
|
|
10
10
|
import { runHoldingLock } from "../../../internal/runHoldingLock";
|
|
11
|
-
import { NativeSourcePackages } from "./NativeSourcePackages";
|
|
12
|
-
import { SourcePluginWorkspace } from "./SourcePluginWorkspace";
|
|
13
11
|
import { GoSourceInputs } from "./GoSourceInputs";
|
|
14
12
|
import { GoToolResolution } from "./GoToolResolution";
|
|
15
13
|
import type { IPluginModuleReplaceDirectory } from "./IPluginModuleReplaceDirectory";
|
|
16
14
|
import type { ITtscBuildContributor } from "./ITtscBuildContributor";
|
|
17
15
|
import type { ITtscSourceBuildCachePaths } from "./ITtscSourceBuildCachePaths";
|
|
16
|
+
import { NativeSourcePackages } from "./NativeSourcePackages";
|
|
18
17
|
import { PluginBinaryUse } from "./PluginBinaryUse";
|
|
19
18
|
import { PluginBuildEnvironmentWitness } from "./PluginBuildEnvironmentWitness";
|
|
20
19
|
import type { PluginBuildLockLease } from "./PluginBuildLockLease";
|
|
@@ -22,6 +21,7 @@ import { PluginBuildLockProtocol } from "./PluginBuildLockProtocol";
|
|
|
22
21
|
import { SourceBuildCacheLayout } from "./SourceBuildCacheLayout";
|
|
23
22
|
import type { SourceBuildFilesystemOperations } from "./SourceBuildFilesystemOperations";
|
|
24
23
|
import { SourcePluginAdmission } from "./SourcePluginAdmission";
|
|
24
|
+
import { SourcePluginWorkspace } from "./SourcePluginWorkspace";
|
|
25
25
|
import { acquirePluginBuildLock } from "./acquirePluginBuildLock";
|
|
26
26
|
import { computeCacheKey } from "./computeCacheKey";
|
|
27
27
|
import { copiesPluginSourceEntry } from "./copiesPluginSourceEntry";
|
|
@@ -44,12 +44,12 @@ import { withGoBuildCacheLease } from "./withGoBuildCacheLease";
|
|
|
44
44
|
* Build one Go source plugin into a cached executable.
|
|
45
45
|
*
|
|
46
46
|
* `opts.env` is the effective environment for this build; the caller merges
|
|
47
|
-
* inherited and constructor layers using native environment-name identity so
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
47
|
+
* inherited and constructor layers using native environment-name identity so a
|
|
48
|
+
* programmatic `TtscCompiler` instance can pin its own Go toolchain
|
|
49
|
+
* (`TTSC_GO_BINARY`), Go build cache (`TTSC_GO_CACHE_DIR`), and Go build
|
|
50
|
+
* variables (`GOFLAGS`, `CGO_*`, …) without mutating the shared `process.env`.
|
|
51
|
+
* CLI callers omit it and inherit `process.env`, so ambient behavior is
|
|
52
|
+
* unchanged.
|
|
53
53
|
*
|
|
54
54
|
* The cache key includes source and toolchain readings. Before publication the
|
|
55
55
|
* scratch inputs are compared with their recorded digests and the external
|
|
@@ -65,16 +65,16 @@ import { withGoBuildCacheLease } from "./withGoBuildCacheLease";
|
|
|
65
65
|
* Optional loader-required package ownership enters the key. Its cold producer
|
|
66
66
|
* admits the entry and linked sources through Go metadata in the actual
|
|
67
67
|
* materialized workspace before compilation; legacy cache artifacts have a
|
|
68
|
-
* different key. Existing binary hits trust the cache producer and key rather
|
|
69
|
-
* executable bytes. Default caches are managed locally, while
|
|
70
|
-
* retain caller-managed pruning policy. Every returned cache key
|
|
71
|
-
* reader token retained by this process until exit; registration
|
|
72
|
-
* builder/collector lease, and other consumers register independent
|
|
73
|
-
* Failure to establish ownership propagates. Private opt-in tracing
|
|
74
|
-
* mandatory key-creation and pre-build witness gates and adds one
|
|
75
|
-
* observation after Go returns. That diagnostic read adds metadata
|
|
76
|
-
* publication permission. A discarded epoch is separately recorded
|
|
77
|
-
* bounded build owner starts again.
|
|
68
|
+
* different key. Existing binary hits trust the cache producer and key rather
|
|
69
|
+
* than rehashing executable bytes. Default caches are managed locally, while
|
|
70
|
+
* explicit roots retain caller-managed pruning policy. Every returned cache key
|
|
71
|
+
* registers a reader token retained by this process until exit; registration
|
|
72
|
+
* shares the builder/collector lease, and other consumers register independent
|
|
73
|
+
* readers. Failure to establish ownership propagates. Private opt-in tracing
|
|
74
|
+
* records the mandatory key-creation and pre-build witness gates and adds one
|
|
75
|
+
* diagnostic observation after Go returns. That diagnostic read adds metadata
|
|
76
|
+
* work, not publication permission. A discarded epoch is separately recorded
|
|
77
|
+
* when the bounded build owner starts again.
|
|
78
78
|
*
|
|
79
79
|
* Explicit worker scopes check cancellation between build phases, copied source
|
|
80
80
|
* entries and native commands, including reader admission and final
|
|
@@ -101,7 +101,10 @@ export function buildSourcePlugin(opts: {
|
|
|
101
101
|
env?: NodeJS.ProcessEnv;
|
|
102
102
|
|
|
103
103
|
/** Loader-required ownership of the entry and named linked packages. */
|
|
104
|
-
packageOwnership?: readonly {
|
|
104
|
+
packageOwnership?: readonly {
|
|
105
|
+
entry: string;
|
|
106
|
+
kind: "executable" | "linked";
|
|
107
|
+
}[];
|
|
105
108
|
|
|
106
109
|
/**
|
|
107
110
|
* Digests of the environment each build directory is keyed on, shared by
|
|
@@ -123,6 +126,9 @@ export function buildSourcePlugin(opts: {
|
|
|
123
126
|
*/
|
|
124
127
|
sourceDigests?: Map<string, string>;
|
|
125
128
|
|
|
129
|
+
/** Pinned-tool manifest readers shared only by this loader request. */
|
|
130
|
+
goModReaders?: Map<string, SourcePluginWorkspace.GoModReader>;
|
|
131
|
+
|
|
126
132
|
ttscVersion: string;
|
|
127
133
|
tsgoVersion: string;
|
|
128
134
|
}): string {
|
|
@@ -136,7 +142,7 @@ export function buildSourcePlugin(opts: {
|
|
|
136
142
|
const environmentDigests = new Map(opts.environmentDigests);
|
|
137
143
|
try {
|
|
138
144
|
const binary = buildSourcePluginAttempt(
|
|
139
|
-
opts,
|
|
145
|
+
attempt === 0 ? opts : { ...opts, goModReaders: undefined },
|
|
140
146
|
env,
|
|
141
147
|
sourceDigests,
|
|
142
148
|
environmentDigests,
|
|
@@ -174,7 +180,9 @@ function buildSourcePluginAttempt(
|
|
|
174
180
|
): string {
|
|
175
181
|
OwnedSynchronousProcess.checkpoint();
|
|
176
182
|
const { dir, entry, source } = resolveSourceBuildTarget(opts);
|
|
177
|
-
const overlayDirs = [
|
|
183
|
+
const overlayDirs = [
|
|
184
|
+
...(opts.overlayDirs ?? SourcePluginWorkspace.findTtscOverlayDirs()),
|
|
185
|
+
].sort();
|
|
178
186
|
const contributors = opts.contributors ?? [];
|
|
179
187
|
const compiler = resolveGoCompiler(env);
|
|
180
188
|
const goBinary = GoToolResolution.resolveGoToolForBuild(
|
|
@@ -186,6 +194,12 @@ function buildSourcePluginAttempt(
|
|
|
186
194
|
OwnedSynchronousProcess.checkpoint();
|
|
187
195
|
// The digest of every directory the key covers, as the key read it, which
|
|
188
196
|
// the build proves against what it compiled.
|
|
197
|
+
const goModReader = SourcePluginWorkspace.createGoModReader(
|
|
198
|
+
goBinary,
|
|
199
|
+
opts.pluginName,
|
|
200
|
+
env,
|
|
201
|
+
opts.goModReaders,
|
|
202
|
+
);
|
|
189
203
|
const environmentWitness: PluginBuildEnvironmentWitness.Record = new Map();
|
|
190
204
|
const key = computeCacheKey({
|
|
191
205
|
contributors,
|
|
@@ -194,6 +208,7 @@ function buildSourcePluginAttempt(
|
|
|
194
208
|
entry,
|
|
195
209
|
env,
|
|
196
210
|
environmentWitness,
|
|
211
|
+
goModReader,
|
|
197
212
|
filesystem: opts.filesystem,
|
|
198
213
|
goBinary,
|
|
199
214
|
overlayDirs,
|
|
@@ -317,6 +332,7 @@ function buildSourcePluginAttempt(
|
|
|
317
332
|
env,
|
|
318
333
|
environmentWitness,
|
|
319
334
|
goBinary,
|
|
335
|
+
goModReader,
|
|
320
336
|
normalizeGoToolPermissions: compiler.bundled,
|
|
321
337
|
key,
|
|
322
338
|
keyedDigests: sourceDigests,
|
|
@@ -394,7 +410,10 @@ function compileSourcePlugin(opts: {
|
|
|
394
410
|
binaryPath: string;
|
|
395
411
|
cacheDir: string;
|
|
396
412
|
contributors: readonly ITtscBuildContributor[];
|
|
397
|
-
packageOwnership?: readonly {
|
|
413
|
+
packageOwnership?: readonly {
|
|
414
|
+
entry: string;
|
|
415
|
+
kind: "executable" | "linked";
|
|
416
|
+
}[];
|
|
398
417
|
dir: string;
|
|
399
418
|
entry: string;
|
|
400
419
|
env: NodeJS.ProcessEnv;
|
|
@@ -406,6 +425,7 @@ function compileSourcePlugin(opts: {
|
|
|
406
425
|
goBuildCacheRoot: string;
|
|
407
426
|
manageGoBuildCache: boolean;
|
|
408
427
|
normalizeGoToolPermissions: boolean;
|
|
428
|
+
goModReader: SourcePluginWorkspace.GoModReader;
|
|
409
429
|
key: string;
|
|
410
430
|
|
|
411
431
|
/** The digest of every directory the key covers, as the key read it. */
|
|
@@ -461,6 +481,7 @@ function compileSourcePlugin(opts: {
|
|
|
461
481
|
opts.dir,
|
|
462
482
|
opts.env,
|
|
463
483
|
opts.goBinary,
|
|
484
|
+
opts.goModReader,
|
|
464
485
|
);
|
|
465
486
|
OwnedSynchronousProcess.checkpoint();
|
|
466
487
|
// Every source the build would otherwise read in place, an overlay and
|
|
@@ -487,11 +508,7 @@ function compileSourcePlugin(opts: {
|
|
|
487
508
|
opts.goBinary,
|
|
488
509
|
opts.env,
|
|
489
510
|
);
|
|
490
|
-
const goModReader =
|
|
491
|
-
opts.goBinary,
|
|
492
|
-
opts.pluginName,
|
|
493
|
-
opts.env,
|
|
494
|
-
);
|
|
511
|
+
const goModReader = opts.goModReader;
|
|
495
512
|
OwnedSynchronousProcess.checkpoint();
|
|
496
513
|
if (opts.contributors.length > 0) {
|
|
497
514
|
mergeContributors({
|
|
@@ -543,12 +560,20 @@ function compileSourcePlugin(opts: {
|
|
|
543
560
|
const observations = NativeSourcePackages.read({
|
|
544
561
|
cwd: scratchDir,
|
|
545
562
|
entries: opts.packageOwnership.map((input) => input.entry),
|
|
546
|
-
env: GoSourceInputs.goBuildEnv(
|
|
563
|
+
env: GoSourceInputs.goBuildEnv(
|
|
564
|
+
opts.goBinary,
|
|
565
|
+
goBuildCacheRoot,
|
|
566
|
+
opts.env,
|
|
567
|
+
),
|
|
547
568
|
goBinary: opts.goBinary,
|
|
548
569
|
pluginName: opts.pluginName,
|
|
549
570
|
});
|
|
550
571
|
opts.packageOwnership.forEach((input, index) => {
|
|
551
|
-
NativeSourcePackages.kind(
|
|
572
|
+
NativeSourcePackages.kind(
|
|
573
|
+
observations[index]!,
|
|
574
|
+
`${opts.pluginName} ${input.entry}`,
|
|
575
|
+
input.kind,
|
|
576
|
+
);
|
|
552
577
|
});
|
|
553
578
|
}
|
|
554
579
|
runGoBuild(
|
|
@@ -629,10 +654,14 @@ function compileSourcePlugin(opts: {
|
|
|
629
654
|
taskFailure = { error };
|
|
630
655
|
throw error;
|
|
631
656
|
} finally {
|
|
632
|
-
SourceNativeRetirement.releaseResource(
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
657
|
+
SourceNativeRetirement.releaseResource(
|
|
658
|
+
scratchDir,
|
|
659
|
+
() => {
|
|
660
|
+
fs.rmSync(scratchDir, { recursive: true, force: true });
|
|
661
|
+
SourceNativeRetirement.forget(opts.cacheDir);
|
|
662
|
+
},
|
|
663
|
+
taskFailure,
|
|
664
|
+
);
|
|
636
665
|
}
|
|
637
666
|
}
|
|
638
667
|
|
|
@@ -1009,7 +1038,6 @@ function resolveSourceBuildTarget(opts: {
|
|
|
1009
1038
|
return { dir: moduleRoot, entry, source };
|
|
1010
1039
|
}
|
|
1011
1040
|
|
|
1012
|
-
|
|
1013
1041
|
/**
|
|
1014
1042
|
* The path, below a build's scratch directory, of the tree holding its copies
|
|
1015
1043
|
* of the sources outside the module.
|
|
@@ -6,6 +6,7 @@ import { GoToolResolution } from "./GoToolResolution";
|
|
|
6
6
|
import type { ITtscBuildContributor } from "./ITtscBuildContributor";
|
|
7
7
|
import type { PluginBuildEnvironmentWitness } from "./PluginBuildEnvironmentWitness";
|
|
8
8
|
import type { SourceBuildFilesystemOperations } from "./SourceBuildFilesystemOperations";
|
|
9
|
+
import type { SourcePluginWorkspace } from "./SourcePluginWorkspace";
|
|
9
10
|
import { hashPluginBuildEnvironment } from "./hashPluginBuildEnvironment";
|
|
10
11
|
import { pluginModuleReplaceDirectories } from "./pluginModuleReplaceDirectories";
|
|
11
12
|
import { pluginSourceDigest } from "./pluginSourceDigest";
|
|
@@ -20,8 +21,8 @@ import { pluginSourceDigest } from "./pluginSourceDigest";
|
|
|
20
21
|
* source files. An optional required package-ownership population also frames
|
|
21
22
|
* the key, separating binaries admitted by the matching cold producer from
|
|
22
23
|
* legacy artifacts; requests without that authority retain their existing key.
|
|
23
|
-
* Contributors are sorted by name; unique names remove input
|
|
24
|
-
*
|
|
24
|
+
* Contributors are sorted by name; unique names remove input order dependence,
|
|
25
|
+
* while equal-name rows retain the sort's input order.
|
|
25
26
|
*
|
|
26
27
|
* Each source directory enters the key as its digest (`pluginSourceDigest`),
|
|
27
28
|
* which the transform envelope reports, with the environment below, as the
|
|
@@ -53,7 +54,10 @@ export function computeCacheKey(inputs: {
|
|
|
53
54
|
env?: NodeJS.ProcessEnv;
|
|
54
55
|
|
|
55
56
|
/** Required package ownership, admitted by the matching cold producer. */
|
|
56
|
-
packageOwnership?: readonly {
|
|
57
|
+
packageOwnership?: readonly {
|
|
58
|
+
entry: string;
|
|
59
|
+
kind: "executable" | "linked";
|
|
60
|
+
}[];
|
|
57
61
|
|
|
58
62
|
/**
|
|
59
63
|
* Digests of the environment each build directory is keyed on
|
|
@@ -72,6 +76,7 @@ export function computeCacheKey(inputs: {
|
|
|
72
76
|
/** Byte adapter for delegated SDK identity; plugin source digests use fs. */
|
|
73
77
|
filesystem?: Partial<SourceBuildFilesystemOperations>;
|
|
74
78
|
goBinary?: string;
|
|
79
|
+
goModReader?: SourcePluginWorkspace.GoModReader;
|
|
75
80
|
overlayDirs?: readonly string[];
|
|
76
81
|
|
|
77
82
|
/**
|
|
@@ -102,11 +107,23 @@ export function computeCacheKey(inputs: {
|
|
|
102
107
|
hash.update(JSON.stringify(["platform", process.platform, process.arch]));
|
|
103
108
|
hash.update(JSON.stringify(["entry", inputs.entry]));
|
|
104
109
|
if (inputs.packageOwnership !== undefined)
|
|
105
|
-
hash.update(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
+
hash.update(
|
|
111
|
+
JSON.stringify([
|
|
112
|
+
"package-ownership",
|
|
113
|
+
1,
|
|
114
|
+
[...inputs.packageOwnership].sort((left, right) =>
|
|
115
|
+
left.entry < right.entry
|
|
116
|
+
? -1
|
|
117
|
+
: left.entry > right.entry
|
|
118
|
+
? 1
|
|
119
|
+
: left.kind < right.kind
|
|
120
|
+
? -1
|
|
121
|
+
: left.kind > right.kind
|
|
122
|
+
? 1
|
|
123
|
+
: 0,
|
|
124
|
+
),
|
|
125
|
+
]),
|
|
126
|
+
);
|
|
110
127
|
// Private materialization changes native package working directories. Keep
|
|
111
128
|
// binaries produced by the former absolute-ancestry layout out of this
|
|
112
129
|
// layout's cache admissions, including callers with no SDK overlays.
|
|
@@ -139,6 +156,7 @@ export function computeCacheKey(inputs: {
|
|
|
139
156
|
inputs.dir,
|
|
140
157
|
env,
|
|
141
158
|
goBinary,
|
|
159
|
+
inputs.goModReader,
|
|
142
160
|
)) {
|
|
143
161
|
hashSourceDirectory(
|
|
144
162
|
hash,
|
|
@@ -2,6 +2,7 @@ import crypto from "node:crypto";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
+
import { GoEnvironmentReading } from "./GoEnvironmentReading";
|
|
5
6
|
import { GoSourceInputs } from "./GoSourceInputs";
|
|
6
7
|
import { GoToolResolution } from "./GoToolResolution";
|
|
7
8
|
import { PluginBuildEnvironmentWitness } from "./PluginBuildEnvironmentWitness";
|
|
@@ -377,7 +378,13 @@ function resolveGoBuildEnvironment(
|
|
|
377
378
|
): Map<string, string> {
|
|
378
379
|
const values = new Map<string, string>();
|
|
379
380
|
if (goBinary !== undefined) {
|
|
380
|
-
const parsed =
|
|
381
|
+
const parsed = GoEnvironmentReading.read(
|
|
382
|
+
goBinary,
|
|
383
|
+
cwd,
|
|
384
|
+
env,
|
|
385
|
+
GO_BUILD_ENV_KEYS,
|
|
386
|
+
witness,
|
|
387
|
+
);
|
|
381
388
|
if (parsed !== undefined) {
|
|
382
389
|
try {
|
|
383
390
|
for (const key of GO_BUILD_ENV_KEYS) {
|
|
@@ -408,81 +415,6 @@ function resolveGoBuildEnvironment(
|
|
|
408
415
|
return values;
|
|
409
416
|
}
|
|
410
417
|
|
|
411
|
-
/**
|
|
412
|
-
* The Go environment file each Go tool and environment was last seen to name,
|
|
413
|
-
* which lets a reading witness that file before `go env` reads it.
|
|
414
|
-
*/
|
|
415
|
-
const goEnvironmentFiles = new Map<string, string | null>();
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* Run `go env -json` for the build keys and `GOENV`, the file `go env -w`
|
|
419
|
-
* writes, witnessing that file before the run reads it.
|
|
420
|
-
*
|
|
421
|
-
* The file decides the reading without being part of it, so only its metadata
|
|
422
|
-
* is kept, and it must be taken before the read: metadata taken after would
|
|
423
|
-
* describe an edit that landed between the two, and a reading of the old
|
|
424
|
-
* content would pass as proven. Which file `go env` reads is known only from
|
|
425
|
-
* its answer, so the file this Go tool and environment named last is witnessed
|
|
426
|
-
* first, and a reading that names another is taken again with that one
|
|
427
|
-
* witnessed. A file that keeps moving between runs leaves the reading
|
|
428
|
-
* unwitnessable, so a kept reading or a build keyed on it never holds.
|
|
429
|
-
*
|
|
430
|
-
* @returns The parsed answer, or `undefined` when `go env` failed.
|
|
431
|
-
*/
|
|
432
|
-
function readGoEnvironment(
|
|
433
|
-
goBinary: string,
|
|
434
|
-
cwd: string,
|
|
435
|
-
env: NodeJS.ProcessEnv,
|
|
436
|
-
witness: PluginBuildEnvironmentWitness.Record | undefined,
|
|
437
|
-
): Record<string, unknown> | undefined {
|
|
438
|
-
const memo = crypto.createHash("sha256").update(goBinary);
|
|
439
|
-
for (const [key, value] of Object.entries(env).sort(([left], [right]) =>
|
|
440
|
-
left < right ? -1 : left > right ? 1 : 0,
|
|
441
|
-
)) {
|
|
442
|
-
if (value !== undefined)
|
|
443
|
-
memo.update(`\0${key.length}:${key}${value.length}:${value}`);
|
|
444
|
-
}
|
|
445
|
-
const memoKey = memo.digest("hex");
|
|
446
|
-
let named = goEnvironmentFiles.get(memoKey);
|
|
447
|
-
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
448
|
-
if (named !== undefined && named !== null)
|
|
449
|
-
PluginBuildEnvironmentWitness.add(witness, named);
|
|
450
|
-
const result = spawnGoTool(
|
|
451
|
-
goBinary,
|
|
452
|
-
["env", "-json", ...GO_BUILD_ENV_KEYS, "GOENV"],
|
|
453
|
-
{
|
|
454
|
-
cwd,
|
|
455
|
-
encoding: "utf8",
|
|
456
|
-
env: GoSourceInputs.goBuildEnv(goBinary, undefined, env),
|
|
457
|
-
windowsHide: true,
|
|
458
|
-
},
|
|
459
|
-
);
|
|
460
|
-
if (result.error !== undefined || result.status !== 0) return undefined;
|
|
461
|
-
let parsed: Record<string, unknown>;
|
|
462
|
-
try {
|
|
463
|
-
parsed = JSON.parse(result.stdout) as Record<string, unknown>;
|
|
464
|
-
} catch {
|
|
465
|
-
return undefined;
|
|
466
|
-
}
|
|
467
|
-
const reported =
|
|
468
|
-
typeof parsed.GOENV === "string" &&
|
|
469
|
-
parsed.GOENV !== "" &&
|
|
470
|
-
parsed.GOENV !== "off"
|
|
471
|
-
? parsed.GOENV
|
|
472
|
-
: null;
|
|
473
|
-
if (reported === named) return parsed;
|
|
474
|
-
if (named !== undefined && named !== null) witness?.delete(named);
|
|
475
|
-
named = reported;
|
|
476
|
-
goEnvironmentFiles.set(memoKey, named);
|
|
477
|
-
// No file to witness: the reading depends on none.
|
|
478
|
-
if (named === null) return parsed;
|
|
479
|
-
if (witness === undefined) return parsed;
|
|
480
|
-
}
|
|
481
|
-
if (named !== undefined && named !== null)
|
|
482
|
-
PluginBuildEnvironmentWitness.refuse(witness, named);
|
|
483
|
-
return undefined;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
418
|
function normalizeGoBuildEnvValue(
|
|
487
419
|
key: string,
|
|
488
420
|
value: string,
|
|
@@ -2,12 +2,11 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
4
|
import { resolvePhysicalPath } from "../../../internal/pathIdentity/resolvePhysicalPath";
|
|
5
|
-
import { GoSourceInputs } from "./GoSourceInputs";
|
|
6
5
|
import { GoToolResolution } from "./GoToolResolution";
|
|
7
6
|
import type { IPluginModuleReplaceDirectory } from "./IPluginModuleReplaceDirectory";
|
|
7
|
+
import { SourcePluginWorkspace } from "./SourcePluginWorkspace";
|
|
8
8
|
import { resolveGoCompiler } from "./resolveGoCompiler";
|
|
9
9
|
import { selectPluginModuleReplaceDirectories } from "./selectPluginModuleReplaceDirectories";
|
|
10
|
-
import { spawnGoTool } from "./spawnGoTool";
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* The local directories outside a plugin's Go module that its `go.mod`
|
|
@@ -38,19 +37,21 @@ import { spawnGoTool } from "./spawnGoTool";
|
|
|
38
37
|
* fails or supplies an unusable JSON result. A no-marker manifest skips Go
|
|
39
38
|
* parsing here; this is not general go.mod validation.
|
|
40
39
|
* @evidence contracts/common.md#principled-implementation When the replace marker triggers a query, Go parses its grammar. Unversioned local targets selected outside best-effort root/target spellings are reported, resolving relative paths from the original module; unavailable identity resolution does not certify complete physical containment.
|
|
41
|
-
* @evidence contracts/common.md#clear-and-simple-design A cheap no-replace scan avoids
|
|
40
|
+
* @evidence contracts/common.md#clear-and-simple-design A cheap no-replace scan avoids unnecessary parsing; the common selected-tool/load reader owns Go syntax, and this operation owns current physical containment.
|
|
42
41
|
* @evidence contracts/common.md#prohibited-implementation-shortcuts The implementation uses Go's supported mod-edit interface instead of a handwritten grammar or special-casing particular dependency names.
|
|
43
42
|
* @evidence contracts/common.md#meaningful-documentation Native paragraphs explain external versus internal replacement ownership, original-directory resolution and returned spellings; failure behavior is documented.
|
|
44
43
|
* @evidence contracts/portability.md#os-neutral-implementation Node path and physical-path resolution determine containment; Go local-path syntax accepts native absolute paths and Windows-relative backslashes only on Windows.
|
|
45
|
-
* @evidence contracts/performance.md#efficient-algorithms Full manifest read/UTF-8 marker scan can skip Go parsing. Otherwise
|
|
44
|
+
* @evidence contracts/performance.md#efficient-algorithms Full manifest read/UTF-8 marker scan can skip Go parsing. Otherwise selected-tool reader acquisition and a current manifest read precede a pass over all parsed directives, not only returned ones. Equivalent bytes share Go JSON acquisition only in the supplied fixed-environment load registry; a new syntax record pays native tool/env/capture and full JSON costs; each selected local target uses native best-effort identity resolution, which can traverse ancestors/probe case. Sorting returned module/version strings processes their bytes, with complete manifest/output/path arrays retained transiently and no replacement content digest scan here.
|
|
46
45
|
*
|
|
47
|
-
* @
|
|
48
|
-
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources This operation acquires no retained handle
|
|
46
|
+
* @evidence contracts/performance.md#reuse-equivalent-work The optional pinned-tool/load reader or registry shares Go parsing of identical current bytes with package observation and workspace creation. Physical replacement containment is recomputed every call, never borrowed from that syntax memo.
|
|
47
|
+
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources This operation acquires no retained handle; the supplied load registry retains syntax readers until its caller releases the load. Without it the reader is call-owned. Native capture cleanup remains delegated and can fail; current physical identity contexts are call-owned and returned directories do not pin future state.
|
|
49
48
|
*/
|
|
50
49
|
export function pluginModuleReplaceDirectories(
|
|
51
50
|
moduleRoot: string,
|
|
52
51
|
env: NodeJS.ProcessEnv,
|
|
53
52
|
goBinary?: string,
|
|
53
|
+
reader?: SourcePluginWorkspace.GoModReader,
|
|
54
|
+
readers?: Map<string, SourcePluginWorkspace.GoModReader>,
|
|
54
55
|
): IPluginModuleReplaceDirectory[] {
|
|
55
56
|
const root = path.resolve(moduleRoot);
|
|
56
57
|
let text: string;
|
|
@@ -70,27 +71,14 @@ export function pluginModuleReplaceDirectories(
|
|
|
70
71
|
env,
|
|
71
72
|
root,
|
|
72
73
|
);
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
});
|
|
79
|
-
if (result.error !== undefined || result.status !== 0)
|
|
80
|
-
throw new Error(
|
|
81
|
-
`ttsc: reading ${path.join(root, "go.mod")} failed: ${
|
|
82
|
-
result.error?.message ?? (result.stderr || result.stdout)
|
|
83
|
-
}`,
|
|
84
|
-
);
|
|
85
|
-
const parsed = JSON.parse(result.stdout) as {
|
|
86
|
-
Replace?: readonly {
|
|
87
|
-
New?: { Path?: string; Version?: string };
|
|
88
|
-
Old?: { Path?: string; Version?: string };
|
|
89
|
-
}[];
|
|
90
|
-
};
|
|
74
|
+
const parsed = (
|
|
75
|
+
reader ?? SourcePluginWorkspace.createGoModReader(go, root, env, readers)
|
|
76
|
+
).read(root);
|
|
77
|
+
// Parsed syntax may be shared; physical containment is current native state
|
|
78
|
+
// and must be recomputed even when these manifest bytes are unchanged.
|
|
91
79
|
return selectPluginModuleReplaceDirectories(
|
|
92
80
|
root,
|
|
93
|
-
parsed.
|
|
81
|
+
parsed.directives,
|
|
94
82
|
resolvePhysicalPath,
|
|
95
83
|
);
|
|
96
84
|
}
|