Skip to content

Commit 36e859d

Browse files
committed
search: implement link_requires
We have to track whether a component is a link only component, and the only add the link flags.
1 parent bd9cc04 commit 36e859d

5 files changed

Lines changed: 115 additions & 26 deletions

File tree

src/cps/search.cpp

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <fstream>
2222
#include <memory>
2323
#include <string>
24+
#include <unordered_map>
2425
#include <unordered_set>
2526

2627
namespace 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())));

tests/cases/cps-config.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,15 @@ name = "requires component from self nested"
133133
cps = "full"
134134
args = ["flags", "--component", "requires-self", "--cflags", "--print-errors"]
135135
expected = "-fvectorize -I/usr/local/include -I/opt/include -DBAR=2 -DFOO=1 -DOTHER"
136+
137+
[[case]]
138+
name = "link requires nested"
139+
cps = "link-requires"
140+
args = ["flags", "--component", "nested", "--cflags", "--libs", "--print-errors"]
141+
expected = "-lfake"
142+
143+
[[case]]
144+
name = "same component twice"
145+
cps = "multiple-components"
146+
args = ["flags", "--component", "same-component-twice", "--cflags", "--print-errors"]
147+
expected = "-I/something"

tests/cases/pkg-config-compat.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,9 @@ name = "parsing pc file"
5959
cps = "pc-variables"
6060
args = ["pkg-config", "--cflags"]
6161
expected = "-I/home/kaniini/pkg/include/libfoo"
62+
63+
[[case]]
64+
name = "link requires"
65+
cps = "link-requires"
66+
args = ["pkg-config", "--cflags", "--libs", "--print-errors"]
67+
expected = "-L/usr/lib/ -flto -l/something/lib/libfoo.so -lbar"
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "link-requires",
3+
"cps_version": "0.13.0",
4+
"version": "1.0.0",
5+
"requires": {
6+
"full": null,
7+
"multiple-components": null
8+
},
9+
"prefix": "/prefix",
10+
"components": {
11+
"default": {
12+
"type": "interface",
13+
"link_requires": [
14+
"full"
15+
]
16+
},
17+
"nested": {
18+
"type": "interface",
19+
"link_requires": [
20+
"multiple-components:link-requires"
21+
]
22+
}
23+
},
24+
"default_components": [
25+
"default"
26+
]
27+
}

tests/cps-files/lib/cps/multiple-components.cps

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,19 @@
6868
"requires": [
6969
"minimal:sample0"
7070
]
71+
},
72+
"link-requires": {
73+
"type": "interface",
74+
"link_requires": [
75+
"minimal:sample1"
76+
]
77+
},
78+
"same-component-twice": {
79+
"type": "interface",
80+
"requires": [
81+
":sample3",
82+
":sample4"
83+
]
7184
}
7285
},
7386
"default_components": [

0 commit comments

Comments
 (0)