Skip to content

Commit b679ea5

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 da54e19 commit b679ea5

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
@@ -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())));

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)