wyvrnpm 2.19.0 → 2.20.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/package.json +1 -1
- package/src/build/cmake-exe.js +72 -0
- package/src/build/cmake.js +29 -3
- package/src/build/default-generator.js +129 -0
- package/src/commands/build.js +14 -4
- package/src/commands/install.js +16 -0
package/package.json
CHANGED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
// Default CMake install location on Windows (the Kitware MSI default). When
|
|
7
|
+
// CMake isn't on PATH — common in stripped CI agents or a fresh dev box where
|
|
8
|
+
// the installer's "add to PATH" box was unchecked — fall back to this so
|
|
9
|
+
// install/build/source-build still work instead of failing with "is CMake on
|
|
10
|
+
// PATH?".
|
|
11
|
+
const WINDOWS_DEFAULT_CMAKE = 'C:\\Program Files\\CMake\\bin\\cmake.exe';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Decide which `cmake` command to spawn. Pure — all environment inputs are
|
|
15
|
+
* injected, so it's testable across platforms.
|
|
16
|
+
*
|
|
17
|
+
* 1. cmake found on PATH → `'cmake'` (let spawn / PATH resolve it; preserves
|
|
18
|
+
* the pre-fix behaviour and respects a user's chosen cmake).
|
|
19
|
+
* 2. else, on Windows, the default install location exists → its absolute
|
|
20
|
+
* path.
|
|
21
|
+
* 3. else → `'cmake'` so the caller's spawn fails with the normal
|
|
22
|
+
* "is CMake installed and on PATH?" error rather than a confusing ENOENT
|
|
23
|
+
* on a hard-coded path.
|
|
24
|
+
*
|
|
25
|
+
* @param {{ platform: string, pathValue: string|undefined,
|
|
26
|
+
* exists: (p: string) => boolean }} args
|
|
27
|
+
* @returns {string}
|
|
28
|
+
*/
|
|
29
|
+
function pickCmakeExe({ platform, pathValue, exists }) {
|
|
30
|
+
const isWin = platform === 'win32';
|
|
31
|
+
const pathLib = isWin ? path.win32 : path.posix;
|
|
32
|
+
const delim = isWin ? ';' : ':';
|
|
33
|
+
// .bat/.cmd cover shim installs (e.g. via package managers); .exe is the
|
|
34
|
+
// stock installer.
|
|
35
|
+
const names = isWin ? ['cmake.exe', 'cmake.bat', 'cmake.cmd'] : ['cmake'];
|
|
36
|
+
|
|
37
|
+
for (const dir of (pathValue || '').split(delim).filter(Boolean)) {
|
|
38
|
+
for (const name of names) {
|
|
39
|
+
if (exists(pathLib.join(dir, name))) return 'cmake';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (isWin && exists(WINDOWS_DEFAULT_CMAKE)) return WINDOWS_DEFAULT_CMAKE;
|
|
44
|
+
return 'cmake';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let cached; // undefined = unprobed; string = resolved command (per-process)
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Resolve the cmake executable to spawn, memoised per-process (PATH and the
|
|
51
|
+
* install location don't change mid-run). Returns either `'cmake'` (on PATH /
|
|
52
|
+
* give-up) or an absolute path to the Windows-default cmake.exe.
|
|
53
|
+
*
|
|
54
|
+
* @returns {string}
|
|
55
|
+
*/
|
|
56
|
+
function resolveCmakeExe() {
|
|
57
|
+
if (cached === undefined) {
|
|
58
|
+
cached = pickCmakeExe({
|
|
59
|
+
platform: process.platform,
|
|
60
|
+
pathValue: process.env.PATH,
|
|
61
|
+
exists: fs.existsSync,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return cached;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = {
|
|
68
|
+
resolveCmakeExe,
|
|
69
|
+
WINDOWS_DEFAULT_CMAKE,
|
|
70
|
+
// Exported for unit tests
|
|
71
|
+
pickCmakeExe,
|
|
72
|
+
};
|
package/src/build/cmake.js
CHANGED
|
@@ -5,6 +5,9 @@ const path = require('path');
|
|
|
5
5
|
const { spawn } = require('child_process');
|
|
6
6
|
|
|
7
7
|
const { loadMsvcEnv } = require('./msvc-env');
|
|
8
|
+
const { archToVsPlatform, isVisualStudioGenerator } = require('../toolchain');
|
|
9
|
+
const { detectDefaultGenerator } = require('./default-generator');
|
|
10
|
+
const { resolveCmakeExe } = require('./cmake-exe');
|
|
8
11
|
const log = require('../logger');
|
|
9
12
|
|
|
10
13
|
/**
|
|
@@ -142,7 +145,7 @@ async function pickGenerator(candidates) {
|
|
|
142
145
|
*/
|
|
143
146
|
function runCmake(args, opts = {}) {
|
|
144
147
|
return new Promise((resolve, reject) => {
|
|
145
|
-
const proc = spawn(
|
|
148
|
+
const proc = spawn(resolveCmakeExe(), args, {
|
|
146
149
|
stdio: 'inherit',
|
|
147
150
|
cwd: opts.cwd,
|
|
148
151
|
env: opts.env || process.env,
|
|
@@ -190,15 +193,21 @@ function isMultiConfigGenerator(generatorName) {
|
|
|
190
193
|
* configureExtra: string[],
|
|
191
194
|
* buildType?: string|null, // single-config only
|
|
192
195
|
* configurations?: string[]|null, // multi-config only, pins CMAKE_CONFIGURATION_TYPES
|
|
196
|
+
* vsPlatform?: string|null, // VS generators only — emits -A <platform>
|
|
193
197
|
* }} args
|
|
194
198
|
* @returns {string[]}
|
|
195
199
|
*/
|
|
196
200
|
function buildConfigureArgs({
|
|
197
201
|
sourceDir, buildDir, installDir, toolchainFile, generator, configureExtra,
|
|
198
|
-
buildType, configurations,
|
|
202
|
+
buildType, configurations, vsPlatform = null,
|
|
199
203
|
}) {
|
|
200
204
|
const args = ['-S', sourceDir, '-B', buildDir];
|
|
201
205
|
if (generator) args.push('-G', generator);
|
|
206
|
+
// Visual Studio generators default to host x64 unless the target platform is
|
|
207
|
+
// pinned with -A. Without this, an x86/ARM profile silently produces x64
|
|
208
|
+
// binaries (PLAN-VS-ARCH-PIN gate 3). Mapped from the profile arch by the
|
|
209
|
+
// caller; null for non-VS generators.
|
|
210
|
+
if (vsPlatform) args.push('-A', vsPlatform);
|
|
202
211
|
args.push(`-DCMAKE_TOOLCHAIN_FILE=${toolchainFile}`);
|
|
203
212
|
args.push(`-DCMAKE_INSTALL_PREFIX=${installDir}`);
|
|
204
213
|
// Single-config generators bake CMAKE_BUILD_TYPE into the cache.
|
|
@@ -257,7 +266,22 @@ async function configureBuildInstall({ recipe, srcRoot, buildPaths, toolchainFil
|
|
|
257
266
|
|
|
258
267
|
// Resolve generator from the fallback chain (probes each candidate's
|
|
259
268
|
// backing tool). `null` → omit `-G`, let CMake pick its default.
|
|
260
|
-
|
|
269
|
+
let generator = await pickGenerator(recipe.generators);
|
|
270
|
+
|
|
271
|
+
// No recipe generator → CMake's Windows default is the newest VS, which
|
|
272
|
+
// builds host x64 unless `-A` is pinned. Resolve it to a concrete VS
|
|
273
|
+
// generator so the -A below applies (PLAN-VS-ARCH-PIN gate 3). Non-Windows /
|
|
274
|
+
// non-MSVC leaves `generator` null → CMake default, no -A (target arch is
|
|
275
|
+
// the compiler's job there).
|
|
276
|
+
if (!generator && profile && profile.os === 'Windows' && profile.compiler === 'msvc') {
|
|
277
|
+
generator = await detectDefaultGenerator();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// VS generators need -A <platform> to honour the profile arch — recipe-pinned
|
|
281
|
+
// VS generators get it too, not just the detected default.
|
|
282
|
+
const vsPlatform = isVisualStudioGenerator(generator)
|
|
283
|
+
? archToVsPlatform(profile && profile.arch)
|
|
284
|
+
: null;
|
|
261
285
|
|
|
262
286
|
// On Windows with an MSVC profile + non-VS generator (Ninja, Make, ...),
|
|
263
287
|
// auto-enter a Developer Command Prompt-equivalent env so `cl.exe` is on
|
|
@@ -281,6 +305,7 @@ async function configureBuildInstall({ recipe, srcRoot, buildPaths, toolchainFil
|
|
|
281
305
|
installDir: buildPaths.install,
|
|
282
306
|
toolchainFile,
|
|
283
307
|
generator,
|
|
308
|
+
vsPlatform,
|
|
284
309
|
configureExtra: recipe.configure,
|
|
285
310
|
buildType: null, // multi-config picks at build time
|
|
286
311
|
configurations: configList, // pin CMAKE_CONFIGURATION_TYPES so all 4 build-<Config>.ninja files get generated
|
|
@@ -313,6 +338,7 @@ async function configureBuildInstall({ recipe, srcRoot, buildPaths, toolchainFil
|
|
|
313
338
|
installDir: buildPaths.install,
|
|
314
339
|
toolchainFile,
|
|
315
340
|
generator,
|
|
341
|
+
vsPlatform,
|
|
316
342
|
configureExtra: recipe.configure,
|
|
317
343
|
buildType: cfg,
|
|
318
344
|
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
|
|
5
|
+
const { isVisualStudioGenerator } = require('../toolchain');
|
|
6
|
+
const { resolveCmakeExe } = require('./cmake-exe');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Parse `cmake --help` output for the platform's default generator. CMake
|
|
10
|
+
* marks the default with a leading `* ` in the "Generators" section, e.g.:
|
|
11
|
+
*
|
|
12
|
+
* The following generators are available on this platform (* marks default):
|
|
13
|
+
* * Visual Studio 17 2022 [arch] = Generates Visual Studio 2022 project files.
|
|
14
|
+
* Visual Studio 16 2019 [arch] = Generates Visual Studio 2019 project files.
|
|
15
|
+
* Ninja = Generates build.ninja files.
|
|
16
|
+
*
|
|
17
|
+
* Returns the default generator's name verbatim (with any trailing ` [arch]`
|
|
18
|
+
* annotation stripped), or null when no default marker is present. The name is
|
|
19
|
+
* returned as-is — deciding whether it's a Visual Studio generator is the
|
|
20
|
+
* caller's job.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} helpText
|
|
23
|
+
* @returns {string|null}
|
|
24
|
+
*/
|
|
25
|
+
function parseDefaultGenerator(helpText) {
|
|
26
|
+
if (typeof helpText !== 'string') return null;
|
|
27
|
+
for (const line of helpText.split(/\r?\n/)) {
|
|
28
|
+
const m = line.match(/^\*\s+(.+?)\s*=/);
|
|
29
|
+
if (!m) continue;
|
|
30
|
+
return m[1].replace(/\s*\[arch\]\s*$/, '').trim();
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Spawn `cmake --help` and resolve its stdout. Resolves null (never rejects)
|
|
37
|
+
* when cmake is absent from PATH or exits non-zero — generator detection is
|
|
38
|
+
* best-effort, and an unavailable cmake simply means the caller falls back to
|
|
39
|
+
* leaving the generator unpinned, exactly as before this feature.
|
|
40
|
+
*
|
|
41
|
+
* @returns {Promise<string|null>}
|
|
42
|
+
*/
|
|
43
|
+
function runCmakeHelp() {
|
|
44
|
+
return new Promise((resolve) => {
|
|
45
|
+
let out = '';
|
|
46
|
+
const cmakeExe = resolveCmakeExe();
|
|
47
|
+
// shell:true (Windows) lets a bare `cmake` resolve .bat/.cmd shims on PATH.
|
|
48
|
+
// But a resolved absolute fallback path contains spaces ("Program Files"),
|
|
49
|
+
// which a shell would word-split — spawn it directly without a shell.
|
|
50
|
+
const proc = spawn(cmakeExe, ['--help'], {
|
|
51
|
+
shell: process.platform === 'win32' && cmakeExe === 'cmake',
|
|
52
|
+
});
|
|
53
|
+
proc.stdout.on('data', (chunk) => { out += chunk.toString(); });
|
|
54
|
+
// cmake not on PATH → detection unavailable; recoverable, fall back to null.
|
|
55
|
+
proc.on('error', () => resolve(null));
|
|
56
|
+
proc.on('close', (code) => resolve(code === 0 ? out : null));
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Per-process memo of the production (no-injection) probe result. The default
|
|
61
|
+
// generator can't change mid-run, so a single `cmake --help` spawn is enough
|
|
62
|
+
// for the whole process — important when one process drives many installs
|
|
63
|
+
// (e.g. a source-build that resolves several deps, or the test suite). Calls
|
|
64
|
+
// that inject `platform`/`runHelp` (tests) bypass this cache to stay hermetic.
|
|
65
|
+
let cachedDefault; // undefined = unprobed; string|null = probed result
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Detect the Visual Studio generator CMake would use by default on this
|
|
69
|
+
* machine. On Windows with VS installed, CMake's implicit default is the
|
|
70
|
+
* newest installed VS — which builds host x64 unless `-A`/architecture is
|
|
71
|
+
* pinned. Returns that VS generator name so callers can pin it (and the
|
|
72
|
+
* matching `-A`), or null when the default is not a VS generator
|
|
73
|
+
* (non-Windows, or VS not installed → today's behaviour, no pin).
|
|
74
|
+
*
|
|
75
|
+
* `platform` and `runHelp` are injectable for tests; the defaults probe the
|
|
76
|
+
* real machine and memoise the result per-process.
|
|
77
|
+
*
|
|
78
|
+
* @param {{ platform?: string, runHelp?: () => Promise<string|null> }} [opts]
|
|
79
|
+
* @returns {Promise<string|null>}
|
|
80
|
+
*/
|
|
81
|
+
async function detectDefaultGenerator({ platform, runHelp } = {}) {
|
|
82
|
+
const injected = platform !== undefined || runHelp !== undefined;
|
|
83
|
+
if (!injected && cachedDefault !== undefined) return cachedDefault;
|
|
84
|
+
|
|
85
|
+
const plat = platform ?? process.platform;
|
|
86
|
+
let result = null;
|
|
87
|
+
if (plat === 'win32') {
|
|
88
|
+
const helpText = await (runHelp ?? runCmakeHelp)();
|
|
89
|
+
const name = helpText ? parseDefaultGenerator(helpText) : null;
|
|
90
|
+
// Only VS generators need an arch pin; everything else (Ninja, NMake, …)
|
|
91
|
+
// already honours the active vcvarsall env for target arch.
|
|
92
|
+
result = isVisualStudioGenerator(name) ? name : null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!injected) cachedDefault = result;
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Resolve the generator a CMake invocation will actually use, given an
|
|
101
|
+
* optional explicit choice (CLI `--generator` or a recipe `build.generator`)
|
|
102
|
+
* and the active profile.
|
|
103
|
+
*
|
|
104
|
+
* - An explicit non-empty generator always wins, verbatim.
|
|
105
|
+
* - Otherwise, on a Windows/MSVC profile, fall back to the detected default
|
|
106
|
+
* VS generator so callers can pin `-A`/architecture against it.
|
|
107
|
+
* - Otherwise null — the caller leaves the generator unpinned (CMake picks
|
|
108
|
+
* its default), exactly as before this feature. On non-Windows toolchains
|
|
109
|
+
* target arch is the compiler's job, not the generator's.
|
|
110
|
+
*
|
|
111
|
+
* @param {{ explicit?: string|null,
|
|
112
|
+
* profile?: { os?: string, compiler?: string }|null,
|
|
113
|
+
* detect?: () => Promise<string|null> }} [args]
|
|
114
|
+
* @returns {Promise<string|null>}
|
|
115
|
+
*/
|
|
116
|
+
async function resolveEffectiveGenerator({ explicit = null, profile = null, detect = detectDefaultGenerator } = {}) {
|
|
117
|
+
if (typeof explicit === 'string' && explicit.trim()) return explicit;
|
|
118
|
+
if (profile && profile.os === 'Windows' && profile.compiler === 'msvc') {
|
|
119
|
+
return detect();
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = {
|
|
125
|
+
resolveEffectiveGenerator,
|
|
126
|
+
detectDefaultGenerator,
|
|
127
|
+
// Exported for unit tests
|
|
128
|
+
parseDefaultGenerator,
|
|
129
|
+
};
|
package/src/commands/build.js
CHANGED
|
@@ -7,6 +7,8 @@ const { spawn } = require('child_process');
|
|
|
7
7
|
const { normalizeRecipe, hasBuildRecipe, VALID_CMAKE_CONFIGS, DEFAULT_RECIPE } = require('../build/recipe');
|
|
8
8
|
const { loadMsvcEnv } = require('../build/msvc-env');
|
|
9
9
|
const { archToVsPlatform, isVisualStudioGenerator } = require('../toolchain');
|
|
10
|
+
const { resolveEffectiveGenerator } = require('../build/default-generator');
|
|
11
|
+
const { resolveCmakeExe } = require('../build/cmake-exe');
|
|
10
12
|
const { LOCAL_OVERLAY_FILENAME, readLocalOverlay } = require('../conf');
|
|
11
13
|
const { resolveBinaryDirRoot, DEFAULT_BINARY_DIR_ROOT } = require('../binary-dir');
|
|
12
14
|
const { buildContext } = require('../context');
|
|
@@ -243,7 +245,7 @@ function runCmake(args, env = null) {
|
|
|
243
245
|
return new Promise((resolve, reject) => {
|
|
244
246
|
const opts = { stdio: 'inherit' };
|
|
245
247
|
if (env) opts.env = env;
|
|
246
|
-
const proc = spawn(
|
|
248
|
+
const proc = spawn(resolveCmakeExe(), args, opts);
|
|
247
249
|
proc.on('error', (err) => {
|
|
248
250
|
reject(new Error(
|
|
249
251
|
`Failed to spawn cmake: ${err.message}\n` +
|
|
@@ -384,9 +386,17 @@ async function build(argv) {
|
|
|
384
386
|
if (recipe.generators.length > 0) [presetGenerator] = recipe.generators;
|
|
385
387
|
} catch { /* templates need options → skip env activation */ }
|
|
386
388
|
}
|
|
387
|
-
// CLI --generator wins
|
|
388
|
-
//
|
|
389
|
-
|
|
389
|
+
// CLI --generator wins, then the recipe's generator, then — on a
|
|
390
|
+
// Windows/MSVC profile with neither — CMake's detected default VS
|
|
391
|
+
// generator. Resolving the default here keeps the `-A` pin below and the
|
|
392
|
+
// vcvarsall-skip decision consistent with the generator `cmake --preset`
|
|
393
|
+
// will actually use (install pins the same generator into the preset).
|
|
394
|
+
// Non-Windows / non-MSVC → null, unchanged from before.
|
|
395
|
+
// See claude/PLAN-VS-ARCH-PIN.md.
|
|
396
|
+
effectiveGenerator = await resolveEffectiveGenerator({
|
|
397
|
+
explicit: effectiveGenerator ?? presetGenerator,
|
|
398
|
+
profile: activeProfile,
|
|
399
|
+
});
|
|
390
400
|
cmakeEnv = await loadMsvcEnv({
|
|
391
401
|
profile: activeProfile,
|
|
392
402
|
skipForGenerator: effectiveGenerator,
|
package/src/commands/install.js
CHANGED
|
@@ -18,6 +18,7 @@ const { resolveBinaryDirRoot } = require('../binary
|
|
|
18
18
|
const { hashProfile, formatProfile, mergeProfile } = require('../profile');
|
|
19
19
|
const { generateToolchain, generateCMakePresets } = require('../toolchain');
|
|
20
20
|
const { normalizeRecipe, hasBuildRecipe } = require('../build/recipe');
|
|
21
|
+
const { resolveEffectiveGenerator } = require('../build/default-generator');
|
|
21
22
|
const { buildContext } = require('../context');
|
|
22
23
|
const {
|
|
23
24
|
normalizeOptionsDeclaration,
|
|
@@ -661,6 +662,21 @@ async function install(argv) {
|
|
|
661
662
|
}
|
|
662
663
|
}
|
|
663
664
|
|
|
665
|
+
// When no generator was pinned by a recipe (the common recipe-less consumer
|
|
666
|
+
// case), CMake falls back to its platform default. On Windows that's the
|
|
667
|
+
// newest Visual Studio, which silently targets host x64 unless
|
|
668
|
+
// `-A`/architecture is pinned — so an x86 (or any non-host-arch) profile
|
|
669
|
+
// would produce mismatched binaries that find_package() later rejects with a
|
|
670
|
+
// pointer-size error. Resolve the default to a concrete VS generator so the
|
|
671
|
+
// preset carries both `generator` and `architecture` (buildConfigurePreset
|
|
672
|
+
// pins arch only when it knows the generator is VS). Non-Windows / non-MSVC
|
|
673
|
+
// → null, leaving the preset generator-less exactly as before. See
|
|
674
|
+
// claude/PLAN-VS-ARCH-PIN.md.
|
|
675
|
+
presetGenerator = await resolveEffectiveGenerator({
|
|
676
|
+
explicit: presetGenerator,
|
|
677
|
+
profile: baseProfile,
|
|
678
|
+
});
|
|
679
|
+
|
|
664
680
|
// PLAN-CONF.md §4.3 / §5 presets sink: fold effectiveConf's
|
|
665
681
|
// cmake.cache namespace into the preset's cacheVariables. Runs
|
|
666
682
|
// regardless of whether the project has its own build recipe —
|