@@ -1157,6 +1157,7 @@ struct BuildOverrides {
11571157 bool force_static = false ; // --static (or implied by musl target)
11581158 std::string package_filter; // -p <name>: only build this workspace member
11591159 std::string profile; // --profile <name> (default "release")
1160+ std::string features; // --features a,b,c (root package activation)
11601161};
11611162
11621163// `prepare_build` builds the BuildContext for any verb that compiles.
@@ -2885,6 +2886,68 @@ prepare_build(bool print_fingerprint,
28852886
28862887 computeUsageRequirements ();
28872888
2889+ // ─── Feature activation (Cargo-style, additive) ────────────────────
2890+ // activated(pkg) = pkg.[features].default ∪ features requested for it
2891+ // (root: --features; deps: the root dep spec's `features = [...]`).
2892+ // Implied features expand transitively. Each active feature becomes
2893+ // -DMCPP_FEATURE_<NAME> on that package's compile flags.
2894+ // (Transitive dep→dep feature requests are not yet propagated.)
2895+ {
2896+ auto sanitize = [](std::string f) {
2897+ for (auto & c : f)
2898+ c = std::isalnum (static_cast <unsigned char >(c))
2899+ ? static_cast <char >(std::toupper (static_cast <unsigned char >(c))) : ' _' ;
2900+ return f;
2901+ };
2902+ auto activate = [](const mcpp::manifest::Manifest& pm,
2903+ const std::vector<std::string>& requested) {
2904+ std::vector<std::string> act, q;
2905+ if (auto it = pm.featuresMap .find (" default" ); it != pm.featuresMap .end ())
2906+ q.insert (q.end (), it->second .begin (), it->second .end ());
2907+ q.insert (q.end (), requested.begin (), requested.end ());
2908+ std::set<std::string> seen;
2909+ while (!q.empty ()) {
2910+ auto f = q.back (); q.pop_back ();
2911+ if (f == " default" || !seen.insert (f).second ) continue ;
2912+ act.push_back (f);
2913+ if (auto it = pm.featuresMap .find (f); it != pm.featuresMap .end ())
2914+ q.insert (q.end (), it->second .begin (), it->second .end ());
2915+ }
2916+ return act;
2917+ };
2918+ auto apply = [&](mcpp::modgraph::PackageRoot& pkg,
2919+ const std::vector<std::string>& requested) {
2920+ for (auto & f : activate (pkg.manifest , requested)) {
2921+ auto def = " -DMCPP_FEATURE_" + sanitize (f);
2922+ pkg.manifest .buildConfig .cflags .push_back (def);
2923+ pkg.manifest .buildConfig .cxxflags .push_back (def);
2924+ pkg.privateBuild .cflags .push_back (def);
2925+ pkg.privateBuild .cxxflags .push_back (def);
2926+ }
2927+ };
2928+ if (!packages.empty ()) {
2929+ std::vector<std::string> rootReq;
2930+ for (std::size_t p = 0 ; p < overrides.features .size ();) {
2931+ auto c = overrides.features .find_first_of (" , " , p);
2932+ auto tok = overrides.features .substr (
2933+ p, c == std::string::npos ? std::string::npos : c - p);
2934+ if (!tok.empty ()) rootReq.push_back (tok);
2935+ if (c == std::string::npos) break ;
2936+ p = c + 1 ;
2937+ }
2938+ apply (packages[0 ], rootReq);
2939+ }
2940+ for (std::size_t i = 1 ; i < packages.size (); ++i) {
2941+ auto & pname = packages[i].manifest .package .name ;
2942+ std::vector<std::string> req;
2943+ for (auto & [dname, dspec] : m->dependencies ) {
2944+ if (dname == pname || dspec.shortName == pname) { req = dspec.features ; break ; }
2945+ }
2946+ if (!req.empty () || packages[i].manifest .featuresMap .contains (" default" ))
2947+ apply (packages[i], req);
2948+ }
2949+ }
2950+
28882951 // Modgraph: regex scanner by default; opt-in to compiler-driven P1689
28892952 // scanner via env var MCPP_SCANNER=p1689 (see docs/27).
28902953 auto scan = [&] {
@@ -3547,6 +3610,7 @@ int cmd_build(const mcpplibs::cmdline::ParsedArgs& parsed) {
35473610 if (auto t = parsed.value (" target" )) ov.target_triple = *t;
35483611 if (auto p = parsed.value (" package" )) ov.package_filter = *p;
35493612 if (auto pr = parsed.value (" profile" )) ov.profile = *pr;
3613+ if (auto fs = parsed.value (" features" )) ov.features = *fs;
35503614 ov.force_static = parsed.is_flag_set (" static" );
35513615
35523616 // P0: try fast-path if inputs haven't changed.
@@ -5595,6 +5659,8 @@ int run(int argc, char** argv) {
55955659 .help (" Build only the named workspace member" ))
55965660 .option (cl::Option (" profile" ).takes_value ().value_name (" NAME" )
55975661 .help (" Build profile: release (default) | dev | dist | <[profile.*] name>" ))
5662+ .option (cl::Option (" features" ).takes_value ().value_name (" LIST" )
5663+ .help (" Activate root-package features (comma-separated)" ))
55985664 .action (wrap_rc (cmd_build)))
55995665 .subcommand (cl::App (" run" )
56005666 .description (" Build + run a binary target (after `--`, args are passed to it)" )
0 commit comments