wyvrnpm 2.0.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +382 -4
- package/bin/wyvrn.js +96 -3
- package/cmake/cpp.cmake +9 -0
- package/cmake/functions.cmake +224 -0
- package/cmake/macros.cmake +233 -0
- package/cmake/options.cmake +23 -0
- package/cmake/variables.cmake +171 -0
- package/package.json +2 -1
- package/src/build/cache.js +101 -0
- package/src/build/clone.js +170 -0
- package/src/build/cmake.js +342 -0
- package/src/build/index.js +244 -0
- package/src/build/msvc-env.js +217 -0
- package/src/build/recipe.js +129 -0
- package/src/commands/build.js +242 -0
- package/src/commands/clean.js +25 -9
- package/src/commands/configure.js +6 -5
- package/src/commands/init.js +3 -2
- package/src/commands/install.js +61 -14
- package/src/commands/link.js +18 -15
- package/src/commands/profile.js +15 -12
- package/src/commands/publish.js +67 -31
- package/src/compat.js +232 -0
- package/src/config.js +3 -1
- package/src/download.js +369 -87
- package/src/logger.js +78 -0
- package/src/profile.js +28 -0
- package/src/providers/file.js +4 -3
- package/src/providers/http.js +3 -2
- package/src/providers/s3.js +3 -2
- package/src/resolve.js +33 -7
- package/src/toolchain/deps.js +164 -0
- package/src/toolchain/index.js +141 -0
- package/src/toolchain/presets.js +191 -0
- package/src/toolchain/template.cmake +66 -0
package/src/logger.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// ANSI-coloured, level-tagged logger for wyvrnpm CLI output.
|
|
4
|
+
//
|
|
5
|
+
// All user-facing lines go through this module so the `[wyvrn]` prefix and
|
|
6
|
+
// level markers are applied consistently. Callers pass the message content
|
|
7
|
+
// only — the prefix is never written at call sites.
|
|
8
|
+
//
|
|
9
|
+
// Colour is emitted when the target stream is a TTY, unless overridden by
|
|
10
|
+
// the NO_COLOR or FORCE_COLOR environment variables (see https://no-color.org).
|
|
11
|
+
|
|
12
|
+
const PREFIX = '[wyvrn]';
|
|
13
|
+
|
|
14
|
+
const ANSI = {
|
|
15
|
+
reset: '\x1b[0m',
|
|
16
|
+
bold: '\x1b[1m',
|
|
17
|
+
dim: '\x1b[2m',
|
|
18
|
+
red: '\x1b[31m',
|
|
19
|
+
green: '\x1b[32m',
|
|
20
|
+
yellow: '\x1b[33m',
|
|
21
|
+
cyan: '\x1b[36m',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function colorEnabled(stream) {
|
|
25
|
+
if (process.env.NO_COLOR) return false;
|
|
26
|
+
if (process.env.FORCE_COLOR) return true;
|
|
27
|
+
return !!(stream && stream.isTTY);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function paint(text, code, stream) {
|
|
31
|
+
return colorEnabled(stream) ? `${code}${text}${ANSI.reset}` : text;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function emit(stream, consoleFn, coloredPrefix, message) {
|
|
35
|
+
// Every line in a multi-line message gets the prefix — so multi-line errors
|
|
36
|
+
// line up visually without the caller having to repeat `[wyvrn]` per line.
|
|
37
|
+
const out = String(message)
|
|
38
|
+
.split('\n')
|
|
39
|
+
.map((line) => `${coloredPrefix} ${line}`)
|
|
40
|
+
.join('\n');
|
|
41
|
+
consoleFn(out);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function info(message) {
|
|
45
|
+
emit(
|
|
46
|
+
process.stdout,
|
|
47
|
+
console.log,
|
|
48
|
+
paint(PREFIX, ANSI.dim, process.stdout),
|
|
49
|
+
message,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function warn(message) {
|
|
54
|
+
const p = paint(PREFIX, ANSI.yellow, process.stderr);
|
|
55
|
+
const tag = paint('warn', ANSI.yellow + ANSI.bold, process.stderr);
|
|
56
|
+
emit(process.stderr, console.warn, `${p} ${tag}`, message);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function error(message) {
|
|
60
|
+
const p = paint(PREFIX, ANSI.red, process.stderr);
|
|
61
|
+
const tag = paint('error', ANSI.red + ANSI.bold, process.stderr);
|
|
62
|
+
emit(process.stderr, console.error, `${p} ${tag}`, message);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function success(message) {
|
|
66
|
+
const p = paint(PREFIX, ANSI.green, process.stdout);
|
|
67
|
+
const tag = paint('ok', ANSI.green + ANSI.bold, process.stdout);
|
|
68
|
+
emit(process.stdout, console.log, `${p} ${tag}`, message);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function debug(message) {
|
|
72
|
+
if (!process.env.WYVRNPM_DEBUG) return;
|
|
73
|
+
const p = paint(PREFIX, ANSI.dim, process.stderr);
|
|
74
|
+
const tag = paint('debug', ANSI.cyan, process.stderr);
|
|
75
|
+
emit(process.stderr, console.error, `${p} ${tag}`, message);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = { info, warn, error, success, debug };
|
package/src/profile.js
CHANGED
|
@@ -214,6 +214,33 @@ function getGitRepo(cwd) {
|
|
|
214
214
|
return tryExec(`git -C "${cwd || '.'}" remote get-url origin`) || null;
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Check whether `sha` is reachable from any remote tracking branch or tag.
|
|
219
|
+
* Used at publish time to warn when the publisher's HEAD hasn't been pushed
|
|
220
|
+
* — a commit that isn't reachable from origin cannot be retrieved by
|
|
221
|
+
* downstream `--build=missing` consumers.
|
|
222
|
+
*
|
|
223
|
+
* @param {string} sha
|
|
224
|
+
* @param {string} [cwd]
|
|
225
|
+
* @returns {boolean} true if the commit is reachable on origin
|
|
226
|
+
*/
|
|
227
|
+
function isShaOnRemote(sha, cwd) {
|
|
228
|
+
if (!sha) return false;
|
|
229
|
+
// --remotes limits to remote-tracking branches; include tags too so
|
|
230
|
+
// tagged release commits on deleted branches still count as reachable.
|
|
231
|
+
const branches = tryExec(`git -C "${cwd || '.'}" branch -r --contains ${sha}`);
|
|
232
|
+
if (branches && branches.trim().length > 0) return true;
|
|
233
|
+
const tags = tryExec(`git -C "${cwd || '.'}" tag --contains ${sha}`);
|
|
234
|
+
if (tags && tags.trim().length > 0) {
|
|
235
|
+
// A local tag is only useful if it's also on origin. Check.
|
|
236
|
+
const remoteTags = tryExec(`git -C "${cwd || '.'}" ls-remote --tags origin`);
|
|
237
|
+
if (remoteTags && tags.split(/\s+/).some((t) => t && remoteTags.includes(`refs/tags/${t}`))) {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
|
|
217
244
|
/**
|
|
218
245
|
* Format a profile for human-readable display (Conan-style).
|
|
219
246
|
*
|
|
@@ -354,6 +381,7 @@ module.exports = {
|
|
|
354
381
|
sha256Of,
|
|
355
382
|
getGitSha,
|
|
356
383
|
getGitRepo,
|
|
384
|
+
isShaOnRemote,
|
|
357
385
|
formatProfile,
|
|
358
386
|
mergeProfile,
|
|
359
387
|
// Profile file management
|
package/src/providers/file.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const BaseProvider = require('./base');
|
|
6
|
+
const log = require('../logger');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* File system provider — handles local directories and SMB/UNC shares.
|
|
@@ -56,12 +57,12 @@ class FileProvider extends BaseProvider {
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
_copy(src, dest) {
|
|
59
|
-
|
|
60
|
+
log.info(` → ${dest}`);
|
|
60
61
|
fs.copyFileSync(src, dest);
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
_writeJson(filePath, data) {
|
|
64
|
-
|
|
65
|
+
log.info(` → ${filePath}`);
|
|
65
66
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
66
67
|
}
|
|
67
68
|
|
|
@@ -87,7 +88,7 @@ class FileProvider extends BaseProvider {
|
|
|
87
88
|
this._copy(files.zip, path.join(destDir, 'wyvrn.zip'));
|
|
88
89
|
|
|
89
90
|
const latestPath = path.join(packageDir, 'latest.json');
|
|
90
|
-
|
|
91
|
+
log.info(` → ${latestPath}`);
|
|
91
92
|
fs.writeFileSync(latestPath, JSON.stringify({ version }), 'utf8');
|
|
92
93
|
}
|
|
93
94
|
|
package/src/providers/http.js
CHANGED
|
@@ -6,6 +6,7 @@ const https = require('https');
|
|
|
6
6
|
const { pipeline } = require('stream/promises');
|
|
7
7
|
const { Readable } = require('stream');
|
|
8
8
|
const BaseProvider = require('./base');
|
|
9
|
+
const log = require('../logger');
|
|
9
10
|
|
|
10
11
|
function pinDependencies(rawDeps, lockedDeps) {
|
|
11
12
|
if (!rawDeps || !lockedDeps || Object.keys(lockedDeps).length === 0) return rawDeps;
|
|
@@ -45,7 +46,7 @@ class HttpProvider extends BaseProvider {
|
|
|
45
46
|
const parsed = new URL(url);
|
|
46
47
|
const headers = { 'Content-Type': contentType, 'Content-Length': body.length };
|
|
47
48
|
if (token) headers['Authorization'] = `Bearer ${token}`;
|
|
48
|
-
|
|
49
|
+
log.info(` → PUT ${url}`);
|
|
49
50
|
const req = this._lib(url).request(
|
|
50
51
|
{ hostname: parsed.hostname, port: parsed.port || undefined, path: parsed.pathname, method: 'PUT', headers },
|
|
51
52
|
(res) => {
|
|
@@ -66,7 +67,7 @@ class HttpProvider extends BaseProvider {
|
|
|
66
67
|
const parsed = new URL(url);
|
|
67
68
|
const headers = { 'Content-Type': contentType, 'Content-Length': buffer.length };
|
|
68
69
|
if (token) headers['Authorization'] = `Bearer ${token}`;
|
|
69
|
-
|
|
70
|
+
log.info(` → PUT ${url}`);
|
|
70
71
|
const req = this._lib(url).request(
|
|
71
72
|
{ hostname: parsed.hostname, port: parsed.port || undefined, path: parsed.pathname, method: 'PUT', headers },
|
|
72
73
|
(res) => {
|
package/src/providers/s3.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const BaseProvider = require('./base');
|
|
6
|
+
const log = require('../logger');
|
|
6
7
|
|
|
7
8
|
const RE_S3_URI = /^s3:\/\//;
|
|
8
9
|
const RE_S3_VIRTUAL_HOSTED = /^https?:\/\/([^.]+)\.s3[.-][^/]*\.amazonaws\.com/;
|
|
@@ -79,7 +80,7 @@ class S3Provider extends BaseProvider {
|
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
async _putFile(client, bucket, key, filePath, contentType, PutObjectCommand) {
|
|
82
|
-
|
|
83
|
+
log.info(` → s3://${bucket}/${key}`);
|
|
83
84
|
await client.send(new PutObjectCommand({
|
|
84
85
|
Bucket: bucket, Key: key,
|
|
85
86
|
Body: fs.readFileSync(filePath),
|
|
@@ -88,7 +89,7 @@ class S3Provider extends BaseProvider {
|
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
async _putBuffer(client, bucket, key, content, contentType, PutObjectCommand) {
|
|
91
|
-
|
|
92
|
+
log.info(` → s3://${bucket}/${key}`);
|
|
92
93
|
await client.send(new PutObjectCommand({
|
|
93
94
|
Bucket: bucket, Key: key,
|
|
94
95
|
Body: content,
|
package/src/resolve.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
|
|
6
|
+
const log = require('./logger');
|
|
7
|
+
|
|
6
8
|
/**
|
|
7
9
|
* Checks if a version string represents a linked package.
|
|
8
10
|
* @param {string} version
|
|
@@ -121,6 +123,30 @@ async function fetchPackageManifest(name, version, packageSources, platform, htt
|
|
|
121
123
|
|
|
122
124
|
for (const source of packageSources) {
|
|
123
125
|
const base = source.replace(/\/$/, '');
|
|
126
|
+
|
|
127
|
+
// v2: look up any available profile hash from versions.json, then fetch that manifest
|
|
128
|
+
try {
|
|
129
|
+
const idxResp = await httpClient(`${base}/v2/${name}/versions.json`);
|
|
130
|
+
if (idxResp.ok) {
|
|
131
|
+
const idx = await idxResp.json();
|
|
132
|
+
const profiles = idx?.versions?.[version]?.profiles;
|
|
133
|
+
if (profiles) {
|
|
134
|
+
const profileHash = Object.keys(profiles)[0];
|
|
135
|
+
if (profileHash) {
|
|
136
|
+
const mResp = await httpClient(`${base}/v2/${name}/${version}/${profileHash}/wyvrn.json`);
|
|
137
|
+
if (mResp.ok) {
|
|
138
|
+
const data = await mResp.json();
|
|
139
|
+
cache.set(cacheKey, data);
|
|
140
|
+
return data;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} catch {
|
|
146
|
+
// fall through to v1
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// v1 legacy paths
|
|
124
150
|
const candidates = [
|
|
125
151
|
`${base}/${platform}/${name}/${version}/wyvrn.json`,
|
|
126
152
|
`${base}/${platform}/${name}/${version}/razer.json`,
|
|
@@ -140,7 +166,7 @@ async function fetchPackageManifest(name, version, packageSources, platform, htt
|
|
|
140
166
|
}
|
|
141
167
|
}
|
|
142
168
|
|
|
143
|
-
|
|
169
|
+
log.warn(`could not fetch manifest for ${name}@${version} from any source`);
|
|
144
170
|
cache.set(cacheKey, null);
|
|
145
171
|
return null;
|
|
146
172
|
}
|
|
@@ -181,9 +207,9 @@ async function resolveDependencies(rootDeps, packageSources, platform, httpClien
|
|
|
181
207
|
if (version === 'latest') {
|
|
182
208
|
try {
|
|
183
209
|
version = await resolveLatestVersion(name, packageSources, platform, httpClient);
|
|
184
|
-
|
|
210
|
+
log.info(`Resolved "${name}@latest" → ${version}`);
|
|
185
211
|
} catch (err) {
|
|
186
|
-
|
|
212
|
+
log.warn(err.message);
|
|
187
213
|
continue;
|
|
188
214
|
}
|
|
189
215
|
}
|
|
@@ -200,8 +226,8 @@ async function resolveDependencies(rootDeps, packageSources, platform, httpClien
|
|
|
200
226
|
// Conflict: keep the latest version.
|
|
201
227
|
const winner = compareVersions(version, existing) > 0 ? version : existing;
|
|
202
228
|
if (winner !== existing) {
|
|
203
|
-
|
|
204
|
-
`
|
|
229
|
+
log.warn(
|
|
230
|
+
`Version conflict for "${name}": ${existing} vs ${version} — using ${winner}`,
|
|
205
231
|
);
|
|
206
232
|
resolved.set(name, winner);
|
|
207
233
|
// Re-fetch the winning version's manifest to pull in its transitive deps.
|
|
@@ -218,9 +244,9 @@ async function resolveDependencies(rootDeps, packageSources, platform, httpClien
|
|
|
218
244
|
const localPath = getLinkedPath(version);
|
|
219
245
|
manifest = readLocalManifest(localPath);
|
|
220
246
|
if (manifest) {
|
|
221
|
-
|
|
247
|
+
log.info(`Linked: ${name} → ${localPath}`);
|
|
222
248
|
} else {
|
|
223
|
-
|
|
249
|
+
log.info(`Linked: ${name} → ${localPath} (no manifest found)`);
|
|
224
250
|
}
|
|
225
251
|
} else {
|
|
226
252
|
manifest = await fetchPackageManifest(
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generate the contents of `wyvrn_deps.cmake`.
|
|
5
|
+
*
|
|
6
|
+
* This file is loaded via `CMAKE_PROJECT_INCLUDE` — it runs after the
|
|
7
|
+
* consumer's first `project()` call, which is when CMake variables like
|
|
8
|
+
* `CMAKE_CXX_COMPILER_ID`, `CMAKE_SIZEOF_VOID_P`, `CMAKE_SYSTEM_NAME`, and
|
|
9
|
+
* `WIN32` / `APPLE` / `UNIX` become valid. The bundled utility scripts
|
|
10
|
+
* (`variables.cmake` etc.) rely on those, so they cannot be included from
|
|
11
|
+
* the toolchain body.
|
|
12
|
+
*
|
|
13
|
+
* @param {object} args
|
|
14
|
+
* @param {string[]} args.packageNames Resolved dep names in topological order.
|
|
15
|
+
* @param {string} args.profileHash 16-char profile hash (for logging).
|
|
16
|
+
* @returns {string} CMake file content.
|
|
17
|
+
*/
|
|
18
|
+
function generateDepsCmake({ packageNames, profileHash }) {
|
|
19
|
+
const findPackageCalls = packageNames
|
|
20
|
+
.map((name) => ` find_package(${name} CONFIG QUIET)`)
|
|
21
|
+
.join('\n');
|
|
22
|
+
|
|
23
|
+
const resolvedList = packageNames.map((n) => ` ${n}`).join('\n');
|
|
24
|
+
|
|
25
|
+
return `# Generated by wyvrnpm — do not edit.
|
|
26
|
+
# Loaded after project() via CMAKE_PROJECT_INCLUDE.
|
|
27
|
+
# Profile hash: ${profileHash}
|
|
28
|
+
|
|
29
|
+
if(WYVRN_DEPS_INCLUDED)
|
|
30
|
+
return()
|
|
31
|
+
endif()
|
|
32
|
+
set(WYVRN_DEPS_INCLUDED TRUE)
|
|
33
|
+
|
|
34
|
+
# List of every package resolved by \`wyvrnpm install\`, in topological order.
|
|
35
|
+
set(WYVRN_RESOLVED_PACKAGES
|
|
36
|
+
${resolvedList || ' # (none)'}
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# ── Bundled wyvrnpm CMake utilities ───────────────────────────────────────────
|
|
40
|
+
# Included in the order the utility scripts expect.
|
|
41
|
+
include("\${WYVRNPM_CMAKE_DIR}/variables.cmake")
|
|
42
|
+
include("\${WYVRNPM_CMAKE_DIR}/options.cmake")
|
|
43
|
+
include("\${WYVRNPM_CMAKE_DIR}/cpp.cmake")
|
|
44
|
+
include("\${WYVRNPM_CMAKE_DIR}/functions.cmake")
|
|
45
|
+
include("\${WYVRNPM_CMAKE_DIR}/macros.cmake")
|
|
46
|
+
|
|
47
|
+
# ── Sanity checks (warn only — do not override the consumer's environment) ───
|
|
48
|
+
if(DEFINED CMAKE_CXX_COMPILER_ID AND DEFINED WYVRN_PROFILE_COMPILER)
|
|
49
|
+
set(_wyvrn_expected_compiler_id "")
|
|
50
|
+
if(WYVRN_PROFILE_COMPILER STREQUAL "msvc")
|
|
51
|
+
set(_wyvrn_expected_compiler_id "MSVC")
|
|
52
|
+
elseif(WYVRN_PROFILE_COMPILER STREQUAL "gcc")
|
|
53
|
+
set(_wyvrn_expected_compiler_id "GNU")
|
|
54
|
+
elseif(WYVRN_PROFILE_COMPILER STREQUAL "clang")
|
|
55
|
+
set(_wyvrn_expected_compiler_id "Clang")
|
|
56
|
+
elseif(WYVRN_PROFILE_COMPILER STREQUAL "apple-clang")
|
|
57
|
+
set(_wyvrn_expected_compiler_id "AppleClang")
|
|
58
|
+
endif()
|
|
59
|
+
if(_wyvrn_expected_compiler_id AND NOT CMAKE_CXX_COMPILER_ID STREQUAL _wyvrn_expected_compiler_id)
|
|
60
|
+
message(WARNING
|
|
61
|
+
"wyvrnpm profile expects compiler '\${WYVRN_PROFILE_COMPILER}' "
|
|
62
|
+
"(CMake id '\${_wyvrn_expected_compiler_id}') but the active compiler "
|
|
63
|
+
"is '\${CMAKE_CXX_COMPILER_ID}'. Installed binaries may be ABI-incompatible."
|
|
64
|
+
)
|
|
65
|
+
endif()
|
|
66
|
+
endif()
|
|
67
|
+
|
|
68
|
+
if(DEFINED CMAKE_SIZEOF_VOID_P AND DEFINED WYVRN_PROFILE_ARCH)
|
|
69
|
+
if(WYVRN_PROFILE_ARCH STREQUAL "x86_64" OR WYVRN_PROFILE_ARCH STREQUAL "armv8")
|
|
70
|
+
if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
|
|
71
|
+
message(WARNING
|
|
72
|
+
"wyvrnpm profile expects 64-bit arch '\${WYVRN_PROFILE_ARCH}' "
|
|
73
|
+
"but CMake is configuring a 32-bit build (CMAKE_SIZEOF_VOID_P=\${CMAKE_SIZEOF_VOID_P})."
|
|
74
|
+
)
|
|
75
|
+
endif()
|
|
76
|
+
elseif(WYVRN_PROFILE_ARCH STREQUAL "x86")
|
|
77
|
+
if(NOT CMAKE_SIZEOF_VOID_P EQUAL 4)
|
|
78
|
+
message(WARNING
|
|
79
|
+
"wyvrnpm profile expects 32-bit arch 'x86' "
|
|
80
|
+
"but CMake is configuring a 64-bit build (CMAKE_SIZEOF_VOID_P=\${CMAKE_SIZEOF_VOID_P})."
|
|
81
|
+
)
|
|
82
|
+
endif()
|
|
83
|
+
endif()
|
|
84
|
+
endif()
|
|
85
|
+
|
|
86
|
+
# ── wyvrnpm_find_dependencies() ───────────────────────────────────────────────
|
|
87
|
+
# One-shot helper: resolves every package that wyvrnpm installed.
|
|
88
|
+
# Packages that don't ship a CMake config are silently skipped (CONFIG QUIET).
|
|
89
|
+
function(wyvrnpm_find_dependencies)
|
|
90
|
+
${findPackageCalls || ' # (no dependencies)'}
|
|
91
|
+
|
|
92
|
+
# Post-find aliasing: ensure every resolved package has a wyvrn::<name>
|
|
93
|
+
# target, creating an INTERFACE alias when the package exports only
|
|
94
|
+
# <name>::<name>. Skipped when wyvrn::<name> already exists.
|
|
95
|
+
foreach(_wyvrn_pkg IN LISTS WYVRN_RESOLVED_PACKAGES)
|
|
96
|
+
if(TARGET wyvrn::\${_wyvrn_pkg})
|
|
97
|
+
# Already provided by the package's own CMake config.
|
|
98
|
+
elseif(TARGET \${_wyvrn_pkg}::\${_wyvrn_pkg})
|
|
99
|
+
add_library(wyvrn::\${_wyvrn_pkg} INTERFACE IMPORTED)
|
|
100
|
+
set_target_properties(wyvrn::\${_wyvrn_pkg} PROPERTIES
|
|
101
|
+
INTERFACE_LINK_LIBRARIES \${_wyvrn_pkg}::\${_wyvrn_pkg}
|
|
102
|
+
)
|
|
103
|
+
endif()
|
|
104
|
+
endforeach()
|
|
105
|
+
endfunction()
|
|
106
|
+
|
|
107
|
+
# ── wyvrnpm_enable_runtime_dll_copy(target) ───────────────────────────────────
|
|
108
|
+
# Adds a POST_BUILD command that copies every runtime DLL the target depends on
|
|
109
|
+
# (transitively, via \$<TARGET_RUNTIME_DLLS:...>) next to the target's own
|
|
110
|
+
# binary — so consumers can run the executable straight from the build dir
|
|
111
|
+
# without \`PATH\` tinkering. No-op on non-Windows platforms and on non-
|
|
112
|
+
# executable targets.
|
|
113
|
+
#
|
|
114
|
+
# Respects the per-target opt-out property WYVRN_COPY_RUNTIME_DLLS OFF.
|
|
115
|
+
# Requires CMake ≥ 3.21 (for TARGET_RUNTIME_DLLS).
|
|
116
|
+
function(wyvrnpm_enable_runtime_dll_copy target)
|
|
117
|
+
if(NOT WIN32)
|
|
118
|
+
return()
|
|
119
|
+
endif()
|
|
120
|
+
if(NOT TARGET \${target})
|
|
121
|
+
message(WARNING "wyvrnpm_enable_runtime_dll_copy: target '\${target}' does not exist")
|
|
122
|
+
return()
|
|
123
|
+
endif()
|
|
124
|
+
get_target_property(_wyvrn_type \${target} TYPE)
|
|
125
|
+
if(NOT _wyvrn_type STREQUAL "EXECUTABLE")
|
|
126
|
+
return()
|
|
127
|
+
endif()
|
|
128
|
+
get_target_property(_wyvrn_optout \${target} WYVRN_COPY_RUNTIME_DLLS)
|
|
129
|
+
if(_wyvrn_optout STREQUAL "OFF" OR _wyvrn_optout STREQUAL "FALSE")
|
|
130
|
+
return()
|
|
131
|
+
endif()
|
|
132
|
+
add_custom_command(TARGET \${target} POST_BUILD
|
|
133
|
+
COMMAND \${CMAKE_COMMAND} -E copy_if_different
|
|
134
|
+
"\$<TARGET_RUNTIME_DLLS:\${target}>"
|
|
135
|
+
"\$<TARGET_FILE_DIR:\${target}>"
|
|
136
|
+
COMMAND_EXPAND_LISTS
|
|
137
|
+
VERBATIM
|
|
138
|
+
)
|
|
139
|
+
endfunction()
|
|
140
|
+
|
|
141
|
+
# ── wyvrnpm_finalize_targets() ────────────────────────────────────────────────
|
|
142
|
+
# Discovers all executable targets in the calling directory (top-level only,
|
|
143
|
+
# not recursive) and wires each one up for runtime-DLL copy. Call this at the
|
|
144
|
+
# end of the consumer's top-level CMakeLists.txt, AFTER all add_executable()
|
|
145
|
+
# calls, so every target is visible.
|
|
146
|
+
#
|
|
147
|
+
# Scope: uses the BUILDSYSTEM_TARGETS directory property of the calling
|
|
148
|
+
# directory. For multi-subdirectory projects where executables are added from
|
|
149
|
+
# subdirectories, consumers should either:
|
|
150
|
+
# (a) call wyvrnpm_enable_runtime_dll_copy(<tgt>) explicitly for each exe, or
|
|
151
|
+
# (b) call wyvrnpm_finalize_targets() from each subdirectory.
|
|
152
|
+
function(wyvrnpm_finalize_targets)
|
|
153
|
+
get_property(_wyvrn_targets DIRECTORY PROPERTY BUILDSYSTEM_TARGETS)
|
|
154
|
+
foreach(_wyvrn_t IN LISTS _wyvrn_targets)
|
|
155
|
+
get_target_property(_wyvrn_type \${_wyvrn_t} TYPE)
|
|
156
|
+
if(_wyvrn_type STREQUAL "EXECUTABLE")
|
|
157
|
+
wyvrnpm_enable_runtime_dll_copy(\${_wyvrn_t})
|
|
158
|
+
endif()
|
|
159
|
+
endforeach()
|
|
160
|
+
endfunction()
|
|
161
|
+
`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = { generateDepsCmake };
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const { generateDepsCmake } = require('./deps');
|
|
7
|
+
const { generateCMakePresets } = require('./presets');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Absolute path to the bundled `cmake/` directory shipped inside the
|
|
11
|
+
* wyvrnpm npm package. Returned in CMake-friendly form (forward slashes).
|
|
12
|
+
*
|
|
13
|
+
* @returns {string}
|
|
14
|
+
*/
|
|
15
|
+
function getBundledCmakeDir() {
|
|
16
|
+
const dir = path.resolve(__dirname, '..', '..', 'cmake');
|
|
17
|
+
return dir.replace(/\\/g, '/');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Substitute `{{KEY}}` placeholders in a template string.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} template
|
|
24
|
+
* @param {Record<string, string>} values
|
|
25
|
+
* @returns {string}
|
|
26
|
+
*/
|
|
27
|
+
function renderTemplate(template, values) {
|
|
28
|
+
return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
|
29
|
+
if (Object.prototype.hasOwnProperty.call(values, key)) {
|
|
30
|
+
return values[key];
|
|
31
|
+
}
|
|
32
|
+
throw new Error(`Toolchain template has no value for placeholder {{${key}}}`);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Produce the toolchain template's placeholder substitutions.
|
|
38
|
+
*
|
|
39
|
+
* @param {object} args
|
|
40
|
+
* @param {import('../profile').WyvrnProfile} args.profile
|
|
41
|
+
* @param {string} args.profileName
|
|
42
|
+
* @param {string} args.profileHash
|
|
43
|
+
* @param {string[]} args.packageNames
|
|
44
|
+
* @returns {Record<string, string>}
|
|
45
|
+
*/
|
|
46
|
+
function buildPlaceholders({ profile, profileName, profileHash, packageNames }) {
|
|
47
|
+
const prefixPathEntries = packageNames
|
|
48
|
+
.map((name) => ` "\${CMAKE_CURRENT_LIST_DIR}/${name}"`)
|
|
49
|
+
.join('\n');
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
GENERATED_AT: new Date().toISOString(),
|
|
53
|
+
PROFILE_NAME: profileName,
|
|
54
|
+
PROFILE_HASH: profileHash,
|
|
55
|
+
PROFILE_OS: profile.os ?? '',
|
|
56
|
+
PROFILE_ARCH: profile.arch ?? '',
|
|
57
|
+
PROFILE_COMPILER: profile.compiler ?? '',
|
|
58
|
+
PROFILE_COMPILER_VERSION: profile['compiler.version'] ?? '',
|
|
59
|
+
PROFILE_CPPSTD: profile['compiler.cppstd'] ?? '20',
|
|
60
|
+
PROFILE_RUNTIME: profile['compiler.runtime'] ?? '',
|
|
61
|
+
WYVRNPM_CMAKE_DIR: getBundledCmakeDir(),
|
|
62
|
+
PREFIX_PATH_ENTRIES: prefixPathEntries || ' # (no dependencies)',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Generate the three toolchain artefacts under `destDir`:
|
|
68
|
+
* - wyvrn_toolchain.cmake
|
|
69
|
+
* - wyvrn_deps.cmake
|
|
70
|
+
* - wyvrn_profile.json
|
|
71
|
+
*
|
|
72
|
+
* ───────────────────────────────────────────────────────────────────────────
|
|
73
|
+
* CONTRACT (PLAN-CMAKE-TOOLCHAIN Phase F ↔ PLAN-BUILD-MISSING Phase 3)
|
|
74
|
+
* ───────────────────────────────────────────────────────────────────────────
|
|
75
|
+
* This function is the **single source of truth** for translating a build
|
|
76
|
+
* profile into a CMake configuration. Two callers exist:
|
|
77
|
+
*
|
|
78
|
+
* 1. `install` — writes the toolchain into a consumer project's
|
|
79
|
+
* `wyvrn_internal/`, so `cmake --preset` picks it
|
|
80
|
+
* up at configure time.
|
|
81
|
+
* 2. `buildFromSource` — (not yet implemented; PLAN-BUILD-MISSING §3)
|
|
82
|
+
* writes the toolchain into a scratch build dir
|
|
83
|
+
* when we source-build a package via
|
|
84
|
+
* `--build=missing`. The package is compiled
|
|
85
|
+
* with THIS function's output, guaranteeing the
|
|
86
|
+
* resulting binary's profile → flag mapping is
|
|
87
|
+
* bit-for-bit identical to what a consumer sees.
|
|
88
|
+
*
|
|
89
|
+
* Do NOT replicate this mapping elsewhere. If PLAN-BUILD-MISSING §3 finds it
|
|
90
|
+
* needs additional behaviour (e.g. extra CMake cache variables), extend this
|
|
91
|
+
* function — do not branch.
|
|
92
|
+
*
|
|
93
|
+
* Callers choose `destDir` and `packageNames`:
|
|
94
|
+
* - `destDir` — any directory; in source-build, points at the scratch
|
|
95
|
+
* `wyvrn_internal/` co-located with the cloned source.
|
|
96
|
+
* - `packageNames` — in `install`, the consumer's resolved deps; in
|
|
97
|
+
* source-build, the *package's own* resolved deps.
|
|
98
|
+
*
|
|
99
|
+
* Profile arguments (`profile`, `profileName`, `profileHash`) are always the
|
|
100
|
+
* active consumer profile — source-build still uses the consumer's profile,
|
|
101
|
+
* not the package's. That is the whole point of the source-build path.
|
|
102
|
+
* ───────────────────────────────────────────────────────────────────────────
|
|
103
|
+
*
|
|
104
|
+
* @param {object} args
|
|
105
|
+
* @param {string} args.destDir Where to write the three files.
|
|
106
|
+
* @param {import('../profile').WyvrnProfile} args.profile
|
|
107
|
+
* @param {string} args.profileName
|
|
108
|
+
* @param {string} args.profileHash
|
|
109
|
+
* @param {string[]} args.packageNames Resolved dep names in topological order.
|
|
110
|
+
* @returns {{ toolchain: string, deps: string, profile: string }}
|
|
111
|
+
* Absolute paths of the three files written.
|
|
112
|
+
*/
|
|
113
|
+
function generateToolchain({ destDir, profile, profileName, profileHash, packageNames }) {
|
|
114
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
115
|
+
|
|
116
|
+
const templatePath = path.join(__dirname, 'template.cmake');
|
|
117
|
+
const template = fs.readFileSync(templatePath, 'utf8');
|
|
118
|
+
const placeholders = buildPlaceholders({ profile, profileName, profileHash, packageNames });
|
|
119
|
+
const toolchainContent = renderTemplate(template, placeholders);
|
|
120
|
+
const depsContent = generateDepsCmake({ packageNames, profileHash });
|
|
121
|
+
const profileContent = JSON.stringify({ name: profileName, hash: profileHash, ...profile }, null, 2) + '\n';
|
|
122
|
+
|
|
123
|
+
const toolchainPath = path.join(destDir, 'wyvrn_toolchain.cmake');
|
|
124
|
+
const depsPath = path.join(destDir, 'wyvrn_deps.cmake');
|
|
125
|
+
const profilePath = path.join(destDir, 'wyvrn_profile.json');
|
|
126
|
+
|
|
127
|
+
fs.writeFileSync(toolchainPath, toolchainContent, 'utf8');
|
|
128
|
+
fs.writeFileSync(depsPath, depsContent, 'utf8');
|
|
129
|
+
fs.writeFileSync(profilePath, profileContent, 'utf8');
|
|
130
|
+
|
|
131
|
+
return { toolchain: toolchainPath, deps: depsPath, profile: profilePath };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
module.exports = {
|
|
135
|
+
generateToolchain,
|
|
136
|
+
generateCMakePresets,
|
|
137
|
+
getBundledCmakeDir,
|
|
138
|
+
// Exported for tests
|
|
139
|
+
buildPlaceholders,
|
|
140
|
+
renderTemplate,
|
|
141
|
+
};
|