-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathtypes.cppm
More file actions
1348 lines (1264 loc) · 71.1 KB
/
Copy pathtypes.cppm
File metadata and controls
1348 lines (1264 loc) · 71.1 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
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// mcpp.manifest:types — shared manifest data model.
//
// Everything both descriptor formats (mcpp.toml, xpkg .lua) synthesize
// into, plus format-agnostic helpers. No parsing lives here.
export module mcpp.manifest.types;
import std;
import mcpp.pm.dep_spec; // M5.x pm/ subsystem refactor: DependencySpec lives here
import mcpp.pm.compat; // Legacy dependency-key compatibility helpers
import mcpp.pm.index_spec; // IndexSpec for [indices] section
import mcpp.platform;
export namespace mcpp::manifest {
// PR-R1 transitional: the dependency data model has moved into
// `mcpp.pm.dep_spec`. The aliases below keep `mcpp::manifest::DependencySpec`
// and `mcpp::manifest::kDefaultNamespace` available as before so existing
// callers (`cli.cppm`, `fetcher.cppm`, ...) compile unchanged. A later
// refactor PR will migrate call sites to reference `mcpp::pm::` directly
// and these aliases can disappear.
using DependencySpec = mcpp::pm::DependencySpec;
inline constexpr auto kDefaultNamespace = mcpp::pm::kDefaultNamespace;
inline constexpr auto kCompatNamespace = mcpp::pm::kCompatNamespace;
struct CppStandardConfig {
std::string canonical = "c++23";
std::string flag = "-std=c++23";
int level = 23;
bool gnuDialect = false;
// standard = "c++fly": latest level + all experimental gates the
// resolved toolchain supports (toolchain/cppfly.cppm owns the mapping).
bool experimental = false;
};
struct Package {
std::string name;
std::string namespace_; // xpkg V1 namespace field (0.0.6+); empty = infer from name
std::string version;
std::string standard = "c++23"; // C++ standard (M5.0: moved from [language])
std::string description;
std::string license;
std::vector<std::string> authors;
std::string repo;
std::vector<std::string> platforms; // declared supported platforms (CI matrix hint)
// Resolution source carried into machine-readable runtime provenance.
// Version dependencies use `index+<name>@<snapshot>`; path/git packages
// use their corresponding immutable-or-local source spelling. Parsing a
// standalone manifest leaves this empty; prepare_build fills it once the
// resolver knows which index/source actually answered.
std::string sourceProvenance;
};
struct Language {
std::string standard = "c++23";
bool modules = true;
bool importStd = true;
};
// Author-asserted scan result for one source glob (scan_overrides).
// Files matched by the glob bypass the M1 text scan entirely; the declared
// (provides, imports) enter the module graph directly. Sound because the
// declaration is verified against the compiler's own P1689 (.ddi) output
// at build time — assertion + verification instead of computation.
// Design: .agents/docs/2026-07-08-scanner-backend-abstraction-design.md §3-pre.
struct ScanOverride {
std::vector<std::string> provides; // module logical names the file exports
std::vector<std::string> imports; // module logical names the file imports
};
struct Modules {
std::vector<std::string> sources; // glob patterns
std::vector<std::string> exports_; // declared module names (optional)
bool strict = false;
// glob → declared scan result; every glob must match ≥1 source file.
std::map<std::string, ScanOverride> scanOverrides;
};
struct Target {
std::string name;
enum Kind { Library, Binary, SharedLibrary, TestBinary } kind;
std::string main; // for binary / test
std::string soname; // ABI name for shared libraries, e.g. libfoo.so.1
// Per-target compile flags. SCOPE: applied ONLY to this target's exclusive
// entry source (its `main`) — never to shared module/impl objects, which are
// compiled once and linked into every target (the build's compile-once model;
// see src/build/plan.cppm). `defines` are sugar desugared to `-D<x>` at plan
// time and applied to both the C and C++ entry compile. Use these for flags
// that are private to a binary's own entry (e.g. `-DBUILD_SERVER=1`,
// `-Wno-deprecated`); for divergence that must reach shared code, use a
// workspace member or a [features] knob instead.
std::vector<std::string> cflags;
std::vector<std::string> cxxflags;
std::vector<std::string> defines;
// Build gate: this target is emitted ONLY when every listed feature is
// active in the current build (otherwise it is silently skipped). Gate
// only — it does not activate features (use --features / [features].default).
std::vector<std::string> requiredFeatures;
};
// `DependencySpec` and `kDefaultNamespace` have moved to mcpp.pm.dep_spec.
// Aliases at the top of this file keep `mcpp::manifest::DependencySpec`
// resolvable for unchanged call sites.
// `[toolchain]` section per docs/21-toolchain-and-tools.md
// linux = "gcc@15.1.0"
// macos = "llvm@20"
// windows = "msvc@system"
// default = "gcc@15.1.0" (used when current platform isn't listed)
struct Toolchain {
std::map<std::string, std::string> byPlatform; // platform -> "pkg@ver"
// Returns the toolchain spec for a platform, falling back to "default".
std::optional<std::string> for_platform(std::string_view platform) const {
if (auto it = byPlatform.find(std::string(platform)); it != byPlatform.end()) {
return it->second;
}
if (auto it = byPlatform.find("default"); it != byPlatform.end()) {
return it->second;
}
return std::nullopt;
}
};
// `[build] flags = [{ glob = "...", ... }]` — per-glob compile flags (G4).
// A VECTOR, not a map: declaration order is application order (a later
// entry's flags land later on the command line, so GNU "last flag wins"
// gives it precedence over an earlier, broader glob). Private build flags —
// they never enter usage requirements / never propagate to consumers.
struct GlobFlags {
std::string glob; // matched against package-root-relative paths
std::vector<std::string> cflags; // C units (.c/.m)
std::vector<std::string> cxxflags; // C++ units (.cpp/.cc/.cxx/.cppm)
std::vector<std::string> asmflags; // assembly units (.S/.s via cc, .asm via nasm)
std::vector<std::string> defines; // desugars to -D on every matched unit kind
// #253: non-empty when this entry came from `features.<name>.flags` and was
// folded in at feature activation (prepare_build). Diagnostic context only
// (names the owning feature in the zero-hit warning); deliberately NOT part
// of the fingerprint — the active feature set is already fingerprinted via
// the -DMCPP_FEATURE_* cflags.
std::string featureOrigin;
};
// The additive build inputs — the ONLY things any conditional axis may
// contribute (#258).
//
// mcpp has two conditional axes: `[target.'cfg(...)']` (platform) and
// `[features.<name>]`. Each used to hand-pick which build fields it could
// carry, and they picked DIFFERENT subsets — cfg took cflags/cxxflags/
// ldflags/sources, features took sources/defines/flags. "Which build inputs
// may be contributed conditionally" was being decided twice, differently,
// which is exactly the architectural debt the batch ledger warns about.
//
// Membership here is the answer, and it is a type rather than a hand-kept
// list, so it cannot drift from the struct it describes. Two properties
// qualify a field:
//
// 1. APPENDING is its merge semantics. Scalars (linkage, cStandard, the
// profile knobs) would need last-wins override semantics — a different
// operation, and a separate design.
// 2. It is consumed AFTER the conditional merge point. `target` and
// `linkage` are consumed BEFORE it — indeed `target` SELECTS the triple
// the cfg predicate is evaluated against, so conditioning it is
// circular by construction.
//
// Two deliberate non-members worth naming, because their exclusion is about
// category rather than mechanics:
// • generatedFiles is a side-effecting materialization ACTION, not an
// input (and root/dep materialize on opposite sides of the merge — see
// the design doc; that ordering bug is tracked separately).
// • featureDefines are INTERFACE contributions that propagate along Public
// edges, not private build inputs. Per-glob `defines` (GlobFlags::defines
// below) are private and per-TU, so those DO belong here.
struct BuildInputs {
std::vector<std::string> sources; // glob patterns
std::vector<std::string> cflags;
std::vector<std::string> cxxflags;
std::vector<std::string> ldflags;
// #296: package-level preprocessor macros. Unlike per-target `defines`
// (which only reach the binary's own entry TU), these reach EVERY TU in
// the package — module interface units included — so they participate in
// the P1689 module scan, which is what makes a macro-guarded `import`
// resolvable. Desugared to `-D<x>` on both the C and C++ channels
// (fold_build_defines_into_flags in prepare.cppm) after the conditional
// merge and before the manifest is snapshotted into packages[] /
// fingerprinted. A member HERE rather than on BuildConfig so the cfg axis
// can carry it: `[target.'cfg(windows)'.build] defines = [...]` must work,
// and membership of this type is what guarantees it (see above).
std::vector<std::string> defines;
std::vector<GlobFlags> globFlags; // flags = [...] (ordered)
std::vector<std::filesystem::path> includeDirs; // relative to package root
// #249: emitted as -idirafter (searched after the toolchain's system dirs)
std::vector<std::filesystem::path> includeDirsAfter;
// ⭐⭐ WHICH OF `includeDirs` A CONSUMER MUST NOT RECEIVE.
//
// `publicUsage` has always taken `privateBuild`'s include directories
// ENTIRE, so a package is built from exactly the set it publishes. For
// almost every package those are the same set. For one that vendors a
// library with an internal header overlay they are not, and the difference
// reaches every consumer.
//
// `mcpplibs/openkal-musl` states the case in its own source
// (`port/include/features.h`), having found it three times:
//
// ⓘ THIS IS THE SECOND-BEST REMEDY. The first would be for a package to
// distinguish the directories it is BUILT FROM from the directories it
// PUBLISHES. Measured 2026-08-22: mcpp cannot express it.
//
// musl's build reaches its own declarations through `src/include`, whose
// headers define `hidden`, `weak` and `weak_alias` — names that mean
// something only to musl's own sources. Publishing that directory hands
// those macros to every consumer, and which consumer breaks on which name
// was discovered one at a time: a C++ one on `restrict`, then on linkage;
// a C one (compiler-rt) on `weak`, which it writes itself.
//
// ⚠️ A SUBSET OF `includeDirs`, NOT A SECOND LIST, AND THE REASON IS ORDER.
// The relative order of the two kinds is load-bearing: moving musl's
// internal directories after the public ones makes musl's OWN build find
// the public `<features.h>` first and fail with `unknown type name hidden`
// (measured, same file). Two arrays in TOML cannot express one order, so
// `includeDirs` stays the single ordered list and this one says which of
// its entries stop at the package boundary. An entry here that is not in
// `includeDirs` withholds nothing, and is reported as such rather than
// passing in silence.
std::vector<std::filesystem::path> privateIncludeDirs;
// What the `std` module source of a package that IS a standard library
// needs on its command line.
//
// A MEMBER OF THIS TYPE AND NOT OF THE MANIFEST, for the same reason
// `defines` is: membership here is what makes the cfg axis carry it. A
// package supplying one C++ runtime over SEVERAL C libraries needs
// different flags per C library — `-D_GNU_SOURCE` is right for musl and
// glibc and wrong for picolibc — and while this lived beside the package's
// identity there was no spelling for that difference.
std::vector<std::string> stdModuleFlags;
};
// The single additive merge. Every conditional axis folds through this, so
// "how does a contribution combine with the base" has one answer: append, in
// declaration order, which gives later entries GNU last-wins precedence.
inline void append(BuildInputs& dst, const BuildInputs& src) {
dst.sources.insert(dst.sources.end(), src.sources.begin(), src.sources.end());
dst.cflags.insert(dst.cflags.end(), src.cflags.begin(), src.cflags.end());
dst.cxxflags.insert(dst.cxxflags.end(), src.cxxflags.begin(), src.cxxflags.end());
dst.ldflags.insert(dst.ldflags.end(), src.ldflags.begin(), src.ldflags.end());
dst.defines.insert(dst.defines.end(), src.defines.begin(), src.defines.end());
dst.globFlags.insert(dst.globFlags.end(),
src.globFlags.begin(), src.globFlags.end());
dst.includeDirs.insert(dst.includeDirs.end(),
src.includeDirs.begin(), src.includeDirs.end());
dst.includeDirsAfter.insert(dst.includeDirsAfter.end(),
src.includeDirsAfter.begin(),
src.includeDirsAfter.end());
dst.privateIncludeDirs.insert(dst.privateIncludeDirs.end(),
src.privateIncludeDirs.begin(),
src.privateIncludeDirs.end());
dst.stdModuleFlags.insert(dst.stdModuleFlags.end(),
src.stdModuleFlags.begin(),
src.stdModuleFlags.end());
}
// A build-graph node declared by a build program (`mcpp:action=`).
//
// The architectural point (see
// .agents/docs/2026-08-05-build-mcpp-extensibility-architecture.md §3.1):
// build.mcpp answers "what does this build look like" — CONFIGURATION — and is
// a bad place to do WORK. Generating sources, linting, signing and packaging
// are work: they want to be incremental, parallel and attributable, which a
// once-per-prepare program can never be. So instead of DOING the work, the
// program DECLARES it, and it becomes an edge in the build graph.
//
// One primitive, four wirings. `role` is not four mechanisms — it is where
// the same edge's outputs attach:
//
// Source — outputs join the compile set (protoc, a transpiler)
// Check — outputs are a stamp; nothing consumes them (clang-tidy, a
// format or ABI check). Runs alongside compilation by default,
// because serialising every compile behind a linter is a cost
// nobody accepts and "the build still fails" is just as true.
// Object — outputs join the LINK set (a resource compiler, objcopy
// embedding a blob, a generated .def, a pre-built .o)
// Artifact — inputs are link outputs (codesign, packaging, size budgets)
//
// `Object` completes the table (mcpp#365). The other three attach to the
// compile inputs, to nothing, and to the link OUTPUTS — leaving the link
// INPUTS, the one attachment point a build graph obviously has, inexpressible.
// The consequence was not theoretical: a Windows resource could only reach the
// linker by naming a pre-built `.res` in `[build].ldflags`, where it is a flat
// string in the link command rather than a file in the graph — so editing the
// icon produced "ninja: no work to do". A missing attachment point does not
// stop people; it makes them route around the graph.
//
// INV-D, the constraint that makes this expressible at all: the declaration
// must name its OUTPUT FILES, not merely promise some. mcpp fixes the source
// set, the fingerprint, compile_commands.json and the module topo order during
// prepare, and all of them need to know which files exist. Content may arrive
// later; names may not.
struct BuildAction {
enum class Role { Source, Check, Object, Artifact };
std::string id; // diagnostics + edge naming
// Which package's `build.mcpp` declared this. Filled by the engine when
// actions are collected into the plan, NOT by the build program — the
// program does not know, and the engine already does.
//
// Load-bearing, not bookkeeping: the ordering edge an action needs is
// scoped to the declaring package, because `include_dir` colours only that
// package's own translation units. A build-wide ordering would express a
// dependency that does not exist and put it on the critical path of a
// build whose wall clock is dominated by one. Spelled the same way
// `CompileUnit::packageName` is (`qualified_package_name`), because the
// two are matched against each other.
std::string packageName;
Role role = Role::Source;
std::vector<std::string> inputs; // absolute or package-relative
std::vector<std::string> outputs; // ditto; declared, see INV-D
// Object only: which link units receive the outputs. Empty = every LINKED
// IMAGE of the declaring package — binary, shared library AND test binary.
// Test binaries are in the default set because they link the same library
// code: leaving them out made `mcpp build` pass and `mcpp test` fail with
// `undefined symbol` on the very symbol the action exists to provide. It is
// also the only workable default, because test link units are DISCOVERED
// from tests/*.cpp — their names are not in mcpp.toml, and a build.mcpp that
// spells one stops building under plain `mcpp build`, where it does not
// exist. (`[resources]` deliberately excludes them: an icon belongs to what
// the project ships, not to a test runner.)
//
// Artifact infers its target from `${mcpp.target_file:NAME}` appearing in
// its inputs; Object cannot, because it runs BEFORE the link and so has no
// link output to name. Naming the targets is the only honest option, and
// EVERY unknown name is an error — including one alongside a name that did
// match, which is the shape a typo actually takes.
std::vector<std::string> targets;
std::vector<std::string> command; // argv; NOT a shell string
// Serialised module facts for a generated OUTPUT, when it is a module
// interface. Same "declare instead of discover" trade `[modules].scan_overrides`
// already makes — and the reason a generated `.cppm` does not need its
// content to exist during prepare.
std::vector<std::string> provides;
std::vector<std::string> imports;
// Check only: make compilation wait for this to pass. Off by default.
bool blocking = false;
std::string description;
};
// `[resources]` — metadata and assets compiled INTO the produced artifact
// (mcpp#365).
//
// SCOPE. Today only PE targets consume this: `icon` becomes RT_GROUP_ICON and
// the version fields become an RT_VERSION resource. On ELF/Mach-O the whole
// section is INAPPLICABLE — not degraded, not skipped-with-a-warning: there is
// no consumer, the build is byte-identical, and nothing is said. That is why
// the section is spelled `[resources]` and not `[windows]`, and why it does not
// need (or accept) a `cfg(windows)` predicate: an icon is a cross-platform
// CONCEPT — only the file format and the embedding mechanism are per-OS — so a
// future macOS `.icns` / Linux `.desktop` consumer extends THIS section instead
// of splitting the axis three ways. It also could not live in the conditional
// channel: `[target.'cfg(...)'.build]` carries BuildInputs and nothing else.
//
// A DECLARED FILE THAT DOES NOT EXIST IS AN ERROR, deliberately, and this is a
// documented deviation from what #365 asked for. Every other declared input in
// mcpp behaves this way (`main = "..."` must match exactly one file,
// scan_overrides globs must match ≥1, a missing nasm is fatal), and "missing →
// silently skip" would institutionalise the very failure this feature exists to
// fix: a release binary shipping with no icon and no version metadata, with
// nothing in the build output saying so. Not wanting an icon is already
// expressible — delete the line.
struct ResourceVersionInfo {
std::string company; // default: [package].authors[0]
std::string product; // default: [package].name
std::string description; // default: [package].description
std::string copyright; // default: synthesised from authors/license
std::string originalFilename; // default: the produced file name
std::string internalName; // default: [package].name
bool empty() const {
return company.empty() && product.empty() && description.empty()
&& copyright.empty() && originalFilename.empty() && internalName.empty();
}
};
struct Resources {
std::filesystem::path icon; // e.g. "assets/app.ico"
std::vector<std::filesystem::path> files; // author-written .rc sources
// Escape hatch for the .rc input scanner: a file name reached through a
// macro (`1 ICON APP_ICON`) is invisible to it. mcpp names what it could not
// resolve and points here — same "declare when discovery is not enough"
// trade as [modules].scan_overrides.
std::vector<std::filesystem::path> extraInputs;
// Unset = the default rule: synthesise a version resource unless the author
// supplied their own .rc (in which case they own the resource ID space).
std::optional<bool> versionInfo;
ResourceVersionInfo info;
bool declared() const {
return !icon.empty() || !files.empty() || versionInfo.has_value()
|| !info.empty() || !extraInputs.empty();
}
// The 3-row rule from the design doc, in one place.
bool synthesize_version_info() const {
if (versionInfo.has_value()) return *versionInfo;
return files.empty();
}
};
// `[build]` section — tunables for the build backend.
//
// M5.0: now also carries `sources` (moved from [modules]) and `include_dirs`
// (new). Defaults are injected by load() after parse if these are empty.
//
// Inherits the additive inputs rather than nesting them: `buildConfig.cflags`
// is read in ~150 places, and a BuildConfig genuinely IS a set of build
// inputs plus the selection axis and resolved policy scalars.
struct BuildConfig : BuildInputs {
// How `mcpp run` / `mcpp test` execute an artifact this host cannot run,
// as an argv template (the artifact path is appended, or substituted for
// `{}`).
//
// On BuildConfig rather than only in `[target.<triple>].runner` because
// the value is MACHINE-SPECIFIC: the emulator lives in a package payload
// whose path carries a home and a version, so only a `build.mcpp` can
// compute it — and a build program writes into BuildConfig. A
// board-support package emitting `mcpp:runner=` is the intended producer;
// the manifest key remains the consumer's override.
//
// ⚠️ EXACTLY ONE provider among the dependencies. Two board-support
// packages both claiming to know how to run the artifact is a
// configuration error, not something to merge: appending would produce an
// argv that is neither one's, and it would fail at exec time with no
// indication of which package contributed which token.
std::vector<std::string> runner;
// Was `sources` WRITTEN, as opposed to merely being empty?
//
// Presence is semantic here for the same reason it is on
// `XlingsConfig::subosDeclared`: an absent key selects the default glob,
// while an explicit `sources = []` selects "compile nothing". A container
// alone cannot tell those apart, and until this flag existed it did not:
// `sources = []` and deleting the line produced byte-identical build
// graphs, so an author had NO spelling for "nothing".
//
// A binary distribution package needs that spelling. It ships prebuilt
// artifacts and, in the header-only shape, no compilable source at all —
// yet any file left under `src/` would be swept up by the default glob and
// compiled into the consumer's build, where it can collide with the very
// symbols the prebuilt library already defines.
//
// Deliberately on BuildConfig and not on BuildInputs: the conditional axis
// (`[target.'cfg(...)'.build]`) only ever APPENDS sources, so "declared
// empty" has no meaning there — it is the same as contributing nothing.
bool sourcesDeclared = false;
// `[build] jobs` — how many compiles to run at once. A decimal count,
// "auto", or empty (the default) meaning "let the backend decide".
//
// Kept as TEXT rather than a number so that "auto" survives into the build
// that actually runs: resolving it at parse time would freeze one machine's
// core count into a value that then travels with the manifest.
std::string jobs;
// `[build] bmi_schedule` — when the BMI becomes visible to importers:
// "auto" (default), "on", "off".
//
// "on" publishes each module's BMI as soon as it exists and moves code
// generation onto a separate edge, so downstream units stop waiting for
// work they do not need. The per-compiler strategy that implements it
// (`detach-codegen` for gcc, `two-phase` for clang) is chosen by
// mcpp.build.schedule::decide and reported by `mcpp build --verbose`.
//
// NAMED FOR WHAT IT SCHEDULES. It was `schedule`, which said only that
// something was being scheduled — and disagreed with its own environment
// override, `MCPP_BMI_SCHEDULE`. The two spellings now match.
//
// Text for the same reason `jobs` is: the meaning of "auto" depends on the
// compiler doing the build, and resolving it at parse time would freeze one
// machine's answer into a manifest that travels.
std::string bmiSchedule;
// feature name → extra source globs gated by that feature. A glob listed
// here is EXCLUDED from the default build and only compiled/linked when the
// feature is active for this package (resolved in prepare_build). Lets a
// dependency expose an optional component (e.g. gtest's gtest_main.cc behind
// the "main" feature) without it being linked by default — see
// .agents/docs/2026-06-25-gtest-main-feature-and-add-dev-design.md.
std::map<std::string, std::vector<std::string>> featureSources;
// feature name → package-owned preprocessor defines (e.g. "-DEIGEN_USE_BLAS").
// Feature System v2 Stage 1: when the feature is active these are appended to
// the package's compile flags alongside the automatic -DMCPP_FEATURE_<NAME>
// (resolved in prepare_build). Restricted by convention to the package's own
// namespaced macros — features do NOT inject free-form cflags/ldflags, which
// would break feature-union composition. See
// .agents/docs/2026-06-29-feature-capability-model-design.md.
std::map<std::string, std::vector<std::string>> featureDefines;
// #253: feature name → per-glob compile flags gated by that feature. Same
// ordered GlobFlags model as `globFlags` below; when the feature is active
// the entries are appended AFTER the base globFlags (prepare_build), so a
// feature rule wins over a broader base rule via "last flag wins". Lets a
// feature's group-specific flags co-locate with its sources (e.g. opencv
// dnn's mlas defines) instead of living as base rules whose globs go dead
// on feature-off builds. Private per-TU flags — never propagate (contrast
// featureDefines above, which are interface switches).
std::map<std::string, std::vector<GlobFlags>> featureFlags;
// [build] module_extensions — extra file extensions this package's module
// INTERFACES use, on top of the built-in `.cppm`. Additive and opt-in:
// `.ccm` / `.cxxm` / `.ixx` are NOT built in, because widening the
// built-in set also widens the default source glob, which would make a
// published package with a vendored MSVC-only `.ixx` start compiling it on
// the next mcpp upgrade — a break its author cannot fix.
//
// Consumed through mcpp.source_kind (never read raw): the table it builds
// decides the graph shape, so this vector is part of the fingerprint.
// Scoped to the declaring package — a dependency is classified by its own
// manifest, never by its consumer's.
std::vector<std::string> moduleExtensions;
// [build] build_program_timeout — seconds this package's build.mcpp may
// run before mcpp kills it. 0 = no limit; nullopt = use the built-in 600.
//
// `optional` is load-bearing, not style. With a plain `int` the default
// value would have to be 0, which MEANS "no limit" — so every project that
// never mentions the key would silently lose its run bound.
//
// Deliberately NOT part of the fingerprint: it changes no edge in the
// graph, and folding it in would make raising a timeout rebuild the whole
// project — the opposite of what someone raising a timeout wants.
std::optional<int> buildProgramTimeoutSecs;
std::map<std::filesystem::path, std::string> generatedFiles; // Form B package-owned support files
// Build-graph nodes declared by this package's build program
// (`mcpp:action=`). Empty for every package that does not use one, so an
// ordinary build is untouched.
std::vector<BuildAction> actions;
bool staticStdlib = true;
// #336 — the C++ runtime DISTRIBUTION contract: what the artifact promises
// about the machine that runs it ("self-contained" | "toolchain-coupled" |
// "host-coupled"). Empty = unset, in which case `staticStdlib` supplies it
// (true → self-contained, false → host-coupled), which is exactly what that
// flag has always been documented to mean.
//
// Why a separate field rather than widening the bool: the bool spells a
// MECHANISM ("statically link the stdlib") and expanded into three
// different per-platform meanings — including a silent no-op on
// Linux/libc++, where it produced a toolchain-coupled artifact while
// claiming to be static. The contract spells the INTENT, and
// build/distribution.cppm maps intent to mechanism in one total function.
std::string cxxRuntime;
// Per-role override for test binaries. Empty = follow `cxxRuntime`.
// Tests are the one role whose contract legitimately diverges: they never
// leave the build machine, so "link the host's runtime" is a defensible
// choice there and an indefensible one for a shipped artifact.
std::string cxxRuntimeTests;
// Per-role override for shared libraries. Empty = the role default, which
// on ELF is toolchain-coupled (see `dist::default_contract`): a .so that
// embeds its own libstdc++ exports it into the process's single global
// symbol namespace and becomes the executable's C++ runtime by accident.
// Setting this to "self-contained" is supported and additionally emits
// `--exclude-libs` so the escape hatch cannot re-open that.
std::string cxxRuntimeShared;
// "" (default = dynamic), "static", "dynamic" — chosen at resolve
// time from --static / --target / [target.<triple>].linkage. Wired
// through to ninja backend as the `-static` link flag.
std::string linkage;
// [build] target = "<triple>" — the project's default build target
// (≙ cargo's build.target). Used when no --target flag is passed;
// "default to fully-static musl" belongs here, not in a toolchain name
// (static output is a product property, not a compiler-family property).
std::string target;
// M5.x C-language support: `cStandard` controls -std= for the C compile
// rule (.c files); empty → backend default ("c11" today). The cflags /
// cxxflags / ldflags vectors themselves live in BuildInputs above.
// Dialect-class C++ flags: flags that change what the standard library's
// headers DECLARE or participate in module dialect checks (issue #210's
// -freflection: libstdc++'s <meta> is gated on __cpp_impl_reflection).
// These are module-graph-global — they ride -std='s channels (global
// cxxflags for every TU incl. deps, the std/std.compat BMI prebuild,
// scan commands). Populated from [build] dialect_cxxflags plus
// auto-promotion of known flags found in [build] cxxflags
// (see dialect_flags()).
std::vector<std::string> dialectCxxflags;
std::string cStandard;
// Escape hatch for the hermetic link check: a sandbox toolchain whose
// CRT/loader resolve OUTSIDE the sandbox is a hard error by default
// (silent host contamination, or issue #195's bare-CRT link failure);
// set true to deliberately link against host libraries.
// MCPP_ALLOW_HOST_LIBS=1 is the per-invocation equivalent.
bool allowHostLibs = false;
// macOS minimum supported OS version for produced binaries
// (LC_BUILD_VERSION minos), e.g. "14.0". Mirrors the ecosystem
// conventions around deployment targets (the MACOSX_DEPLOYMENT_TARGET
// env var that cargo/rustc/cc honor; SwiftPM's `platforms:` manifest
// field; CMAKE_OSX_DEPLOYMENT_TARGET). Precedence: the env var (an
// explicit per-invocation override) wins over this manifest default;
// empty + no env = toolchain/SDK default. No effect off macOS.
std::string macosDeploymentTarget;
// Resolved build-profile knobs (from [profile.<name>] + built-in defaults).
std::string optLevel = "2"; // -O level
bool debug = false; // -g
bool lto = false; // -flto
bool strip = false; // link -s
// `[build].default-profile` (alias: `profile`) — the project's DEFAULT
// profile when no --profile/--dev/--release is passed. The global convention
// default stays "release"; this lets a project opt its plain `mcpp build`
// into e.g. "dev" without typing --profile. Precedence: --profile/--dev/
// --release flag > [build].default-profile > "release". NOTE (distribution
// footgun): a project that defaults to dev should pass `--profile release`
// when producing a distributable (a pack-time release guard is a follow-up).
std::string defaultProfile;
// `[build] dependency_linkage` — "static" (default) | "shared" (#519).
//
// How this build wants its DEPENDENCIES to arrive: merged into the images
// that use them, or as separate shared libraries beside them. A separate
// axis from `[target.<triple>].linkage`, which answers the same-sounding
// question about the C LIBRARY — and they are not independent, because a
// statically linked image cannot load a shared object at all
// (mcpp.build.linkage_form).
//
// A SCALAR, so it is deliberately absent from the `cfg(...)` channel:
// that channel appends, and this needs last-wins. Overridable per profile
// and per dependency edge. Empty = "static", which is byte-for-byte what
// mcpp did before the key existed.
std::string dependencyLinkage;
// `[build] cache` — "global" (default) | "local" | "off". Project-level
// default for the global dependency cache; --cache and MCPP_BUILD_CACHE
// both override it. Validated in prepare_build (unknown value: warning, or
// error under --strict) rather than here, so parsing a manifest never
// depends on the build-mode vocabulary.
std::string cacheMode;
};
// Canonical package identity used by runtime requirements/artifacts. A short
// name is never sufficient here: two indices may legitimately contain the
// same name, and provenance must still be attributable after the build.
struct PackageId {
std::string namespace_;
std::string name;
std::string version;
std::string sourceProvenance;
std::string canonical() const {
std::string out;
if (!namespace_.empty()) {
out += namespace_;
out += '.';
}
out += name;
if (!version.empty()) {
out += '@';
out += version;
}
return out;
}
auto operator<=>(const PackageId&) const = default;
};
inline PackageId package_id(const Package& package) {
PackageId out;
out.namespace_ = package.namespace_.empty()
? std::string(kDefaultNamespace) : package.namespace_;
out.name = package.name;
const auto prefix = out.namespace_ + ".";
if (out.name.starts_with(prefix) && out.name.size() > prefix.size())
out.name.erase(0, prefix.size());
out.version = package.version;
out.sourceProvenance = package.sourceProvenance;
return out;
}
// Provider-neutral runtime facts. `requester`/`provider` are resolver-owned:
// descriptors declare the generic fact, then BuildPlan stamps the exact
// PackageId that supplied it. This prevents a package from spoofing another
// package's identity and keeps same-short-name providers distinguishable.
struct RuntimeRequirement {
std::string kind;
std::string value;
std::string phase = "run"; // link | run
// How the loader finds whatever satisfies this, e.g. "rpath-of-dispatch",
// "json-dir", "glvnd-dispatch". DECLARED, never inferred by mcpp: the
// mechanism is a property of the provider's ecosystem, and inferring it
// from the capability name would put provider-specific knowledge in mcpp
// (`test_runtime_contract` gates exactly that).
//
// It earns its place because the mechanisms are not interchangeable: an
// EGL vendor is found through a JSON file whose library_path is ABSOLUTE,
// while GLX is found through the dispatch library's own DT_RPATH — so
// "copy the directory across" satisfies one and not the other. Empty means
// "not declared", which is reported as unknown rather than guessed.
std::string discovery;
PackageId requester;
bool required = true;
};
struct RuntimeArtifact {
std::string role;
PackageId provider;
std::filesystem::path path;
std::string provenance;
std::string abi;
std::string digest;
std::string hostFingerprint;
};
// Platform-neutral link intent. Platform spelling belongs to flags.cppm;
// notably runtimeSearchDirs are not link-library search paths.
struct LinkIntent {
std::vector<std::string> libraries;
std::vector<std::filesystem::path> linkLibraryDirs;
std::vector<std::filesystem::path> transitiveNeededDirs;
std::vector<std::filesystem::path> runtimeSearchDirs;
std::vector<std::string> frameworks;
std::vector<std::filesystem::path> deployFiles;
};
// `[runtime]` — requirements needed when linking/launching built binaries.
struct RuntimeConfig {
std::vector<std::filesystem::path> libraryDirs; // relative to package root
std::vector<std::string> dlopenLibs; // runtime-loaded sonames
std::vector<std::string> capabilities; // host/system capabilities REQUIRED
// Capabilities this package explicitly FULFILS. Only this field creates a
// descriptor-owned provider fact; legacy `capabilities` is a requirement
// and can never promote its requester into a provider.
std::vector<std::string> provides;
// [runtime.<capability>] provider = "<pkg>" — explicit provider selection
// (the three-tier knob: default/auto → explicit override).
std::map<std::string, std::string> providerOverrides;
// New structured contract. The four legacy vectors above remain readable
// for one compatibility train and are normalized by BuildPlan.
std::vector<RuntimeRequirement> requirements;
std::vector<RuntimeArtifact> artifacts;
LinkIntent linkIntent;
};
// `[xlings]` — the project's build ENVIRONMENT (L-1). The subsection names mirror
// xlings' own `.xlings.json` schema 1:1, so mcpp materializes them verbatim into
// `<proj>/.mcpp/.xlings.json` (no translation layer): `deps` (host build-tools
// installed by xlings), `[xlings.workspace]` (tool→version pins, the general form
// of `[toolchain]`), `subos` (a named per-project sandbox), `[xlings.envs]`
// (env vars applied by xvm shims). See
// .agents/docs/2026-06-29-manifest-environment-and-platform-design.md (L-1).
struct XlingsConfig {
std::vector<std::string> deps; // → .xlings.json "deps"
std::map<std::string, std::string> workspace; // → "workspace" (tool → version)
std::string subos; // → "subos" (named project sandbox)
// Presence is semantic: an absent key selects McppDefault, while an
// explicitly written `subos = "default"` selects NamedSubos("default").
// A string alone cannot distinguish absence from an invalid empty value.
bool subosDeclared = false;
std::map<std::string, std::string> envs; // → "envs" (env var → value)
bool empty() const {
return deps.empty() && workspace.empty() && !subosDeclared && envs.empty();
}
};
// `[target.<triple>]` — per-target overrides.
// Picked up when caller passes --target <triple> to build/run/test.
struct TargetEntry {
std::string toolchain; // e.g. "gcc@15.1.0-musl"; empty = inherit [toolchain]
std::string linkage; // "static" | "dynamic" | "" (= auto by libc)
// How `mcpp run` executes an artifact for this target when the artifact
// cannot run on this machine (a freestanding image: wrong ISA, no loader).
// A TEMPLATE and never a default — which emulator, which machine model and
// which firmware mode are board facts, and an engine that guesses one is an
// engine a different board has to fight. The artifact path is appended, or
// substituted for `{}` when the template contains it.
std::vector<std::string> runner;
// #336 — per-target C++ runtime contract, same vocabulary as
// [build].cxx_runtime and overriding it for this triple. It lives HERE,
// beside `linkage`, rather than in the `cfg(...)` conditional channel:
// both describe what the produced artifact depends on at run time, both
// are resolved before the build inputs are merged, and the conditional
// channel deliberately carries build INPUTS and nothing else
// (ConditionalConfig). One axis, one scoping rule.
std::string cxxRuntime;
// The target's C library, overriding the `sysroot` column of the target
// table for this triple. Same axis as `toolchain` overriding `pin`: one
// names the compiler the target resolves, the other names the C library,
// and both were engine-only until a project had a reason to disagree.
//
// ⚠️ TWO MEMBERS AND NOT AN `std::optional<std::string>`, AND THE REASON IS
// NOT STYLE.
//
// ABSENT and EMPTY are different answers — absent inherits the target row,
// `sysroot = ""` is the ZERO-LIBC tier — so a plain string alone cannot
// carry the distinction. An optional can, and was the first version.
//
// But an `std::optional<std::string>` DATA MEMBER of an exported struct
// forces this module's interface to materialise that specialisation's
// special-member machinery, and under clang with the MSVC standard library
// that broke every downstream translation unit constructing one:
//
// MSVC\include\optional:307: error: no matching constructor for
// initialization of '_SMF_control<_Optional_construct_base<basic_string…
//
// The errors named test files the change never touched — the signature of a
// std type in a newly-exported interface poisoning the importers' module
// files rather than failing where it was written. `std::optional<std::string>`
// already appeared in this module as a RETURN type without incident; a
// member is what forces the instantiation.
//
// Two plain members carry the same information and instantiate nothing.
std::string sysroot;
bool sysrootDeclared = false;
// ⚠️ NO per-role field here. There used to be a `cxxRuntimeTests`, and it was
// parsed nowhere and applied nowhere — a configuration key that looked
// available and did nothing (#418). The per-target channel carries the
// SCALAR contract only; `[build].cxx_runtime`'s table form already covers
// the role split, and an unsupported key in `[target.<triple>]` is now
// reported rather than dropped.
};
// `[target.'cfg(...)'.build]` — platform-conditional build flags (L1). The
// predicate is the raw `[target.<predicate>]` key (e.g. `cfg(windows)`,
// `cfg(all(linux, not(arch="aarch64")))`, or a bare triple). It is stored
// DEFERRED here because manifest parsing is target-agnostic; prepare_build
// evaluates it against the RESOLVED target (host triple for a native build,
// the --target triple for a cross build) and merges matching flags into
// buildConfig. See .agents/docs/2026-06-29-manifest-environment-and-platform-design.md.
struct ConditionalConfig {
std::string predicate; // the [target.<predicate>] key
// Everything `[target.<pred>.build]` may contribute, and nothing else.
//
// Previously four hand-listed vectors, which is why per-glob `flags` was
// inexpressible here while the xpkg descriptor's `mcpp.<os>` sections
// supported it (#258): the conditional reader maintained its own subset
// of [build]'s keys and nobody noticed it had fallen behind. Carrying the
// BuildInputs type instead means the set cannot drift, and a key outside
// it — `linkage`, `target`, a profile knob — is simply not a member, so
// it cannot silently parse into a field nothing downstream reads.
//
// Conditional source globs (G1b) live in `inputs.sources`: appended to
// [build].sources when the predicate matches the resolved target — the
// declarative gate for arch-specific code (x86 .asm on x86 targets only).
// `!`-exclusion globs work there too (the scanner handles positive+
// negative sets).
BuildInputs inputs;
// `[target.<sel>.runtime]` — the DIALECT-NEUTRAL half of a link line.
//
// `inputs.ldflags` above is spelled the GNU way and native `cl.exe` rejects
// `-L`. These two keys say the same thing without committing to a spelling,
// and `render_link_intent_flags` renders them as `/LIBPATH:` + `<n>.lib` or
// `-L` + `-l<n>` depending on the target. A generated package carries BOTH,
// because an older mcpp reads only the first — see the note where they are
// merged for why the newer client must then IGNORE the ldflags rather than
// add to them.
std::vector<std::filesystem::path> linkLibraryDirs;
std::vector<std::string> libraries;
// Conditional dependencies (Phase 1b): merged into the corresponding
// manifest maps in prepare_build when the predicate matches the resolved
// target — before dependency resolution, so they resolve like any dep.
std::map<std::string, DependencySpec> dependencies;
std::map<std::string, DependencySpec> devDependencies;
std::map<std::string, DependencySpec> buildDependencies;
// #359: `[target.<sel>.feature-deps.<feature>]`. The conditional channel
// carried three of the four dependency maps and silently lacked the
// fourth, which is the exact failure this struct's `BuildInputs` comment
// above describes for #258 — the conditional reader kept its own subset of
// the keys and fell behind without anyone noticing.
//
// It is load-bearing for build-time provisions: a library that declares a
// host tool behind a feature (`grpc`'s `codegen` pulling protoc) has no
// other way to say "not on this platform", and an unconditional
// declaration turns an unsupported platform into a hard error raised from
// inside the LIBRARY's manifest, which its user cannot work around.
std::map<std::string, std::map<std::string, DependencySpec>> featureDeps;
};
// `[lib]` — library "root" interface convention.
//
// Convention-over-configuration: a library package's primary module
// interface lives at `src/<package-tail>.cppm`, where `<package-tail>` is
// the last dotted segment of `[package].name` (e.g. `mcpplibs.tinyhttps`
// → `src/tinyhttps.cppm`). That file declares `export module
// <full-package-name>;` and re-exports the public partitions. The lib
// root then drives:
// * `[modules].exports` default (the lib root's module = the only
// externally-visible base module),
// * `mcpp publish` xpkg generation (consumer just `import <name>;`),
// * downstream tooling (docs / explain) entry point.
//
// Override the convention with `[lib].path = "src/foo.cppm"` (cargo-style)
// — the file must still `export module <package-name>;` (no partition).
//
// Lib-root is only meaningful for projects that ship a `kind = "lib"`
// target. Pure-binary projects (mcpp itself, scaffolded `mcpp new`)
// don't trigger any lib-root checks.
struct LibConfig {
std::filesystem::path path; // explicit override; empty = use convention
};
// `[pack]` — `mcpp pack` configuration. See docs/35-pack-design.md.
//
// `default_mode` picks the bundling strategy when the user runs bare
// `mcpp pack` (no `--mode` flag):
// "static" — full musl static, no PT_INTERP / RUNPATH
// "bundle-project" — bundle only project's third-party .so (default)
// "bundle-all" — bundle every dynamic dep including libc / libstdc++
struct PackConfig {
std::string defaultMode; // empty → "bundle-project"
// ⚠️ THERE IS DELIBERATELY NO `[pack] profile`. Which profile `mcpp pack`
// builds with is `--profile` > `[build] default-profile` > "release" —
// packaging only changes the LAST step (from "dev"), because a fourth
// precedence level would have to be resolved before `prepare_build` runs
// and this manifest is what `prepare_build` produces.
// Strip the SHIPPED artifacts (not a link-time `-s`; see mcpp.pack.strip).
// Tri-state: unset = the default (strip), which is what a published binary
// wants. `false` ships the artifact exactly as built.
std::optional<bool> strip;
// Where the separated `*.debug` files go, package-root-relative or
// absolute. Empty = do not separate, which is the default: most publishers
// do not ship a debug package, and writing one by default would double the
// output of every `mcpp pack`.
std::string debugSymbols;
std::vector<std::string> include; // extra files/globs to ship
std::vector<std::string> exclude; // patterns to drop from include
// Mode C overrides — let the user expand or contract the PEP 600
// skip list when their target distros differ from the default
// assumption ("modern desktop Linux").
std::vector<std::string> alsoSkip; // libs to ALSO skip on top of PEP 600
std::vector<std::string> forceBundle; // libs to bundle even if PEP 600 says skip
};
// `[workspace]` — multi-package workspace support (0.0.11+).
//
// A workspace root mcpp.toml declares member packages. Members share
// a unified lock file, target directory, and can inherit dependency
// versions via `.workspace = true`.
//
// Virtual workspace (no [package]): pure management node.
// Rooted workspace ([package] + [workspace]): root is also a package.
struct WorkspaceConfig {
std::vector<std::string> members; // relative paths to member dirs
std::vector<std::string> exclude; // paths to exclude
std::map<std::string, DependencySpec> dependencies; // [workspace.dependencies]
bool present = false;
};
// [profile.<name>] — bundled build settings (opt level, debug, lto, strip).
struct Profile {
std::string optLevel = "2";
bool debug = false;
bool lto = false;
bool strip = false;
// `dependency_linkage`, per profile (#519).
//
// OPTIONAL, and that is load-bearing rather than stylistic: resolving a
// profile REPLACES the whole struct with the declared one, so a plain
// value would make `[profile.dev] opt = 0` silently reset a
// `[build] dependency_linkage = "shared"` back to the field default.
// Absent means "whatever [build] said".
std::optional<std::string> dependencyLinkage;
// Passthrough escape hatch (fixed keys, open values — I6 completeness):
std::vector<std::string> cflags;
std::vector<std::string> cxxflags;
std::vector<std::string> ldflags;
};
struct Manifest {
std::filesystem::path sourcePath; // mcpp.toml's filesystem path
// Unknown top-level keys silently skipped while synthesizing from an
// xpkg mcpp segment — surfaced as warnings by `mcpp xpkg parse` so
// schema evolution is loud in lint instead of invisible.
std::vector<std::string> xpkgUnknownKeys;
// ⚠️ CAPABILITY NAMES INSIDE THE RESERVED `mcpp:` PREFIX THAT THIS ENGINE
// DOES NOT KNOW, AND WHY THEY ARE RECORDED RATHER THAN REFUSED HERE.
//
// The reserved prefix is a closed set so that a misspelled layer name is an
// error instead of a silently disabled behaviour. Refusing at PARSE time
// made the set closed in a second, unintended sense: a package declaring a
// layer added after the reader was released failed to load AT ALL, so the
// vocabulary could never be extended by a published package.
//
// Measured 2026-08-24, `openkal-llvm-runtime` declaring the newly named
// compiler-runtime layer, read by the release before it:
//
// error: dependency 'openkal-llvm-runtime': mcpp.toml: error:
// `provides = ["mcpp:compiler-runtime=compiler-rt"]` names no
// capability mcpp knows.
//
// Whose manifest it is decides the answer. A name in the ROOT project's own
// manifest is the author's to fix and they are looking at the build — an
// error. A name in a DEPENDENCY's manifest was written against a newer
// engine, and the correct response is to ignore the layer and say so, which
// is what this engine already does for every other unknown key.
std::vector<std::string> unknownCapabilities;
Package package;
Language language;
Modules modules;
std::vector<Target> targets;
// version-string keyed dependencies (M2 short form only).
std::map<std::string, DependencySpec> dependencies;
std::map<std::string, DependencySpec> devDependencies;
std::map<std::string, DependencySpec> buildDependencies; // host-side tools (M5+ behavior)
Toolchain toolchain; // optional; empty == fallback
BuildConfig buildConfig;
Resources resources; // [resources] (mcpp#365)
RuntimeConfig runtimeConfig;
XlingsConfig xlings; // [xlings] build environment (L-1)
std::vector<ConditionalConfig> conditionalConfigs; // [target.'cfg(...)'.build], deferred