@@ -3148,7 +3148,34 @@ prepare_build(bool print_fingerprint,
31483148 for (auto & f : activateFeatures (pm, requested, seedDefault)) {
31493149 auto it = pm.featureDeps .find (f);
31503150 if (it == pm.featureDeps .end ()) continue ;
3151- for (auto & [k, spec] : it->second ) pm.dependencies .try_emplace (k, spec);
3151+ for (auto & [k, spec] : it->second ) {
3152+ auto [pos, fresh] = pm.dependencies .try_emplace (k, spec);
3153+ if (fresh) continue ;
3154+ // #359: the key already exists unconditionally, and dropping
3155+ // the feature's spec here loses REQUESTS the feature exists to
3156+ // make. gRPC is the shape: it depends on compat.protobuf
3157+ // always, and its `codegen` feature has to add
3158+ // `tools = ["protoc"], reexport = true` to that same edge —
3159+ // which is precisely what must NOT be paid for by a consumer
3160+ // who did not ask for codegen, so moving it to the
3161+ // unconditional entry is not an option either.
3162+ //
3163+ // Additive fields merge; identity fields (version/path/git) do
3164+ // not, keeping "a conditional section never silently
3165+ // overrides an unconditional one" intact. Same rule the
3166+ // per-edge feature request already follows.
3167+ auto & dst = pos->second ;
3168+ for (auto const & t : spec.tools )
3169+ if (std::find (dst.tools .begin (), dst.tools .end (), t)
3170+ == dst.tools .end ())
3171+ dst.tools .push_back (t);
3172+ for (auto const & f2 : spec.features )
3173+ if (std::find (dst.features .begin (), dst.features .end (), f2)
3174+ == dst.features .end ())
3175+ dst.features .push_back (f2);
3176+ dst.hostModule = dst.hostModule || spec.hostModule ;
3177+ dst.reexport = dst.reexport || spec.reexport ;
3178+ }
31523179 }
31533180 };
31543181
0 commit comments