wyvrnpm 2.10.2 → 2.12.2
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/README.md +1914 -1860
- package/bin/{wyvrn.js → wyvrnpm.js} +66 -0
- package/cmake/cpp.cmake +9 -9
- package/cmake/functions.cmake +224 -224
- package/cmake/macros.cmake +284 -284
- package/package.json +3 -2
- package/src/auth.js +66 -66
- package/src/binary-dir.js +95 -0
- package/src/bootstrap/cookbook.js +196 -196
- package/src/bootstrap/detect.js +150 -150
- package/src/bootstrap/index.js +220 -220
- package/src/bootstrap/version.js +72 -72
- package/src/build/cache.js +344 -344
- package/src/build/clone.js +170 -170
- package/src/build/cmake.js +342 -342
- package/src/build/index.js +299 -297
- package/src/build/msvc-env.js +260 -260
- package/src/build/recipe.js +188 -188
- package/src/commands/add.js +141 -141
- package/src/commands/bootstrap.js +96 -96
- package/src/commands/build.js +482 -452
- package/src/commands/cache.js +189 -189
- package/src/commands/clean.js +80 -80
- package/src/commands/configure.js +92 -92
- package/src/commands/init.js +70 -70
- package/src/commands/install-skill.js +115 -115
- package/src/commands/install.js +730 -674
- package/src/commands/link.js +320 -320
- package/src/commands/profile.js +237 -237
- package/src/commands/publish.js +584 -555
- package/src/commands/show.js +252 -252
- package/src/commands/version.js +187 -0
- package/src/compat.js +273 -273
- package/src/conf/index.js +415 -391
- package/src/conf/namespaces.js +94 -94
- package/src/context.js +230 -230
- package/src/http-fetch.js +53 -53
- package/src/ignore.js +118 -118
- package/src/logger.js +122 -122
- package/src/options.js +303 -303
- package/src/settings-overrides.js +152 -152
- package/src/toolchain/deps.js +164 -164
- package/src/toolchain/index.js +212 -212
- package/src/toolchain/presets.js +356 -340
- package/src/toolchain/template.cmake +77 -77
- package/src/upload-built.js +265 -265
- package/src/version-range.js +301 -301
- package/src/zip-safe.js +52 -52
- package/src/zip-stream.js +126 -0
package/src/build/msvc-env.js
CHANGED
|
@@ -1,260 +1,260 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { spawn } = require('child_process');
|
|
6
|
-
|
|
7
|
-
const log = require('../logger');
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Auto-activate the MSVC build environment (the moral equivalent of a
|
|
11
|
-
* "Developer PowerShell for VS 2022") when a source-build on Windows picks
|
|
12
|
-
* a non-VS generator and `cl.exe` isn't on PATH.
|
|
13
|
-
*
|
|
14
|
-
* Without this, Ninja/Make/etc. fail at CMake's compiler-detection step
|
|
15
|
-
* because MSVC's `cl.exe` lives under the VS install tree and is only put
|
|
16
|
-
* on PATH by `vcvarsall.bat`. VS generators sidestep this because MSBuild
|
|
17
|
-
* locates MSVC via VS's own toolset resolution.
|
|
18
|
-
*
|
|
19
|
-
* Strategy:
|
|
20
|
-
* 1. Check if cl.exe is already on PATH → return null (nothing to do).
|
|
21
|
-
* 2. Locate the VS installation via `vswhere.exe -latest`.
|
|
22
|
-
* 3. Run `vcvarsall.bat <arch>` in `cmd /s /c` and capture `set` output.
|
|
23
|
-
* 4. Parse KEY=VALUE lines, return them as a shallow-merged env object
|
|
24
|
-
* suitable for `child_process.spawn({ env })`.
|
|
25
|
-
*
|
|
26
|
-
* Returns `null` when nothing needs to be done OR when VS isn't found —
|
|
27
|
-
* callers fall back to the ambient environment and CMake produces its own
|
|
28
|
-
* error if that turns out to be missing a compiler.
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
const VSWHERE_DEFAULT = path.join(
|
|
32
|
-
process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)',
|
|
33
|
-
'Microsoft Visual Studio', 'Installer', 'vswhere.exe',
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
function tryCapture(cmd, args, opts = {}) {
|
|
37
|
-
return new Promise((resolve) => {
|
|
38
|
-
const proc = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'ignore'], ...opts });
|
|
39
|
-
let out = '';
|
|
40
|
-
proc.stdout.on('data', (c) => { out += c.toString(); });
|
|
41
|
-
proc.on('error', () => resolve(null));
|
|
42
|
-
proc.on('close', (code) => resolve(code === 0 ? out : null));
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* @returns {Promise<boolean>}
|
|
48
|
-
*/
|
|
49
|
-
async function isClOnPath() {
|
|
50
|
-
if (process.platform !== 'win32') return false;
|
|
51
|
-
// `where cl` is the Windows-native PATH probe.
|
|
52
|
-
return new Promise((resolve) => {
|
|
53
|
-
const proc = spawn('where', ['cl'], { stdio: 'ignore' });
|
|
54
|
-
proc.on('error', () => resolve(false));
|
|
55
|
-
proc.on('close', (code) => resolve(code === 0));
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Return the latest VS installation path via vswhere, or null if not found.
|
|
61
|
-
* @returns {Promise<string|null>}
|
|
62
|
-
*/
|
|
63
|
-
async function findVsInstall() {
|
|
64
|
-
if (!fs.existsSync(VSWHERE_DEFAULT)) return null;
|
|
65
|
-
const out = await tryCapture(VSWHERE_DEFAULT, [
|
|
66
|
-
'-latest',
|
|
67
|
-
'-requires', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
|
|
68
|
-
'-property', 'installationPath',
|
|
69
|
-
]);
|
|
70
|
-
return out ? out.trim() : null;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function findVcvarsall(vsInstallPath) {
|
|
74
|
-
const p = path.join(vsInstallPath, 'VC', 'Auxiliary', 'Build', 'vcvarsall.bat');
|
|
75
|
-
return fs.existsSync(p) ? p : null;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* The complete set of arch arguments `vcvarsall.bat` accepts AND that we
|
|
80
|
-
* are willing to splice into a shell command. Explicit allow-list — any
|
|
81
|
-
* value outside this set is rejected (EVALUATION.md S5).
|
|
82
|
-
*/
|
|
83
|
-
const VCVARSALL_ARCH_ALLOWLIST = new Set(['x64', 'x86', 'arm64', 'arm']);
|
|
84
|
-
|
|
85
|
-
const ARCH_MAP = {
|
|
86
|
-
x86_64: 'x64',
|
|
87
|
-
x86: 'x86',
|
|
88
|
-
armv8: 'arm64',
|
|
89
|
-
armv7: 'arm',
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Map a wyvrnpm profile arch to the argument `vcvarsall.bat` expects.
|
|
94
|
-
* Throws on an unknown profile arch — silently defaulting to `x64` would
|
|
95
|
-
* (a) produce a build for the wrong target, and (b) hand any shell-unsafe
|
|
96
|
-
* input to cmd.exe through captureVcvarsEnv's template.
|
|
97
|
-
*/
|
|
98
|
-
function mapArch(profileArch) {
|
|
99
|
-
const arg = ARCH_MAP[profileArch];
|
|
100
|
-
if (!arg || !VCVARSALL_ARCH_ALLOWLIST.has(arg)) {
|
|
101
|
-
throw new Error(
|
|
102
|
-
`unsupported MSVC profile arch ${JSON.stringify(profileArch)}; ` +
|
|
103
|
-
`expected one of ${Object.keys(ARCH_MAP).join(', ')}`,
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
return arg;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Capture the environment produced by running vcvarsall.bat.
|
|
111
|
-
*
|
|
112
|
-
* Strategy: shell out to `cmd`, run `vcvarsall.bat <arch>`, then print a
|
|
113
|
-
* marker followed by the post-vcvars environment (`set` dumps KEY=VALUE
|
|
114
|
-
* lines for every env var). Everything before the marker is vcvarsall's
|
|
115
|
-
* own output; everything after is the env we want.
|
|
116
|
-
*
|
|
117
|
-
* Uses `shell: true` so Node hands the full command line to `cmd.exe`
|
|
118
|
-
* verbatim — avoids the usual quoting pitfalls when the VS install path
|
|
119
|
-
* contains spaces.
|
|
120
|
-
*
|
|
121
|
-
* @param {string} vcvarsallPath
|
|
122
|
-
* @param {string} archArg
|
|
123
|
-
* @returns {Promise<object|null>}
|
|
124
|
-
*/
|
|
125
|
-
async function captureVcvarsEnv(vcvarsallPath, archArg) {
|
|
126
|
-
// Defense-in-depth at the shell boundary (EVALUATION.md S5). mapArch
|
|
127
|
-
// already restricts archArg to the allow-list; re-assert here so this
|
|
128
|
-
// function is safe to call directly (e.g. from a future caller that
|
|
129
|
-
// skips mapArch). Likewise refuse a vcvarsall path containing a
|
|
130
|
-
// double-quote — it would break out of the quoted command template.
|
|
131
|
-
if (!VCVARSALL_ARCH_ALLOWLIST.has(archArg)) {
|
|
132
|
-
throw new Error(`refusing to spawn cmd.exe with unsafe arch arg ${JSON.stringify(archArg)}`);
|
|
133
|
-
}
|
|
134
|
-
if (vcvarsallPath.includes('"')) {
|
|
135
|
-
throw new Error(`refusing to spawn cmd.exe with quote in vcvarsall path ${JSON.stringify(vcvarsallPath)}`);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const MARKER = '___WYVRN_ENV_MARKER___';
|
|
139
|
-
// `call` is required so the batch file returns control instead of
|
|
140
|
-
// `exit`-ing the cmd shell; otherwise the `echo` + `set` after it
|
|
141
|
-
// never run.
|
|
142
|
-
const cmdLine =
|
|
143
|
-
`call "${vcvarsallPath}" ${archArg} ` +
|
|
144
|
-
`&& echo ${MARKER} ` +
|
|
145
|
-
`&& set`;
|
|
146
|
-
|
|
147
|
-
return new Promise((resolve) => {
|
|
148
|
-
const proc = spawn(cmdLine, {
|
|
149
|
-
shell: true,
|
|
150
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
151
|
-
});
|
|
152
|
-
let stdout = '';
|
|
153
|
-
let stderr = '';
|
|
154
|
-
proc.stdout.on('data', (c) => { stdout += c.toString(); });
|
|
155
|
-
proc.stderr.on('data', (c) => { stderr += c.toString(); });
|
|
156
|
-
proc.on('error', (err) => {
|
|
157
|
-
log.warn(` vcvarsall spawn error: ${err.message}`);
|
|
158
|
-
resolve(null);
|
|
159
|
-
});
|
|
160
|
-
proc.on('close', (code) => {
|
|
161
|
-
if (code !== 0) {
|
|
162
|
-
const tail = (stderr || stdout).trim().split(/\r?\n/).slice(-5).join(' | ');
|
|
163
|
-
log.warn(` vcvarsall exited ${code}; last lines: ${tail}`);
|
|
164
|
-
resolve(null);
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
const markerIdx = stdout.indexOf(MARKER);
|
|
168
|
-
if (markerIdx === -1) {
|
|
169
|
-
const tail = stdout.trim().split(/\r?\n/).slice(-5).join(' | ');
|
|
170
|
-
log.warn(` vcvarsall marker not found; last stdout lines: ${tail}`);
|
|
171
|
-
resolve(null);
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
// Everything after the marker line is the env dump.
|
|
175
|
-
const envDump = stdout.slice(markerIdx + MARKER.length);
|
|
176
|
-
const env = { ...process.env };
|
|
177
|
-
let matched = 0;
|
|
178
|
-
for (const line of envDump.split(/\r?\n/)) {
|
|
179
|
-
const m = line.match(/^([A-Za-z_][A-Za-z0-9_()]*)=(.*)$/);
|
|
180
|
-
if (m) { env[m[1]] = m[2]; matched += 1; }
|
|
181
|
-
}
|
|
182
|
-
if (matched < 5 || !env.VSINSTALLDIR) {
|
|
183
|
-
log.warn(
|
|
184
|
-
` vcvarsall env dump looks incomplete ` +
|
|
185
|
-
`(${matched} vars, VSINSTALLDIR=${env.VSINSTALLDIR ? 'set' : 'MISSING'})`,
|
|
186
|
-
);
|
|
187
|
-
resolve(null);
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
resolve(env);
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Compute a build-environment override for the given profile, or return
|
|
197
|
-
* null to indicate "use the ambient environment". Callers pass the result
|
|
198
|
-
* as `spawn(..., { env })`.
|
|
199
|
-
*
|
|
200
|
-
* Only activates when:
|
|
201
|
-
* - Running on Windows
|
|
202
|
-
* - Profile says compiler=msvc
|
|
203
|
-
* - `cl.exe` is NOT already on PATH (so we're outside a Dev shell)
|
|
204
|
-
* - `skipForGenerator` does not start with "Visual Studio" (VS generators
|
|
205
|
-
* don't need this — MSBuild resolves MSVC itself)
|
|
206
|
-
*
|
|
207
|
-
* @param {{
|
|
208
|
-
* profile: { compiler?: string, arch?: string },
|
|
209
|
-
* skipForGenerator?: string|null,
|
|
210
|
-
* }} args
|
|
211
|
-
* @returns {Promise<object|null>}
|
|
212
|
-
*/
|
|
213
|
-
async function loadMsvcEnv({ profile, skipForGenerator }) {
|
|
214
|
-
if (process.platform !== 'win32') return null;
|
|
215
|
-
if (!profile || profile.compiler !== 'msvc') return null;
|
|
216
|
-
if (skipForGenerator && skipForGenerator.startsWith('Visual Studio')) return null;
|
|
217
|
-
if (await isClOnPath()) return null;
|
|
218
|
-
|
|
219
|
-
const vsPath = await findVsInstall();
|
|
220
|
-
if (!vsPath) {
|
|
221
|
-
log.warn(' MSVC profile requested but vswhere/VS not found — CMake will error if cl.exe is missing');
|
|
222
|
-
return null;
|
|
223
|
-
}
|
|
224
|
-
const vcvarsall = findVcvarsall(vsPath);
|
|
225
|
-
if (!vcvarsall) {
|
|
226
|
-
log.warn(` MSVC profile requested but vcvarsall.bat not found under ${vsPath}`);
|
|
227
|
-
return null;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
let archArg;
|
|
231
|
-
try {
|
|
232
|
-
archArg = mapArch(profile.arch);
|
|
233
|
-
} catch (err) {
|
|
234
|
-
// Unknown arch for an MSVC profile is genuinely a configuration bug
|
|
235
|
-
// (MSVC only ships toolsets for x64/x86/arm64/arm). Surface clearly
|
|
236
|
-
// and fall through to the ambient env — CMake will then error with
|
|
237
|
-
// a usable compiler-detection message instead of us silently
|
|
238
|
-
// cross-targeting x64.
|
|
239
|
-
log.warn(` ${err.message}`);
|
|
240
|
-
return null;
|
|
241
|
-
}
|
|
242
|
-
log.info(` activating MSVC build env: vcvarsall.bat ${archArg} (${vsPath})`);
|
|
243
|
-
const env = await captureVcvarsEnv(vcvarsall, archArg);
|
|
244
|
-
if (!env) {
|
|
245
|
-
log.warn(` vcvarsall.bat ${archArg} failed — CMake will error if cl.exe is missing`);
|
|
246
|
-
return null;
|
|
247
|
-
}
|
|
248
|
-
return env;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
module.exports = {
|
|
252
|
-
loadMsvcEnv,
|
|
253
|
-
// Exported for tests
|
|
254
|
-
isClOnPath,
|
|
255
|
-
findVsInstall,
|
|
256
|
-
findVcvarsall,
|
|
257
|
-
mapArch,
|
|
258
|
-
captureVcvarsEnv,
|
|
259
|
-
VCVARSALL_ARCH_ALLOWLIST,
|
|
260
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { spawn } = require('child_process');
|
|
6
|
+
|
|
7
|
+
const log = require('../logger');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Auto-activate the MSVC build environment (the moral equivalent of a
|
|
11
|
+
* "Developer PowerShell for VS 2022") when a source-build on Windows picks
|
|
12
|
+
* a non-VS generator and `cl.exe` isn't on PATH.
|
|
13
|
+
*
|
|
14
|
+
* Without this, Ninja/Make/etc. fail at CMake's compiler-detection step
|
|
15
|
+
* because MSVC's `cl.exe` lives under the VS install tree and is only put
|
|
16
|
+
* on PATH by `vcvarsall.bat`. VS generators sidestep this because MSBuild
|
|
17
|
+
* locates MSVC via VS's own toolset resolution.
|
|
18
|
+
*
|
|
19
|
+
* Strategy:
|
|
20
|
+
* 1. Check if cl.exe is already on PATH → return null (nothing to do).
|
|
21
|
+
* 2. Locate the VS installation via `vswhere.exe -latest`.
|
|
22
|
+
* 3. Run `vcvarsall.bat <arch>` in `cmd /s /c` and capture `set` output.
|
|
23
|
+
* 4. Parse KEY=VALUE lines, return them as a shallow-merged env object
|
|
24
|
+
* suitable for `child_process.spawn({ env })`.
|
|
25
|
+
*
|
|
26
|
+
* Returns `null` when nothing needs to be done OR when VS isn't found —
|
|
27
|
+
* callers fall back to the ambient environment and CMake produces its own
|
|
28
|
+
* error if that turns out to be missing a compiler.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
const VSWHERE_DEFAULT = path.join(
|
|
32
|
+
process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)',
|
|
33
|
+
'Microsoft Visual Studio', 'Installer', 'vswhere.exe',
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
function tryCapture(cmd, args, opts = {}) {
|
|
37
|
+
return new Promise((resolve) => {
|
|
38
|
+
const proc = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'ignore'], ...opts });
|
|
39
|
+
let out = '';
|
|
40
|
+
proc.stdout.on('data', (c) => { out += c.toString(); });
|
|
41
|
+
proc.on('error', () => resolve(null));
|
|
42
|
+
proc.on('close', (code) => resolve(code === 0 ? out : null));
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @returns {Promise<boolean>}
|
|
48
|
+
*/
|
|
49
|
+
async function isClOnPath() {
|
|
50
|
+
if (process.platform !== 'win32') return false;
|
|
51
|
+
// `where cl` is the Windows-native PATH probe.
|
|
52
|
+
return new Promise((resolve) => {
|
|
53
|
+
const proc = spawn('where', ['cl'], { stdio: 'ignore' });
|
|
54
|
+
proc.on('error', () => resolve(false));
|
|
55
|
+
proc.on('close', (code) => resolve(code === 0));
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Return the latest VS installation path via vswhere, or null if not found.
|
|
61
|
+
* @returns {Promise<string|null>}
|
|
62
|
+
*/
|
|
63
|
+
async function findVsInstall() {
|
|
64
|
+
if (!fs.existsSync(VSWHERE_DEFAULT)) return null;
|
|
65
|
+
const out = await tryCapture(VSWHERE_DEFAULT, [
|
|
66
|
+
'-latest',
|
|
67
|
+
'-requires', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
|
|
68
|
+
'-property', 'installationPath',
|
|
69
|
+
]);
|
|
70
|
+
return out ? out.trim() : null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function findVcvarsall(vsInstallPath) {
|
|
74
|
+
const p = path.join(vsInstallPath, 'VC', 'Auxiliary', 'Build', 'vcvarsall.bat');
|
|
75
|
+
return fs.existsSync(p) ? p : null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The complete set of arch arguments `vcvarsall.bat` accepts AND that we
|
|
80
|
+
* are willing to splice into a shell command. Explicit allow-list — any
|
|
81
|
+
* value outside this set is rejected (EVALUATION.md S5).
|
|
82
|
+
*/
|
|
83
|
+
const VCVARSALL_ARCH_ALLOWLIST = new Set(['x64', 'x86', 'arm64', 'arm']);
|
|
84
|
+
|
|
85
|
+
const ARCH_MAP = {
|
|
86
|
+
x86_64: 'x64',
|
|
87
|
+
x86: 'x86',
|
|
88
|
+
armv8: 'arm64',
|
|
89
|
+
armv7: 'arm',
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Map a wyvrnpm profile arch to the argument `vcvarsall.bat` expects.
|
|
94
|
+
* Throws on an unknown profile arch — silently defaulting to `x64` would
|
|
95
|
+
* (a) produce a build for the wrong target, and (b) hand any shell-unsafe
|
|
96
|
+
* input to cmd.exe through captureVcvarsEnv's template.
|
|
97
|
+
*/
|
|
98
|
+
function mapArch(profileArch) {
|
|
99
|
+
const arg = ARCH_MAP[profileArch];
|
|
100
|
+
if (!arg || !VCVARSALL_ARCH_ALLOWLIST.has(arg)) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`unsupported MSVC profile arch ${JSON.stringify(profileArch)}; ` +
|
|
103
|
+
`expected one of ${Object.keys(ARCH_MAP).join(', ')}`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
return arg;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Capture the environment produced by running vcvarsall.bat.
|
|
111
|
+
*
|
|
112
|
+
* Strategy: shell out to `cmd`, run `vcvarsall.bat <arch>`, then print a
|
|
113
|
+
* marker followed by the post-vcvars environment (`set` dumps KEY=VALUE
|
|
114
|
+
* lines for every env var). Everything before the marker is vcvarsall's
|
|
115
|
+
* own output; everything after is the env we want.
|
|
116
|
+
*
|
|
117
|
+
* Uses `shell: true` so Node hands the full command line to `cmd.exe`
|
|
118
|
+
* verbatim — avoids the usual quoting pitfalls when the VS install path
|
|
119
|
+
* contains spaces.
|
|
120
|
+
*
|
|
121
|
+
* @param {string} vcvarsallPath
|
|
122
|
+
* @param {string} archArg
|
|
123
|
+
* @returns {Promise<object|null>}
|
|
124
|
+
*/
|
|
125
|
+
async function captureVcvarsEnv(vcvarsallPath, archArg) {
|
|
126
|
+
// Defense-in-depth at the shell boundary (EVALUATION.md S5). mapArch
|
|
127
|
+
// already restricts archArg to the allow-list; re-assert here so this
|
|
128
|
+
// function is safe to call directly (e.g. from a future caller that
|
|
129
|
+
// skips mapArch). Likewise refuse a vcvarsall path containing a
|
|
130
|
+
// double-quote — it would break out of the quoted command template.
|
|
131
|
+
if (!VCVARSALL_ARCH_ALLOWLIST.has(archArg)) {
|
|
132
|
+
throw new Error(`refusing to spawn cmd.exe with unsafe arch arg ${JSON.stringify(archArg)}`);
|
|
133
|
+
}
|
|
134
|
+
if (vcvarsallPath.includes('"')) {
|
|
135
|
+
throw new Error(`refusing to spawn cmd.exe with quote in vcvarsall path ${JSON.stringify(vcvarsallPath)}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const MARKER = '___WYVRN_ENV_MARKER___';
|
|
139
|
+
// `call` is required so the batch file returns control instead of
|
|
140
|
+
// `exit`-ing the cmd shell; otherwise the `echo` + `set` after it
|
|
141
|
+
// never run.
|
|
142
|
+
const cmdLine =
|
|
143
|
+
`call "${vcvarsallPath}" ${archArg} ` +
|
|
144
|
+
`&& echo ${MARKER} ` +
|
|
145
|
+
`&& set`;
|
|
146
|
+
|
|
147
|
+
return new Promise((resolve) => {
|
|
148
|
+
const proc = spawn(cmdLine, {
|
|
149
|
+
shell: true,
|
|
150
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
151
|
+
});
|
|
152
|
+
let stdout = '';
|
|
153
|
+
let stderr = '';
|
|
154
|
+
proc.stdout.on('data', (c) => { stdout += c.toString(); });
|
|
155
|
+
proc.stderr.on('data', (c) => { stderr += c.toString(); });
|
|
156
|
+
proc.on('error', (err) => {
|
|
157
|
+
log.warn(` vcvarsall spawn error: ${err.message}`);
|
|
158
|
+
resolve(null);
|
|
159
|
+
});
|
|
160
|
+
proc.on('close', (code) => {
|
|
161
|
+
if (code !== 0) {
|
|
162
|
+
const tail = (stderr || stdout).trim().split(/\r?\n/).slice(-5).join(' | ');
|
|
163
|
+
log.warn(` vcvarsall exited ${code}; last lines: ${tail}`);
|
|
164
|
+
resolve(null);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const markerIdx = stdout.indexOf(MARKER);
|
|
168
|
+
if (markerIdx === -1) {
|
|
169
|
+
const tail = stdout.trim().split(/\r?\n/).slice(-5).join(' | ');
|
|
170
|
+
log.warn(` vcvarsall marker not found; last stdout lines: ${tail}`);
|
|
171
|
+
resolve(null);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
// Everything after the marker line is the env dump.
|
|
175
|
+
const envDump = stdout.slice(markerIdx + MARKER.length);
|
|
176
|
+
const env = { ...process.env };
|
|
177
|
+
let matched = 0;
|
|
178
|
+
for (const line of envDump.split(/\r?\n/)) {
|
|
179
|
+
const m = line.match(/^([A-Za-z_][A-Za-z0-9_()]*)=(.*)$/);
|
|
180
|
+
if (m) { env[m[1]] = m[2]; matched += 1; }
|
|
181
|
+
}
|
|
182
|
+
if (matched < 5 || !env.VSINSTALLDIR) {
|
|
183
|
+
log.warn(
|
|
184
|
+
` vcvarsall env dump looks incomplete ` +
|
|
185
|
+
`(${matched} vars, VSINSTALLDIR=${env.VSINSTALLDIR ? 'set' : 'MISSING'})`,
|
|
186
|
+
);
|
|
187
|
+
resolve(null);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
resolve(env);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Compute a build-environment override for the given profile, or return
|
|
197
|
+
* null to indicate "use the ambient environment". Callers pass the result
|
|
198
|
+
* as `spawn(..., { env })`.
|
|
199
|
+
*
|
|
200
|
+
* Only activates when:
|
|
201
|
+
* - Running on Windows
|
|
202
|
+
* - Profile says compiler=msvc
|
|
203
|
+
* - `cl.exe` is NOT already on PATH (so we're outside a Dev shell)
|
|
204
|
+
* - `skipForGenerator` does not start with "Visual Studio" (VS generators
|
|
205
|
+
* don't need this — MSBuild resolves MSVC itself)
|
|
206
|
+
*
|
|
207
|
+
* @param {{
|
|
208
|
+
* profile: { compiler?: string, arch?: string },
|
|
209
|
+
* skipForGenerator?: string|null,
|
|
210
|
+
* }} args
|
|
211
|
+
* @returns {Promise<object|null>}
|
|
212
|
+
*/
|
|
213
|
+
async function loadMsvcEnv({ profile, skipForGenerator }) {
|
|
214
|
+
if (process.platform !== 'win32') return null;
|
|
215
|
+
if (!profile || profile.compiler !== 'msvc') return null;
|
|
216
|
+
if (skipForGenerator && skipForGenerator.startsWith('Visual Studio')) return null;
|
|
217
|
+
if (await isClOnPath()) return null;
|
|
218
|
+
|
|
219
|
+
const vsPath = await findVsInstall();
|
|
220
|
+
if (!vsPath) {
|
|
221
|
+
log.warn(' MSVC profile requested but vswhere/VS not found — CMake will error if cl.exe is missing');
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
const vcvarsall = findVcvarsall(vsPath);
|
|
225
|
+
if (!vcvarsall) {
|
|
226
|
+
log.warn(` MSVC profile requested but vcvarsall.bat not found under ${vsPath}`);
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
let archArg;
|
|
231
|
+
try {
|
|
232
|
+
archArg = mapArch(profile.arch);
|
|
233
|
+
} catch (err) {
|
|
234
|
+
// Unknown arch for an MSVC profile is genuinely a configuration bug
|
|
235
|
+
// (MSVC only ships toolsets for x64/x86/arm64/arm). Surface clearly
|
|
236
|
+
// and fall through to the ambient env — CMake will then error with
|
|
237
|
+
// a usable compiler-detection message instead of us silently
|
|
238
|
+
// cross-targeting x64.
|
|
239
|
+
log.warn(` ${err.message}`);
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
log.info(` activating MSVC build env: vcvarsall.bat ${archArg} (${vsPath})`);
|
|
243
|
+
const env = await captureVcvarsEnv(vcvarsall, archArg);
|
|
244
|
+
if (!env) {
|
|
245
|
+
log.warn(` vcvarsall.bat ${archArg} failed — CMake will error if cl.exe is missing`);
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
return env;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
module.exports = {
|
|
252
|
+
loadMsvcEnv,
|
|
253
|
+
// Exported for tests
|
|
254
|
+
isClOnPath,
|
|
255
|
+
findVsInstall,
|
|
256
|
+
findVcvarsall,
|
|
257
|
+
mapArch,
|
|
258
|
+
captureVcvarsEnv,
|
|
259
|
+
VCVARSALL_ARCH_ALLOWLIST,
|
|
260
|
+
};
|