@@ -225,6 +225,15 @@ std::string canonical_compile_flags(const mcpp::manifest::Manifest& m) {
225225 s += " ldflag:" ;
226226 s += flag;
227227 }
228+ // Per-glob flags (G4): full ordered serialization — glob + every list —
229+ // so editing any entry (or reordering) re-fingerprints the output dir.
230+ for (auto const & gf : m.buildConfig .globFlags ) {
231+ s += " globflags:" ; s += gf.glob ;
232+ for (auto const & f : gf.cflags ) { s += " gc:" ; s += f; }
233+ for (auto const & f : gf.cxxflags ) { s += " gxx:" ; s += f; }
234+ for (auto const & f : gf.asmflags ) { s += " gas:" ; s += f; }
235+ for (auto const & f : gf.defines ) { s += " gd:" ; s += f; }
236+ }
228237 return s;
229238}
230239
@@ -327,6 +336,31 @@ materialize_generated_files(const std::filesystem::path& root,
327336 return {};
328337}
329338
339+ // L1 cfg merge for ONE manifest (root or dependency): append the matching
340+ // conditional cflags/cxxflags/ldflags and sources (G1b) to its buildConfig.
341+ // Sources also update the legacy modules.sources mirror — the scanner walks
342+ // that. Conditional dependency maps are root-only and handled at the root
343+ // call site; a dependency's conditional configs otherwise evaluate the same
344+ // way (descriptor `target_cfg` must not be silently inert).
345+ void merge_conditional_build (mcpp::manifest::Manifest& m,
346+ const cfgpred::Ctx& ctx,
347+ std::string_view targetTriple)
348+ {
349+ for (auto const & cc : m.conditionalConfigs ) {
350+ if (!cfgpred::matches (cc.predicate , ctx, targetTriple)) continue ;
351+ m.buildConfig .cflags .insert (m.buildConfig .cflags .end (),
352+ cc.cflags .begin (), cc.cflags .end ());
353+ m.buildConfig .cxxflags .insert (m.buildConfig .cxxflags .end (),
354+ cc.cxxflags .begin (), cc.cxxflags .end ());
355+ m.buildConfig .ldflags .insert (m.buildConfig .ldflags .end (),
356+ cc.ldflags .begin (), cc.ldflags .end ());
357+ for (auto const & s : cc.sources ) {
358+ m.buildConfig .sources .push_back (s);
359+ m.modules .sources .push_back (s);
360+ }
361+ }
362+ }
363+
330364bool is_std_module (std::string_view name) {
331365 return name == " std" || name == " std.compat" ;
332366}
@@ -717,18 +751,14 @@ prepare_build(bool print_fingerprint,
717751 // flags append to buildConfig, mirroring the [profile] merge above.
718752 if (!m->conditionalConfigs .empty ()) {
719753 auto cc_ctx = cfgpred::context_for (overrides.target_triple );
754+ merge_conditional_build (*m, cc_ctx, overrides.target_triple );
720755 for (auto const & cc : m->conditionalConfigs ) {
721756 if (!cfgpred::matches (cc.predicate , cc_ctx, overrides.target_triple ))
722757 continue ;
723- m->buildConfig .cflags .insert (m->buildConfig .cflags .end (),
724- cc.cflags .begin (), cc.cflags .end ());
725- m->buildConfig .cxxflags .insert (m->buildConfig .cxxflags .end (),
726- cc.cxxflags .begin (), cc.cxxflags .end ());
727- m->buildConfig .ldflags .insert (m->buildConfig .ldflags .end (),
728- cc.ldflags .begin (), cc.ldflags .end ());
729758 // Conditional dependencies (Phase 1b): merge into the manifest maps
730759 // before dependency resolution so they resolve like any dep. insert()
731760 // keeps an existing unconditional entry (no silent override).
761+ // Root-only — a dependency's own conditional deps are out of scope.
732762 m->dependencies .insert (cc.dependencies .begin (), cc.dependencies .end ());
733763 m->devDependencies .insert (cc.devDependencies .begin (), cc.devDependencies .end ());
734764 m->buildDependencies .insert (cc.buildDependencies .begin (), cc.buildDependencies .end ());
@@ -962,6 +992,16 @@ prepare_build(bool print_fingerprint,
962992 // when its source/inputs/env change. Leaf-only: it cannot gate the top-level
963993 // dependency graph. Skipped under a cross --target (host program, host run).
964994 // See .agents/docs/2026-06-30-l3-build-mcpp-implementation-design.md.
995+ // Root [generated_files]: materialize before build.mcpp and the modgraph
996+ // scan so synthesized sources are globbed like any on-disk file. (The
997+ // per-dependency call sits in the dep resolution loop below; the root
998+ // manifest needs its own.)
999+ if (!m->buildConfig .generatedFiles .empty ()) {
1000+ if (auto r = materialize_generated_files (*root, *m); !r) {
1001+ return std::unexpected (r.error ());
1002+ }
1003+ }
1004+
9651005 if (auto bp = mcpp::build::run_build_program (
9661006 *m, *root, explicit_compiler, *tc, m->cppStandard .canonical ,
9671007 /* isCross=*/ !overrides.target_triple .empty ());
@@ -1563,6 +1603,15 @@ prepare_build(bool print_fingerprint,
15631603 " dependency '{}': {}" , depName, r.error ()));
15641604 }
15651605
1606+ // Dependency-side L1 cfg merge (flags + sources): a descriptor's
1607+ // `target_cfg` / a dep mcpp.toml's [target.'cfg(...)'.build] must
1608+ // evaluate here too — before its globs expand.
1609+ if (!manifest->conditionalConfigs .empty ()) {
1610+ merge_conditional_build (*manifest,
1611+ cfgpred::context_for (overrides.target_triple ),
1612+ overrides.target_triple );
1613+ }
1614+
15661615 return std::pair{effRoot, std::move (*manifest)};
15671616 };
15681617
@@ -2502,14 +2551,27 @@ prepare_build(bool print_fingerprint,
25022551 auto & bc = pkg.manifest .buildConfig ;
25032552 if (!bc.featureSources .empty ()) {
25042553 if (!includeDevDeps) {
2505- std::set<std::string> gated;
2554+ // glob → owned by at least one ACTIVE feature?
2555+ std::set<std::string> activeNow (active.begin (), active.end ());
2556+ std::map<std::string, bool > gated;
25062557 for (auto & [f, globs] : bc.featureSources )
2507- for (auto & g : globs) gated.insert (g);
2558+ for (auto & g : globs)
2559+ gated[g] = gated[g] || activeNow.contains (f);
25082560 auto drop = [&](std::vector<std::string>& v) {
25092561 std::erase_if (v, [&](const std::string& s) { return gated.contains (s); });
25102562 };
25112563 drop (bc.sources );
25122564 drop (pkg.manifest .modules .sources );
2565+ // Dropping the glob STRING is not enough: files it matches
2566+ // may still be covered by a broader base glob (the default
2567+ // src/** — the mcpp.toml G5 case). An inactive gate becomes
2568+ // a `!` exclusion so the gate actually gates; active gates
2569+ // are re-added below.
2570+ for (auto & [g, isActive] : gated) {
2571+ if (isActive || g.starts_with (" !" )) continue ;
2572+ bc.sources .push_back (" !" + g);
2573+ pkg.manifest .modules .sources .push_back (" !" + g);
2574+ }
25132575 }
25142576 std::set<std::string> activeSet (active.begin (), active.end ());
25152577 auto add = [](std::vector<std::string>& v, const std::string& g) {
0 commit comments