2121#include < fstream>
2222#include < memory>
2323#include < string>
24+ #include < unordered_map>
2425#include < unordered_set>
2526
2627namespace fs = std::filesystem;
@@ -31,6 +32,12 @@ namespace cps::search {
3132
3233 using version::to_string;
3334
35+ // / @brief Details about how a required componenet should be used
36+ struct ComponentDetails {
37+ // / @brief If this component is only required for linking, not compiling
38+ bool link_only;
39+ };
40+
3441 // / @brief A CPS file, along with the components in that CPS file to
3542 // / load
3643 class Dependency {
@@ -40,7 +47,7 @@ namespace cps::search {
4047 // / @brief The loaded CPS file
4148 loader::Package package;
4249 // / @brief the components from that CPS file to use
43- std::vector <std::string> components;
50+ std::unordered_map <std::string, ComponentDetails > components;
4451 };
4552
4653 // / @brief A DAG node
@@ -375,65 +382,86 @@ namespace cps::search {
375382 // / @brief Calculate the required components in the graph
376383 // / @param node The node to process
377384 // / @param components the components required from this node
378- void set_components (std::shared_ptr<Node> node, const std::vector<std::string> & components,
379- bool default_components) {
380- // If this package needs the default_components, then add to the list of used components
385+ void set_components (std::shared_ptr<Node> node, std::vector<std::string> components, bool default_components,
386+ bool link_only = false ) {
387+ const auto & component_updater = [&node, &link_only](std::string name) {
388+ if (const auto & entry = node->data .components .find (name); entry != node->data .components .end ()) {
389+ entry->second .link_only |= link_only;
390+ } else {
391+ node->data .components .emplace (std::move (name), ComponentDetails{link_only});
392+ }
393+ };
394+
395+ // Set the components that this package's dependees want
381396 if (default_components && node->data .package .default_components ) {
382- const std::vector<std::string> & defs = node->data .package .default_components .value ();
383- node-> data . components .insert (node-> data . components .end (), defs.begin (), defs.end ());
397+ const auto & defs = node->data .package .default_components .value ();
398+ components.insert (components.end (), defs.begin (), defs.end ());
384399 }
385400 // Then add all of the explicitly listed components
386- node-> data . components . insert (node-> data . components .end (), components. begin (), components.end ());
401+ std::for_each ( components.begin (), components.end (), component_updater );
387402
388403 // Handle self requirements first
389404 // This takes the form `"requires": [":a", ":b"]`
390405 // These must be handled before child dependencies, as they may alter the requirements placed on the
391406 // children..
392- std::vector<std::string> self_requires = node->data .components ;
407+ std::vector<std::pair<std::string, ComponentDetails>> self_requires;
408+ std::transform (node->data .components .begin (), node->data .components .end (),
409+ std::back_insert_iterator (self_requires),
410+ [](std::pair<std::string, ComponentDetails> entry) { return entry; });
393411 std::unordered_set<std::string> processed;
394412 bool self_defaults = default_components;
395413 while (!self_requires.empty ()) {
396- const std::string c_name = self_requires.back ();
414+ const auto [this_name, this_comp] = self_requires.back ();
397415 self_requires.pop_back ();
398- if (processed.find (c_name ) != processed.end ()) {
416+ if (processed.find (this_name ) != processed.end ()) {
399417 continue ;
400418 }
401- processed.emplace (c_name );
419+ processed.emplace (this_name );
402420
403- const loader::Component & component = node->data .package .components .at (c_name );
421+ const loader::Component & component = node->data .package .components .at (this_name );
404422 auto && required = process_requires (component.require );
405423 if (auto && self = required.find (" " ); self != required.end ()) {
406424 // Don't insert these twice
425+ std::vector<std::string> self_comps = self->second .components ;
407426 if (!self_defaults && self->second .defaults && node->data .package .default_components ) {
408427 self_defaults = true ;
409428 const std::vector<std::string> & defs = node->data .package .default_components .value ();
410- node-> data . components . insert (node-> data . components .end (), defs.begin (), defs.end ());
429+ self_comps. insert (self_comps .end (), defs.begin (), defs.end ());
411430 }
412- for (auto && comp : self->second .components ) {
431+ std::for_each (self_comps.begin (), self_comps.end (), component_updater);
432+
433+ for (auto && comp : self_comps) {
413434 if (processed.find (comp) != processed.end ()) {
414435 continue ;
415436 }
416- self_requires.emplace_back (comp);
417- node->data .components .emplace_back (comp);
437+ self_requires.emplace_back (std::make_pair (comp, ComponentDetails{link_only}));
418438 }
419439 }
420440 }
421441
422442 // Walk the list of components for this component, adding component
423443 // requirements recursively for external requirements.
424- for (const std::string & c_name : node->data .components ) {
444+ for (const auto & [this_name, this_comp] : node->data .components ) {
425445 // It's possible that the Package::Requires section listed
426446 // dependencies we don't actually need. If we don't need them we
427447 // can trim the graph
428448 std::vector<std::shared_ptr<Node>> trimmed;
429449
430450 // This *should* be validated such that we won't have an exception
431- const loader::Component & component = node->data .package .components .at (c_name );
451+ const loader::Component & component = node->data .package .components .at (this_name );
432452 auto && required = process_requires (component.require );
433453 for (std::shared_ptr<Node> & child : node->depends ) {
434454 if (auto && child_comps = required.find (child->data .package .name ); child_comps != required.end ()) {
435455 trimmed.emplace_back (child);
436- set_components (child, child_comps->second .components , child_comps->second .defaults );
456+ set_components (child, child_comps->second .components , child_comps->second .defaults , link_only);
457+ }
458+ }
459+ auto && link_required = process_requires (component.link_requires );
460+ for (std::shared_ptr<Node> & child : node->depends ) {
461+ if (auto && child_comps = link_required.find (child->data .package .name );
462+ child_comps != link_required.end ()) {
463+ trimmed.emplace_back (child);
464+ set_components (child, child_comps->second .components , child_comps->second .defaults , true );
437465 }
438466 }
439467 node->depends = trimmed;
@@ -482,27 +510,30 @@ namespace cps::search {
482510 return p;
483511 };
484512
485- for (const auto & c_name : node->data .components ) {
513+ for (const auto & [comp_name, cps_comp] : node->data .components ) {
486514 // We should have already errored if this is not the case
487- auto && f = node->data .package .components .find (c_name );
515+ auto && f = node->data .package .components .find (comp_name );
488516 utils::assert_fn (
489517 f != node->data .package .components .end (),
490- fmt::format (" Could not find component {} of package {}" , c_name , node->data .package .name ));
518+ fmt::format (" Could not find component {} of package {}" , comp_name , node->data .package .name ));
491519 auto && comp = f->second ;
492520
493521 // Convert prefix at this point because:
494522 // 1. we are about to lose which CPS file the information came
495523 // from
496524 // 2. if we do it at the search point we have to plumb overrides
497525 // deep into that
498- merge_result<loader::KnownLanguages, fs::path>(comp.includes , result.includes , prefix_replacer);
499- merge_result (comp.definitions , result.definitions );
500- merge_result (comp.compile_flags , result.compile_flags );
526+ if (!cps_comp.link_only ) {
527+ merge_result<loader::KnownLanguages, fs::path>(comp.includes , result.includes , prefix_replacer);
528+ merge_result (comp.definitions , result.definitions );
529+ merge_result (comp.compile_flags , result.compile_flags );
530+ }
501531 merge_result (comp.link_libraries , result.link_libraries );
502532 merge_result (comp.link_flags , result.link_flags );
503533 if (comp.type != loader::Type::interface) {
504534 if (!comp.location ) {
505- return tl::make_unexpected (fmt::format (" Component `{}` requires 'location' attribute" , c_name));
535+ return tl::make_unexpected (
536+ fmt::format (" Component `{}` requires 'location' attribute" , comp_name));
506537 }
507538 result.link_location .emplace_back (
508539 prefix_replacer (comp.link_location .value_or (comp.location .value ())));
0 commit comments