Repository navigation
Expand file tree
/
Copy pathbuild.mcpp
More file actions
609 lines (567 loc) · 35.5 KB
/
Copy pathbuild.mcpp
File metadata and controls
609 lines (567 loc) · 35.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
// The deps members' decisions, and the files rules-qt reads by default, one
// case per decision (mcpp#734, design §6).
// Each case states a build context, runs the member, and reads the action it
// declared and the files it wrote. Nothing is installed or run.
import std;
import mcpp;
import mcpp.plugins.testing;
import mcpp.plugins.toolset;
import mcpp.deps; // the 0.16.0 names, for the compatibility case
import mcpp.deps.vcpkg;
import mcpp.deps.cmake;
import mcpp.rules.qt;
import mcpp.plugins.tool;
namespace t = mcpp::plugins::testing;
namespace ts = mcpp::plugins::toolset;
namespace {
// A vcpkg root with the three standard triplets the cases use, and a manifest.
t::context with_vcpkg(t::context c) {
const bool win = std::string_view(c.values.front().second).find("windows") != std::string_view::npos;
return c.xpkg("xim", "vcpkg", "{root}/vcpkg")
.file(win ? "vcpkg/vcpkg.exe" : "vcpkg/vcpkg")
.file("vcpkg/triplets/x64-windows.cmake",
"set(VCPKG_TARGET_ARCHITECTURE x64)\nset(VCPKG_CRT_LINKAGE dynamic)\nset(VCPKG_LIBRARY_LINKAGE dynamic)\n")
.file("vcpkg/triplets/x64-windows-static.cmake",
"set(VCPKG_TARGET_ARCHITECTURE x64)\nset(VCPKG_CRT_LINKAGE static)\nset(VCPKG_LIBRARY_LINKAGE static)\n")
.file("vcpkg/triplets/x64-linux.cmake",
"set(VCPKG_TARGET_ARCHITECTURE x64)\nset(VCPKG_CRT_LINKAGE dynamic)\nset(VCPKG_LIBRARY_LINKAGE static)\n"
"set(VCPKG_CMAKE_SYSTEM_NAME Linux)\n")
.file("pkg/vcpkg.json", "{ \"dependencies\": [\"fmt\"] }\n");
}
t::context with_subproject(t::context c) {
return c.xpkg("xim", "cmake", "{root}/cmake")
.file("cmake/bin/cmake").file("cmake/bin/cmake.exe")
.file("pkg/greet/CMakeLists.txt", "project(greet C)\n");
}
// Output directories of other build systems inside a tree `at`: an MSBuild
// intermediate directory, a CMake build directory, a tagged cache directory and
// one whose tag lacks the signature. `x64/` itself is a directory of sources.
t::context with_foreign_output(t::context c, const std::string& at, const std::string& ext) {
return c.file(at + "/x64/simd" + ext, "class Simd { Q_OBJECT };\n")
.file(at + "/x64/Release/app.tlog/app.lastbuildstate")
.file(at + "/x64/Release/qt/uic/ui_form" + ext, "class Ui { Q_OBJECT };\n")
.file(at + "/cmake-build-debug/CMakeCache.txt")
.file(at + "/cmake-build-debug/gen/stale" + ext, "class Stale { Q_OBJECT };\n")
.file(at + "/cache/CACHEDIR.TAG", "Signature: 8a477f597d28d172789f06886806bc55\n")
.file(at + "/cache/blob" + ext)
.file(at + "/notag/CACHEDIR.TAG", "not a tag\n")
.file(at + "/notag/kept" + ext);
}
// A stand-in Qt SDK: the tools rules-qt looks for, never run.
t::context with_qt(t::context c) {
for (auto const* tool : { "moc", "lupdate", "lrelease" })
c = c.file(std::string("qt/bin/") + tool).file(std::string("qt/bin/") + tool + ".exe");
return c.file("pkg/widget.h", "class Widget { Q_OBJECT };\n").file("pkg/app.ts");
}
int vcpkg_use(mcpp::deps::vcpkg::options o = {}) {
o.libraries = {"fmt"};
return mcpp::deps::vcpkg::use(o) ? 0 : 1;
}
int cmake_use(mcpp::deps::cmake::options o = {}) {
o.source = "greet";
o.libraries = {"greet"};
return mcpp::deps::cmake::use(o) ? 0 : 1;
}
// The one generated triplet of a case, by file name.
std::string derived_triplet(const t::result& r) {
for (auto const& f : r.files_under("out/deps-vcpkg/triplets"))
if (f.find("-mcpp-") != std::string::npos) return f;
return {};
}
std::string stem(const std::string& path) {
return std::filesystem::path(path).stem().string();
}
// The derived triplet's name under two different roots: equal names mean the
// name, and the text it is hashed from, hold no path (design §6.6).
std::string first_linux_name;
// The resolver under the `deps-cmake` spec, which every case above shares.
mcpp::plugins::tool::found tool_resolve(mcpp::plugins::tool::choice c = {}) {
return mcpp::plugins::tool::resolve(mcpp::deps::cmake_spec("mcpp.deps.cmake"), c);
}
} // namespace
int main(int argc, char** argv) {
return t::run(argc, argv, {
// ── deps-vcpkg ──────────────────────────────────────────────────────
{ "vcpkg: a Visual Studio toolset is selected through its instance",
with_vcpkg(t::row::windows_visual_studio()),
[] { return vcpkg_use(); },
[](const t::result& r, t::checker& c) {
c.expect(r.exit_code == 0, "the member succeeds");
const auto a = r.line("mcpp:action=", {"deps-vcpkg:install:x64-windows\""});
c.expect(!a.empty(), "the installation uses the standard triplet x64-windows");
c.expect(a.find("VCPKG_VISUAL_STUDIO_PATH=") != std::string::npos,
"the action selects the instance with VCPKG_VISUAL_STUDIO_PATH");
c.expect(a.find("--host-triplet") == std::string::npos, "the host triplet stays vcpkg's");
c.expect(derived_triplet(r).empty(), "no triplet is derived");
} },
{ "vcpkg: another toolset version of the instance derives a triplet that names it",
with_vcpkg(t::row::windows_visual_studio("14.43.34808", "14.44.35207")),
[] { return vcpkg_use(); },
[](const t::result& r, t::checker& c) {
const auto f = derived_triplet(r);
c.expect(!f.empty(), "a triplet x64-windows-mcpp-<hash> is derived");
c.expect(r.file(f).find("set(VCPKG_PLATFORM_TOOLSET_VERSION 14.43.34808)") != std::string::npos,
"the derived triplet names the resolved toolset version");
c.expect(r.file(f).find("VCPKG_CHAINLOAD_TOOLCHAIN_FILE") == std::string::npos,
"the instance's own loading stays in place");
c.expect(r.has_line("mcpp:action=", {"VCPKG_VISUAL_STUDIO_PATH="}),
"the action still selects the instance");
} },
{ "vcpkg: a managed toolset is named in a chain-loaded derived triplet",
with_vcpkg(t::row::windows_managed()),
[] { return vcpkg_use(); },
[](const t::result& r, t::checker& c) {
c.expect(r.exit_code == 0, "the member succeeds");
const auto f = derived_triplet(r);
const auto name = stem(f);
c.expect(name.starts_with("x64-windows-mcpp-"), "the derived triplet is x64-windows-mcpp-<hash>");
const auto text = r.file(f);
c.expect(text.find("set(VCPKG_LIBRARY_LINKAGE dynamic)") != std::string::npos,
"the base triplet is inlined, not included");
c.expect(text.find("\"${CMAKE_CURRENT_LIST_DIR}/mcpp-chain-windows.cmake\"") != std::string::npos,
"the chain-loaded toolchain is found beside the triplet");
c.expect(text.find("MCPP_VCPKG_CXX") != std::string::npos && text.find(" INCLUDE") != std::string::npos
&& text.find(" LIB") != std::string::npos,
"the tools and their environment pass through untracked");
c.expect(text.find("# toolset: msvc 14.44.35207; sdk 10.0.26100.0 (chain)") != std::string::npos,
"the triplet carries the toolset's identity");
c.expect(text.find("variable_watch(VCPKG_PLATFORM_TOOLSET") != std::string::npos,
"an MSBuild port is refused by name");
c.expect(text.find(r.root.generic_string()) == std::string::npos, "the triplet holds no path");
const auto chain = r.file("out/deps-vcpkg/triplets/mcpp-chain-windows.cmake");
c.expect(chain.find("file(TO_CMAKE_PATH \"$ENV{MCPP_VCPKG_CXX}\"") != std::string::npos,
"the toolchain reads each tool from the environment through TO_CMAKE_PATH");
c.expect(chain.find("/scripts/toolchains/windows.cmake\")") != std::string::npos,
"the toolchain includes vcpkg's windows.cmake");
c.expect(chain.find(r.root.generic_string()) == std::string::npos, "the toolchain holds no path");
const auto a = r.line("mcpp:action=", {"deps-vcpkg:install:" + name});
c.expect(!a.empty(), "the installation uses the derived triplet");
c.expect(a.find("\"--host-triplet=" + name + "\"") != std::string::npos,
"the host triplet is the derived one");
c.expect(a.find("\"VCPKG_KEEP_ENV_VARS=PATH\"") != std::string::npos,
"vcpkg keeps the PATH the action sets");
c.expect(a.find("\"PATH=") != std::string::npos && a.find("Hostx64") != std::string::npos,
"the toolset's directories are on that PATH");
c.expect(a.find("VCPKG_VISUAL_STUDIO_PATH") == std::string::npos, "no instance is selected");
} },
{ "vcpkg: a self-contained program's ports link the C runtime statically",
with_vcpkg(t::row::windows_managed()
.set("MCPP_CXX_RUNTIME", "self-contained").set("MCPP_MSVC_CRT_LINKAGE", "static")),
[] { return vcpkg_use(); },
[](const t::result& r, t::checker& c) {
const auto f = derived_triplet(r);
c.expect(stem(f).starts_with("x64-windows-static-mcpp-"), "the base triplet is x64-windows-static");
c.expect(r.file(f).find("set(VCPKG_CRT_LINKAGE static)") != std::string::npos,
"the derived triplet states the static C runtime");
} },
{ "vcpkg: a project triplet that contradicts the program's C runtime is refused",
with_vcpkg(t::row::windows_visual_studio()
.set("MCPP_CXX_RUNTIME", "self-contained").set("MCPP_MSVC_CRT_LINKAGE", "static")),
[] { return vcpkg_use({.triplet = "x64-windows"}); },
[](const t::result& r, t::checker& c) {
c.expect(r.exit_code != 0, "the member fails");
c.expect(r.errors.find("the triplet 'x64-windows' links the C runtime dynamic") != std::string::npos
&& r.errors.find("links it static (the program's C++ runtime contract, self-contained)")
!= std::string::npos,
"the error names both statements");
c.expect(r.count("mcpp:action=") == 0, "nothing is installed");
} },
{ "vcpkg: crt_linkage states the ports' linkage and wins over the contract",
with_vcpkg(t::row::windows_visual_studio()
.set("MCPP_CXX_RUNTIME", "self-contained").set("MCPP_MSVC_CRT_LINKAGE", "static")),
[] { return vcpkg_use({.triplet = "x64-windows", .crt_linkage = "dynamic"}); },
[](const t::result& r, t::checker& c) {
c.expect(r.exit_code == 0, "the member succeeds");
c.expect(r.has_line("mcpp:action=", {"deps-vcpkg:install:x64-windows\""}),
"the project's triplet is used");
} },
{ "vcpkg: detected leaves the toolset to vcpkg and says until when",
with_vcpkg(t::row::windows_visual_studio()),
[] { return vcpkg_use({.toolset = {.toolset = ts::source::detected}}); },
[](const t::result& r, t::checker& c) {
c.expect(r.has_line("mcpp:action=", {"deps-vcpkg:install:x64-windows\""}),
"the standard triplet is used");
c.expect(!r.has_line("mcpp:action=", {"VCPKG_VISUAL_STUDIO_PATH"}), "no instance is selected");
c.expect(r.has_line("mcpp:diagnostic=note", {"kept until 2027-03-28"}),
"the compatibility note is printed");
} },
{ "vcpkg: the Linux GCC row keeps vcpkg's detection, and says why",
with_vcpkg(t::row::linux_gcc()),
[] {
const auto r = ts::resolve();
std::printf("reason=%s\n", r ? r->reason.c_str() : "");
return vcpkg_use();
},
[](const t::result& r, t::checker& c) {
c.expect(r.has_line("mcpp:action=", {"deps-vcpkg:install:x64-linux\""}),
"the standard triplet x64-linux is used");
c.expect(!r.has_line("mcpp:action=", {"MCPP_VCPKG_CXX="}), "no compiler is named");
c.expect(r.has_line("reason=", {"GCC payload"}), "the resolution states why");
} },
{ "vcpkg: the Linux libc++ row hands over mcpp's clang through linux.cmake",
with_vcpkg(t::row::linux_libcxx()),
[] { return vcpkg_use(); },
[](const t::result& r, t::checker& c) {
const auto name = stem(derived_triplet(r));
c.expect(name.starts_with("x64-linux-mcpp-"), "the derived triplet is x64-linux-mcpp-<hash>");
c.expect(r.file("out/deps-vcpkg/triplets/mcpp-chain-linux.cmake")
.find("/scripts/toolchains/linux.cmake\")") != std::string::npos,
"the toolchain includes vcpkg's linux.cmake");
const auto a = r.line("mcpp:action=", {"deps-vcpkg:install:" + name});
c.expect(a.find("MCPP_VCPKG_CXX=") != std::string::npos && a.find("llvm/bin/clang++") != std::string::npos,
"clang++ is handed to vcpkg");
c.expect(a.find("--host-triplet") == std::string::npos, "host tools keep vcpkg's host triplet");
c.expect(a.find("\"PATH=") == std::string::npos, "PATH is left alone off the MSVC ABI");
first_linux_name = name;
} },
{ "vcpkg: the derived triplet's name does not depend on where the tools are",
with_vcpkg(t::row::linux_libcxx()),
[] { return vcpkg_use(); },
[](const t::result& r, t::checker& c) {
c.expect(!first_linux_name.empty() && stem(derived_triplet(r)) == first_linux_name,
"the same toolset under another root derives the same triplet");
} },
// ── deps-cmake ──────────────────────────────────────────────────────
{ "cmake: a Visual Studio toolset is named, with Ninja, by default",
with_subproject(t::row::windows_visual_studio("14.43.34808", "14.44.35207")),
[] { return cmake_use(); },
[](const t::result& r, t::checker& c) {
c.expect(r.exit_code == 0, "the member succeeds");
std::string script;
for (auto const& f : r.files_under("out/deps-cmake/greet"))
if (f.ends_with("greet.cmake")) script = r.file(f);
c.expect(script.find("[==[Ninja]==]") != std::string::npos, "the Ninja generator is used");
c.expect(script.find("-DCMAKE_CXX_COMPILER=") != std::string::npos
&& script.find("cl.exe") != std::string::npos, "cl.exe is named");
c.expect(script.find("CMAKE_GENERATOR_INSTANCE") == std::string::npos,
"no Visual Studio generator is pointed at the instance");
c.expect(r.line("mcpp:action=", {"deps-cmake:greet"}).find("\"INCLUDE=") != std::string::npos,
"the tools run with the engine's environment");
} },
{ "cmake: generator visual_studio keeps the Visual Studio generator on that instance",
with_subproject(t::row::windows_visual_studio("14.43.34808", "14.44.35207")),
[] {
mcpp::deps::cmake::options o;
o.generator = mcpp::deps::cmake::options::generator_kind::visual_studio;
return cmake_use(o);
},
[](const t::result& r, t::checker& c) {
const auto script = r.file("out/deps-cmake/greet/greet.cmake");
c.expect(script.find("[==[-DCMAKE_GENERATOR_INSTANCE=") != std::string::npos,
"CMAKE_GENERATOR_INSTANCE names the instance");
c.expect(script.find("[==[version=14.43.34808]==]") != std::string::npos,
"-T version= names a toolset other than the instance's default");
c.expect(script.find("[==[Ninja]==]") == std::string::npos, "the generator stays CMake's default");
c.expect(script.find("MultiThreaded$<$<CONFIG:Debug>:Debug>DLL") != std::string::npos,
"the C runtime follows the program's (dynamic)");
c.expect(script.find("-DCMAKE_GENERATOR_INSTANCE=<instance>") != std::string::npos,
"the key states the instance by the toolset it provides");
} },
{ "cmake: a managed toolset is named, with Ninja and mcpp's ninja",
with_subproject(t::row::windows_managed()
.set("MCPP_CXX_RUNTIME", "self-contained").set("MCPP_MSVC_CRT_LINKAGE", "static")),
[] { return cmake_use(); },
[](const t::result& r, t::checker& c) {
std::string script;
for (auto const& f : r.files_under("out/deps-cmake/greet"))
if (f.ends_with("greet.cmake")) script = r.file(f);
c.expect(script.find("[==[Ninja]==]") != std::string::npos, "the Ninja generator is used");
c.expect(script.find("-DCMAKE_MAKE_PROGRAM=") != std::string::npos
&& script.find("ninja/ninja.exe") != std::string::npos, "mcpp's ninja runs it");
c.expect(script.find("-DCMAKE_CXX_COMPILER=") != std::string::npos
&& script.find("cl.exe") != std::string::npos, "cl.exe is named");
c.expect(script.find("MultiThreaded$<$<CONFIG:Debug>:Debug>]==]") != std::string::npos,
"the C runtime follows the program's (static)");
const auto a = r.line("mcpp:action=", {"deps-cmake:greet"});
c.expect(a.find("\"INCLUDE=") != std::string::npos && a.find("\"PATH=") != std::string::npos,
"the tools run with the engine's environment");
c.expect(script.find("/greet/build-") != std::string::npos,
"the build directory is keyed by the toolset");
} },
{ "cmake: compilers in cache_args are the project's decision",
with_subproject(t::row::linux_gcc()),
[] {
mcpp::deps::cmake::options o;
o.cache_args = {"-DCMAKE_CXX_COMPILER=/opt/other/g++"};
return cmake_use(o);
},
[](const t::result& r, t::checker& c) {
const auto script = r.file("out/deps-cmake/greet/greet.cmake");
c.expect(script.find("[==[Ninja]==]") == std::string::npos, "no generator is imposed");
c.expect(script.find("gcc/bin/g++") == std::string::npos, "the row's compiler is not named");
} },
{ "cmake: the compiler and the generator are chosen by the argument's name",
t::row::linux_gcc(),
[] {
namespace c = mcpp::deps::cmake;
auto say = [](const char* what, bool v) { std::printf("%s=%d\n", what, v ? 1 : 0); };
using args = std::vector<std::string>;
say("launcher", c::chooses_compiler(args{"-DCMAKE_CXX_COMPILER_LAUNCHER=sccache"}));
say("target", c::chooses_compiler(args{"-DCMAKE_C_COMPILER_TARGET=x86_64-linux-gnu"}));
say("compiler", c::chooses_compiler(args{"-DCMAKE_CXX_COMPILER=/opt/cxx"}));
say("typed", c::chooses_compiler(args{"-DCMAKE_CXX_COMPILER:FILEPATH=/opt/cxx"}));
say("spaced", c::chooses_compiler(args{"-D", "CMAKE_C_COMPILER=/opt/cc"}));
say("toolchain_file", c::chooses_compiler(args{"-DCMAKE_TOOLCHAIN_FILE=t.cmake"}));
say("toolchain", c::chooses_compiler(args{"--toolchain", "t.cmake"}));
say("toolchain_eq", c::chooses_compiler(args{"--toolchain=t.cmake"}));
say("platform", c::chooses_generator(args{"-DCMAKE_GENERATOR_PLATFORM=x64"}));
say("gen_toolset", c::chooses_generator(args{"-DCMAKE_GENERATOR_TOOLSET=v143"}));
say("dash_g", c::chooses_generator(args{"-G", "Ninja"}));
say("generator", c::chooses_generator(args{"-DCMAKE_GENERATOR=Ninja"}));
return 0;
},
[](const t::result& r, t::checker& c) {
for (auto const* no : {"launcher=0", "target=0", "platform=0", "gen_toolset=0"})
c.expect(r.has_line(no), std::string(no) + ": the argument refines, it does not choose");
for (auto const* yes : {"compiler=1", "typed=1", "spaced=1", "toolchain_file=1", "toolchain=1",
"toolchain_eq=1", "dash_g=1", "generator=1"})
c.expect(r.has_line(yes), std::string(yes) + ": the argument chooses");
} },
{ "cmake: a launcher in cache_args keeps the named toolset and Ninja",
with_subproject(t::row::windows_managed()),
[] {
mcpp::deps::cmake::options o;
o.cache_args = {"-DCMAKE_CXX_COMPILER_LAUNCHER=sccache"};
return cmake_use(o);
},
[](const t::result& r, t::checker& c) {
std::string script;
for (auto const& f : r.files_under("out/deps-cmake/greet"))
if (f.ends_with("greet.cmake")) script = r.file(f);
c.expect(script.find("[==[Ninja]==]") != std::string::npos, "the Ninja generator is used");
c.expect(script.find("cl.exe") != std::string::npos, "cl.exe is named");
c.expect(script.find("[==[-DCMAKE_CXX_COMPILER_LAUNCHER=sccache]==]") != std::string::npos,
"the launcher reaches CMake");
} },
{ "cmake: a named toolset's installation is keyed without a path of the machine",
with_subproject(t::row::windows_managed()),
[] { return cmake_use(); },
[](const t::result& r, t::checker& c) {
std::string script;
for (auto const& f : r.files_under("out/deps-cmake/greet"))
if (f.ends_with("greet.cmake")) script = r.file(f);
c.expect(script.find("AN INSTALLATION IS BUILT ONCE") != std::string::npos, "the script consults the cache");
const auto from = script.find("set(recipe [==[");
const auto to = script.find("]==])", from);
const auto recipe = from == std::string::npos ? std::string() : script.substr(from, to - from);
c.expect(recipe.find("toolset msvc 14.44.35207; sdk 10.0.26100.0 (chain)") != std::string::npos,
"the key states the toolset's identity");
c.expect(recipe.find("-DCMAKE_CXX_COMPILER=<cl.exe>") != std::string::npos,
"the key names the compiler by its file name");
c.expect(recipe.find("arg <source>") != std::string::npos && recipe.find("-DCMAKE_INSTALL_PREFIX=<prefix>") != std::string::npos,
"the subproject's directories are placeholders");
c.expect(recipe.find(r.root.generic_string()) == std::string::npos, "the key holds no path");
c.expect(script.find("set(listed [==[\nCMakeLists.txt\n") != std::string::npos,
"the subproject's files are listed relative to it");
} },
{ "cmake: a toolset CMake finds itself is not keyed",
with_subproject(t::row::linux_gcc()),
[] { return cmake_use(); },
[](const t::result& r, t::checker& c) {
c.expect(r.file("out/deps-cmake/greet/greet.cmake").find("AN INSTALLATION IS BUILT ONCE")
== std::string::npos, "the GCC row, where CMake finds its own compiler, is not keyed");
} },
{ "cmake: cache off writes the uncached script",
with_subproject(t::row::windows_managed()),
[] {
mcpp::deps::cmake::options o;
o.cache = "off";
return cmake_use(o);
},
[](const t::result& r, t::checker& c) {
std::string script;
for (auto const& f : r.files_under("out/deps-cmake/greet"))
if (f.ends_with("greet.cmake")) script = r.file(f);
c.expect(!script.empty() && script.find("AN INSTALLATION IS BUILT ONCE") == std::string::npos,
"the script does not consult the cache");
} },
{ "cmake: another build system's output directory in a subproject is not part of the key",
with_foreign_output(with_subproject(t::row::windows_managed()), "pkg/greet", ".c"),
[] { return cmake_use(); },
[](const t::result& r, t::checker& c) {
std::string script;
for (auto const& f : r.files_under("out/deps-cmake/greet"))
if (f.ends_with("greet.cmake")) script = r.file(f);
const auto from = script.find("set(listed [==[");
const auto listed = from == std::string::npos ? std::string()
: script.substr(from, script.find("]==])", from) - from);
c.expect(listed.find("\nx64/simd.c\n") != std::string::npos, "a directory named x64 is listed");
c.expect(listed.find("\nnotag/kept.c\n") != std::string::npos,
"a CACHEDIR.TAG without the signature marks nothing");
c.expect(listed.find("x64/Release") == std::string::npos, "an MSBuild intermediate directory is not");
c.expect(listed.find("cmake-build-debug") == std::string::npos, "a CMake build directory is not");
c.expect(listed.find("cache/") == std::string::npos, "a tagged cache directory is not");
} },
// ── rules-qt ────────────────────────────────────────────────────────
{ "qt: moc and lupdate read the package's files, not another build system's output",
with_foreign_output(with_qt(t::row::linux_gcc()), "pkg", ".h"),
[] {
mcpp::rules::qt::options o;
o.root = "../qt";
o.modules = {"Core"};
o.i18n.ts = {"app.ts"};
o.i18n.update_sources = true;
return mcpp::rules::qt::compile(o) ? 0 : 1;
},
[](const t::result& r, t::checker& c) {
c.expect(r.exit_code == 0, "the member succeeds");
const auto u = r.line("mcpp:action=", {"qt:lupdate:app"});
c.expect(u.find("pkg/widget.h") != std::string::npos && u.find("pkg/x64/simd.h") != std::string::npos
&& u.find("pkg/notag/kept.h") != std::string::npos,
"lupdate reads the package's sources, a directory named x64 among them");
c.expect(u.find("x64/Release") == std::string::npos && u.find("cmake-build-debug") == std::string::npos
&& u.find("pkg/cache/") == std::string::npos,
"lupdate reads nothing another build system wrote");
c.expect(r.has_line("mcpp:action=", {"qt:moc:", "widget"}) && r.has_line("mcpp:action=", {"qt:moc:", "simd"}),
"moc reads the package's headers");
c.expect(!r.has_line("mcpp:action=", {"qt:moc:", "ui_form"}) && !r.has_line("mcpp:action=", {"qt:moc:", "stale"}),
"moc reads no header another build system wrote");
} },
// ── the tool resolver (0.19.0, mcpp#755) ────────────────────────────
//
// One order for every member: the build program's choice, the member's
// legacy variable, the engine's override, the declared payload. Each
// case states what the engine would answer and reads which source the
// resolver took -- and, for a choice, that no payload was requested.
{ "tool: a choice names the program and asks for no payload",
// Relative to the package root, which is where a build program's own
// option would name it.
t::row::linux_libcxx().file("pkg/opt/bin/cmake")
.xpkg("xim", "cmake", "{root}/payload")
.xpkg_source("xim", "cmake", "pending"),
[] {
const auto f = tool_resolve(mcpp::plugins::tool::program(
std::string(mcpp::manifest_dir()) + "/opt/bin/cmake"));
std::printf("source=%s program=%s\n",
std::string(mcpp::plugins::tool::name(f.source)).c_str(),
f.program.c_str());
return f ? 0 : 1;
},
[](const t::result& r, t::checker& c) {
c.expect(r.has_line("source=choice", "opt/bin/cmake"), "the choice is the source");
c.expect(!r.has_line("mcpp:xpkg-request="), "a choice requests no payload");
c.expect(r.has_line("mcpp:decision=tool:mcpp.deps.cmake:cmake", {"choice", "opt/bin/cmake"}),
"the decision names the choice and the program");
} },
{ "tool: a stated path resolves whichever executable suffix the file carries",
// What a shell hands a build program on Windows: `command -v cmake`
// answers a path with no `.exe`, for a `cmake.exe`. Process creation
// there appends the suffix, so the resolver does too -- stated on a
// Linux row, because the rule is the same on every host.
t::row::linux_libcxx().file("pkg/opt/bin/cmake.exe")
.xpkg_source("xim", "cmake", "pending"),
[] {
const auto f = tool_resolve(mcpp::plugins::tool::program(
std::string(mcpp::manifest_dir()) + "/opt/bin/cmake"));
std::printf("source=%s program=%s\n",
std::string(mcpp::plugins::tool::name(f.source)).c_str(),
f.program.c_str());
return f ? 0 : 1;
},
[](const t::result& r, t::checker& c) {
c.expect(r.has_line("source=choice", "opt/bin/cmake.exe"),
"the file beside the stated path answers");
c.expect(!r.has_line("mcpp:xpkg-request="), "a choice requests no payload");
} },
{ "tool: an override answers, and the payload is not consulted",
t::row::linux_libcxx().file("opt/bin/cmake")
.xpkg("xim", "cmake", "{root}/opt")
.xpkg_source("xim", "cmake", "override")
.xpkg_program("xim", "cmake", "{root}/opt/bin/cmake"),
[] {
const auto f = tool_resolve();
std::printf("source=%s program=%s\n",
std::string(mcpp::plugins::tool::name(f.source)).c_str(),
f.program.c_str());
return f ? 0 : 1;
},
[](const t::result& r, t::checker& c) {
c.expect(r.has_line("source=override", "opt/bin/cmake"), "the override is the source");
c.expect(!r.has_line("mcpp:xpkg-request="), "an override requests nothing");
} },
{ "tool: a payload declared on request is asked for, and nothing is planned",
t::row::linux_libcxx().xpkg_source("xim", "cmake", "pending"),
[] {
const auto f = tool_resolve();
std::printf("pending=%d program=%s\n", f.pending() ? 1 : 0, f.program.c_str());
return 0;
},
[](const t::result& r, t::checker& c) {
c.expect(r.has_line("pending=1 program="), "the resolver reports it is pending");
c.expect(r.has_line("mcpp:xpkg-request=xim:cmake"), "the payload is requested");
c.expect(!r.has_line("mcpp:decision="), "a pending payload decides nothing yet");
} },
{ "tool: the installed payload answers when nothing else does",
t::row::linux_libcxx().file("payload/bin/cmake")
.xpkg("xim", "cmake", "{root}/payload"),
[] {
const auto f = tool_resolve();
std::printf("source=%s program=%s\n",
std::string(mcpp::plugins::tool::name(f.source)).c_str(),
f.program.c_str());
return f ? 0 : 1;
},
[](const t::result& r, t::checker& c) {
c.expect(r.has_line("source=payload", "payload/bin/cmake"), "the payload is the source");
c.expect(r.has_line("mcpp:decision=tool:mcpp.deps.cmake:cmake", {"payload"}),
"the decision names the payload");
} },
{ "tool: nothing names it, and the refusal lists every way to",
t::row::linux_libcxx(),
[] {
const auto f = tool_resolve();
std::printf("%s\n", mcpp::plugins::tool::describe_missing(
mcpp::deps::cmake_spec("mcpp.deps.cmake"), f).c_str());
return 0;
},
[](const t::result& r, t::checker& c) {
c.expect(r.has_line("mcpp.deps.cmake: no cmake."), "it names the member and the tool");
c.expect(r.has_line(" consulted: options::cmake (not set)"), "it names the option");
c.expect(r.has_line(" o.cmake = "), "it names the build program's way");
c.expect(r.has_line(" [xlings.overrides] \"xim:cmake\" = \"/path\""),
"it names the manifest's way");
c.expect(r.has_line(" MCPP_XLINGS_OVERRIDE_XIM_CMAKE=/path"),
"it names the environment's way");
} },
{ "qt: rcc tracks the resource paths stored in XML text children",
with_qt(t::row::linux_gcc())
.file("qt/bin/rcc").file("qt/bin/rcc.exe")
.file("pkg/res/app.qrc",
"<RCC><qresource prefix=\"/\">\n"
" <file>assets/greeting.txt</file>\n"
" <file alias=\"sample.zip\"> \n"
" data/Sample<!-- split text -->Project.zip \n"
" </file>\n"
"</qresource></RCC>\n")
.file("pkg/res/assets/greeting.txt", "hello\n")
.file("pkg/res/data/SampleProject.zip", "resource contents\n"),
[] {
mcpp::rules::qt::options o;
o.root = "../qt";
o.modules = {"Core"};
o.resources = {"res/app.qrc"};
return mcpp::rules::qt::compile(o) ? 0 : 1;
},
[](const t::result& r, t::checker& c) {
c.expect(r.exit_code == 0, "the member succeeds");
const auto a = r.line("mcpp:action=", {"qt:rcc:app"});
c.expect(a.find("pkg/res/assets/greeting.txt") != std::string::npos,
"rcc tracks a resource relative to the qrc directory");
c.expect(a.find("pkg/res/data/SampleProject.zip") != std::string::npos,
"rcc tracks the aliased source after joining and trimming text children");
} },
// ── compatibility ───────────────────────────────────────────────────
{ "compat: program_compilers keeps its 0.16.0 answer and says until when",
t::row::linux_libcxx().file("llvm/bin/clang").file("llvm/bin/clang++"),
[] {
const auto cc = mcpp::deps::program_compilers();
std::printf("answer=%s\n", cc.cxx.c_str());
return cc ? 0 : 1;
},
[](const t::result& r, t::checker& c) {
c.expect(r.has_line("answer=", {"llvm/bin/clang++"}), "mcpp's clang++ on the libc++ row");
c.expect(r.has_line("mcpp:diagnostic=note", {"program_compilers is kept until 2027-03-28"}),
"the compatibility note is printed");
} },
});
}