windows-development-sandbox 1.27.1 → 1.28.0
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/app/context.d.ts +5 -0
- package/dist/app/context.js +13 -0
- package/dist/app/features/accounts/accounts.def.d.ts +16 -0
- package/dist/app/features/accounts/accounts.run.d.ts +4 -0
- package/dist/app/features/accounts/services.def.d.ts +15 -1
- package/dist/app/features/accounts/services.def.js +67 -31
- package/dist/app/features/accounts/services.run.d.ts +7 -2
- package/dist/app/features/accounts/services.run.js +25 -6
- package/dist/app/features/defs.d.ts +17 -0
- package/dist/app/registry.d.ts +1 -1
- package/dist/app/registry.js +15 -2
- package/dist/bin/agent-sandbox.js +2 -2
- package/dist/bin/{chunk-RSIX7C6D.js → chunk-CB44HTTN.js} +444 -175
- package/dist/bin/{chunk-4TLSWPG7.js → chunk-UZ5S3EMB.js} +1 -1
- package/dist/bin/{chunk-QUHNXHVG.js → chunk-ZMZI3625.js} +46 -41
- package/dist/bin/claude-host.js +2 -2
- package/dist/bin/claude-sbx.js +2 -2
- package/dist/bin/codex-sbx.js +2 -2
- package/dist/bin/gemini-sbx.js +2 -2
- package/dist/bin/sandbox.js +2 -2
- package/dist/bin/sbx-hide.js +2 -2
- package/dist/bin/sbx-show.js +2 -2
- package/dist/cli/menu/menu.js +6 -1
- package/dist/cli/menu/pages/accounts-page.js +9 -5
- package/dist/core/schema/sandbox.js +1 -1
- package/dist/guest/apps/app-installer.js +7 -5
- package/dist/guest/apps/comet-unpack.js +2 -2
- package/dist/guest/daemon/heartbeat.js +1 -0
- package/dist/guest/governor/core.d.ts +53 -1
- package/dist/guest/governor/core.js +95 -1
- package/dist/guest/governor/decide.d.ts +4 -0
- package/dist/guest/governor/decide.js +2 -0
- package/dist/guest/governor/governor.js +23 -9
- package/dist/guest/runtime/pools.d.ts +4 -4
- package/dist/guest/runtime/pools.js +4 -15
- package/dist/guest/runtime/process.d.ts +5 -2
- package/dist/guest/runtime/process.js +23 -3
- package/dist/guest/toolchains/engines/epic-launcher.d.ts +6 -0
- package/dist/guest/toolchains/engines/epic-launcher.js +19 -1
- package/dist/guest/toolchains/engines/unreal-engines.js +2 -1
- package/dist/guest/toolchains/installers.d.ts +3 -3
- package/dist/guest/toolchains/installers.js +5 -5
- package/dist/guest/toolchains/native/unreal.js +22 -3
- package/dist/guest/toolchains/native/vcruntime.js +2 -2
- package/dist/guest/toolchains/native/vs-persist.d.ts +2 -2
- package/dist/guest/toolchains/runtimes.js +7 -6
- package/dist/guest/toolchains/signature.d.ts +2 -0
- package/dist/guest/toolchains/signature.js +9 -2
- package/dist/guest/toolchains/step-scheduler.js +1 -1
- package/dist/host/accounts/deliver.js +15 -7
- package/dist/host/life/crash-report.d.ts +4 -0
- package/dist/host/life/crash-report.js +30 -1
- package/dist/host/life/last-end.js +1 -0
- package/dist/host/pc-installs/discover.js +5 -2
- package/dist/host/pc-installs/unreal.d.ts +3 -1
- package/dist/host/pc-installs/unreal.js +27 -1
- package/dist/host/tools/windows-terminal.d.ts +4 -1
- package/dist/host/tools/windows-terminal.js +25 -9
- package/dist/runtime/agent.js +39 -23
- package/dist/runtime/daemon.js +279 -121
- package/dist/runtime/db.js +37 -17
- package/dist/runtime/main.js +340 -109
- package/dist/runtime/netproxy.js +31 -13
- package/dist/runtime/tool.js +37 -16
- package/dist/runtime/view.js +150 -38
- package/dist/selftest/agent-paths.test.js +3 -1
- package/dist/selftest/app.test.js +20 -2
- package/dist/selftest/boot-runner.test.js +15 -1
- package/dist/selftest/channel.test.js +33 -0
- package/dist/selftest/engines.test.js +22 -1
- package/dist/selftest/heavy-load.test.js +116 -4
- package/dist/selftest/live-terminal.test.js +103 -0
- package/dist/selftest/menu-screens.js +52 -1
- package/dist/selftest/menu.test.js +29 -3
- package/dist/selftest/personalization.test.js +2 -1
- package/dist/selftest/service-accounts.test.js +77 -2
- package/dist/selftest/support/engine-golden.js +2 -0
- package/dist/selftest/ui-text.test.js +4 -0
- package/dist/shared/accounts/kinds.js +9 -9
- package/dist/shared/apps/catalogue-gamedev.js +1 -0
- package/dist/shared/apps/catalogue.d.ts +2 -0
- package/dist/shared/channel/schema.d.ts +2 -0
- package/dist/shared/channel/schema.js +4 -0
- package/dist/shared/concurrency/pools.js +1 -1
- package/dist/shared/download/archive.js +4 -2
- package/dist/shared/download/index.d.ts +1 -1
- package/dist/shared/download/index.js +1 -1
- package/dist/shared/download/spawn.d.ts +7 -1
- package/dist/shared/download/spawn.js +16 -1
- package/dist/shared/memory/index.d.ts +1 -0
- package/dist/shared/memory/index.js +1 -0
- package/dist/shared/memory/policy.d.ts +12 -0
- package/dist/shared/memory/policy.js +4 -0
- package/dist/shared/memory/unreal-build.d.ts +2 -0
- package/dist/shared/memory/unreal-build.js +21 -0
- package/dist/shared/paths/pc-installs.d.ts +8 -0
- package/dist/shared/pty/clipboard-watch.d.ts +4 -0
- package/dist/shared/pty/clipboard-watch.js +40 -0
- package/dist/shared/pty/ctrl-c.d.ts +13 -7
- package/dist/shared/pty/ctrl-c.js +35 -19
- package/dist/shared/pty/index.d.ts +1 -0
- package/dist/shared/pty/index.js +1 -0
- package/dist/shared/pty/terminal.d.ts +1 -1
- package/dist/shared/pty/terminal.js +16 -13
- package/dist/shared/unreal/launcher-files.d.ts +7 -0
- package/dist/shared/unreal/launcher-files.js +32 -1
- package/dist/shared/web/ctrl-c.d.ts +26 -0
- package/dist/shared/web/ctrl-c.js +64 -0
- package/dist/shared/web/index.d.ts +1 -0
- package/dist/shared/web/index.js +1 -0
- package/dist/web/index.html +1 -1
- package/dist/web/{main-VPMYTSUL.js → main-7NY4X6YU.js} +67 -67
- package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
- package/package.json +1 -1
- package/sandbox/csharp/ClipboardWatch.cs +59 -0
- package/sandbox/csharp/MemGov.cs +752 -7
- package/sandbox/scripts/watch-clipboard.ps1 +8 -0
- package/schemas/config.schema.json +10 -10
- package/schemas/sandbox-config.schema.json +9 -9
|
@@ -1,6 +1,94 @@
|
|
|
1
1
|
import { now } from '../../shared/channel/run-dir.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { BURST_GATE } from '../../shared/memory/index.js';
|
|
2
4
|
import { errMsg } from '../../core/util/errors.js';
|
|
3
|
-
import { decide, HOLD_MS, INITIAL_STATE } from './decide.js';
|
|
5
|
+
import { decide, GATE_NAMES, HOLD_MS, INITIAL_STATE, } from './decide.js';
|
|
6
|
+
/** memgov.exe's arguments: samples every 2 s and the memory gate for this profile. */
|
|
7
|
+
export function memgovArgs(profile) {
|
|
8
|
+
const g = BURST_GATE[profile];
|
|
9
|
+
return [
|
|
10
|
+
'serve',
|
|
11
|
+
'--interval',
|
|
12
|
+
'2',
|
|
13
|
+
'--gate',
|
|
14
|
+
[g.growthMb, g.windowMs, g.releaseMs, g.maxHoldMs].join(':'),
|
|
15
|
+
'--gate-names',
|
|
16
|
+
GATE_NAMES.join(','),
|
|
17
|
+
];
|
|
18
|
+
}
|
|
19
|
+
const GateBase = { event: z.literal('gate'), at: z.number() };
|
|
20
|
+
const GateName = z.string().max(128);
|
|
21
|
+
const GateCount = z.number().int().min(0);
|
|
22
|
+
/** What the memory gate in memgov reports on its stdout (one JSON line each). */
|
|
23
|
+
export const GateEventSchema = z.discriminatedUnion('kind', [
|
|
24
|
+
z.strictObject({
|
|
25
|
+
...GateBase,
|
|
26
|
+
kind: z.literal('ready'),
|
|
27
|
+
mode: z.enum(['notify', 'poll']),
|
|
28
|
+
growthMb: GateCount,
|
|
29
|
+
windowMs: GateCount,
|
|
30
|
+
}),
|
|
31
|
+
z.strictObject({ ...GateBase, kind: z.literal('off'), reason: z.string().max(200) }),
|
|
32
|
+
z.strictObject({
|
|
33
|
+
...GateBase,
|
|
34
|
+
kind: z.literal('trip'),
|
|
35
|
+
grewMb: GateCount,
|
|
36
|
+
ms: GateCount,
|
|
37
|
+
jobMb: GateCount,
|
|
38
|
+
held: z.array(z.strictObject({ pid: z.number().int(), name: GateName })).max(64),
|
|
39
|
+
}),
|
|
40
|
+
z.strictObject({ ...GateBase, kind: z.literal('hold'), pid: z.number().int(), name: GateName }),
|
|
41
|
+
z.strictObject({
|
|
42
|
+
...GateBase,
|
|
43
|
+
kind: z.literal('release'),
|
|
44
|
+
pid: z.number().int(),
|
|
45
|
+
name: GateName,
|
|
46
|
+
heldMs: GateCount,
|
|
47
|
+
forced: z.boolean(),
|
|
48
|
+
}),
|
|
49
|
+
]);
|
|
50
|
+
/** One memory-log line for a gate event (names cut to 64 characters). */
|
|
51
|
+
export function gateLine(e) {
|
|
52
|
+
const cut = (s) => s.slice(0, 64);
|
|
53
|
+
switch (e.kind) {
|
|
54
|
+
case 'ready':
|
|
55
|
+
return ('gate: on (' +
|
|
56
|
+
e.mode +
|
|
57
|
+
"): new compilers wait when the agents' programs grow by more than " +
|
|
58
|
+
e.growthMb +
|
|
59
|
+
' MB within ' +
|
|
60
|
+
e.windowMs / 1000 +
|
|
61
|
+
' s');
|
|
62
|
+
case 'off':
|
|
63
|
+
return 'gate: off (' + e.reason + '): builds are only slowed down by the dial';
|
|
64
|
+
case 'trip':
|
|
65
|
+
return ("gate: the agents' programs grew " +
|
|
66
|
+
e.grewMb +
|
|
67
|
+
' MB in ' +
|
|
68
|
+
(e.ms / 1000).toFixed(1) +
|
|
69
|
+
' s (now ' +
|
|
70
|
+
e.jobMb +
|
|
71
|
+
' MB); holding ' +
|
|
72
|
+
e.held.length +
|
|
73
|
+
': ' +
|
|
74
|
+
e.held
|
|
75
|
+
.slice(0, 8)
|
|
76
|
+
.map((p) => cut(p.name) + ' (' + p.pid + ')')
|
|
77
|
+
.join(', ') +
|
|
78
|
+
(e.held.length > 8 ? ' …' : ''));
|
|
79
|
+
case 'hold':
|
|
80
|
+
return 'gate: ' + cut(e.name) + ' (' + e.pid + ') waits at start while a build burst goes through in turn';
|
|
81
|
+
case 'release':
|
|
82
|
+
return ('gate: ' +
|
|
83
|
+
cut(e.name) +
|
|
84
|
+
' (' +
|
|
85
|
+
e.pid +
|
|
86
|
+
') goes on after ' +
|
|
87
|
+
(e.heldMs / 1000).toFixed(1) +
|
|
88
|
+
' s' +
|
|
89
|
+
(e.forced ? ' (the longest it may wait)' : ''));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
4
92
|
const STEP_TEXT = [
|
|
5
93
|
'builds back to normal',
|
|
6
94
|
'build programs at lower priority',
|
|
@@ -55,6 +143,7 @@ export class GovernorCore {
|
|
|
55
143
|
commitPercent: d.commitPercent,
|
|
56
144
|
step: d.state.step,
|
|
57
145
|
hostStep: nowMs < d.state.hostUntil ? d.state.hostStep : 0,
|
|
146
|
+
held: sample.gateHeld ?? 0,
|
|
58
147
|
diskQueue: null,
|
|
59
148
|
top: [...sample.procs]
|
|
60
149
|
.sort((a, b) => b.mb - a.mb)
|
|
@@ -89,6 +178,7 @@ export class GovernorCore {
|
|
|
89
178
|
paused: [...this.held]
|
|
90
179
|
.slice(0, 8)
|
|
91
180
|
.map(([pid, name]) => ({ name: name.slice(0, 64), pid, seconds: HOLD_MS / 1000 })),
|
|
181
|
+
held: sample.gateHeld ?? 0,
|
|
92
182
|
cpuPercent: d.state.cpuRate,
|
|
93
183
|
pcLow: nowMs < d.state.hostUntil,
|
|
94
184
|
lastAction: this.lastAction.slice(0, 300),
|
|
@@ -102,6 +192,10 @@ export class GovernorCore {
|
|
|
102
192
|
this.deps.saveStatus(status);
|
|
103
193
|
}
|
|
104
194
|
}
|
|
195
|
+
/** An event from the memory gate in memgov: goes to the memory log. */
|
|
196
|
+
onGate(event) {
|
|
197
|
+
this.note(gateLine(event));
|
|
198
|
+
}
|
|
105
199
|
apply(action) {
|
|
106
200
|
const port = this.deps.port;
|
|
107
201
|
try {
|
|
@@ -16,6 +16,8 @@ export interface MemSample {
|
|
|
16
16
|
readonly commitAvailMb: number;
|
|
17
17
|
/** Only the processes in the agents' job. */
|
|
18
18
|
readonly procs: readonly ProcSample[];
|
|
19
|
+
/** Build programs the memory gate holds now (memgov). */
|
|
20
|
+
readonly gateHeld?: number;
|
|
19
21
|
}
|
|
20
22
|
export declare const isNeverTouched: (name: string) => boolean;
|
|
21
23
|
export declare const canLower: (name: string) => boolean;
|
|
@@ -24,6 +26,8 @@ export declare const isLinker: (name: string) => boolean;
|
|
|
24
26
|
export declare function linkerSlots(totalMb: number, cpus: number): number;
|
|
25
27
|
export declare const isHeavyCompiler: (name: string) => boolean;
|
|
26
28
|
export declare const canPause: (name: string) => boolean;
|
|
29
|
+
/** What the memory gate in memgov may freeze (compilers, linkers, Unreal's shader workers); passed as --gate-names. */
|
|
30
|
+
export declare const GATE_NAMES: readonly string[];
|
|
27
31
|
export type Level = 'ok' | 'throttled' | 'suspending';
|
|
28
32
|
export interface GovernorState {
|
|
29
33
|
/** The dial: 0 nothing wrong, 1 lower priority, 2 less CPU and fewer compilers, 3 and 4 harder. */
|
|
@@ -109,6 +109,8 @@ export function linkerSlots(totalMb, cpus) {
|
|
|
109
109
|
}
|
|
110
110
|
export const isHeavyCompiler = (name) => !isNeverTouched(name) && HEAVY.has(key(name));
|
|
111
111
|
export const canPause = (name) => !isNeverTouched(name) && COMPILERS.has(key(name));
|
|
112
|
+
/** What the memory gate in memgov may freeze (compilers, linkers, Unreal's shader workers); passed as --gate-names. */
|
|
113
|
+
export const GATE_NAMES = [...COMPILERS, 'shadercompileworker'].filter((n) => !NEVER.has(n)).sort();
|
|
112
114
|
export const INITIAL_STATE = {
|
|
113
115
|
step: 0,
|
|
114
116
|
calm: 0,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// The memory governor: samples the sandbox's memory every 2 seconds (memgov.exe, sandbox/csharp/MemGov.cs) and turns
|
|
2
2
|
// a dial as it fills: the build programs the agents started get lower priority, lower memory priority, less CPU, and
|
|
3
|
-
// take turns (decide.ts).
|
|
3
|
+
// take turns (decide.ts). A memory gate in memgov also holds new compilers when memory grows faster than a few GB in
|
|
4
|
+
// seconds. Builds slow down; nothing fails. What it does goes to the memory log and run\pressure.json.
|
|
4
5
|
import fs from 'node:fs';
|
|
5
6
|
import os from 'node:os';
|
|
6
7
|
import path from 'node:path';
|
|
@@ -8,14 +9,14 @@ import { spawn } from 'node:child_process';
|
|
|
8
9
|
import { createInterface } from 'node:readline';
|
|
9
10
|
import { z } from 'zod';
|
|
10
11
|
import { writeJsonAtomic } from '../../shared/io/json.js';
|
|
11
|
-
import { DIAL_STEPS, maxCompilers } from '../../shared/memory/index.js';
|
|
12
|
+
import { BURST_GATE, DIAL_STEPS, maxCompilers } from '../../shared/memory/index.js';
|
|
12
13
|
import { ensureCompiled } from '../runtime/csc.js';
|
|
13
14
|
import { dataDir } from '../runtime/paths.js';
|
|
14
15
|
import { warn } from '../runtime/log.js';
|
|
15
16
|
import { run } from '../runtime/paths.js';
|
|
16
17
|
import { session } from '../runtime/session.js';
|
|
17
18
|
import { errMsg } from '../../core/util/errors.js';
|
|
18
|
-
import { GovernorCore } from './core.js';
|
|
19
|
+
import { GateEventSchema, GovernorCore, memgovArgs, } from './core.js';
|
|
19
20
|
const SampleSchema = z.object({
|
|
20
21
|
ramTotalMb: z.number(),
|
|
21
22
|
ramAvailMb: z.number(),
|
|
@@ -29,27 +30,36 @@ const SampleSchema = z.object({
|
|
|
29
30
|
prio: z.enum(['idle', 'below', 'normal']).catch('normal'),
|
|
30
31
|
}))
|
|
31
32
|
.max(1024),
|
|
33
|
+
gateHeld: z.number().int().min(0).max(1024).optional().catch(undefined),
|
|
32
34
|
});
|
|
33
35
|
/** memgov.exe as a child process: samples come out of its stdout, commands go into its stdin. */
|
|
34
36
|
class MemGovProcess {
|
|
37
|
+
args;
|
|
35
38
|
onSample;
|
|
39
|
+
onGate;
|
|
36
40
|
child = null;
|
|
37
|
-
constructor(onSample) {
|
|
41
|
+
constructor(args, onSample, onGate) {
|
|
42
|
+
this.args = args;
|
|
38
43
|
this.onSample = onSample;
|
|
44
|
+
this.onGate = onGate;
|
|
39
45
|
}
|
|
40
46
|
start() {
|
|
41
47
|
try {
|
|
42
|
-
const child = spawn(ensureCompiled('memgov'), [
|
|
48
|
+
const child = spawn(ensureCompiled('memgov'), [...this.args], {
|
|
43
49
|
stdio: ['pipe', 'pipe', 'ignore'],
|
|
44
50
|
windowsHide: true,
|
|
45
51
|
});
|
|
46
52
|
this.child = child;
|
|
47
53
|
createInterface({ input: child.stdout }).on('line', (line) => {
|
|
48
54
|
try {
|
|
49
|
-
|
|
55
|
+
const raw = JSON.parse(line);
|
|
56
|
+
if (typeof raw === 'object' && raw !== null && raw.event === 'gate')
|
|
57
|
+
this.onGate(GateEventSchema.parse(raw));
|
|
58
|
+
else
|
|
59
|
+
this.onSample(SampleSchema.parse(raw));
|
|
50
60
|
}
|
|
51
61
|
catch (e) {
|
|
52
|
-
warn('governor: bad
|
|
62
|
+
warn('governor: bad line from memgov: ' + errMsg(e));
|
|
53
63
|
}
|
|
54
64
|
});
|
|
55
65
|
child.stdin.on('error', () => undefined);
|
|
@@ -120,13 +130,17 @@ export class Governor {
|
|
|
120
130
|
first.availablePercent +
|
|
121
131
|
'% free RAM or ' +
|
|
122
132
|
first.commitPercent +
|
|
123
|
-
'% commit, in steps'
|
|
133
|
+
'% commit, in steps; holds new compilers when memory grows faster than ' +
|
|
134
|
+
BURST_GATE[profile].growthMb +
|
|
135
|
+
' MB in ' +
|
|
136
|
+
BURST_GATE[profile].windowMs / 1000 +
|
|
137
|
+
' s';
|
|
124
138
|
}
|
|
125
139
|
start() {
|
|
126
140
|
if (session.pressureResponse === false)
|
|
127
141
|
return;
|
|
128
142
|
const profile = session.loadProfile === 'gentle' ? 'gentle' : 'normal';
|
|
129
|
-
const proc = new MemGovProcess((s) => this.core?.onSample(s));
|
|
143
|
+
const proc = new MemGovProcess(memgovArgs(profile), (s) => this.core?.onSample(s), (e) => this.core?.onGate(e));
|
|
130
144
|
this.core = new GovernorCore({
|
|
131
145
|
port: proc,
|
|
132
146
|
profile,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { type SpawnOptions } from 'node:child_process';
|
|
2
1
|
import { heavy, installer, net } from '../../shared/concurrency/index.js';
|
|
2
|
+
import { lowerPriority, type SpawnAsyncOptions } from '../../shared/download/index.js';
|
|
3
3
|
export { net, heavy, installer };
|
|
4
4
|
/** Called at the start of the boot and of the daemon: the downloader and the extractor use the pools from then on. */
|
|
5
5
|
export declare function installPools(): void;
|
|
6
6
|
/** Lowers a process (and the children it starts from now on) to below-normal priority; best effort. */
|
|
7
|
-
export declare
|
|
7
|
+
export declare const belowNormal: typeof lowerPriority;
|
|
8
8
|
export interface InstallerOptions {
|
|
9
9
|
/** Below-normal priority for the installer and what it starts (the background Visual Studio). */
|
|
10
10
|
readonly lowPriority?: boolean;
|
|
@@ -20,5 +20,5 @@ export interface InstallerOptions {
|
|
|
20
20
|
export declare function runInstaller(file: string, args: string[], o?: InstallerOptions): Promise<number>;
|
|
21
21
|
/** runInstaller that throws like execAsync when the installer fails. */
|
|
22
22
|
export declare function runInstallerChecked(file: string, args: string[], o?: InstallerOptions): Promise<void>;
|
|
23
|
-
/** execAsync for heavy local work (npm, uv, rustup ...): one of the two heavy slots. */
|
|
24
|
-
export declare function execHeavy(file: string, args: string[], options?:
|
|
23
|
+
/** execAsync for heavy local work (npm, uv, rustup ...): one of the two heavy slots, at below-normal priority. */
|
|
24
|
+
export declare function execHeavy(file: string, args: string[], options?: SpawnAsyncOptions): Promise<void>;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
// The guest's work pools: network bodies, heavy local work and installers. A task takes one slot at the leaf (the
|
|
2
2
|
// call that downloads, unpacks or installs), never while it holds another: the limiter refuses nested pools.
|
|
3
|
-
import os from 'node:os';
|
|
4
3
|
import { spawn } from 'node:child_process';
|
|
5
4
|
import path from 'node:path';
|
|
6
5
|
import { heavy, installer, net, INSTALLER_WAIT_MS, installerRetry } from '../../shared/concurrency/index.js';
|
|
7
|
-
import { downloader, limitExtraction } from '../../shared/download/index.js';
|
|
6
|
+
import { downloader, limitExtraction, lowerPriority } from '../../shared/download/index.js';
|
|
8
7
|
import { log } from './log.js';
|
|
9
8
|
import { execAsync } from './process.js';
|
|
10
9
|
export { net, heavy, installer };
|
|
@@ -13,18 +12,8 @@ export function installPools() {
|
|
|
13
12
|
downloader.limit = net;
|
|
14
13
|
limitExtraction(heavy);
|
|
15
14
|
}
|
|
16
|
-
const BELOW_NORMAL = os.constants.priority.PRIORITY_BELOW_NORMAL;
|
|
17
15
|
/** Lowers a process (and the children it starts from now on) to below-normal priority; best effort. */
|
|
18
|
-
export
|
|
19
|
-
if (pid === undefined)
|
|
20
|
-
return;
|
|
21
|
-
try {
|
|
22
|
-
os.setPriority(pid, BELOW_NORMAL);
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
/* the process may have ended already; a normal priority only makes it a little slower to share the machine */
|
|
26
|
-
}
|
|
27
|
-
}
|
|
16
|
+
export const belowNormal = lowerPriority;
|
|
28
17
|
/**
|
|
29
18
|
* Runs an installer in the one installer slot and starts it again while another process holds the Windows Installer
|
|
30
19
|
* lock (exit 1618). Resolves with the exit code of the last try.
|
|
@@ -54,7 +43,7 @@ export async function runInstallerChecked(file, args, o = {}) {
|
|
|
54
43
|
if (code !== 0)
|
|
55
44
|
throw new Error(path.basename(file) + ' ' + args.slice(0, 3).join(' ') + ' failed with exit code ' + code);
|
|
56
45
|
}
|
|
57
|
-
/** execAsync for heavy local work (npm, uv, rustup ...): one of the two heavy slots. */
|
|
46
|
+
/** execAsync for heavy local work (npm, uv, rustup ...): one of the two heavy slots, at below-normal priority. */
|
|
58
47
|
export function execHeavy(file, args, options = {}) {
|
|
59
|
-
return heavy.run(() => execAsync(file, args, options));
|
|
48
|
+
return heavy.run(() => execAsync(file, args, { ...options, lowPriority: true }));
|
|
60
49
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type SpawnSyncOptions } from 'node:child_process';
|
|
2
|
+
import { type SpawnAsyncOptions } from '../../shared/download/index.js';
|
|
2
3
|
/** Takes the output of the programs started while the boot draws its progress (boot/boot-console.ts). */
|
|
3
4
|
export interface OutputSink {
|
|
4
5
|
/** Whether programs started now hand their output over instead of writing to the console. */
|
|
@@ -11,7 +12,7 @@ export declare function routeOutput(sink: OutputSink | null): void;
|
|
|
11
12
|
/** Runs a program, streaming output, and throws with a readable message when it fails. */
|
|
12
13
|
export declare function exec(file: string, args: string[], options?: SpawnSyncOptions): void;
|
|
13
14
|
/** exec without blocking: other toolchains keep downloading while this one runs an installer. */
|
|
14
|
-
export declare function execAsync(file: string, args: string[], options?:
|
|
15
|
+
export declare function execAsync(file: string, args: string[], options?: SpawnAsyncOptions): Promise<void>;
|
|
15
16
|
export declare function execOutput(file: string, args: string[], options?: SpawnSyncOptions): string;
|
|
16
17
|
/** Runs sandbox\scripts\<name> (a .ps1 in the launch folder) with `args`; throws when it fails. */
|
|
17
18
|
export declare function runScript(name: string, args?: readonly string[]): void;
|
|
@@ -19,6 +20,8 @@ export declare function runScript(name: string, args?: readonly string[]): void;
|
|
|
19
20
|
export declare function runScriptAsync(name: string, args?: readonly string[]): Promise<void>;
|
|
20
21
|
/** runScript for what it prints ('' when it fails). */
|
|
21
22
|
export declare function scriptOutput(name: string, args?: readonly string[], options?: SpawnSyncOptions): string;
|
|
23
|
+
/** scriptOutput without blocking the event loop ('' when the script fails). */
|
|
24
|
+
export declare function scriptOutputAsync(name: string, args?: readonly string[]): Promise<string>;
|
|
22
25
|
/** Runs sandbox\scripts\<name> with a JSON file (-<param> path) holding `data`, removed afterwards. */
|
|
23
26
|
export declare function runScriptWithJson(name: string, param: string, data: unknown, args?: readonly string[]): void;
|
|
24
27
|
/** runScriptWithJson without blocking the event loop. */
|
|
@@ -2,7 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { spawn, spawnSync } from 'node:child_process';
|
|
5
|
-
import { moveIntoPlace, spawnAsync, stagingFolder } from '../../shared/download/index.js';
|
|
5
|
+
import { lowerPriority, moveIntoPlace, spawnAsync, stagingFolder, } from '../../shared/download/index.js';
|
|
6
6
|
import { safeRemove } from '../../shared/io/safe-remove.js';
|
|
7
7
|
import { robocopyErrors, robocopyFailed, robocopyMeaning } from '../../shared/io/robocopy-result.js';
|
|
8
8
|
import { SYSTEM } from '../../shared/system.js';
|
|
@@ -57,15 +57,18 @@ export function exec(file, args, options = {}) {
|
|
|
57
57
|
}
|
|
58
58
|
/** exec without blocking: other toolchains keep downloading while this one runs an installer. */
|
|
59
59
|
export async function execAsync(file, args, options = {}) {
|
|
60
|
+
const { lowPriority, ...spawnOptions } = options;
|
|
60
61
|
const code = captureOutput()
|
|
61
62
|
? await new Promise((resolve, reject) => {
|
|
62
|
-
const child = spawn(file, args, { stdio: ['inherit', 'pipe', 'pipe'], windowsHide: true, ...
|
|
63
|
+
const child = spawn(file, args, { stdio: ['inherit', 'pipe', 'pipe'], windowsHide: true, ...spawnOptions });
|
|
64
|
+
if (lowPriority)
|
|
65
|
+
lowerPriority(child.pid);
|
|
63
66
|
logLines(child.stdout);
|
|
64
67
|
logLines(child.stderr);
|
|
65
68
|
child.once('error', reject);
|
|
66
69
|
child.once('close', (c) => resolve(c ?? -1));
|
|
67
70
|
})
|
|
68
|
-
: await spawnAsync(file, args, { stdio: 'inherit', ...
|
|
71
|
+
: await spawnAsync(file, args, { stdio: 'inherit', ...spawnOptions, ...(lowPriority ? { lowPriority } : {}) });
|
|
69
72
|
if (code !== 0)
|
|
70
73
|
throw new Error(path.basename(file) + ' ' + args.slice(0, 3).join(' ') + ' failed with exit code ' + code);
|
|
71
74
|
}
|
|
@@ -131,6 +134,23 @@ export function runScriptAsync(name, args = []) {
|
|
|
131
134
|
export function scriptOutput(name, args = [], options = {}) {
|
|
132
135
|
return execOutput(SYSTEM.powershell(), scriptArgs(name, args), options);
|
|
133
136
|
}
|
|
137
|
+
/** scriptOutput without blocking the event loop ('' when the script fails). */
|
|
138
|
+
export function scriptOutputAsync(name, args = []) {
|
|
139
|
+
return new Promise((resolve) => {
|
|
140
|
+
const child = spawn(SYSTEM.powershell(), scriptArgs(name, args), {
|
|
141
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
142
|
+
windowsHide: true,
|
|
143
|
+
});
|
|
144
|
+
let out = '';
|
|
145
|
+
child.stdout?.setEncoding('utf8');
|
|
146
|
+
child.stdout?.on('data', (chunk) => {
|
|
147
|
+
if (out.length < CAPTURE_BYTES)
|
|
148
|
+
out += chunk;
|
|
149
|
+
});
|
|
150
|
+
child.once('error', () => resolve(''));
|
|
151
|
+
child.once('close', (code) => resolve(code === 0 ? out.trim() : ''));
|
|
152
|
+
});
|
|
153
|
+
}
|
|
134
154
|
/** Runs sandbox\scripts\<name> with a JSON file (-<param> path) holding `data`, removed afterwards. */
|
|
135
155
|
export function runScriptWithJson(name, param, data, args = []) {
|
|
136
156
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'sbx-'));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PcInstall } from '../../../shared/paths/pc-installs.js';
|
|
1
2
|
import { type LauncherEntry } from '../../../shared/unreal/index.js';
|
|
2
3
|
/** The launcher's program: newer MSIs install into Program Files, older ones into Program Files (x86). */
|
|
3
4
|
export declare function launcherExe(): string | null;
|
|
@@ -17,6 +18,11 @@ export declare function placeEngines(persist: boolean): LauncherPlaceResult;
|
|
|
17
18
|
export declare function savedEntries(): LauncherEntry[];
|
|
18
19
|
/** Puts the saved manifests back where the launcher reads them; the engine folders they name. */
|
|
19
20
|
export declare function restoreLauncherItems(): string[];
|
|
21
|
+
/**
|
|
22
|
+
* The manifests of this PC's engines (session.json) where the launcher reads them: without one it drops the engine
|
|
23
|
+
* from LauncherInstalled.dat when it starts. Only a manifest of the engine's own folder; one of ours is replaced.
|
|
24
|
+
*/
|
|
25
|
+
export declare function writePcLauncherItems(items: readonly Pick<PcInstall, 'kind' | 'guest' | 'launcherItem'>[]): number;
|
|
20
26
|
/** Merges these entries into the launcher's LauncherInstalled.dat (foreign entries kept). */
|
|
21
27
|
export declare function writeLauncherEntries(entries: readonly LauncherEntry[]): void;
|
|
22
28
|
/**
|
|
@@ -9,7 +9,7 @@ import { z } from 'zod';
|
|
|
9
9
|
import { readBoundedFile } from '../../../shared/io/bounded.js';
|
|
10
10
|
import { readValid, writeJsonAtomic } from '../../../shared/io/json.js';
|
|
11
11
|
import { SYSTEM } from '../../../shared/system.js';
|
|
12
|
-
import { enginePlaceProblem, ENGINE_NEED_BYTES, entriesUnder, launcherInstalledFile, launcherManifestsDir, launcherSettingsFiles, MAX_LAUNCHER_FILE_BYTES, mergeLauncherInstalled, restoreItems, saveItems, setLauncherInstallLocation, } from '../../../shared/unreal/index.js';
|
|
12
|
+
import { enginePlaceProblem, ENGINE_NEED_BYTES, entriesUnder, itemInstallLocation, ITEM_NAME, launcherInstalledFile, launcherManifestsDir, launcherSettingsFiles, MAX_LAUNCHER_FILE_BYTES, mergeLauncherInstalled, restoreItems, saveItems, setLauncherInstallLocation, } from '../../../shared/unreal/index.js';
|
|
13
13
|
import { errMsg } from '../../../core/util/errors.js';
|
|
14
14
|
import { runInstaller } from '../../runtime/pools.js';
|
|
15
15
|
import { exists, mkdirp } from '../../runtime/fs.js';
|
|
@@ -93,6 +93,24 @@ export function savedEntries() {
|
|
|
93
93
|
export function restoreLauncherItems() {
|
|
94
94
|
return restoreItems(SAVED_MANIFESTS, launcherManifestsDir(programData()), ENGINES_DIR, exists);
|
|
95
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* The manifests of this PC's engines (session.json) where the launcher reads them: without one it drops the engine
|
|
98
|
+
* from LauncherInstalled.dat when it starts. Only a manifest of the engine's own folder; one of ours is replaced.
|
|
99
|
+
*/
|
|
100
|
+
export function writePcLauncherItems(items) {
|
|
101
|
+
const dir = launcherManifestsDir(programData());
|
|
102
|
+
let written = 0;
|
|
103
|
+
for (const i of items) {
|
|
104
|
+
if (i.kind !== 'unreal' || !i.launcherItem || !ITEM_NAME.test(i.launcherItem.name))
|
|
105
|
+
continue;
|
|
106
|
+
const at = itemInstallLocation(i.launcherItem.text);
|
|
107
|
+
if (!at || path.win32.resolve(at).toLowerCase() !== path.win32.resolve(i.guest).toLowerCase())
|
|
108
|
+
continue;
|
|
109
|
+
fs.writeFileSync(path.join(mkdirp(dir), i.launcherItem.name), i.launcherItem.text, 'utf8');
|
|
110
|
+
written++;
|
|
111
|
+
}
|
|
112
|
+
return written;
|
|
113
|
+
}
|
|
96
114
|
/** Merges these entries into the launcher's LauncherInstalled.dat (foreign entries kept). */
|
|
97
115
|
export function writeLauncherEntries(entries) {
|
|
98
116
|
if (!entries.length)
|
|
@@ -16,7 +16,7 @@ import { runScriptWithJsonAsync } from '../../runtime/process.js';
|
|
|
16
16
|
import { projectWorkspace } from '../../runtime/project-context.js';
|
|
17
17
|
import { session } from '../../runtime/session.js';
|
|
18
18
|
import { setUnrealMcp, writeAddonMcpServers } from '../../setup/addon-mcp.js';
|
|
19
|
-
import { saveLauncherState, savedEntries, writeLauncherEntries } from './epic-launcher.js';
|
|
19
|
+
import { saveLauncherState, savedEntries, writeLauncherEntries, writePcLauncherItems } from './epic-launcher.js';
|
|
20
20
|
import { DDC_DIR, ENGINES_DIR, SAVED_DIR, updateUnrealStatus } from './unreal-status.js';
|
|
21
21
|
const SAVED_MANIFESTS = path.join(SAVED_DIR, 'Manifests');
|
|
22
22
|
/** The real disk, read bounded. */
|
|
@@ -76,6 +76,7 @@ export async function registerEngines(engines, selectorFrom, ctx, aliases = [])
|
|
|
76
76
|
if (values.length)
|
|
77
77
|
await runScriptWithJsonAsync('set-registry-values.ps1', 'PlanFile', { values });
|
|
78
78
|
try {
|
|
79
|
+
writePcLauncherItems(ctx.pcInstalls.items);
|
|
79
80
|
writeLauncherEntries(launcherEntries(engines, ctx));
|
|
80
81
|
}
|
|
81
82
|
catch (e) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { SIGNERS, type AppSigner } from '../../shared/signer.js';
|
|
2
|
-
import { signedBy } from './signature.js';
|
|
2
|
+
import { signedBy, signedByAsync } from './signature.js';
|
|
3
3
|
export { SIGNERS };
|
|
4
|
-
export { signedBy };
|
|
4
|
+
export { signedBy, signedByAsync };
|
|
5
5
|
/** A downloader `verify` hook: refuses a file not signed by `signer` (it counts as integrity for requireChecksum). */
|
|
6
|
-
export declare function signatureCheck(signer: AppSigner, what: string): (file: string) => void
|
|
6
|
+
export declare function signatureCheck(signer: AppSigner, what: string): (file: string) => Promise<void>;
|
|
7
7
|
/** The installer at `file` (downloaded once from `url`), checked against its signer and, when given, its SHA-256. */
|
|
8
8
|
export declare function signedInstaller(url: string, file: string, what: string, signer: AppSigner, sha256?: string | null): Promise<string>;
|
|
@@ -6,13 +6,13 @@ import { downloader } from '../../shared/download/index.js';
|
|
|
6
6
|
import { exists, mkdirp } from '../runtime/fs.js';
|
|
7
7
|
import { log } from '../runtime/log.js';
|
|
8
8
|
import { SIGNERS } from '../../shared/signer.js';
|
|
9
|
-
import { signedBy } from './signature.js';
|
|
9
|
+
import { signedBy, signedByAsync } from './signature.js';
|
|
10
10
|
export { SIGNERS };
|
|
11
|
-
export { signedBy };
|
|
11
|
+
export { signedBy, signedByAsync };
|
|
12
12
|
/** A downloader `verify` hook: refuses a file not signed by `signer` (it counts as integrity for requireChecksum). */
|
|
13
13
|
export function signatureCheck(signer, what) {
|
|
14
|
-
return (file) => {
|
|
15
|
-
if (!
|
|
14
|
+
return async (file) => {
|
|
15
|
+
if (!(await signedByAsync(file, signer)))
|
|
16
16
|
throw new Error(what + ' is not signed by its vendor');
|
|
17
17
|
};
|
|
18
18
|
}
|
|
@@ -23,7 +23,7 @@ export async function signedInstaller(url, file, what, signer, sha256 = null) {
|
|
|
23
23
|
mkdirp(path.dirname(file));
|
|
24
24
|
await downloader.file(url, file, { sha256, verify: signatureCheck(signer, path.basename(file)) });
|
|
25
25
|
}
|
|
26
|
-
if (!
|
|
26
|
+
if (!(await signedByAsync(file, signer))) {
|
|
27
27
|
fs.rmSync(file, { force: true });
|
|
28
28
|
throw new Error(path.basename(file) + ' is not signed by its vendor; deleted it (it is downloaded again next boot)');
|
|
29
29
|
}
|
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
// Unreal Engine development, machine-wide (toolchains.unreal, once per boot): the DirectX End-User Runtime the engine
|
|
2
2
|
// lists and the Epic Games Launcher (real installers: they run every boot), the launcher pointed at the data disk for
|
|
3
|
-
// engines and given back the engines it installed there, the Windows SDK debuggers for crash dumps,
|
|
4
|
-
// UnrealBuildTool finds Visual Studio the way it looks (the Setup Configuration API)
|
|
5
|
-
// step (visual-studio.ts), which the plan runs first; each project's engine is set up
|
|
3
|
+
// engines and given back the engines it installed there, the Windows SDK debuggers for crash dumps, a check that
|
|
4
|
+
// UnrealBuildTool finds Visual Studio the way it looks (the Setup Configuration API), and its parallel compiles capped.
|
|
5
|
+
// Visual Studio itself is its own step (visual-studio.ts), which the plan runs first; each project's engine is set up
|
|
6
|
+
// by engines/unreal-engines.ts.
|
|
6
7
|
import fs from 'node:fs';
|
|
7
8
|
import os from 'node:os';
|
|
8
9
|
import path from 'node:path';
|
|
9
10
|
import { errMsg } from '../../../core/util/errors.js';
|
|
10
11
|
import { readValid } from '../../../shared/io/json.js';
|
|
11
12
|
import { safeRemove } from '../../../shared/io/safe-remove.js';
|
|
13
|
+
import { buildJobs, ubtBuildConfiguration, usableCpus } from '../../../shared/memory/index.js';
|
|
12
14
|
import { VsSetupReportSchema, vsVerdict } from '../../../shared/unreal/index.js';
|
|
13
15
|
import { runInstaller } from '../../runtime/pools.js';
|
|
14
16
|
import { dryRun } from '../../runtime/paths.js';
|
|
15
17
|
import { exists, mkdirp } from '../../runtime/fs.js';
|
|
16
18
|
import { log, warn } from '../../runtime/log.js';
|
|
17
19
|
import { runScriptAsync } from '../../runtime/process.js';
|
|
20
|
+
import { session } from '../../runtime/session.js';
|
|
18
21
|
import { ensureLauncher, fetchLauncher, placeEngines, restoreLauncherItems } from '../engines/epic-launcher.js';
|
|
19
22
|
import { resetUnrealStatus, updateUnrealStatus } from '../engines/unreal-status.js';
|
|
20
23
|
import { SIGNERS, signedInstaller } from '../installers.js';
|
|
@@ -68,6 +71,18 @@ async function checkVisualStudio() {
|
|
|
68
71
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
69
72
|
}
|
|
70
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* UnrealBuildTool's per-user BuildConfiguration.xml (%APPDATA%, wiped with the sandbox): compiles at once from the
|
|
76
|
+
* sandbox's build budget, so a build start cannot outrun the memory Windows backs the sandbox with. Returns the cap.
|
|
77
|
+
*/
|
|
78
|
+
function capUnrealBuilds() {
|
|
79
|
+
const cpus = usableCpus(os.cpus().length, session.cpuLimitPercent ?? null);
|
|
80
|
+
const jobs = buildJobs(cpus, os.totalmem() / 1024 ** 3);
|
|
81
|
+
const appData = process.env.APPDATA ?? 'C:\\Users\\WDAGUtilityAccount\\AppData\\Roaming';
|
|
82
|
+
const dir = mkdirp(path.join(appData, 'Unreal Engine', 'UnrealBuildTool'));
|
|
83
|
+
fs.writeFileSync(path.join(dir, 'BuildConfiguration.xml'), ubtBuildConfiguration(jobs), 'utf8');
|
|
84
|
+
return jobs;
|
|
85
|
+
}
|
|
71
86
|
/** Runs one part, warning (not failing the step) when it does not work. */
|
|
72
87
|
async function part(what, work, fallback) {
|
|
73
88
|
try {
|
|
@@ -86,6 +101,9 @@ export async function setupUnreal(env, config, _ctx) {
|
|
|
86
101
|
return;
|
|
87
102
|
}
|
|
88
103
|
resetUnrealStatus();
|
|
104
|
+
const jobs = await part('capping parallel compiles', async () => capUnrealBuilds(), 0);
|
|
105
|
+
if (jobs)
|
|
106
|
+
log('Unreal: UnrealBuildTool runs at most ' + jobs + ' compiles at once (the build budget)');
|
|
89
107
|
await part('the DirectX End-User Runtime', installDirectX, undefined);
|
|
90
108
|
const launcher = await part('the Epic Games Launcher', ensureLauncher, null);
|
|
91
109
|
// Before anyone opens the launcher: engines go to the data disk, and it knows the ones it installed there before.
|
|
@@ -131,6 +149,7 @@ export async function setupUnreal(env, config, _ctx) {
|
|
|
131
149
|
vs.version +
|
|
132
150
|
(check.ok ? ' (UnrealBuildTool finds it)' : ' (UnrealBuildTool would NOT find it)')
|
|
133
151
|
: 'no Visual Studio') +
|
|
152
|
+
(jobs ? ', at most ' + jobs + ' compiles at once' : '') +
|
|
134
153
|
', DirectX End-User Runtime' +
|
|
135
154
|
(launcher ? ', Epic Games Launcher' : '') +
|
|
136
155
|
(debuggers ? ', cdb/windbg' : ''));
|
|
@@ -6,7 +6,7 @@ import { dryRun } from '../../runtime/paths.js';
|
|
|
6
6
|
import { exists } from '../../runtime/fs.js';
|
|
7
7
|
import { log } from '../../runtime/log.js';
|
|
8
8
|
import { downloadFile } from '../install.js';
|
|
9
|
-
import { SIGNERS,
|
|
9
|
+
import { SIGNERS, signedByAsync } from '../installers.js';
|
|
10
10
|
import { tool } from '../tool-env.js';
|
|
11
11
|
const VC_REDIST = 'https://aka.ms/vs/17/release/vc_redist.x64.exe';
|
|
12
12
|
/** 1638: a newer version is already installed; 3010: installed, a restart would finish it (not needed for the DLLs). */
|
|
@@ -37,7 +37,7 @@ export async function setupVcRuntime(env, summary = true) {
|
|
|
37
37
|
log('Downloading the Visual C++ runtime (~25 MB once) ...');
|
|
38
38
|
await downloadFile(VC_REDIST, installer);
|
|
39
39
|
}
|
|
40
|
-
if (!
|
|
40
|
+
if (!(await signedByAsync(installer, SIGNERS.microsoft))) {
|
|
41
41
|
fs.rmSync(installer, { force: true });
|
|
42
42
|
throw new Error('vc_redist.x64.exe is not signed by Microsoft; deleted it (it is downloaded again next boot)');
|
|
43
43
|
}
|
|
@@ -71,6 +71,7 @@ export declare const VsPersistMarkerSchema: z.ZodObject<{
|
|
|
71
71
|
installationPath: z.ZodString;
|
|
72
72
|
sdkVersion: z.ZodString;
|
|
73
73
|
folders: z.ZodArray<z.ZodEnum<{
|
|
74
|
+
Shared: "Shared";
|
|
74
75
|
Community: "Community";
|
|
75
76
|
BuildTools: "BuildTools";
|
|
76
77
|
WindowsKits10: "WindowsKits10";
|
|
@@ -78,7 +79,6 @@ export declare const VsPersistMarkerSchema: z.ZodObject<{
|
|
|
78
79
|
"MicrosoftSDKs-v10.0A": "MicrosoftSDKs-v10.0A";
|
|
79
80
|
ReferenceAssemblies: "ReferenceAssemblies";
|
|
80
81
|
Installer: "Installer";
|
|
81
|
-
Shared: "Shared";
|
|
82
82
|
Setup: "Setup";
|
|
83
83
|
instance: "instance";
|
|
84
84
|
}>>;
|
|
@@ -102,6 +102,7 @@ export type VsPersistMarker = z.infer<typeof VsPersistMarkerSchema>;
|
|
|
102
102
|
/** What save-visual-studio.ps1 writes to its result file. */
|
|
103
103
|
export declare const SaveResultSchema: z.ZodObject<{
|
|
104
104
|
copied: z.ZodArray<z.ZodEnum<{
|
|
105
|
+
Shared: "Shared";
|
|
105
106
|
Community: "Community";
|
|
106
107
|
BuildTools: "BuildTools";
|
|
107
108
|
WindowsKits10: "WindowsKits10";
|
|
@@ -109,7 +110,6 @@ export declare const SaveResultSchema: z.ZodObject<{
|
|
|
109
110
|
"MicrosoftSDKs-v10.0A": "MicrosoftSDKs-v10.0A";
|
|
110
111
|
ReferenceAssemblies: "ReferenceAssemblies";
|
|
111
112
|
Installer: "Installer";
|
|
112
|
-
Shared: "Shared";
|
|
113
113
|
Setup: "Setup";
|
|
114
114
|
instance: "instance";
|
|
115
115
|
}>>;
|
|
@@ -28,12 +28,13 @@ export async function setupSingleExe(env, name, repo, pattern, exeName, dir, opt
|
|
|
28
28
|
}
|
|
29
29
|
export async function setupBun(env) {
|
|
30
30
|
await setupSingleExe(env, 'bun', 'oven-sh/bun', arch === 'arm64' ? /^bun-windows-aarch64\.zip$/ : /^bun-windows-x64\.zip$/, 'bun.exe', tool('bun', 'bin'), { size: '35 MB' });
|
|
31
|
-
// Bun cannot keep its cache or temp files on a mapped folder (EINVAL); they go to the sandbox's own disk.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
// Bun cannot keep its cache or temp files on a mapped folder (EINVAL); they go to the sandbox's own disk. The temp
|
|
32
|
+
// folder must exist: programs compiled with Bun (opencode) unpack their DLLs there and fail when it is missing.
|
|
33
|
+
const cache = path.join(localDir, 'bun-cache');
|
|
34
|
+
const temp = path.join(localDir, 'bun-tmp');
|
|
35
|
+
if (!dryRun)
|
|
36
|
+
[cache, temp].forEach((dir) => mkdirp(dir));
|
|
37
|
+
Object.assign(env.vars, { BUN_INSTALL: tool('bun'), BUN_INSTALL_CACHE_DIR: cache, BUN_TMPDIR: temp });
|
|
37
38
|
}
|
|
38
39
|
export async function setupDeno(env) {
|
|
39
40
|
await setupSingleExe(env, 'deno', 'denoland/deno', arch === 'arm64' ? /^deno-aarch64-pc-windows-msvc[.]zip$/ : /^deno-x86_64-pc-windows-msvc[.]zip$/, 'deno.exe', tool('deno', 'bin'), { size: '40 MB' });
|
|
@@ -3,3 +3,5 @@ import { type AppSigner, type Signature } from '../../shared/signer.js';
|
|
|
3
3
|
export declare function signatureOf(file: string): Signature | null;
|
|
4
4
|
/** Whether a file's valid signature is the expected signer's. */
|
|
5
5
|
export declare function signedBy(file: string, signer: AppSigner): boolean;
|
|
6
|
+
/** signedBy without blocking the event loop: a boot check of a big download must not stall the other tasks. */
|
|
7
|
+
export declare function signedByAsync(file: string, signer: AppSigner): Promise<boolean>;
|