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.
Files changed (49) hide show
  1. package/README.md +1914 -1860
  2. package/bin/{wyvrn.js → wyvrnpm.js} +66 -0
  3. package/cmake/cpp.cmake +9 -9
  4. package/cmake/functions.cmake +224 -224
  5. package/cmake/macros.cmake +284 -284
  6. package/package.json +3 -2
  7. package/src/auth.js +66 -66
  8. package/src/binary-dir.js +95 -0
  9. package/src/bootstrap/cookbook.js +196 -196
  10. package/src/bootstrap/detect.js +150 -150
  11. package/src/bootstrap/index.js +220 -220
  12. package/src/bootstrap/version.js +72 -72
  13. package/src/build/cache.js +344 -344
  14. package/src/build/clone.js +170 -170
  15. package/src/build/cmake.js +342 -342
  16. package/src/build/index.js +299 -297
  17. package/src/build/msvc-env.js +260 -260
  18. package/src/build/recipe.js +188 -188
  19. package/src/commands/add.js +141 -141
  20. package/src/commands/bootstrap.js +96 -96
  21. package/src/commands/build.js +482 -452
  22. package/src/commands/cache.js +189 -189
  23. package/src/commands/clean.js +80 -80
  24. package/src/commands/configure.js +92 -92
  25. package/src/commands/init.js +70 -70
  26. package/src/commands/install-skill.js +115 -115
  27. package/src/commands/install.js +730 -674
  28. package/src/commands/link.js +320 -320
  29. package/src/commands/profile.js +237 -237
  30. package/src/commands/publish.js +584 -555
  31. package/src/commands/show.js +252 -252
  32. package/src/commands/version.js +187 -0
  33. package/src/compat.js +273 -273
  34. package/src/conf/index.js +415 -391
  35. package/src/conf/namespaces.js +94 -94
  36. package/src/context.js +230 -230
  37. package/src/http-fetch.js +53 -53
  38. package/src/ignore.js +118 -118
  39. package/src/logger.js +122 -122
  40. package/src/options.js +303 -303
  41. package/src/settings-overrides.js +152 -152
  42. package/src/toolchain/deps.js +164 -164
  43. package/src/toolchain/index.js +212 -212
  44. package/src/toolchain/presets.js +356 -340
  45. package/src/toolchain/template.cmake +77 -77
  46. package/src/upload-built.js +265 -265
  47. package/src/version-range.js +301 -301
  48. package/src/zip-safe.js +52 -52
  49. package/src/zip-stream.js +126 -0
