Skip to content

feat!: add support for conditional dependencies #136

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

Merged
merged 30 commits into from
Jul 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
44dd066
wip
baszalmstra May 5, 2025
39e2b18
finish parsing
baszalmstra May 5, 2025
87b5c89
add conditions to test suite
baszalmstra May 6, 2025
21decc6
add condition to clause
baszalmstra May 6, 2025
9f595c8
wip
baszalmstra May 6, 2025
b459330
only missing complement
baszalmstra May 6, 2025
2c2a4b4
refactored
baszalmstra May 7, 2025
6f0397d
wip
baszalmstra May 7, 2025
cef4b79
add more tests
baszalmstra May 8, 2025
ab891cb
cleanup and split test
baszalmstra May 8, 2025
5846050
conditional operator tests
baszalmstra May 8, 2025
183b7fa
Merge remote-tracking branch 'upstream/main' into condition-dependencies
baszalmstra May 8, 2025
c05d45a
fix: formatting
baszalmstra May 8, 2025
8c22a1f
fix: cpp
baszalmstra May 8, 2025
e786241
add conditional cpp
baszalmstra May 8, 2025
cb97de9
fmt
baszalmstra May 8, 2025
d4be201
fmt
baszalmstra May 8, 2025
898f456
fmt
baszalmstra May 8, 2025
49dc66e
fix: cpp tests
baszalmstra May 9, 2025
ee6be08
fix: cpp tests
baszalmstra May 9, 2025
6c77703
fix initialization order
baszalmstra May 9, 2025
8444730
fix: solver issue
baszalmstra May 9, 2025
3044e76
fmt
baszalmstra May 9, 2025
e12906c
fix small issues
baszalmstra May 9, 2025
6cc440f
Update src/internal/id.rs
baszalmstra May 9, 2025
dacccb5
feat: use ahash where applicable
baszalmstra Jul 21, 2025
073c85a
add condition to pool
wolfv Jul 22, 2025
ebd6716
merge main and update Cargo.lock
wolfv Jul 23, 2025
5d0763a
undo changes to notebook
wolfv Jul 23, 2025
a4d6ccf
remove `pub(crate)`
wolfv Jul 23, 2025
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
181 changes: 149 additions & 32 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ tracing = "0.1.41"
elsa = "1.11.2"
bitvec = "1.0.1"
serde = { version = "1.0", features = ["derive"], optional = true }
futures = { version = "0.3", default-features = false, features = ["alloc"] }
futures = { version = "0.3", default-features = false, features = ["alloc", "async-await"] }
event-listener = "5.4"
indexmap = "2"
tokio = { version = "1.45", features = ["rt"], optional = true }
Expand All @@ -61,3 +61,4 @@ tracing-test = { version = "0.2.5", features = ["no-env-filter"] }
tokio = { version = "1.45.1", features = ["time", "rt"] }
resolvo = { path = ".", features = ["tokio", "version-ranges"] }
serde_json = "1.0"
chumsky = { version = "0.10.1" , features = ["pratt"]}
34 changes: 34 additions & 0 deletions cpp/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,40 @@ fn main() -> anyhow::Result<()> {
constexpr Slice(const T *ptr, uintptr_t len) : ptr(ptr ? const_cast<T*>(ptr) : reinterpret_cast<T*>(sizeof(T))), len(len) {}"
.to_owned(),
);
config.export.body.insert(
"ConditionalRequirement".to_owned(),
r"
/**
* Constructs a new conditional requirement with the specified condition
* and requirement.
*/
constexpr ConditionalRequirement(const ConditionId *condition, Requirement &&requirement) : condition(condition), requirement(std::forward<Requirement>(requirement)) {};
/**
* Constructs a new conditional requirement without a condition.
*/
constexpr ConditionalRequirement(Requirement &&requirement) : condition(nullptr), requirement(std::forward<Requirement>(requirement)) {};
".to_owned());
config.export.body.insert(
"Requirement".to_owned(),
r"
constexpr Requirement(VersionSetId id) : tag(Tag::Single), single({id}) {};
constexpr Requirement(VersionSetUnionId id) : tag(Tag::Union), union_({id}) {};

constexpr bool is_union() const { return tag == Tag::Union; }
constexpr bool is_single() const { return tag == Tag::Single; }
"
.to_owned(),
);

config.export.body.insert(
"Condition".to_owned(),
r"
constexpr Condition(VersionSetId id) : tag(Tag::Requirement), requirement({id}) {};
constexpr Condition(LogicalOperator op, ConditionId lhs, ConditionId rhs) : tag(Tag::Binary), binary({op, lhs, rhs}) {};

constexpr bool is_binary() const { return tag == Tag::Requirement; }
constexpr bool is_requirement() const { return tag == Tag::Binary; }
".to_owned());

cbindgen::Builder::new()
.with_config(config.clone())
Expand Down
18 changes: 1 addition & 17 deletions cpp/include/resolvo.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,6 @@ namespace resolvo {
using cbindgen_private::Problem;
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 @@ -44,6 +27,7 @@ inline String solve(DependencyProvider &provider, const Problem &problem,
private_api::bridge_version_set_name,
private_api::bridge_solvable_name,
private_api::bridge_version_sets_in_union,
private_api::bridge_resolve_condition,
private_api::bridge_get_candidates,
private_api::bridge_sort_candidates,
private_api::bridge_filter_candidates,
Expand Down
21 changes: 17 additions & 4 deletions cpp/include/resolvo_dependency_provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

namespace resolvo {
using cbindgen_private::Candidates;
using cbindgen_private::Condition;
using cbindgen_private::ConditionalRequirement;
using cbindgen_private::ConditionId;
using cbindgen_private::Dependencies;
using cbindgen_private::ExcludedSolvable;
using cbindgen_private::NameId;
Expand Down Expand Up @@ -81,6 +84,11 @@ struct DependencyProvider {
*/
virtual Slice<VersionSetId> version_sets_in_union(VersionSetUnionId version_set_union_id) = 0;

/**
* Returns the condition that the given condition id describes
*/
virtual Condition resolve_condition(ConditionId condition) = 0;

/**
* Obtains a list of solvables that should be considered when a package
* with the given name is requested.
Expand Down Expand Up @@ -138,14 +146,19 @@ 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<DependencyProvider *>(data)->solvable_name(solvable_id);
}
extern "C" inline void bridge_resolve_condition(void *data, ConditionId solvable_id,
Condition *result) {
*result = reinterpret_cast<DependencyProvider *>(data)->resolve_condition(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_version_sets_in_union(void *data,
VersionSetUnionId version_set_union_id,
Slice<VersionSetId> *result) {
*result =
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) {
Expand Down
184 changes: 153 additions & 31 deletions cpp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ mod slice;
mod string;
mod vector;

use std::{ffi::c_void, fmt::Display, ptr::NonNull};
use std::{ffi::c_void, fmt::Display, mem::MaybeUninit, ptr::NonNull};

use resolvo::{HintDependenciesAvailable, KnownDependencies, SolverCache};

use crate::{slice::Slice, string::String, vector::Vector};

/// A unique identifier for a single solvable or candidate of a package. These ids should not be
/// random but rather monotonic increasing. Although it is fine to have gaps, resolvo will
/// allocate some memory based on the maximum id.
/// A unique identifier for a single solvable or candidate of a package. These
/// ids should not be random but rather monotonic increasing. Although it is
/// fine to have gaps, resolvo will allocate some memory based on the maximum
/// id.
///
/// cbindgen:derive-eq
/// cbindgen:derive-neq
#[repr(C)]
Expand Down Expand Up @@ -41,10 +43,11 @@ pub enum Requirement {
/// 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.
/// 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),
Expand Down Expand Up @@ -156,13 +159,120 @@ impl From<StringId> for resolvo::StringId {
}
}

/// A unique identifier for a single condition.
/// cbindgen:derive-eq
/// cbindgen:derive-neq
#[repr(C)]
#[derive(Copy, Clone)]
pub struct ConditionId {
id: u32,
}

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

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

/// 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 Condition {
/// Specifies a dependency on a single version set.
///
/// cbindgen:derive-eq
/// cbindgen:derive-neq
Requirement(VersionSetId),

/// Combines two conditions using a logical operator.
///
/// cbindgen:derive-eq
/// cbindgen:derive-neq
Binary(LogicalOperator, ConditionId, ConditionId),
}

/// Defines how multiple conditions are compared to each other.
#[repr(C)]
#[derive(Copy, Clone)]
pub enum LogicalOperator {
And,
Or,
}

impl From<resolvo::LogicalOperator> for LogicalOperator {
fn from(value: resolvo::LogicalOperator) -> Self {
match value {
resolvo::LogicalOperator::And => LogicalOperator::And,
resolvo::LogicalOperator::Or => LogicalOperator::Or,
}
}
}

impl From<LogicalOperator> for resolvo::LogicalOperator {
fn from(value: LogicalOperator) -> Self {
match value {
LogicalOperator::And => resolvo::LogicalOperator::And,
LogicalOperator::Or => resolvo::LogicalOperator::Or,
}
}
}

impl From<resolvo::Condition> for Condition {
fn from(value: resolvo::Condition) -> Self {
match value {
resolvo::Condition::Requirement(id) => Condition::Requirement(id.into()),
resolvo::Condition::Binary(op, lhs, rhs) => {
Condition::Binary(op.into(), lhs.into(), rhs.into())
}
}
}
}

impl From<Condition> for resolvo::Condition {
fn from(value: Condition) -> Self {
match value {
Condition::Requirement(id) => resolvo::Condition::Requirement(id.into()),
Condition::Binary(op, lhs, rhs) => {
resolvo::Condition::Binary(op.into(), lhs.into(), rhs.into())
}
}
}
}

#[derive(Clone)]
#[repr(C)]
pub struct ConditionalRequirement {
/// Optionally a condition that indicates whether the requirement is enabled or not.
pub condition: *const ConditionId,

/// A requirement on another package.
pub requirement: Requirement,
}

impl From<ConditionalRequirement> for resolvo::ConditionalRequirement {
fn from(value: ConditionalRequirement) -> Self {
Self {
condition: unsafe { value.condition.as_ref() }.copied().map(Into::into),
requirement: value.requirement.into(),
}
}
}

#[derive(Default)]
#[repr(C)]
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<Requirement>,
pub requirements: Vector<ConditionalRequirement>,

/// 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 @@ -294,7 +404,12 @@ pub struct DependencyProvider {
pub version_sets_in_union: unsafe extern "C" fn(
data: *mut c_void,
version_set_union_id: VersionSetUnionId,
) -> Slice<'static, VersionSetId>,
result: NonNull<Slice<'static, VersionSetId>>,
),

/// Returns the condition that the given condition id describes
pub resolve_condition:
unsafe extern "C" fn(data: *mut c_void, condition: ConditionId, result: NonNull<Condition>),

/// Obtains a list of solvables that should be considered when a package
/// with the given name is requested.
Expand Down Expand Up @@ -387,11 +502,32 @@ impl resolvo::Interner for &DependencyProvider {
&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()
.iter()
.copied()
.map(Into::into)
let mut result = MaybeUninit::uninit();
unsafe {
(self.version_sets_in_union)(
self.data,
version_set_union.into(),
NonNull::new_unchecked(result.as_mut_ptr()),
);
result.assume_init()
}
.as_slice()
.iter()
.copied()
.map(Into::into)
}

fn resolve_condition(&self, condition: resolvo::ConditionId) -> resolvo::Condition {
let mut result = MaybeUninit::uninit();
unsafe {
(self.resolve_condition)(
self.data,
condition.into(),
NonNull::new_unchecked(result.as_mut_ptr()),
);
result.assume_init()
}
.into()
}
}

Expand Down Expand Up @@ -486,7 +622,7 @@ impl resolvo::DependencyProvider for &DependencyProvider {

#[repr(C)]
pub struct Problem<'a> {
pub requirements: Slice<'a, Requirement>,
pub requirements: Slice<'a, ConditionalRequirement>,
pub constraints: Slice<'a, VersionSetId>,
pub soft_requirements: Slice<'a, SolvableId>,
}
Expand All @@ -507,7 +643,7 @@ pub extern "C" fn resolvo_solve(
.requirements
.as_slice()
.iter()
.copied()
.cloned()
.map(Into::into)
.collect(),
)
Expand Down Expand Up @@ -545,20 +681,6 @@ pub extern "C" fn resolvo_solve(
}
}

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

#[unsafe(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
Loading
Loading