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
@@ -376,65 +383,86 @@ namespace cps::search {
376383 // / @brief Calculate the required components in the graph
377384 // / @param node The node to process
378385 // / @param components the components required from this node
379- void set_components (std::shared_ptr<Node> node, const std::vector<std::string> & components,
380- bool default_components) {
381- // If this package needs the default_components, then add to the list of used components
386+ void set_components (std::shared_ptr<Node> node, std::vector<std::string> components, bool default_components,
387+ bool link_only = false ) {
388+ const auto & component_updater = [&node, &link_only](std::string name) {
389+ if (const auto & entry = node->data .components .find (name); entry != node->data .components .end ()) {
390+ entry->second .link_only |= link_only;
391+ } else {
392+ node->data .components .emplace (std::move (name), ComponentDetails{link_only});
393+ }
394+ };
395+
396+ // Set the components that this package's dependees want
382397 if (default_components && node->data .package .default_components ) {
383- const std::vector<std::string> & defs = node->data .package .default_components .value ();
384- node-> data . components .insert (node-> data . components .end (), defs.begin (), defs.end ());
398+ const auto & defs = node->data .package .default_components .value ();
399+ components.insert (components.end (), defs.begin (), defs.end ());
385400 }
386401 // Then add all of the explicitly listed components
387- node-> data . components . insert (node-> data . components .end (), components. begin (), components.end ());
402+ std::for_each ( components.begin (), components.end (), component_updater );
388403
389404 // Handle self requirements first
390405 // This takes the form `"requires": [":a", ":b"]`
391406 // These must be handled before child dependencies, as they may alter the requirements placed on the
392407 // children..
393- std::vector<std::string> self_requires = node->data .components ;
408+ std::vector<std::pair<std::string, ComponentDetails>> self_requires;
409+ std::transform (node->data .components .begin (), node->data .components .end (),
410+ std::back_insert_iterator (self_requires),
411+ [](std::pair<std::string, ComponentDetails> entry) { return entry; });
394412 std::unordered_set<std::string> processed;
395413 bool self_defaults = default_components;
396414 while (!self_requires.empty ()) {
397- const std::string c_name = self_requires.back ();
415+ const auto [this_name, this_comp] = self_requires.back ();
398416 self_requires.pop_back ();
399- if (processed.find (c_name ) != processed.end ()) {
417+ if (processed.find (this_name ) != processed.end ()) {
400418 continue ;
401419 }
402- processed.emplace (c_name );
420+ processed.emplace (this_name );
403421
404- const loader::Component & component = node->data .package .components .at (c_name );
422+ const loader::Component & component = node->data .package .components .at (this_name );
405423 auto && required = process_requires (component.require );
406424 if (auto && self = required.find (" " ); self != required.end ()) {
407425 // Don't insert these twice
426+ std::vector<std::string> self_comps = self->second .components ;
408427 if (!self_defaults && self->second .defaults && node->data .package .default_components ) {
409428 self_defaults = true ;
410429 const std::vector<std::string> & defs = node->data .package .default_components .value ();
411- node-> data . components . insert (node-> data . components .end (), defs.begin (), defs.end ());
430+ self_comps. insert (self_comps .end (), defs.begin (), defs.end ());
412431 }
413- for (auto && comp : self->second .components ) {
432+ std::for_each (self_comps.begin (), self_comps.end (), component_updater);
433+
434+ for (auto && comp : self_comps) {
414435 if (processed.find (comp) != processed.end ()) {
415436 continue ;
416437 }
417- self_requires.emplace_back (comp);
418- node->data .components .emplace_back (comp);
438+ self_requires.emplace_back (std::make_pair (comp, ComponentDetails{link_only}));
419439 }
420440 }
421441 }
422442
423443 // Walk the list of components for this component, adding component
424444 // requirements recursively for external requirements.
425- for (const std::string & c_name : node->data .components ) {
445+ for (const auto & [this_name, this_comp] : node->data .components ) {
426446 // It's possible that the Package::Requires section listed
427447 // dependencies we don't actually need. If we don't need them we
428448 // can trim the graph
429449 std::vector<std::shared_ptr<Node>> trimmed;
430450
431451 // This *should* be validated such that we won't have an exception
432- const loader::Component & component = node->data .package .components .at (c_name );
452+ const loader::Component & component = node->data .package .components .at (this_name );
433453 auto && required = process_requires (component.require );
434454 for (std::shared_ptr<Node> & child : node->depends ) {
435455 if (auto && child_comps = required.find (child->data .package .name ); child_comps != required.end ()) {
436456 trimmed.emplace_back (child);
437- set_components (child, child_comps->second .components , child_comps->second .defaults );
457+ set_components (child, child_comps->second .components , child_comps->second .defaults , link_only);
458+ }
459+ }
460+ auto && link_required = process_requires (component.link_requires );
461+ for (std::shared_ptr<Node> & child : node->depends ) {
462+ if (auto && child_comps = link_required.find (child->data .package .name );
463+ child_comps != link_required.end ()) {
464+ trimmed.emplace_back (child);
465+ set_components (child, child_comps->second .components , child_comps->second .defaults , true );
438466 }
439467 }
440468 node->depends = trimmed;
@@ -485,27 +513,30 @@ namespace cps::search {
485513 return s;
486514 };
487515
488- for (const auto & c_name : node->data .components ) {
516+ for (const auto & [comp_name, cps_comp] : node->data .components ) {
489517 // We should have already errored if this is not the case
490- auto && f = node->data .package .components .find (c_name );
518+ auto && f = node->data .package .components .find (comp_name );
491519 utils::assert_fn (
492520 f != node->data .package .components .end (),
493- fmt::format (" Could not find component {} of package {}" , c_name , node->data .package .name ));
521+ fmt::format (" Could not find component {} of package {}" , comp_name , node->data .package .name ));
494522 auto && comp = f->second ;
495523
496524 // Convert prefix at this point because:
497525 // 1. we are about to lose which CPS file the information came
498526 // from
499527 // 2. if we do it at the search point we have to plumb overrides
500528 // deep into that
501- merge_result<loader::KnownLanguages, std::string>(comp.includes , result.includes , prefix_replacer);
502- merge_result (comp.definitions , result.definitions );
503- merge_result (comp.compile_flags , result.compile_flags );
529+ if (!cps_comp.link_only ) {
530+ merge_result<loader::KnownLanguages, std::string>(comp.includes , result.includes , prefix_replacer);
531+ merge_result (comp.definitions , result.definitions );
532+ merge_result (comp.compile_flags , result.compile_flags );
533+ }
504534 merge_result (comp.link_libraries , result.link_libraries );
505535 merge_result (comp.link_flags , result.link_flags );
506536 if (comp.type != loader::Type::interface) {
507537 if (!comp.location ) {
508- return tl::make_unexpected (fmt::format (" Component `{}` requires 'location' attribute" , c_name));
538+ return tl::make_unexpected (
539+ fmt::format (" Component `{}` requires 'location' attribute" , comp_name));
509540 }
510541 result.link_location .emplace_back (
511542 prefix_replacer (comp.link_location .value_or (comp.location .value ())));
0 commit comments