diff --git a/cpp/include/resolvo.h b/cpp/include/resolvo.h index 6a41aa3..48844d6 100644 --- a/cpp/include/resolvo.h +++ b/cpp/include/resolvo.h @@ -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. @@ -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 requirements, +inline String solve(DependencyProvider &provider, Slice requirements, Slice constraints, Vector &result) { cbindgen_private::DependencyProvider bridge{ static_cast(&provider), @@ -24,6 +42,7 @@ inline String solve(DependencyProvider &provider, Slice 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, diff --git a/cpp/include/resolvo_dependency_provider.h b/cpp/include/resolvo_dependency_provider.h index 8cd9258..d32bea1 100644 --- a/cpp/include/resolvo_dependency_provider.h +++ b/cpp/include/resolvo_dependency_provider.h @@ -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. @@ -75,6 +76,11 @@ struct DependencyProvider { */ virtual NameId solvable_name(SolvableId solvable_id) = 0; + /** + * Returns the version sets comprising the given union. + */ + virtual Slice 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. @@ -132,6 +138,11 @@ extern "C" inline NameId bridge_version_set_name(void *data, VersionSetId versio extern "C" inline NameId bridge_solvable_name(void *data, SolvableId solvable_id) { return reinterpret_cast(data)->solvable_name(solvable_id); } +extern "C" inline Slice bridge_version_sets_in_union( + void *data, VersionSetUnionId version_set_union_id) { + return reinterpret_cast(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(data)->get_candidates(package); diff --git a/cpp/src/lib.rs b/cpp/src/lib.rs index f8bd5c6..4aa9998 100644 --- a/cpp/src/lib.rs +++ b/cpp/src/lib.rs @@ -31,6 +31,66 @@ impl From 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 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 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 for crate::VersionSetUnionId { + fn from(id: resolvo::VersionSetUnionId) -> Self { + Self { id: id.0 } + } +} + +impl From 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 @@ -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, + pub requirements: Vector, /// Defines additional constraints on packages that may or may not be part /// of the solution. Different from `requirements`, packages in this set @@ -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: @@ -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 { + 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 { @@ -400,7 +477,7 @@ impl<'d> resolvo::DependencyProvider for &'d DependencyProvider { #[allow(unused)] pub extern "C" fn resolvo_solve( provider: &DependencyProvider, - requirements: Slice, + requirements: Slice, constraints: Slice, error: &mut String, result: &mut Vector, @@ -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::*; diff --git a/cpp/tests/solve.cpp b/cpp/tests/solve.cpp index ffb8c66..13a05fa 100644 --- a/cpp/tests/solve.cpp +++ b/cpp/tests/solve.cpp @@ -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; @@ -31,19 +31,47 @@ struct PackageDatabase : public resolvo::DependencyProvider { resolvo::Pool names; resolvo::Pool strings; std::vector candidates; - std::vector requirements; + std::vector version_sets; + std::vector> 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(requirements.size())}; - requirements.push_back(Requirement{name_id, version_start, version_end}); + auto id = resolvo::VersionSetId{static_cast(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> version_sets) { + std::vector 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(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. */ @@ -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()); @@ -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 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; @@ -137,11 +171,11 @@ struct PackageDatabase : public resolvo::DependencyProvider { resolvo::Slice solvables, resolvo::VersionSetId version_set_id, bool inverse) override { resolvo::Vector 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); } @@ -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 requirements = {db.alloc_requirement("a", 1, 3)}; - resolvo::Vector constraints = {db.alloc_requirement("b", 1, 3), - db.alloc_requirement("c", 1, 3)}; + resolvo::Vector requirements = {db.alloc_requirement("a", 1, 3)}; + resolvo::Vector constraints = {db.alloc_version_set("b", 1, 3), + db.alloc_version_set("c", 1, 3)}; // Solve the problem resolvo::Vector result; @@ -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); + static_assert(std::is_polymorphic_v); + static_assert(std::is_base_of_v); + + 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 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 constraints = {}; + + // Solve the problem + resolvo::Vector 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); +}