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

feat!: Version set unions as solvable requirements #56

Merged
merged 9 commits into from
Aug 5, 2024
21 changes: 20 additions & 1 deletion cpp/include/resolvo.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@
#include "resolvo_internal.h"

namespace resolvo {
using cbindgen_private::Requirement;

/**
* Specifies a requirement (dependency) of a single version set.
*/
inline Requirement requirement_single(VersionSetId id) {
return cbindgen_private::resolvo_requirement_single(id);
}

/**
* Specifies a requirement (dependency) of the union (logical OR) of multiple version sets.
* A solvable belonging to any of the version sets contained in the union satisfies the
* requirement. This variant is typically used for requirements that can be satisfied by two
* or more version sets belonging to different packages.
*/
inline Requirement requirement_union(VersionSetUnionId id) {
return cbindgen_private::resolvo_requirement_union(id);
}

/**
* Called to solve a package problem.
Expand All @@ -12,7 +30,7 @@ namespace resolvo {
* stored in `result`. If the solve was unsuccesfull an error describing the reason is returned and
* the result vector will be empty.
*/
inline String solve(DependencyProvider &provider, Slice<VersionSetId> requirements,
inline String solve(DependencyProvider &provider, Slice<Requirement> requirements,
Slice<VersionSetId> constraints, Vector<SolvableId> &result) {
cbindgen_private::DependencyProvider bridge{
static_cast<void *>(&provider),
Expand All @@ -24,6 +42,7 @@ inline String solve(DependencyProvider &provider, Slice<VersionSetId> requiremen
private_api::bridge_display_string,
private_api::bridge_version_set_name,
private_api::bridge_solvable_name,
private_api::bridge_version_sets_in_union,
private_api::bridge_get_candidates,
private_api::bridge_sort_candidates,
private_api::bridge_filter_candidates,
Expand Down
15 changes: 15 additions & 0 deletions cpp/include/resolvo_dependency_provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ using cbindgen_private::NameId;
using cbindgen_private::SolvableId;
using cbindgen_private::StringId;
using cbindgen_private::VersionSetId;
using cbindgen_private::VersionSetUnionId;

/**
* An interface that implements ecosystem specific logic.
Expand Down Expand Up @@ -75,6 +76,11 @@ struct DependencyProvider {
*/
virtual NameId solvable_name(SolvableId solvable_id) = 0;

/**
* Returns the version sets comprising the given union.
*/
virtual Slice<VersionSetId> version_sets_in_union(VersionSetUnionId version_set_union_id) = 0;

/**
* Obtains a list of solvables that should be considered when a package
* with the given name is requested.
Expand Down Expand Up @@ -133,6 +139,15 @@ extern "C" inline NameId bridge_solvable_name(void *data, SolvableId solvable_id
return reinterpret_cast<DependencyProvider *>(data)->solvable_name(solvable_id);
}

// HACK(clang): For some reason, clang needs this to know that the return type is complete
static_assert(sizeof(Slice<VersionSetId>));

extern "C" inline Slice<VersionSetId> bridge_version_sets_in_union(
void *data, VersionSetUnionId version_set_union_id) {
return reinterpret_cast<DependencyProvider *>(data)->version_sets_in_union(
version_set_union_id);
}

extern "C" inline void bridge_get_candidates(void *data, NameId package, Candidates *result) {
*result = reinterpret_cast<DependencyProvider *>(data)->get_candidates(package);
}
Expand Down
95 changes: 93 additions & 2 deletions cpp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,66 @@ impl From<SolvableId> for resolvo::SolvableId {
}
}

/// Specifies the dependency of a solvable on a set of version sets.
/// cbindgen:derive-eq
/// cbindgen:derive-neq
#[repr(C)]
#[derive(Copy, Clone)]
pub enum Requirement {
/// Specifies a dependency on a single version set.
/// cbindgen:derive-eq
/// cbindgen:derive-neq
Single(VersionSetId),
/// Specifies a dependency on the union (logical OR) of multiple version sets. A solvable
/// belonging to ANY of the version sets contained in the union satisfies the requirement.
/// This variant is typically used for requirements that can be satisfied by two or more
/// version sets belonging to different packages.
/// cbindgen:derive-eq
/// cbindgen:derive-neq
Union(VersionSetUnionId),
}

impl From<resolvo::Requirement> for crate::Requirement {
fn from(value: resolvo::Requirement) -> Self {
match value {
resolvo::Requirement::Single(id) => Requirement::Single(id.into()),
resolvo::Requirement::Union(id) => Requirement::Union(id.into()),
}
}
}

impl From<crate::Requirement> for resolvo::Requirement {
fn from(value: crate::Requirement) -> Self {
match value {
Requirement::Single(id) => resolvo::Requirement::Single(id.into()),
Requirement::Union(id) => resolvo::Requirement::Union(id.into()),
}
}
}

/// A unique identifier for a version set union. A version set union describes
/// the union (logical OR) of a non-empty set of version sets belonging to
/// more than one package.
/// cbindgen:derive-eq
/// cbindgen:derive-neq
#[repr(C)]
#[derive(Copy, Clone)]
pub struct VersionSetUnionId {
id: u32,
}

impl From<resolvo::VersionSetUnionId> for crate::VersionSetUnionId {
fn from(id: resolvo::VersionSetUnionId) -> Self {
Self { id: id.0 }
}
}

impl From<crate::VersionSetUnionId> for resolvo::VersionSetUnionId {
fn from(id: crate::VersionSetUnionId) -> Self {
Self(id.id)
}
}

/// A unique identifier for a single version set. A version set describes a
/// set of versions.
/// cbindgen:derive-eq
Expand Down Expand Up @@ -102,7 +162,7 @@ pub struct Dependencies {
/// A pointer to the first element of a list of requirements. Requirements
/// defines which packages should be installed alongside the depending
/// package and the constraints applied to the package.
pub requirements: Vector<VersionSetId>,
pub requirements: Vector<Requirement>,

/// Defines additional constraints on packages that may or may not be part
/// of the solution. Different from `requirements`, packages in this set
Expand Down Expand Up @@ -230,6 +290,12 @@ pub struct DependencyProvider {
/// Returns the name of the package for the given solvable.
pub solvable_name: unsafe extern "C" fn(data: *mut c_void, solvable_id: SolvableId) -> NameId,

/// Returns the version sets comprising the given union.
pub version_sets_in_union: unsafe extern "C" fn(
data: *mut c_void,
version_set_union_id: VersionSetUnionId,
) -> Slice<'static, VersionSetId>,

/// Obtains a list of solvables that should be considered when a package
/// with the given name is requested.
pub get_candidates:
Expand Down Expand Up @@ -314,6 +380,17 @@ impl<'d> resolvo::Interner for &'d DependencyProvider {
fn solvable_name(&self, solvable: resolvo::SolvableId) -> resolvo::NameId {
unsafe { (self.solvable_name)(self.data, solvable.into()) }.into()
}

fn version_sets_in_union(
&self,
version_set_union: resolvo::VersionSetUnionId,
) -> impl Iterator<Item = resolvo::VersionSetId> {
unsafe { (self.version_sets_in_union)(self.data, version_set_union.into()) }
.as_slice()
.into_iter()
.copied()
.map(Into::into)
}
}

impl<'d> resolvo::DependencyProvider for &'d DependencyProvider {
Expand Down Expand Up @@ -400,7 +477,7 @@ impl<'d> resolvo::DependencyProvider for &'d DependencyProvider {
#[allow(unused)]
pub extern "C" fn resolvo_solve(
provider: &DependencyProvider,
requirements: Slice<VersionSetId>,
requirements: Slice<Requirement>,
constraints: Slice<VersionSetId>,
error: &mut String,
result: &mut Vector<SolvableId>,
Expand Down Expand Up @@ -433,6 +510,20 @@ pub extern "C" fn resolvo_solve(
}
}

#[no_mangle]
#[allow(unused)]
pub extern "C" fn resolvo_requirement_single(version_set_id: VersionSetId) -> Requirement {
Requirement::Single(version_set_id)
}

#[no_mangle]
#[allow(unused)]
pub extern "C" fn resolvo_requirement_union(
version_set_union_id: VersionSetUnionId,
) -> Requirement {
Requirement::Union(version_set_union_id)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
107 changes: 92 additions & 15 deletions cpp/tests/solve.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ struct Candidate {
};

/**
* A requirement for a package.
* A version set for a package.
*/
struct Requirement {
struct VersionSet {
resolvo::NameId name;
uint32_t version_start;
uint32_t version_end;
Expand All @@ -31,19 +31,47 @@ struct PackageDatabase : public resolvo::DependencyProvider {
resolvo::Pool<resolvo::NameId, resolvo::String> names;
resolvo::Pool<resolvo::StringId, resolvo::String> strings;
std::vector<Candidate> candidates;
std::vector<Requirement> requirements;
std::vector<VersionSet> version_sets;
std::vector<std::vector<resolvo::VersionSetId>> version_set_unions;

/**
* Allocates a new requirement and return the id of the requirement.
* Allocates a new version set and return the id of the version set.
*/
resolvo::VersionSetId alloc_requirement(std::string_view package, uint32_t version_start,
resolvo::VersionSetId alloc_version_set(std::string_view package, uint32_t version_start,
uint32_t version_end) {
auto name_id = names.alloc(std::move(package));
auto id = resolvo::VersionSetId{static_cast<uint32_t>(requirements.size())};
requirements.push_back(Requirement{name_id, version_start, version_end});
auto id = resolvo::VersionSetId{static_cast<uint32_t>(version_sets.size())};
version_sets.push_back(VersionSet{name_id, version_start, version_end});
return id;
}

/**
* Allocates a new requirement for a single version set.
*/
resolvo::Requirement alloc_requirement(std::string_view package, uint32_t version_start,
uint32_t version_end) {
auto id = alloc_version_set(package, version_start, version_end);
return resolvo::requirement_single(id);
}

/**
* Allocates a new requirement for a version set union.
*/
resolvo::Requirement alloc_requirement_union(
std::initializer_list<std::tuple<std::string_view, uint32_t, uint32_t>> version_sets) {
std::vector<resolvo::VersionSetId> version_set_union{version_sets.size()};

auto version_sets_it = version_sets.begin();
for (size_t i = 0; i < version_sets.size(); ++i, ++version_sets_it) {
auto [package, version_start, version_end] = *version_sets_it;
version_set_union[i] = alloc_version_set(package, version_start, version_end);
}

auto id = resolvo::VersionSetUnionId{static_cast<uint32_t>(version_set_unions.size())};
version_set_unions.push_back(std::move(version_set_union));
return resolvo::requirement_union(id);
}

/**
* Allocates a new candidate and return the id of the candidate.
*/
Expand Down Expand Up @@ -90,7 +118,7 @@ struct PackageDatabase : public resolvo::DependencyProvider {
}

resolvo::String display_version_set(resolvo::VersionSetId version_set) override {
const auto& req = requirements[version_set.id];
const auto& req = version_sets[version_set.id];
std::stringstream ss;
ss << req.version_start << ".." << req.version_end;
return resolvo::String(ss.str());
Expand All @@ -101,13 +129,19 @@ struct PackageDatabase : public resolvo::DependencyProvider {
}

resolvo::NameId version_set_name(resolvo::VersionSetId version_set_id) override {
return requirements[version_set_id.id].name;
return version_sets[version_set_id.id].name;
}

resolvo::NameId solvable_name(resolvo::SolvableId solvable_id) override {
return candidates[solvable_id.id].name;
}

resolvo::Slice<resolvo::VersionSetId> version_sets_in_union(
resolvo::VersionSetUnionId version_set_union_id) override {
const auto& version_set_ids = version_set_unions[version_set_union_id.id];
return {version_set_ids.data(), version_set_ids.size()};
}

resolvo::Candidates get_candidates(resolvo::NameId package) override {
resolvo::Candidates result;

Expand Down Expand Up @@ -137,11 +171,11 @@ struct PackageDatabase : public resolvo::DependencyProvider {
resolvo::Slice<resolvo::SolvableId> solvables, resolvo::VersionSetId version_set_id,
bool inverse) override {
resolvo::Vector<resolvo::SolvableId> result;
const auto& requirement = requirements[version_set_id.id];
const auto& version_set = version_sets[version_set_id.id];
for (auto solvable : solvables) {
const auto& candidate = candidates[solvable.id];
bool matches = candidate.version >= requirement.version_start &&
candidate.version < requirement.version_end;
bool matches = candidate.version >= version_set.version_start &&
candidate.version < version_set.version_end;
if (matches != inverse) {
result.push_back(solvable);
}
Expand Down Expand Up @@ -183,9 +217,9 @@ SCENARIO("Solve") {
auto c_1 = db.alloc_candidate("c", 1, {});

// Construct a problem to be solved by the solver
resolvo::Vector<resolvo::VersionSetId> requirements = {db.alloc_requirement("a", 1, 3)};
resolvo::Vector<resolvo::VersionSetId> constraints = {db.alloc_requirement("b", 1, 3),
db.alloc_requirement("c", 1, 3)};
resolvo::Vector<resolvo::Requirement> requirements = {db.alloc_requirement("a", 1, 3)};
resolvo::Vector<resolvo::VersionSetId> constraints = {db.alloc_version_set("b", 1, 3),
db.alloc_version_set("c", 1, 3)};

// Solve the problem
resolvo::Vector<resolvo::SolvableId> result;
Expand All @@ -196,3 +230,46 @@ SCENARIO("Solve") {
REQUIRE(result[0] == a_2);
REQUIRE(result[1] == b_2);
}

SCENARIO("Solve Union") {
/// Construct a database with packages a, b, and c.
PackageDatabase db;

// Check that PackageDatabase correctly implements the DependencyProvider interface
static_assert(std::has_virtual_destructor_v<PackageDatabase>);
static_assert(std::is_polymorphic_v<PackageDatabase>);
static_assert(std::is_base_of_v<resolvo::DependencyProvider, PackageDatabase>);

auto a_1 = db.alloc_candidate("a", 1, {});

auto b_1 = db.alloc_candidate("b", 1, {});

auto c_1 = db.alloc_candidate("c", 1, {{db.alloc_requirement("a", 1, 10)}, {}});

auto d_1 = db.alloc_candidate("d", 1, {{db.alloc_requirement("b", 1, 10)}, {}});

auto e_1 = db.alloc_candidate("e", 1,
{{db.alloc_requirement_union({{"a", 1, 10}, {"b", 1, 10}})}, {}});

auto f_1 = db.alloc_candidate(
"f", 1, {{db.alloc_requirement("b", 1, 10)}, {db.alloc_version_set("a", 10, 20)}});

// Construct a problem to be solved by the solver
resolvo::Vector<resolvo::Requirement> requirements = {
db.alloc_requirement_union({{"c", 1, 10}, {"d", 1, 10}}),
db.alloc_requirement("e", 1, 10),
db.alloc_requirement("f", 1, 10),
};
resolvo::Vector<resolvo::VersionSetId> constraints = {};

// Solve the problem
resolvo::Vector<resolvo::SolvableId> result;
resolvo::solve(db, requirements, constraints, result);

// Check the result
REQUIRE(result.size() == 4);
REQUIRE(result[0] == f_1);
REQUIRE(result[1] == e_1);
REQUIRE(result[2] == b_1);
REQUIRE(result[3] == d_1);
}
Loading