windows-development-sandbox 1.23.2 → 1.25.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/config.example.json +5 -3
- package/dist/app/features/data/data.def.d.ts +4 -4
- package/dist/app/features/data/data.run.d.ts +1 -1
- package/dist/app/features/defs.d.ts +161 -6
- package/dist/app/features/defs.js +7 -0
- package/dist/app/features/projects/projects.def.d.ts +1 -1
- package/dist/app/features/projects/projects.run.d.ts +1 -1
- package/dist/app/features/unreal/index.d.ts +2 -0
- package/dist/app/features/unreal/index.js +3 -0
- package/dist/app/features/unreal/unreal.def.d.ts +236 -0
- package/dist/app/features/unreal/unreal.def.js +379 -0
- package/dist/app/features/unreal/unreal.run.d.ts +96 -0
- package/dist/app/features/unreal/unreal.run.js +217 -0
- package/dist/app/registry.js +5 -0
- package/dist/bin/agent-sandbox.js +2 -2
- package/dist/bin/{chunk-7YPEDAJD.js → chunk-3GAXLATK.js} +58 -5
- package/dist/bin/{chunk-USTRSHIE.js → chunk-F4PVBN23.js} +2615 -1515
- package/dist/bin/{chunk-QFSJX2KW.js → chunk-G3TG4ZT7.js} +1 -1
- 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/commands/launch/launch.js +5 -2
- package/dist/cli/commands/registry.js +3 -0
- package/dist/cli/manifest.d.ts +18 -0
- package/dist/cli/manifest.js +26 -0
- package/dist/cli/menu/coverage-exempt.js +1 -0
- package/dist/cli/menu/pages/sandbox-page.d.ts +3 -0
- package/dist/cli/menu/pages/sandbox-page.js +34 -4
- package/dist/cli/menu/views.js +2 -0
- package/dist/core/config/defaults.js +18 -5
- package/dist/core/detect/toolchains.js +0 -6
- package/dist/core/schema/config.d.ts +10 -9
- package/dist/core/schema/pc-installs.d.ts +3 -1
- package/dist/core/schema/pc-installs.js +10 -1
- package/dist/core/schema/toolchains.d.ts +1 -2
- package/dist/core/schema/toolchains.js +4 -7
- package/dist/guest/accounts/kinds/epic.js +18 -13
- package/dist/guest/agents/agent-files.d.ts +11 -7
- package/dist/guest/agents/agent-files.js +37 -11
- package/dist/guest/agents/local-state.d.ts +2 -1
- package/dist/guest/agents/local-state.js +2 -1
- package/dist/guest/apps/after-install.d.ts +1 -1
- package/dist/guest/apps/after-install.js +6 -1
- package/dist/guest/apps/app-installer.d.ts +1 -0
- package/dist/guest/apps/app-installer.js +13 -3
- package/dist/guest/apps/comet-unpack.js +3 -2
- package/dist/guest/apps/resolvers-gamedev.js +55 -1
- package/dist/guest/boot/boot-console.d.ts +28 -0
- package/dist/guest/boot/boot-console.js +98 -0
- package/dist/guest/boot/boot.d.ts +8 -1
- package/dist/guest/boot/boot.js +80 -43
- package/dist/guest/boot/graph.d.ts +9 -5
- package/dist/guest/boot/graph.js +8 -2
- package/dist/guest/boot/guide.js +24 -0
- package/dist/guest/boot/progress-view.d.ts +116 -0
- package/dist/guest/boot/progress-view.js +456 -0
- package/dist/guest/boot/runner.d.ts +20 -10
- package/dist/guest/boot/runner.js +39 -47
- package/dist/guest/daemon/daemon.js +3 -0
- package/dist/guest/runtime/log-line.d.ts +7 -0
- package/dist/guest/runtime/log-line.js +18 -0
- package/dist/guest/runtime/log.d.ts +17 -4
- package/dist/guest/runtime/log.js +40 -17
- package/dist/guest/runtime/process.d.ts +9 -0
- package/dist/guest/runtime/process.js +69 -8
- package/dist/guest/setup/addon-mcp.d.ts +7 -0
- package/dist/guest/setup/addon-mcp.js +19 -4
- package/dist/guest/setup/browser-gpu.js +14 -11
- package/dist/guest/setup/environment.js +9 -4
- package/dist/guest/setup/headroom.js +4 -0
- package/dist/guest/setup/hooks.d.ts +2 -0
- package/dist/guest/setup/hooks.js +4 -0
- package/dist/guest/setup/path-plan.d.ts +12 -0
- package/dist/guest/setup/path-plan.js +18 -0
- package/dist/guest/toolchains/engines/epic-launcher.d.ts +26 -0
- package/dist/guest/toolchains/engines/epic-launcher.js +127 -0
- package/dist/guest/toolchains/engines/unreal-engines.d.ts +21 -1
- package/dist/guest/toolchains/engines/unreal-engines.js +163 -3
- package/dist/guest/toolchains/engines/unreal-status.d.ts +13 -0
- package/dist/guest/toolchains/engines/unreal-status.js +41 -0
- package/dist/guest/toolchains/native/debuggers.d.ts +6 -0
- package/dist/guest/toolchains/native/debuggers.js +57 -0
- package/dist/guest/toolchains/native/mesa.js +15 -8
- package/dist/guest/toolchains/native/rust-extras.d.ts +7 -0
- package/dist/guest/toolchains/native/rust-extras.js +23 -1
- package/dist/guest/toolchains/native/unreal.d.ts +2 -2
- package/dist/guest/toolchains/native/unreal.js +96 -20
- package/dist/guest/toolchains/native/visual-studio.d.ts +14 -14
- package/dist/guest/toolchains/native/visual-studio.js +145 -174
- package/dist/guest/toolchains/native/vs-persist.d.ts +189 -0
- package/dist/guest/toolchains/native/vs-persist.js +256 -0
- package/dist/guest/toolchains/native/vs-request.d.ts +6 -26
- package/dist/guest/toolchains/native/vs-request.js +16 -50
- package/dist/guest/toolchains/plan.js +23 -15
- package/dist/guest/toolchains/tools/vscode-profile.js +2 -0
- package/dist/host/accounts/kinds/epic.js +11 -5
- package/dist/host/accounts/source.d.ts +2 -0
- package/dist/host/accounts/source.js +1 -0
- package/dist/host/migrate/migrations/drop-vs-install-files.d.ts +5 -0
- package/dist/host/migrate/migrations/drop-vs-install-files.js +34 -0
- package/dist/host/migrate/registry.js +7 -1
- package/dist/host/pc-installs/discover.d.ts +2 -0
- package/dist/host/pc-installs/discover.js +25 -5
- package/dist/host/pc-installs/epic.d.ts +3 -0
- package/dist/host/pc-installs/epic.js +33 -2
- package/dist/host/pc-installs/source.d.ts +1 -1
- package/dist/host/pc-installs/unreal-vault.d.ts +11 -0
- package/dist/host/pc-installs/unreal-vault.js +78 -0
- package/dist/host/pc-installs/unreal.d.ts +36 -0
- package/dist/host/pc-installs/unreal.js +145 -2
- package/dist/host/profile/claude-profile.d.ts +8 -0
- package/dist/host/profile/claude-profile.js +43 -4
- package/dist/host/profile/index.d.ts +1 -1
- package/dist/host/profile/index.js +1 -1
- package/dist/runtime/agent.js +148 -33
- package/dist/runtime/daemon.js +3146 -1546
- package/dist/runtime/db.js +148 -33
- package/dist/runtime/main.js +3982 -2049
- package/dist/runtime/netproxy.js +84 -13
- package/dist/runtime/tool.js +125 -31
- package/dist/runtime/view.js +85 -14
- package/dist/selftest/agent-paths.test.js +18 -1
- package/dist/selftest/agent-settings.test.js +36 -5
- package/dist/selftest/agents-web.test.js +12 -0
- package/dist/selftest/boot-console.test.d.ts +2 -0
- package/dist/selftest/boot-console.test.js +291 -0
- package/dist/selftest/boot-runner.test.js +10 -12
- package/dist/selftest/engines.test.js +602 -3
- package/dist/selftest/guest-boot.test.js +7 -1
- package/dist/selftest/heavy-load.test.js +3 -1
- package/dist/selftest/hostile-input.test.js +11 -1
- package/dist/selftest/index.js +4 -0
- package/dist/selftest/layout.test.js +4 -0
- package/dist/selftest/menu-screens.js +3 -1
- package/dist/selftest/migrate.test.js +21 -0
- package/dist/selftest/mixamo.test.d.ts +2 -0
- package/dist/selftest/mixamo.test.js +60 -0
- package/dist/selftest/persisted-installs.test.js +169 -33
- package/dist/selftest/secrets.test.js +36 -1
- package/dist/selftest/support/engine-golden.js +5 -0
- package/dist/selftest/support/fixtures.d.ts +4 -3
- package/dist/selftest/support/unreal-sample.d.ts +2 -0
- package/dist/selftest/support/unreal-sample.js +83 -0
- package/dist/selftest/toolchains.test.js +32 -2
- package/dist/shared/accounts/index.d.ts +1 -1
- package/dist/shared/accounts/index.js +1 -1
- package/dist/shared/accounts/kinds/epic.d.ts +6 -1
- package/dist/shared/accounts/kinds/epic.js +11 -1
- package/dist/shared/apps/associations.d.ts +7 -0
- package/dist/shared/apps/associations.js +26 -0
- package/dist/shared/apps/catalogue-agents-web.d.ts +4 -1
- package/dist/shared/apps/catalogue-agents-web.js +5 -2
- package/dist/shared/apps/catalogue-gamedev.js +71 -1
- package/dist/shared/apps/index.d.ts +2 -0
- package/dist/shared/apps/index.js +2 -0
- package/dist/shared/apps/preview.d.ts +2 -0
- package/dist/shared/apps/preview.js +10 -0
- package/dist/shared/browser-policy.d.ts +14 -0
- package/dist/shared/browser-policy.js +22 -0
- package/dist/shared/channel/schema.d.ts +3 -6
- package/dist/shared/io/index.d.ts +1 -0
- package/dist/shared/io/index.js +1 -0
- package/dist/shared/io/rotate-log.d.ts +2 -0
- package/dist/shared/io/rotate-log.js +16 -0
- package/dist/shared/memory/policy.d.ts +5 -0
- package/dist/shared/memory/policy.js +7 -0
- package/dist/shared/paths/pc-installs.d.ts +13 -2
- package/dist/shared/paths/pc-installs.js +1 -0
- package/dist/shared/pty/terminal.js +2 -1
- package/dist/shared/readiness.d.ts +0 -1
- package/dist/shared/readiness.js +0 -1
- package/dist/shared/signer.d.ts +4 -0
- package/dist/shared/signer.js +2 -0
- package/dist/shared/unreal/engines.d.ts +161 -0
- package/dist/shared/unreal/engines.js +308 -0
- package/dist/shared/unreal/guide.d.ts +19 -0
- package/dist/shared/unreal/guide.js +105 -0
- package/dist/shared/unreal/index.d.ts +7 -0
- package/dist/shared/unreal/index.js +8 -0
- package/dist/shared/unreal/launcher-files.d.ts +88 -0
- package/dist/shared/unreal/launcher-files.js +292 -0
- package/dist/shared/unreal/mixamo.d.ts +5 -0
- package/dist/shared/unreal/mixamo.js +16 -0
- package/dist/shared/unreal/room.d.ts +17 -0
- package/dist/shared/unreal/room.js +34 -0
- package/dist/shared/unreal/status.d.ts +54 -0
- package/dist/shared/unreal/status.js +57 -0
- package/dist/shared/unreal/vault.d.ts +21 -0
- package/dist/shared/unreal/vault.js +67 -0
- package/dist/shared/unreal/vs-check.d.ts +39 -0
- package/dist/shared/unreal/vs-check.js +79 -0
- package/dist/sync/watcher.js +5 -1
- package/dist/web/index.html +1 -1
- package/dist/web/{main-CCILLX2M.js → main-FNE6XEHA.js} +84 -84
- 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/VsSetup.cs +210 -0
- package/sandbox/scripts/get-vs-setup.ps1 +93 -0
- package/sandbox/scripts/restore-visual-studio.ps1 +78 -0
- package/sandbox/scripts/save-visual-studio.ps1 +106 -0
- package/sandbox/scripts/set-registry-values.ps1 +35 -0
- package/schemas/config.schema.json +38 -9
- package/schemas/sandbox-config.schema.json +11 -8
- package/dist/guest/toolchains/native/autosdk.d.ts +0 -3
- package/dist/guest/toolchains/native/autosdk.js +0 -50
|
@@ -7,7 +7,7 @@ import { execOutput } from '../../runtime/process.js';
|
|
|
7
7
|
import { ensureCompiled } from '../../runtime/csc.js';
|
|
8
8
|
import { arch, tool } from '../tool-env.js';
|
|
9
9
|
import { wanted } from '../detect.js';
|
|
10
|
-
import { cargoJobs, chooseTargetDir } from '../../../shared/memory/index.js';
|
|
10
|
+
import { cargoJobs, cargoLinkerVar, chooseTargetDir } from '../../../shared/memory/index.js';
|
|
11
11
|
import { linkerSlots } from '../../governor/decide.js';
|
|
12
12
|
import { warn } from '../../runtime/log.js';
|
|
13
13
|
import { freeBytes } from '../persisted.js';
|
|
@@ -81,6 +81,28 @@ export function linkGate(env, rust, host) {
|
|
|
81
81
|
env.vars.AGENT_LINK_SLOTS +
|
|
82
82
|
' at once');
|
|
83
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* MSVC host: link.exe writes an executable into a mapped folder (the workspace, C:\AgentData) as all zeros, so a
|
|
86
|
+
* fresh build script or test program fails with "The file or directory is corrupted and unreadable" (os error 1392).
|
|
87
|
+
* Rust's own lld (a copy of rust-lld.exe named lld-link.exe, which is how lld picks its mode) writes them whole.
|
|
88
|
+
* `rust.linker` "gnu" keeps link.exe; a linker the user set for the target wins.
|
|
89
|
+
*/
|
|
90
|
+
export function lldLink(env, rust, host) {
|
|
91
|
+
if (!host.endsWith('-pc-windows-msvc') || rust.linker !== 'lld')
|
|
92
|
+
return;
|
|
93
|
+
const variable = cargoLinkerVar(host);
|
|
94
|
+
if (process.env[variable])
|
|
95
|
+
return;
|
|
96
|
+
const rustEnv = { ...process.env, RUSTUP_HOME: tool('rust', 'rustup'), CARGO_HOME: tool('rust', 'cargo') };
|
|
97
|
+
const sysroot = execOutput(tool('rust', 'cargo', 'bin', 'rustc.exe'), ['--print', 'sysroot'], { env: rustEnv });
|
|
98
|
+
const lld = sysroot ? path.join(sysroot, 'lib', 'rustlib', host, 'bin', 'rust-lld.exe') : '';
|
|
99
|
+
if (!lld || !exists(lld))
|
|
100
|
+
return;
|
|
101
|
+
const shim = path.join(mkdirp(tool('shims')), 'lld-link.exe');
|
|
102
|
+
fs.copyFileSync(lld, shim);
|
|
103
|
+
env.vars[variable] = shim;
|
|
104
|
+
env.summary.push('rust linking: lld (link.exe writes empty files into mapped folders)');
|
|
105
|
+
}
|
|
84
106
|
/**
|
|
85
107
|
* Cargo defaults that fit Windows, written only when there is no $CARGO_HOME\config.toml yet: fewer parallel jobs
|
|
86
108
|
* (parallel rustc ran out of paging file) and, for the GNU host, only line tables as debug info (full debug info made
|
|
@@ -2,6 +2,6 @@ import type { Toolchains } from '../../../core/types.js';
|
|
|
2
2
|
import type { ToolCtx } from '../step.js';
|
|
3
3
|
import { type ToolEnv } from '../tool-env.js';
|
|
4
4
|
export { UNREAL_COMPONENTS } from './vs-request.js';
|
|
5
|
-
/** Downloads the DirectX redistributable ahead of the install (only once the licence is accepted). */
|
|
5
|
+
/** Downloads the DirectX redistributable and the launcher ahead of the install (only once the licence is accepted). */
|
|
6
6
|
export declare function fetchDirectX(config: Toolchains['unreal']): Promise<void>;
|
|
7
|
-
export declare function setupUnreal(env: ToolEnv, config: Toolchains['unreal'],
|
|
7
|
+
export declare function setupUnreal(env: ToolEnv, config: Toolchains['unreal'], _ctx: ToolCtx): Promise<void>;
|
|
@@ -1,18 +1,25 @@
|
|
|
1
|
-
// Unreal Engine development (toolchains.unreal): the DirectX End-User Runtime the engine
|
|
2
|
-
//
|
|
3
|
-
// engines
|
|
4
|
-
//
|
|
1
|
+
// Unreal Engine development, machine-wide (toolchains.unreal, once per boot): the DirectX End-User Runtime the engine
|
|
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, and a check that
|
|
4
|
+
// UnrealBuildTool finds Visual Studio the way it looks (the Setup Configuration API). Visual Studio itself is its own
|
|
5
|
+
// step (visual-studio.ts), which the plan runs first; each project's engine is set up by engines/unreal-engines.ts.
|
|
6
|
+
import fs from 'node:fs';
|
|
5
7
|
import os from 'node:os';
|
|
6
8
|
import path from 'node:path';
|
|
7
|
-
import {
|
|
9
|
+
import { errMsg } from '../../../core/util/errors.js';
|
|
10
|
+
import { readValid } from '../../../shared/io/json.js';
|
|
8
11
|
import { safeRemove } from '../../../shared/io/safe-remove.js';
|
|
12
|
+
import { VsSetupReportSchema, vsVerdict } from '../../../shared/unreal/index.js';
|
|
13
|
+
import { runInstaller } from '../../runtime/pools.js';
|
|
9
14
|
import { dryRun } from '../../runtime/paths.js';
|
|
10
15
|
import { exists, mkdirp } from '../../runtime/fs.js';
|
|
11
|
-
import { log } from '../../runtime/log.js';
|
|
12
|
-
import {
|
|
16
|
+
import { log, warn } from '../../runtime/log.js';
|
|
17
|
+
import { runScriptAsync } from '../../runtime/process.js';
|
|
18
|
+
import { ensureLauncher, fetchLauncher, placeEngines, restoreLauncherItems } from '../engines/epic-launcher.js';
|
|
19
|
+
import { resetUnrealStatus, updateUnrealStatus } from '../engines/unreal-status.js';
|
|
13
20
|
import { SIGNERS, signedInstaller } from '../installers.js';
|
|
14
21
|
import { tool } from '../tool-env.js';
|
|
15
|
-
import {
|
|
22
|
+
import { ensureDebuggers } from './debuggers.js';
|
|
16
23
|
import { installedVs } from './visual-studio.js';
|
|
17
24
|
export { UNREAL_COMPONENTS } from './vs-request.js';
|
|
18
25
|
const DIRECTX_URL = 'https://download.microsoft.com/download/8/4/A/84A35BF1-DAFE-4AE8-82AF-AD2AE20B6B14/directx_Jun2010_redist.exe';
|
|
@@ -23,10 +30,13 @@ const directXInstalled = () => {
|
|
|
23
30
|
};
|
|
24
31
|
/** The redistributable (downloaded once, signature checked). */
|
|
25
32
|
const directXRedist = () => signedInstaller(DIRECTX_URL, path.join(tool('visualstudio'), 'directx_Jun2010_redist.exe'), 'the DirectX End-User Runtime (~95 MB once)', SIGNERS.microsoft);
|
|
26
|
-
/** Downloads the DirectX redistributable ahead of the install (only once the licence is accepted). */
|
|
33
|
+
/** Downloads the DirectX redistributable and the launcher ahead of the install (only once the licence is accepted). */
|
|
27
34
|
export async function fetchDirectX(config) {
|
|
28
|
-
if (config.acceptLicense
|
|
35
|
+
if (!config.acceptLicense)
|
|
36
|
+
return;
|
|
37
|
+
if (!directXInstalled())
|
|
29
38
|
await directXRedist();
|
|
39
|
+
await fetchLauncher();
|
|
30
40
|
}
|
|
31
41
|
async function installDirectX() {
|
|
32
42
|
if (directXInstalled())
|
|
@@ -42,19 +52,85 @@ async function installDirectX() {
|
|
|
42
52
|
if (code !== 0)
|
|
43
53
|
throw new Error('the DirectX End-User Runtime installer failed (exit ' + code + ')');
|
|
44
54
|
}
|
|
45
|
-
|
|
55
|
+
/** What UnrealBuildTool's way of finding Visual Studio finds (scripts\get-vs-setup.ps1 calls the Setup API). */
|
|
56
|
+
async function checkVisualStudio() {
|
|
57
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'sbx-vs-check-'));
|
|
58
|
+
const file = path.join(dir, 'result.json');
|
|
59
|
+
try {
|
|
60
|
+
await runScriptAsync('get-vs-setup.ps1', ['-ResultFile', file]);
|
|
61
|
+
const report = readValid(VsSetupReportSchema, file);
|
|
62
|
+
return report ? vsVerdict(report) : { ok: false, text: 'the check left no result' };
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
return { ok: false, text: 'the check failed: ' + errMsg(e) };
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Runs one part, warning (not failing the step) when it does not work. */
|
|
72
|
+
async function part(what, work, fallback) {
|
|
73
|
+
try {
|
|
74
|
+
return await work();
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
warn('Unreal: ' + what + ' failed (' + errMsg(e) + ')');
|
|
78
|
+
return fallback;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export async function setupUnreal(env, config, _ctx) {
|
|
46
82
|
if (!config.acceptLicense)
|
|
47
|
-
throw new Error(
|
|
83
|
+
throw new Error("Unreal tools are installed only with toolchains.unreal.acceptLicense = true: Visual Studio Community and the DirectX End-User Runtime come under Microsoft licence terms (https://visualstudio.microsoft.com/license-terms/), the Epic Games Launcher under Epic's (https://www.epicgames.com/site/eula)");
|
|
48
84
|
if (dryRun) {
|
|
49
|
-
log('(dry run) would install the DirectX End-User Runtime and
|
|
85
|
+
log('(dry run) would install the DirectX End-User Runtime and the Epic Games Launcher');
|
|
50
86
|
return;
|
|
51
87
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
88
|
+
resetUnrealStatus();
|
|
89
|
+
await part('the DirectX End-User Runtime', installDirectX, undefined);
|
|
90
|
+
const launcher = await part('the Epic Games Launcher', ensureLauncher, null);
|
|
91
|
+
// Before anyone opens the launcher: engines go to the data disk, and it knows the ones it installed there before.
|
|
92
|
+
const place = placeEngines(config.persistEngines);
|
|
93
|
+
if (config.persistEngines)
|
|
94
|
+
await part("restoring the launcher's engines", async () => restoreLauncherItems(), []);
|
|
95
|
+
if (place.problem) {
|
|
96
|
+
warn('Unreal: ' + place.problem);
|
|
97
|
+
env.summary.push('UNREAL: ' + place.problem);
|
|
98
|
+
}
|
|
99
|
+
const debuggers = await part('the Windows SDK debuggers', ensureDebuggers, null);
|
|
100
|
+
if (debuggers)
|
|
101
|
+
env.append.push(debuggers);
|
|
56
102
|
const vs = installedVs();
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
'
|
|
103
|
+
const check = vs ? await checkVisualStudio() : { ok: false, text: 'no Visual Studio with the C++ tools' };
|
|
104
|
+
if (!check.ok)
|
|
105
|
+
warn('Unreal: UnrealBuildTool would not find a compiler: ' + check.text);
|
|
106
|
+
else
|
|
107
|
+
log('Unreal: UnrealBuildTool finds ' + check.text);
|
|
108
|
+
const kept = Boolean(vs && /\\visualstudio\\persist\\/i.test(vs.path + '\\'));
|
|
109
|
+
updateUnrealStatus((s) => ({
|
|
110
|
+
...s,
|
|
111
|
+
launcher: {
|
|
112
|
+
installed: launcher !== null,
|
|
113
|
+
installLocation: place.installLocation,
|
|
114
|
+
problem: place.problem,
|
|
115
|
+
freeBytes: place.freeBytes,
|
|
116
|
+
needBytes: place.needBytes,
|
|
117
|
+
},
|
|
118
|
+
visualStudio: {
|
|
119
|
+
version: vs?.version ?? '',
|
|
120
|
+
path: vs?.path ?? '',
|
|
121
|
+
how: vs ? (kept ? 'kept on the data disk' : 'installed in this sandbox') : '',
|
|
122
|
+
check: check.text,
|
|
123
|
+
ok: check.ok,
|
|
124
|
+
},
|
|
125
|
+
debugger: debuggers ? path.join(debuggers, 'cdb.exe') : null,
|
|
126
|
+
}));
|
|
127
|
+
env.summary.push('unreal tools: ' +
|
|
128
|
+
(vs
|
|
129
|
+
? 'Visual Studio ' +
|
|
130
|
+
vs.version +
|
|
131
|
+
(check.ok ? ' (UnrealBuildTool finds it)' : ' (UnrealBuildTool would NOT find it)')
|
|
132
|
+
: 'no Visual Studio') +
|
|
133
|
+
', DirectX End-User Runtime' +
|
|
134
|
+
(launcher ? ', Epic Games Launcher' : '') +
|
|
135
|
+
(debuggers ? ', cdb/windbg' : ''));
|
|
60
136
|
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { type ToolEnv } from '../tool-env.js';
|
|
2
2
|
import { type VsRequest } from './vs-request.js';
|
|
3
|
-
export { BUILD_TOOLS_COMPONENTS,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
3
|
+
export { BUILD_TOOLS_COMPONENTS, UNREAL_BUILD_TOOLS_COMPONENTS, UNREAL_COMPONENTS, visualStudioWanted, type VsRequest, } from './vs-request.js';
|
|
4
|
+
export interface InstalledVs {
|
|
5
|
+
readonly path: string;
|
|
6
|
+
readonly version: string;
|
|
7
|
+
readonly instanceId: string;
|
|
8
|
+
}
|
|
9
|
+
/** The newest Visual Studio (or Build Tools) with the C++ compiler, installed or put back: where it is, or null. */
|
|
10
|
+
export declare function installedVs(): InstalledVs | null;
|
|
11
|
+
/** Whether a later boot only puts Visual Studio back (quick enough to wait for) instead of installing it. */
|
|
12
|
+
export declare function visualStudioSaved(req: VsRequest): boolean;
|
|
13
|
+
/** Downloads the bootstrapper now, so the install later starts without waiting (not needed with a saved copy). */
|
|
10
14
|
export declare function prefetchVisualStudio(req: VsRequest): Promise<void>;
|
|
11
|
-
/** Visual Studio as asked for:
|
|
12
|
-
export declare function ensureVisualStudio(env: ToolEnv, req: VsRequest, o
|
|
13
|
-
keepInstallFiles: boolean;
|
|
15
|
+
/** Visual Studio as asked for: put back from the data disk, or installed (and saved) the first time. */
|
|
16
|
+
export declare function ensureVisualStudio(env: ToolEnv, req: VsRequest, o?: {
|
|
14
17
|
lowPriority?: boolean;
|
|
15
|
-
}): Promise<
|
|
16
|
-
path: string;
|
|
17
|
-
version: string;
|
|
18
|
-
}>;
|
|
18
|
+
}): Promise<InstalledVs>;
|
|
@@ -1,31 +1,28 @@
|
|
|
1
1
|
// Visual Studio for the toolchains that need the real one (vs-request.ts says which): Community with the C++ game
|
|
2
2
|
// workload for Unreal (its build tool finds Visual Studio through the Setup API, so the portable MSVC does not do), or
|
|
3
|
-
// the Build Tools' C++ parts
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// (
|
|
3
|
+
// the Build Tools' C++ parts. Installed once with Microsoft's installer (the bootstrapper is kept in tools\ and run
|
|
4
|
+
// only when Microsoft's signature checks out), then saved on the data disk (tools\visualstudio\persist, vs-persist.ts
|
|
5
|
+
// says what goes where). Later boots put the saved copy back in seconds: no download, no install, nothing big on C:.
|
|
6
|
+
// Never installed while sessions run (they would need a restart); putting a saved copy back only adds to the machine.
|
|
7
7
|
import fs from 'node:fs';
|
|
8
8
|
import os from 'node:os';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { z } from 'zod';
|
|
11
|
-
import { spawnAsync } from '../../../shared/download/index.js';
|
|
12
11
|
import { heavy, runInstaller } from '../../runtime/pools.js';
|
|
13
|
-
import { writeJsonAtomic } from '../../../shared/io/json.js';
|
|
14
|
-
import { robocopyFailed } from '../../../shared/io/robocopy-result.js';
|
|
12
|
+
import { readValid, writeJsonAtomic } from '../../../shared/io/json.js';
|
|
15
13
|
import { safeRemove } from '../../../shared/io/safe-remove.js';
|
|
16
|
-
import {
|
|
17
|
-
import { execOutput } from '../../runtime/process.js';
|
|
14
|
+
import { execOutput, runScriptWithJsonAsync } from '../../runtime/process.js';
|
|
18
15
|
import { exists, mkdirp } from '../../runtime/fs.js';
|
|
19
16
|
import { log, warn } from '../../runtime/log.js';
|
|
20
|
-
import { logsDir } from '../../runtime/paths.js';
|
|
21
17
|
import { duringSession } from '../../runtime/project-context.js';
|
|
22
18
|
import { backgroundRunning } from '../../runtime/status.js';
|
|
23
19
|
import { errMsg } from '../../../core/util/errors.js';
|
|
24
20
|
import { SIGNERS, signedInstaller } from '../installers.js';
|
|
25
21
|
import { assertLocalRoom, freeBytes } from '../persisted.js';
|
|
26
22
|
import { tool } from '../tool-env.js';
|
|
27
|
-
import {
|
|
28
|
-
|
|
23
|
+
import { vsExitText } from './vs-request.js';
|
|
24
|
+
import { MARKER_FILE, markerFrom, newestInstance, persistCovers, productFolder, restoreInput, rewriteInstanceState, SaveResultSchema, saveInput, VsPersistMarkerSchema, winPlaces, } from './vs-persist.js';
|
|
25
|
+
export { BUILD_TOOLS_COMPONENTS, UNREAL_BUILD_TOOLS_COMPONENTS, UNREAL_COMPONENTS, visualStudioWanted, } from './vs-request.js';
|
|
29
26
|
const BOOTSTRAPPERS = {
|
|
30
27
|
community: {
|
|
31
28
|
url: 'https://aka.ms/vs/17/release/vs_community.exe',
|
|
@@ -38,187 +35,140 @@ const BOOTSTRAPPERS = {
|
|
|
38
35
|
name: 'Visual Studio Build Tools 2022',
|
|
39
36
|
},
|
|
40
37
|
};
|
|
41
|
-
|
|
38
|
+
const GB = 1024 ** 3;
|
|
39
|
+
/** What each takes on the sandbox's own disk while it is installed (13 GB more must stay free: assertLocalRoom). */
|
|
42
40
|
const INSTALL_BYTES = {
|
|
43
|
-
community: 7 *
|
|
44
|
-
buildtools: 5 *
|
|
41
|
+
community: 7 * GB,
|
|
42
|
+
buildtools: 5 * GB,
|
|
45
43
|
};
|
|
44
|
+
/** Free room on the data disk the saved copy needs (Community with the Windows SDK is about 6.5 GB). */
|
|
45
|
+
const SAVE_ROOM_BYTES = 8 * GB;
|
|
46
46
|
/** Visual Studio installer exit codes that mean installed: 0, and 3010 (a restart would finish it, not needed here). */
|
|
47
47
|
const OK_EXIT_CODES = [0, 3010];
|
|
48
|
-
/** Free room the offline copy needs before it is made (it grows to about 10 GB). */
|
|
49
|
-
const LAYOUT_ROOM_BYTES = 15 * 1024 ** 3;
|
|
50
48
|
const ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$/;
|
|
51
49
|
const VS_DIR = tool('visualstudio');
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
/**
|
|
50
|
+
const PERSIST_DIR = path.join(VS_DIR, 'persist');
|
|
51
|
+
const MARKER = path.join(PERSIST_DIR, MARKER_FILE);
|
|
52
|
+
/** What older versions left in tools\visualstudio: the offline install files, which the installer never finished. */
|
|
53
|
+
const LEFTOVERS = ['layout', 'layout-buildtools', 'tmp'];
|
|
54
|
+
const PLACES = winPlaces(process.env);
|
|
55
|
+
const INSTALLER_DIR = path.join(PLACES.programFilesX86, 'Microsoft Visual Studio', 'Installer');
|
|
56
|
+
const VSWHERE = path.join(INSTALLER_DIR, 'vswhere.exe');
|
|
57
|
+
const VswhereSchema = z.array(z.looseObject({
|
|
58
|
+
instanceId: z.string(),
|
|
59
|
+
installationPath: z.string(),
|
|
60
|
+
installationVersion: z.string().optional(),
|
|
61
|
+
}));
|
|
62
|
+
/** The newest Visual Studio (or Build Tools) with the C++ compiler, installed or put back: where it is, or null. */
|
|
55
63
|
export function installedVs() {
|
|
56
64
|
if (!exists(VSWHERE))
|
|
57
65
|
return null;
|
|
58
|
-
const
|
|
59
|
-
'-latest',
|
|
66
|
+
const out = execOutput(VSWHERE, [
|
|
60
67
|
'-products',
|
|
61
68
|
'*',
|
|
62
69
|
'-requires',
|
|
63
70
|
'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
|
|
64
|
-
'-
|
|
65
|
-
|
|
71
|
+
'-format',
|
|
72
|
+
'json',
|
|
73
|
+
'-utf8',
|
|
66
74
|
]);
|
|
67
|
-
|
|
68
|
-
return at ? { path: at, version: ask('installationVersion') || 'installed' } : null;
|
|
69
|
-
}
|
|
70
|
-
function layoutDir(product) {
|
|
71
|
-
return path.join(VS_DIR, product === 'community' ? 'layout' : 'layout-buildtools');
|
|
72
|
-
}
|
|
73
|
-
/** The layout arguments of the bootstrapper (the same for the data disk and the staging folder). */
|
|
74
|
-
const layoutArgs = (req, folder) => [
|
|
75
|
-
'--layout',
|
|
76
|
-
folder,
|
|
77
|
-
'--lang',
|
|
78
|
-
'en-US',
|
|
79
|
-
'--quiet',
|
|
80
|
-
'--wait',
|
|
81
|
-
...req.components.flatMap((id) => ['--add', id]),
|
|
82
|
-
];
|
|
83
|
-
/** The bootstrapper's newest log (dd_bootstrapper_*.log) in these folders, or null. */
|
|
84
|
-
function newestBootstrapperLog(folders) {
|
|
85
|
-
const logs = folders.flatMap((dir) => {
|
|
86
|
-
try {
|
|
87
|
-
return fs
|
|
88
|
-
.readdirSync(dir)
|
|
89
|
-
.filter((n) => /^dd_bootstrapper_.*\.log$/i.test(n))
|
|
90
|
-
.map((n) => path.join(dir, n));
|
|
91
|
-
}
|
|
92
|
-
catch {
|
|
93
|
-
return []; // The folder is not there: no log in it.
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
const stamp = (f) => {
|
|
97
|
-
try {
|
|
98
|
-
return fs.statSync(f).mtimeMs;
|
|
99
|
-
}
|
|
100
|
-
catch {
|
|
101
|
-
return 0; // Gone since the listing.
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
return logs.sort((x, y) => stamp(y) - stamp(x))[0] ?? null;
|
|
105
|
-
}
|
|
106
|
-
/** Says why the layout failed and keeps the installer's log where the sandbox's logs are. */
|
|
107
|
-
function reportLayoutFailure(code, temps) {
|
|
108
|
-
warn('Visual Studio: saving the install files failed (exit ' + code + '): ' + vsExitText(code));
|
|
109
|
-
const found = newestBootstrapperLog(temps);
|
|
110
|
-
if (!found)
|
|
111
|
-
return;
|
|
112
|
-
const kept = path.join(logsDir, 'vs-layout-' + new Date().toISOString().slice(0, 10) + '.log');
|
|
75
|
+
let found;
|
|
113
76
|
try {
|
|
114
|
-
|
|
115
|
-
fs.copyFileSync(found, kept);
|
|
116
|
-
warn('Visual Studio: the installer log is ' + found + ' (a copy is in ' + kept + ')');
|
|
77
|
+
found = JSON.parse(out || '[]');
|
|
117
78
|
}
|
|
118
|
-
catch
|
|
119
|
-
|
|
79
|
+
catch {
|
|
80
|
+
return null; // vswhere printed something else (it failed): no Visual Studio to use.
|
|
120
81
|
}
|
|
82
|
+
const first = newestInstance(VswhereSchema.safeParse(found).data ?? []);
|
|
83
|
+
return first
|
|
84
|
+
? { path: first.installationPath, version: first.installationVersion ?? 'installed', instanceId: first.instanceId }
|
|
85
|
+
: null;
|
|
121
86
|
}
|
|
122
|
-
/**
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return !robocopyFailed(code);
|
|
127
|
-
}
|
|
128
|
-
/** The offline copy's installer when it holds every component; made or completed when there is room. */
|
|
129
|
-
async function layoutInstaller(req, bootstrapper, lowPriority) {
|
|
130
|
-
const layout = layoutDir(req.product);
|
|
131
|
-
const marker = path.join(layout, '.complete.json');
|
|
132
|
-
const installer = path.join(layout, BOOTSTRAPPERS[req.product].file);
|
|
133
|
-
const has = () => {
|
|
134
|
-
if (!exists(marker) || !exists(installer))
|
|
135
|
-
return false;
|
|
136
|
-
try {
|
|
137
|
-
const done = LayoutSchema.parse(JSON.parse(fs.readFileSync(marker, 'utf8')));
|
|
138
|
-
return req.components.every((id) => done.components.includes(id));
|
|
139
|
-
}
|
|
140
|
-
catch {
|
|
141
|
-
return false;
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
const finish = () => {
|
|
145
|
-
writeJsonAtomic(marker, { components: [...req.components] });
|
|
146
|
-
return installer;
|
|
147
|
-
};
|
|
148
|
-
if (has())
|
|
149
|
-
return installer;
|
|
150
|
-
if (freeBytes(mkdirp(VS_DIR)) < LAYOUT_ROOM_BYTES) {
|
|
151
|
-
warn('Visual Studio: not keeping the install files (less than 15 GB free in ' + VS_DIR + '); downloading this boot');
|
|
87
|
+
/** The saved copy when it is complete and serves this request, else null. */
|
|
88
|
+
function savedCopy(req) {
|
|
89
|
+
const marker = readValid(VsPersistMarkerSchema, MARKER);
|
|
90
|
+
if (!marker || !persistCovers(marker, req))
|
|
152
91
|
return null;
|
|
92
|
+
const product = path.join(PERSIST_DIR, productFolder(marker.product));
|
|
93
|
+
const files = [
|
|
94
|
+
path.join(product, 'VC', 'Auxiliary', 'Build', 'vcvars64.bat'),
|
|
95
|
+
path.join(PERSIST_DIR, 'instance', 'state.json'),
|
|
96
|
+
path.join(PERSIST_DIR, 'Installer', 'vswhere.exe'),
|
|
97
|
+
path.join(PERSIST_DIR, 'Setup', 'x64', 'Microsoft.VisualStudio.Setup.Configuration.Native.dll'),
|
|
98
|
+
];
|
|
99
|
+
return files.every(exists) ? marker : null;
|
|
100
|
+
}
|
|
101
|
+
/** Whether a later boot only puts Visual Studio back (quick enough to wait for) instead of installing it. */
|
|
102
|
+
export function visualStudioSaved(req) {
|
|
103
|
+
return savedCopy(req) !== null;
|
|
104
|
+
}
|
|
105
|
+
/** Puts the saved copy back: its fixed-path folders, the instance, the registry; then vswhere must find it. */
|
|
106
|
+
async function restoreSaved(marker) {
|
|
107
|
+
const started = Date.now();
|
|
108
|
+
log('Visual Studio: putting back the copy saved in ' + PERSIST_DIR + ' ...');
|
|
109
|
+
const plan = restoreInput(marker, PERSIST_DIR, PLACES);
|
|
110
|
+
await heavy.run(() => runScriptWithJsonAsync('restore-visual-studio.ps1', 'PlanFile', plan));
|
|
111
|
+
const vs = installedVs();
|
|
112
|
+
if (!vs)
|
|
113
|
+
throw new Error('vswhere does not find the Visual Studio that was put back');
|
|
114
|
+
log('Visual Studio ' + vs.version + ' is back in ' + Math.round((Date.now() - started) / 1000) + ' s (' + vs.path + ')');
|
|
115
|
+
return vs;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Saves a fresh install on the data disk for later boots (once). The marker goes last, after the instance's state is
|
|
119
|
+
* moved to the saved folder; a failure leaves no marker and only costs the next boot another install.
|
|
120
|
+
*/
|
|
121
|
+
async function saveInstall(req, vs, lowPriority) {
|
|
122
|
+
const free = freeBytes(mkdirp(PERSIST_DIR));
|
|
123
|
+
if (free < SAVE_ROOM_BYTES) {
|
|
124
|
+
warn('Visual Studio: not saved for later boots (' +
|
|
125
|
+
(free / GB).toFixed(1) +
|
|
126
|
+
' GB free in ' +
|
|
127
|
+
PERSIST_DIR +
|
|
128
|
+
', it needs 8 GB); the next boot installs it again');
|
|
129
|
+
return;
|
|
153
130
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
'
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
131
|
+
fs.rmSync(MARKER, { force: true });
|
|
132
|
+
log('Saving Visual Studio in ' + PERSIST_DIR + ' for later boots (~6.5 GB, once) ...');
|
|
133
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'sbx-vs-'));
|
|
134
|
+
const resultFile = path.join(dir, 'result.json');
|
|
135
|
+
try {
|
|
136
|
+
const plan = saveInput({ product: req.product, installationPath: vs.path, instanceId: vs.instanceId }, PERSIST_DIR, PLACES, resultFile);
|
|
137
|
+
await heavy.run(() => runScriptWithJsonAsync('save-visual-studio.ps1', 'PlanFile', plan, lowPriority ? ['-LowPriority'] : []));
|
|
138
|
+
const result = readValid(SaveResultSchema, resultFile);
|
|
139
|
+
if (!result)
|
|
140
|
+
throw new Error('the save script left no result');
|
|
141
|
+
const state = path.join(PERSIST_DIR, 'instance', 'state.json');
|
|
142
|
+
const saved = path.join(PERSIST_DIR, productFolder(req.product));
|
|
143
|
+
fs.writeFileSync(state, rewriteInstanceState(fs.readFileSync(state, 'utf8'), vs.path, saved), 'utf8');
|
|
144
|
+
// One product is kept: a Build Tools copy goes when Community replaces it (and the other way round).
|
|
145
|
+
safeRemove(path.join(PERSIST_DIR, productFolder(req.product === 'community' ? 'buildtools' : 'community')), VS_DIR);
|
|
146
|
+
writeJsonAtomic(MARKER, markerFrom(req, { installationPath: vs.path, instanceId: vs.instanceId, version: vs.version }, result, new Date().toISOString()));
|
|
147
|
+
log('Visual Studio is saved: later boots put it back in seconds');
|
|
167
148
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const stagedCode = await runInstaller(bootstrapper, layoutArgs(req, staging), { lowPriority });
|
|
171
|
-
const staged = path.join(staging, BOOTSTRAPPERS[req.product].file);
|
|
172
|
-
let kept = false;
|
|
173
|
-
if (OK_EXIT_CODES.includes(stagedCode) && exists(staged)) {
|
|
174
|
-
kept = await copyLayout(staging, layout);
|
|
175
|
-
if (!kept)
|
|
176
|
-
warn('Visual Studio: copying the install files to ' + layout + ' failed');
|
|
149
|
+
catch (e) {
|
|
150
|
+
warn('Visual Studio: saving it for later boots failed (' + errMsg(e) + '); the next boot installs it again');
|
|
177
151
|
}
|
|
178
|
-
|
|
179
|
-
|
|
152
|
+
finally {
|
|
153
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
180
154
|
}
|
|
181
|
-
safeRemove(staging, drive);
|
|
182
|
-
return kept && exists(installer) ? finish() : null;
|
|
183
155
|
}
|
|
184
|
-
/** The bootstrapper, downloaded (net pool) and checked; kept in tools\ for later
|
|
156
|
+
/** The bootstrapper, downloaded (net pool) and checked; kept in tools\ for later installs. */
|
|
185
157
|
function bootstrapperFor(req) {
|
|
186
158
|
const boot = BOOTSTRAPPERS[req.product];
|
|
187
159
|
return signedInstaller(boot.url, path.join(VS_DIR, boot.file), 'the ' + boot.name + ' installer', SIGNERS.microsoft);
|
|
188
160
|
}
|
|
189
|
-
/** Downloads the bootstrapper now, so the install later starts without waiting
|
|
161
|
+
/** Downloads the bootstrapper now, so the install later starts without waiting (not needed with a saved copy). */
|
|
190
162
|
export async function prefetchVisualStudio(req) {
|
|
191
|
-
if (!req.accepted || installedVs())
|
|
163
|
+
if (!req.accepted || installedVs() || savedCopy(req))
|
|
192
164
|
return;
|
|
193
165
|
await bootstrapperFor(req);
|
|
194
166
|
}
|
|
195
|
-
async function install(req,
|
|
167
|
+
async function install(req, lowPriority) {
|
|
196
168
|
const boot = BOOTSTRAPPERS[req.product];
|
|
197
169
|
const bootstrapper = await bootstrapperFor(req);
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
try {
|
|
201
|
-
installer = await layoutInstaller(req, bootstrapper, lowPriority);
|
|
202
|
-
}
|
|
203
|
-
catch (e) {
|
|
204
|
-
warn('Visual Studio: the offline copy failed (' + errMsg(e) + '); downloading this boot');
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
const run = (from, offline) => runInstaller(from, [
|
|
208
|
-
'--quiet',
|
|
209
|
-
'--wait',
|
|
210
|
-
'--norestart',
|
|
211
|
-
'--nocache',
|
|
212
|
-
...(offline ? ['--noWeb'] : []),
|
|
213
|
-
...req.components.flatMap((id) => ['--add', id]),
|
|
214
|
-
], { lowPriority });
|
|
215
|
-
log('Installing ' + boot.name + (installer ? ' from the saved install files' : ' (downloads ~5-8 GB)') + ' ...');
|
|
216
|
-
let code = await run(installer ?? bootstrapper, installer !== null);
|
|
217
|
-
if (installer && (!OK_EXIT_CODES.includes(code) || !installedVs())) {
|
|
218
|
-
// The saved copy is on the shared data disk: if installing from it fails, this boot downloads instead.
|
|
219
|
-
warn('Visual Studio: installing from the saved install files failed (' + vsExitText(code) + '); downloading');
|
|
220
|
-
code = await run(bootstrapper, false);
|
|
221
|
-
}
|
|
170
|
+
log('Installing ' + boot.name + ' (downloads ~5-8 GB, once: it is saved on the data disk afterwards) ...');
|
|
171
|
+
const code = await runInstaller(bootstrapper, ['--quiet', '--wait', '--norestart', '--nocache', ...req.components.flatMap((id) => ['--add', id])], { lowPriority });
|
|
222
172
|
if (!OK_EXIT_CODES.includes(code) || !installedVs())
|
|
223
173
|
throw new Error('the Visual Studio installer failed (exit ' +
|
|
224
174
|
code +
|
|
@@ -227,8 +177,21 @@ async function install(req, keepInstallFiles, lowPriority) {
|
|
|
227
177
|
'); its logs are the dd_*.log files in ' +
|
|
228
178
|
os.tmpdir());
|
|
229
179
|
}
|
|
230
|
-
/**
|
|
231
|
-
|
|
180
|
+
/** Removes what older versions left in tools\visualstudio (best effort). */
|
|
181
|
+
function removeLeftovers() {
|
|
182
|
+
for (const name of LEFTOVERS) {
|
|
183
|
+
const dir = path.join(VS_DIR, name);
|
|
184
|
+
try {
|
|
185
|
+
if (exists(dir))
|
|
186
|
+
safeRemove(dir, VS_DIR);
|
|
187
|
+
}
|
|
188
|
+
catch (e) {
|
|
189
|
+
warn('Visual Studio: could not remove ' + dir + ' (' + errMsg(e) + ')');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/** Visual Studio as asked for: put back from the data disk, or installed (and saved) the first time. */
|
|
194
|
+
export async function ensureVisualStudio(env, req, o = {}) {
|
|
232
195
|
if (!req.accepted)
|
|
233
196
|
throw new Error('Visual Studio (for ' +
|
|
234
197
|
req.for.join(', ') +
|
|
@@ -238,24 +201,32 @@ export async function ensureVisualStudio(env, req, o) {
|
|
|
238
201
|
const bad = req.components.find((id) => !ID.test(id));
|
|
239
202
|
if (bad)
|
|
240
203
|
throw new Error('"' + bad + '" is not a Visual Studio component ID');
|
|
204
|
+
removeLeftovers();
|
|
205
|
+
const name = BOOTSTRAPPERS[req.product].name;
|
|
206
|
+
const done = (vs, how) => {
|
|
207
|
+
env.append.push(INSTALLER_DIR);
|
|
208
|
+
env.summary.push('visual studio: ' + name + ' ' + vs.version + ' at ' + vs.path + ' (' + how + '; for ' + req.for.join(', ') + ')');
|
|
209
|
+
return vs;
|
|
210
|
+
};
|
|
241
211
|
const have = installedVs();
|
|
242
212
|
if (have)
|
|
243
|
-
return have;
|
|
213
|
+
return done(have, 'installed');
|
|
214
|
+
const saved = savedCopy(req);
|
|
215
|
+
if (saved) {
|
|
216
|
+
try {
|
|
217
|
+
return done(await restoreSaved(saved), 'kept on the data disk');
|
|
218
|
+
}
|
|
219
|
+
catch (e) {
|
|
220
|
+
warn('Visual Studio: putting the saved copy back failed (' + errMsg(e) + '); installing it again');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
244
223
|
if (duringSession())
|
|
245
224
|
throw new Error(backgroundRunning('Visual Studio')
|
|
246
225
|
? 'Visual Studio is still installing in the background; it is ready in a few minutes'
|
|
247
226
|
: 'Visual Studio is installed when the sandbox starts: restart it (sandbox restart) to get it');
|
|
248
|
-
assertLocalRoom(INSTALL_BYTES[req.product],
|
|
249
|
-
await install(req, o.
|
|
227
|
+
assertLocalRoom(INSTALL_BYTES[req.product], name);
|
|
228
|
+
await install(req, o.lowPriority === true);
|
|
250
229
|
const vs = installedVs();
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
' ' +
|
|
254
|
-
vs.version +
|
|
255
|
-
' at ' +
|
|
256
|
-
vs.path +
|
|
257
|
-
' (for ' +
|
|
258
|
-
req.for.join(', ') +
|
|
259
|
-
')');
|
|
260
|
-
return vs;
|
|
230
|
+
await saveInstall(req, vs, o.lowPriority === true);
|
|
231
|
+
return done(vs, 'installed, saved for later boots');
|
|
261
232
|
}
|