@@ -1,152 +1,152 @@
1
- 'use strict';
2
-
3
- // Pattern-based settings overrides (EVALUATION.md F11).
4
- //
5
- // `wyvrn.json` may declare a top-level `settingsOverrides` block whose
6
- // keys are glob patterns over dependency names. Every matching pattern
7
- // contributes to the resolved dep's `settings` merge; the per-dep
8
- // literal settings (inside `dependencies`) still win over patterns.
9
- //
10
- // Conan users know this feature as profile-side `boost*:compiler.cppstd=17`.
11
- // Ours lives in the manifest because that's where literal settings
12
- // overrides already live — one fewer concept for recipe authors.
13
- //
14
- // Pure, no I/O. Exported helpers:
15
- // - normalizeSettingsOverrides: validate + sort by specificity
16
- // - resolveOverrideSettings: compute the merged settings for a dep name
17
-
18
- /**
19
- * Valid profile field keys that settings overrides may target.
20
- * Mirrors `HASHED_PROFILE_FIELDS` in src/profile.js, but as the allow-
21
- * list for OVERRIDE values (os/arch/compiler/etc.). If a pattern
22
- * override tries to set something else, reject at parse time rather
23
- * than silently letting it through into mergeProfile.
24
- */
25
- const ALLOWED_SETTINGS_KEYS = new Set([
26
- 'os', 'arch', 'compiler', 'compiler.version', 'compiler.cppstd', 'compiler.runtime',
27
- ]);
28
-
29
- /**
30
- * Convert a glob pattern to a RegExp. Supports `*` (any chars),
31
- * `?` (single char), literal everything else. No brace expansion,
32
- * no negation — keep the mental model small.
33
- *
34
- * @param {string} glob
35
- * @returns {RegExp}
36
- */
37
- function globToRegExp(glob) {
38
- const escaped = glob.replace(/[.+^${}()|[\]\\]/g, '\\$&');
39
- const pattern = escaped.replace(/\*/g, '.*').replace(/\?/g, '.');
40
- return new RegExp(`^${pattern}$`);
41
- }
42
-
43
- /**
44
- * Specificity heuristic: fewer wildcards = more specific. Ties broken
45
- * by longer literal prefix, then lexical order for determinism. More
46
- * specific patterns get applied LAST so they override less-specific
47
- * ones in the merge.
48
- *
49
- * @param {string} glob
50
- * @returns {[number, number, string]} sort key — lower = less specific
51
- */
52
- function specificityKey(glob) {
53
- const wildcards = (glob.match(/[*?]/g) ?? []).length;
54
- const literalPrefix = glob.match(/^[^*?]*/)?.[0].length ?? 0;
55
- // Invert sort order: more wildcards → lower score; more prefix → higher score.
56
- return [-wildcards, literalPrefix, glob];
57
- }
58
-
59
- /**
60
- * Validate + sort a raw `settingsOverrides` block. Returns an array of
61
- * `{ pattern, regex, settings }` in application order (least-specific
62
- * first). A string pattern alone (literal `"openssl"`) works too — it
63
- * matches only that exact name.
64
- *
65
- * Throws on:
66
- * - non-object block
67
- * - non-string keys (JSON can't produce these but defensive)
68
- * - non-object values
69
- * - empty pattern strings
70
- * - unknown settings keys in a value
71
- * - non-string value leaves (settings values must be strings like
72
- * the profile itself: "dynamic", "17", "gcc")
73
- *
74
- * @param {unknown} block
75
- * @returns {Array<{ pattern: string, regex: RegExp, settings: Record<string, string> }>}
76
- */
77
- function normalizeSettingsOverrides(block) {
78
- if (block === undefined || block === null) return [];
79
- if (typeof block !== 'object' || Array.isArray(block)) {
80
- throw new Error('settingsOverrides must be an object mapping globs to settings');
81
- }
82
-
83
- const entries = [];
84
- for (const [rawPattern, rawValue] of Object.entries(block)) {
85
- if (typeof rawPattern !== 'string' || rawPattern.length === 0) {
86
- throw new Error(`settingsOverrides: pattern must be a non-empty string, got ${JSON.stringify(rawPattern)}`);
87
- }
88
- if (!rawValue || typeof rawValue !== 'object' || Array.isArray(rawValue)) {
89
- throw new Error(`settingsOverrides["${rawPattern}"]: value must be an object of settings`);
90
- }
91
-
92
- const settings = {};
93
- for (const [k, v] of Object.entries(rawValue)) {
94
- if (!ALLOWED_SETTINGS_KEYS.has(k)) {
95
- throw new Error(
96
- `settingsOverrides["${rawPattern}"]: unknown key "${k}" — ` +
97
- `allowed: ${[...ALLOWED_SETTINGS_KEYS].join(', ')}`,
98
- );
99
- }
100
- if (typeof v !== 'string' || v.length === 0) {
101
- throw new Error(
102
- `settingsOverrides["${rawPattern}"].${k}: value must be a non-empty string, ` +
103
- `got ${JSON.stringify(v)}`,
104
- );
105
- }
106
- settings[k] = v;
107
- }
108
-
109
- entries.push({ pattern: rawPattern, regex: globToRegExp(rawPattern), settings });
110
- }
111
-
112
- // Sort by specificity ascending so more-specific patterns apply LAST
113
- // (their settings therefore win on per-key merge).
114
- entries.sort((a, b) => {
115
- const ka = specificityKey(a.pattern);
116
- const kb = specificityKey(b.pattern);
117
- for (let i = 0; i < ka.length; i++) {
118
- if (ka[i] < kb[i]) return -1;
119
- if (ka[i] > kb[i]) return 1;
120
- }
121
- return 0;
122
- });
123
-
124
- return entries;
125
- }
126
-
127
- /**
128
- * Merge all matching pattern overrides for `depName` into a flat
129
- * settings object. Less-specific patterns apply first; more-specific
130
- * patterns overlay on top. Does NOT include the literal per-dep
131
- * `settings` (caller layers that on after — literal always wins).
132
- *
133
- * @param {string} depName
134
- * @param {ReturnType<typeof normalizeSettingsOverrides>} normalized
135
- * @returns {Record<string, string>}
136
- */
137
- function resolveOverrideSettings(depName, normalized) {
138
- const out = {};
139
- for (const entry of normalized) {
140
- if (entry.regex.test(depName)) {
141
- Object.assign(out, entry.settings);
142
- }
143
- }
144
- return out;
145
- }
146
-
147
- module.exports = {
148
- ALLOWED_SETTINGS_KEYS,
149
- globToRegExp,
150
- normalizeSettingsOverrides,
151
- resolveOverrideSettings,
152
- };
1
+ 'use strict';
2
+
3
+ // Pattern-based settings overrides (EVALUATION.md F11).
4
+ //
5
+ // `wyvrn.json` may declare a top-level `settingsOverrides` block whose
6
+ // keys are glob patterns over dependency names. Every matching pattern
7
+ // contributes to the resolved dep's `settings` merge; the per-dep
8
+ // literal settings (inside `dependencies`) still win over patterns.
9
+ //
10
+ // Conan users know this feature as profile-side `boost*:compiler.cppstd=17`.
11
+ // Ours lives in the manifest because that's where literal settings
12
+ // overrides already live — one fewer concept for recipe authors.
13
+ //
14
+ // Pure, no I/O. Exported helpers:
15
+ // - normalizeSettingsOverrides: validate + sort by specificity
16
+ // - resolveOverrideSettings: compute the merged settings for a dep name
17
+
18
+ /**
19
+ * Valid profile field keys that settings overrides may target.
20
+ * Mirrors `HASHED_PROFILE_FIELDS` in src/profile.js, but as the allow-
21
+ * list for OVERRIDE values (os/arch/compiler/etc.). If a pattern
22
+ * override tries to set something else, reject at parse time rather
23
+ * than silently letting it through into mergeProfile.
24
+ */
25
+ const ALLOWED_SETTINGS_KEYS = new Set([
26
+ 'os', 'arch', 'compiler', 'compiler.version', 'compiler.cppstd', 'compiler.runtime',
27
+ ]);
28
+
29
+ /**
30
+ * Convert a glob pattern to a RegExp. Supports `*` (any chars),
31
+ * `?` (single char), literal everything else. No brace expansion,
32
+ * no negation — keep the mental model small.
33
+ *
34
+ * @param {string} glob
35
+ * @returns {RegExp}
36
+ */
37
+ function globToRegExp(glob) {
38
+ const escaped = glob.replace(/[.+^${}()|[\]\\]/g, '\\$&');
39
+ const pattern = escaped.replace(/\*/g, '.*').replace(/\?/g, '.');
40
+ return new RegExp(`^${pattern}$`);
41
+ }
42
+
43
+ /**
44
+ * Specificity heuristic: fewer wildcards = more specific. Ties broken
45
+ * by longer literal prefix, then lexical order for determinism. More
46
+ * specific patterns get applied LAST so they override less-specific
47
+ * ones in the merge.
48
+ *
49
+ * @param {string} glob
50
+ * @returns {[number, number, string]} sort key — lower = less specific
51
+ */
52
+ function specificityKey(glob) {
53
+ const wildcards = (glob.match(/[*?]/g) ?? []).length;
54
+ const literalPrefix = glob.match(/^[^*?]*/)?.[0].length ?? 0;
55
+ // Invert sort order: more wildcards → lower score; more prefix → higher score.
56
+ return [-wildcards, literalPrefix, glob];
57
+ }
58
+
59
+ /**
60
+ * Validate + sort a raw `settingsOverrides` block. Returns an array of
61
+ * `{ pattern, regex, settings }` in application order (least-specific
62
+ * first). A string pattern alone (literal `"openssl"`) works too — it
63
+ * matches only that exact name.
64
+ *
65
+ * Throws on:
66
+ * - non-object block
67
+ * - non-string keys (JSON can't produce these but defensive)
68
+ * - non-object values
69
+ * - empty pattern strings
70
+ * - unknown settings keys in a value
71
+ * - non-string value leaves (settings values must be strings like
72
+ * the profile itself: "dynamic", "17", "gcc")
73
+ *
74
+ * @param {unknown} block
75
+ * @returns {Array<{ pattern: string, regex: RegExp, settings: Record<string, string> }>}
76
+ */
77
+ function normalizeSettingsOverrides(block) {
78
+ if (block === undefined || block === null) return [];
79
+ if (typeof block !== 'object' || Array.isArray(block)) {
80
+ throw new Error('settingsOverrides must be an object mapping globs to settings');
81
+ }
82
+
83
+ const entries = [];
84
+ for (const [rawPattern, rawValue] of Object.entries(block)) {
85
+ if (typeof rawPattern !== 'string' || rawPattern.length === 0) {
86
+ throw new Error(`settingsOverrides: pattern must be a non-empty string, got ${JSON.stringify(rawPattern)}`);
87
+ }
88
+ if (!rawValue || typeof rawValue !== 'object' || Array.isArray(rawValue)) {
89
+ throw new Error(`settingsOverrides["${rawPattern}"]: value must be an object of settings`);
90
+ }
91
+
92
+ const settings = {};
93
+ for (const [k, v] of Object.entries(rawValue)) {
94
+ if (!ALLOWED_SETTINGS_KEYS.has(k)) {
95
+ throw new Error(
96
+ `settingsOverrides["${rawPattern}"]: unknown key "${k}" — ` +
97
+ `allowed: ${[...ALLOWED_SETTINGS_KEYS].join(', ')}`,
98
+ );
99
+ }
100
+ if (typeof v !== 'string' || v.length === 0) {
101
+ throw new Error(
102
+ `settingsOverrides["${rawPattern}"].${k}: value must be a non-empty string, ` +
103
+ `got ${JSON.stringify(v)}`,
104
+ );
105
+ }
106
+ settings[k] = v;
107
+ }
108
+
109
+ entries.push({ pattern: rawPattern, regex: globToRegExp(rawPattern), settings });
110
+ }
111
+
112
+ // Sort by specificity ascending so more-specific patterns apply LAST
113
+ // (their settings therefore win on per-key merge).
114
+ entries.sort((a, b) => {
115
+ const ka = specificityKey(a.pattern);
116
+ const kb = specificityKey(b.pattern);
117
+ for (let i = 0; i < ka.length; i++) {
118
+ if (ka[i] < kb[i]) return -1;
119
+ if (ka[i] > kb[i]) return 1;
120
+ }
121
+ return 0;
122
+ });
123
+
124
+ return entries;
125
+ }
126
+
127
+ /**
128
+ * Merge all matching pattern overrides for `depName` into a flat
129
+ * settings object. Less-specific patterns apply first; more-specific
130
+ * patterns overlay on top. Does NOT include the literal per-dep
131
+ * `settings` (caller layers that on after — literal always wins).
132
+ *
133
+ * @param {string} depName
134
+ * @param {ReturnType<typeof normalizeSettingsOverrides>} normalized
135
+ * @returns {Record<string, string>}
136
+ */
137
+ function resolveOverrideSettings(depName, normalized) {
138
+ const out = {};
139
+ for (const entry of normalized) {
140
+ if (entry.regex.test(depName)) {
141
+ Object.assign(out, entry.settings);
142
+ }
143
+ }
144
+ return out;
145
+ }
146
+
147
+ module.exports = {
148
+ ALLOWED_SETTINGS_KEYS,
149
+ globToRegExp,
150
+ normalizeSettingsOverrides,
151
+ resolveOverrideSettings,
152
+ };
@@ -1,164 +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 };
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 };