Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement the Component::link_requires #116

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/cps/loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,8 @@ namespace cps::loader {
.value_or(std::vector<std::string>{});
auto const location = CPS_TRY(get_optional<std::string>(comp, name, "location"));
auto const link_location = CPS_TRY(get_optional<std::string>(comp, name, "link_location"));
auto const link_requires = CPS_TRY(get_optional<std::vector<std::string>>(comp, name, "link_requires"))
.value_or(std::vector<std::string>{});
auto const require = CPS_TRY(get_optional<std::vector<std::string>>(comp, name, "requires"))
.value_or(std::vector<std::string>{});

Expand All @@ -268,6 +270,7 @@ namespace cps::loader {
.definitions = std::move(definitions),
.link_flags = std::move(link_flags),
.link_libraries = std::move(link_libraries),
.link_requires = std::move(link_requires),
.location = std::move(location),
.link_location = std::move(link_location),
.require = std::move(require),
Expand Down
2 changes: 1 addition & 1 deletion src/cps/loader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ namespace cps::loader {
std::vector<std::string> link_flags;
// TODO: std::vector<LinkLanguage> link_languages;
std::vector<std::string> link_libraries;
// TODO: link_requires
std::vector<std::string> link_requires;
std::optional<std::string> location;
std::optional<std::string> link_location;
std::vector<std::string> require; // requires is a keyword
Expand Down
1 change: 1 addition & 0 deletions src/cps/pc_compat/pc_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ namespace cps::pc_compat {
.definitions = loader::Defines{},
.link_flags = link_flags,
.link_libraries = {},
.link_requires = {},
// TODO: Currently lib location is hard coded to appease assertions. This would
// need to implement linker-like search to replicate current behavior.
.location = fmt::format("@prefix@/lib/{}.a", name),
Expand Down
83 changes: 57 additions & 26 deletions src/cps/search.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <fstream>
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>

namespace fs = std::filesystem;
Expand All @@ -31,6 +32,12 @@ namespace cps::search {

using version::to_string;

/// @brief Details about how a required componenet should be used
struct ComponentDetails {
/// @brief If this component is only required for linking, not compiling
bool link_only;
};

/// @brief A CPS file, along with the components in that CPS file to
/// load
class Dependency {
Expand All @@ -40,7 +47,7 @@ namespace cps::search {
/// @brief The loaded CPS file
loader::Package package;
/// @brief the components from that CPS file to use
std::vector<std::string> components;
std::unordered_map<std::string, ComponentDetails> components;
};

/// @brief A DAG node
Expand Down Expand Up @@ -376,65 +383,86 @@ namespace cps::search {
/// @brief Calculate the required components in the graph
/// @param node The node to process
/// @param components the components required from this node
void set_components(std::shared_ptr<Node> node, const std::vector<std::string> & components,
bool default_components) {
// If this package needs the default_components, then add to the list of used components
void set_components(std::shared_ptr<Node> node, std::vector<std::string> components, bool default_components,
bool link_only = false) {
const auto & component_updater = [&node, &link_only](std::string name) {
if (const auto & entry = node->data.components.find(name); entry != node->data.components.end()) {
entry->second.link_only |= link_only;
} else {
node->data.components.emplace(std::move(name), ComponentDetails{link_only});
}
};

// Set the components that this package's dependees want
if (default_components && node->data.package.default_components) {
const std::vector<std::string> & defs = node->data.package.default_components.value();
node->data.components.insert(node->data.components.end(), defs.begin(), defs.end());
const auto & defs = node->data.package.default_components.value();
components.insert(components.end(), defs.begin(), defs.end());
}
// Then add all of the explicitly listed components
node->data.components.insert(node->data.components.end(), components.begin(), components.end());
std::for_each(components.begin(), components.end(), component_updater);

// Handle self requirements first
// This takes the form `"requires": [":a", ":b"]`
// These must be handled before child dependencies, as they may alter the requirements placed on the
// children..
std::vector<std::string> self_requires = node->data.components;
std::vector<std::pair<std::string, ComponentDetails>> self_requires;
std::transform(node->data.components.begin(), node->data.components.end(),
std::back_insert_iterator(self_requires),
[](std::pair<std::string, ComponentDetails> entry) { return entry; });
std::unordered_set<std::string> processed;
bool self_defaults = default_components;
while (!self_requires.empty()) {
const std::string c_name = self_requires.back();
const auto [this_name, this_comp] = self_requires.back();
self_requires.pop_back();
if (processed.find(c_name) != processed.end()) {
if (processed.find(this_name) != processed.end()) {
continue;
}
processed.emplace(c_name);
processed.emplace(this_name);

const loader::Component & component = node->data.package.components.at(c_name);
const loader::Component & component = node->data.package.components.at(this_name);
auto && required = process_requires(component.require);
if (auto && self = required.find(""); self != required.end()) {
// Don't insert these twice
std::vector<std::string> self_comps = self->second.components;
if (!self_defaults && self->second.defaults && node->data.package.default_components) {
self_defaults = true;
const std::vector<std::string> & defs = node->data.package.default_components.value();
node->data.components.insert(node->data.components.end(), defs.begin(), defs.end());
self_comps.insert(self_comps.end(), defs.begin(), defs.end());
}
for (auto && comp : self->second.components) {
std::for_each(self_comps.begin(), self_comps.end(), component_updater);

for (auto && comp : self_comps) {
if (processed.find(comp) != processed.end()) {
continue;
}
self_requires.emplace_back(comp);
node->data.components.emplace_back(comp);
self_requires.emplace_back(std::make_pair(comp, ComponentDetails{link_only}));
}
}
}

// Walk the list of components for this component, adding component
// requirements recursively for external requirements.
for (const std::string & c_name : node->data.components) {
for (const auto & [this_name, this_comp] : node->data.components) {
// It's possible that the Package::Requires section listed
// dependencies we don't actually need. If we don't need them we
// can trim the graph
std::vector<std::shared_ptr<Node>> trimmed;

// This *should* be validated such that we won't have an exception
const loader::Component & component = node->data.package.components.at(c_name);
const loader::Component & component = node->data.package.components.at(this_name);
auto && required = process_requires(component.require);
for (std::shared_ptr<Node> & child : node->depends) {
if (auto && child_comps = required.find(child->data.package.name); child_comps != required.end()) {
trimmed.emplace_back(child);
set_components(child, child_comps->second.components, child_comps->second.defaults);
set_components(child, child_comps->second.components, child_comps->second.defaults, link_only);
}
}
auto && link_required = process_requires(component.link_requires);
for (std::shared_ptr<Node> & child : node->depends) {
if (auto && child_comps = link_required.find(child->data.package.name);
child_comps != link_required.end()) {
trimmed.emplace_back(child);
set_components(child, child_comps->second.components, child_comps->second.defaults, true);
}
}
node->depends = trimmed;
Expand Down Expand Up @@ -485,27 +513,30 @@ namespace cps::search {
return s;
};

for (const auto & c_name : node->data.components) {
for (const auto & [comp_name, cps_comp] : node->data.components) {
// We should have already errored if this is not the case
auto && f = node->data.package.components.find(c_name);
auto && f = node->data.package.components.find(comp_name);
utils::assert_fn(
f != node->data.package.components.end(),
fmt::format("Could not find component {} of package {}", c_name, node->data.package.name));
fmt::format("Could not find component {} of package {}", comp_name, node->data.package.name));
auto && comp = f->second;

// Convert prefix at this point because:
// 1. we are about to lose which CPS file the information came
// from
// 2. if we do it at the search point we have to plumb overrides
// deep into that
merge_result<loader::KnownLanguages, std::string>(comp.includes, result.includes, prefix_replacer);
merge_result(comp.definitions, result.definitions);
merge_result(comp.compile_flags, result.compile_flags);
if (!cps_comp.link_only) {
merge_result<loader::KnownLanguages, std::string>(comp.includes, result.includes, prefix_replacer);
merge_result(comp.definitions, result.definitions);
merge_result(comp.compile_flags, result.compile_flags);
}
merge_result(comp.link_libraries, result.link_libraries);
merge_result(comp.link_flags, result.link_flags);
if (comp.type != loader::Type::interface) {
if (!comp.location) {
return tl::make_unexpected(fmt::format("Component `{}` requires 'location' attribute", c_name));
return tl::make_unexpected(
fmt::format("Component `{}` requires 'location' attribute", comp_name));
}
result.link_location.emplace_back(
prefix_replacer(comp.link_location.value_or(comp.location.value())));
Expand Down
12 changes: 12 additions & 0 deletions tests/cases/cps-config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,15 @@ name = "requires component from self nested"
cps = "full"
args = ["flags", "--component", "requires-self", "--cflags", "--print-errors"]
expected = "-fvectorize -I/usr/local/include -I/opt/include -DBAR=2 -DFOO=1 -DOTHER"

[[case]]
name = "link requires nested"
cps = "link-requires"
args = ["flags", "--component", "nested", "--cflags", "--libs", "--print-errors"]
expected = "-lfake"

[[case]]
name = "same component twice"
cps = "multiple-components"
args = ["flags", "--component", "same-component-twice", "--cflags", "--print-errors"]
expected = "-I/something"
6 changes: 6 additions & 0 deletions tests/cases/pkg-config-compat.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,9 @@ name = "parsing pc file"
cps = "pc-variables"
args = ["pkg-config", "--cflags"]
expected = "-I/home/kaniini/pkg/include/libfoo"

[[case]]
name = "link requires"
cps = "link-requires"
args = ["pkg-config", "--cflags", "--libs", "--print-errors"]
expected = "-L/usr/lib/ -flto -l/something/lib/libfoo.so -lbar"
27 changes: 27 additions & 0 deletions tests/cps-files/lib/cps/link-requires.cps
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "link-requires",
"cps_version": "0.13.0",
"version": "1.0.0",
"requires": {
"full": null,
"multiple-components": null
},
"prefix": "/prefix",
"components": {
"default": {
"type": "interface",
"link_requires": [
"full"
]
},
"nested": {
"type": "interface",
"link_requires": [
"multiple-components:link-requires"
]
}
},
"default_components": [
"default"
]
}
13 changes: 13 additions & 0 deletions tests/cps-files/lib/cps/multiple-components.cps
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,19 @@
"requires": [
"minimal:sample0"
]
},
"link-requires": {
"type": "interface",
"link_requires": [
"minimal:sample1"
]
},
"same-component-twice": {
"type": "interface",
"requires": [
":sample3",
":sample4"
]
}
},
"default_components": [
Expand Down
Loading