wyvrnpm 2.20.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wyvrnpm",
3
- "version": "2.20.0",
3
+ "version": "2.20.1",
4
4
  "description": "A simple, static-hosting-compatible C++ package manager",
5
5
  "keywords": [
6
6
  "c++",
@@ -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
+ };
@@ -7,6 +7,7 @@ const { spawn } = require('child_process');
7
7
  const { loadMsvcEnv } = require('./msvc-env');
8
8
  const { archToVsPlatform, isVisualStudioGenerator } = require('../toolchain');
9
9
  const { detectDefaultGenerator } = require('./default-generator');
10
+ const { resolveCmakeExe } = require('./cmake-exe');
10
11
  const log = require('../logger');
11
12
 
12
13
  /**
@@ -144,7 +145,7 @@ async function pickGenerator(candidates) {
144
145
  */
145
146
  function runCmake(args, opts = {}) {
146
147
  return new Promise((resolve, reject) => {
147
- const proc = spawn('cmake', args, {
148
+ const proc = spawn(resolveCmakeExe(), args, {
148
149
  stdio: 'inherit',
149
150
  cwd: opts.cwd,
150
151
  env: opts.env || process.env,
@@ -1,124 +1,129 @@
1
- 'use strict';
2
-
3
- const { spawn } = require('child_process');
4
-
5
- const { isVisualStudioGenerator } = require('../toolchain');
6
-
7
- /**
8
- * Parse `cmake --help` output for the platform's default generator. CMake
9
- * marks the default with a leading `* ` in the "Generators" section, e.g.:
10
- *
11
- * The following generators are available on this platform (* marks default):
12
- * * Visual Studio 17 2022 [arch] = Generates Visual Studio 2022 project files.
13
- * Visual Studio 16 2019 [arch] = Generates Visual Studio 2019 project files.
14
- * Ninja = Generates build.ninja files.
15
- *
16
- * Returns the default generator's name verbatim (with any trailing ` [arch]`
17
- * annotation stripped), or null when no default marker is present. The name is
18
- * returned as-is deciding whether it's a Visual Studio generator is the
19
- * caller's job.
20
- *
21
- * @param {string} helpText
22
- * @returns {string|null}
23
- */
24
- function parseDefaultGenerator(helpText) {
25
- if (typeof helpText !== 'string') return null;
26
- for (const line of helpText.split(/\r?\n/)) {
27
- const m = line.match(/^\*\s+(.+?)\s*=/);
28
- if (!m) continue;
29
- return m[1].replace(/\s*\[arch\]\s*$/, '').trim();
30
- }
31
- return null;
32
- }
33
-
34
- /**
35
- * Spawn `cmake --help` and resolve its stdout. Resolves null (never rejects)
36
- * when cmake is absent from PATH or exits non-zero generator detection is
37
- * best-effort, and an unavailable cmake simply means the caller falls back to
38
- * leaving the generator unpinned, exactly as before this feature.
39
- *
40
- * @returns {Promise<string|null>}
41
- */
42
- function runCmakeHelp() {
43
- return new Promise((resolve) => {
44
- let out = '';
45
- const proc = spawn('cmake', ['--help'], {
46
- shell: process.platform === 'win32',
47
- });
48
- proc.stdout.on('data', (chunk) => { out += chunk.toString(); });
49
- // cmake not on PATH detection unavailable; recoverable, fall back to null.
50
- proc.on('error', () => resolve(null));
51
- proc.on('close', (code) => resolve(code === 0 ? out : null));
52
- });
53
- }
54
-
55
- // Per-process memo of the production (no-injection) probe result. The default
56
- // generator can't change mid-run, so a single `cmake --help` spawn is enough
57
- // for the whole process — important when one process drives many installs
58
- // (e.g. a source-build that resolves several deps, or the test suite). Calls
59
- // that inject `platform`/`runHelp` (tests) bypass this cache to stay hermetic.
60
- let cachedDefault; // undefined = unprobed; string|null = probed result
61
-
62
- /**
63
- * Detect the Visual Studio generator CMake would use by default on this
64
- * machine. On Windows with VS installed, CMake's implicit default is the
65
- * newest installed VS which builds host x64 unless `-A`/architecture is
66
- * pinned. Returns that VS generator name so callers can pin it (and the
67
- * matching `-A`), or null when the default is not a VS generator
68
- * (non-Windows, or VS not installed today's behaviour, no pin).
69
- *
70
- * `platform` and `runHelp` are injectable for tests; the defaults probe the
71
- * real machine and memoise the result per-process.
72
- *
73
- * @param {{ platform?: string, runHelp?: () => Promise<string|null> }} [opts]
74
- * @returns {Promise<string|null>}
75
- */
76
- async function detectDefaultGenerator({ platform, runHelp } = {}) {
77
- const injected = platform !== undefined || runHelp !== undefined;
78
- if (!injected && cachedDefault !== undefined) return cachedDefault;
79
-
80
- const plat = platform ?? process.platform;
81
- let result = null;
82
- if (plat === 'win32') {
83
- const helpText = await (runHelp ?? runCmakeHelp)();
84
- const name = helpText ? parseDefaultGenerator(helpText) : null;
85
- // Only VS generators need an arch pin; everything else (Ninja, NMake, …)
86
- // already honours the active vcvarsall env for target arch.
87
- result = isVisualStudioGenerator(name) ? name : null;
88
- }
89
-
90
- if (!injected) cachedDefault = result;
91
- return result;
92
- }
93
-
94
- /**
95
- * Resolve the generator a CMake invocation will actually use, given an
96
- * optional explicit choice (CLI `--generator` or a recipe `build.generator`)
97
- * and the active profile.
98
- *
99
- * - An explicit non-empty generator always wins, verbatim.
100
- * - Otherwise, on a Windows/MSVC profile, fall back to the detected default
101
- * VS generator so callers can pin `-A`/architecture against it.
102
- * - Otherwise null — the caller leaves the generator unpinned (CMake picks
103
- * its default), exactly as before this feature. On non-Windows toolchains
104
- * target arch is the compiler's job, not the generator's.
105
- *
106
- * @param {{ explicit?: string|null,
107
- * profile?: { os?: string, compiler?: string }|null,
108
- * detect?: () => Promise<string|null> }} [args]
109
- * @returns {Promise<string|null>}
110
- */
111
- async function resolveEffectiveGenerator({ explicit = null, profile = null, detect = detectDefaultGenerator } = {}) {
112
- if (typeof explicit === 'string' && explicit.trim()) return explicit;
113
- if (profile && profile.os === 'Windows' && profile.compiler === 'msvc') {
114
- return detect();
115
- }
116
- return null;
117
- }
118
-
119
- module.exports = {
120
- resolveEffectiveGenerator,
121
- detectDefaultGenerator,
122
- // Exported for unit tests
123
- parseDefaultGenerator,
124
- };
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
+ };
@@ -8,6 +8,7 @@ const { normalizeRecipe, hasBuildRecipe, VALID_CMAKE_CONFIGS, DEFAULT_RECIPE } =
8
8
  const { loadMsvcEnv } = require('../build/msvc-env');
9
9
  const { archToVsPlatform, isVisualStudioGenerator } = require('../toolchain');
10
10
  const { resolveEffectiveGenerator } = require('../build/default-generator');
11
+ const { resolveCmakeExe } = require('../build/cmake-exe');
11
12
  const { LOCAL_OVERLAY_FILENAME, readLocalOverlay } = require('../conf');
12
13
  const { resolveBinaryDirRoot, DEFAULT_BINARY_DIR_ROOT } = require('../binary-dir');
13
14
  const { buildContext } = require('../context');
@@ -244,7 +245,7 @@ function runCmake(args, env = null) {
244
245
  return new Promise((resolve, reject) => {
245
246
  const opts = { stdio: 'inherit' };
246
247
  if (env) opts.env = env;
247
- const proc = spawn('cmake', args, opts);
248
+ const proc = spawn(resolveCmakeExe(), args, opts);
248
249
  proc.on('error', (err) => {
249
250
  reject(new Error(
250
251
  `Failed to spawn cmake: ${err.message}\n` +