From 44dd06619ee0b9ff797ff7f40cba419e714666f7 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Mon, 5 May 2025 12:39:03 +0200 Subject: [PATCH 01/28] wip --- Cargo.lock | 4 +- src/conditional_requirement.rs | 47 +++++++++++++++++ src/internal/id.rs | 20 ++++++- src/internal/mod.rs | 1 - src/lib.rs | 13 ++++- src/requirement.rs | 29 ++++++++--- tests/solver.rs | 95 ++++++++++++++++++++++------------ 7 files changed, 166 insertions(+), 43 deletions(-) create mode 100644 src/conditional_requirement.rs diff --git a/Cargo.lock b/Cargo.lock index fda3b2b4..46ba5110 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1119,9 +1119,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "solve-snapshot" diff --git a/src/conditional_requirement.rs b/src/conditional_requirement.rs new file mode 100644 index 00000000..30998afd --- /dev/null +++ b/src/conditional_requirement.rs @@ -0,0 +1,47 @@ +use crate::{Requirement, VersionSetId}; +use crate::internal::id::ConditionId; + +/// A [`ConditionalRequirement`] is a requirement that is only enforced when a +/// certain condition holds. +#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ConditionalRequirement { + /// The requirement is enforced only when the condition evaluates to true. + pub condition: Option, + + /// A requirement on another package. + pub requirement: Requirement, +} + +/// A condition defines a boolean expression that evaluates to true or false +/// based on whether one or more other requirements are true or false. +#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum Condition { + /// Defines a combination of conditions using logical operators. + Binary(LogicalOperator, ConditionId, ConditionId), + + /// The condition is only true if the requirement is true. + Requirement(VersionSetId), +} + +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum LogicalOperator { + /// The condition is true if both operands are true. + And, + + /// The condition is true if either operand is true. + Or, +} + +// Constructs a `ConditionalRequirement` from a `Requirement` without a +// condition. +impl From for ConditionalRequirement { + fn from(value: Requirement) -> Self { + Self { + condition: None, + requirement: value, + } + } +} diff --git a/src/internal/id.rs b/src/internal/id.rs index 24835717..d7250c01 100644 --- a/src/internal/id.rs +++ b/src/internal/id.rs @@ -1,6 +1,6 @@ use std::{ fmt::{Display, Formatter}, - num::NonZeroU32, + num::{NonZero, NonZeroU32}, }; use crate::{Interner, internal::arena::ArenaId}; @@ -56,6 +56,24 @@ impl ArenaId for VersionSetId { } } +/// The id associated with a Condition. +#[repr(transparent)] +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(transparent))] +pub struct ConditionId(pub NonZero); + +impl ArenaId for ConditionId { + fn from_usize(x: usize) -> Self { + let id = (x + 1).try_into().expect("condition id too big"); + Self(unsafe { NonZero::new_unchecked(id) }) + } + + fn to_usize(self) -> usize { + (self.0.get() - 1) as usize + } +} + /// The id associated with a union (logical OR) of two or more version sets. #[repr(transparent)] #[derive(Clone, Default, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] diff --git a/src/internal/mod.rs b/src/internal/mod.rs index 08a55f42..a28ff2e0 100644 --- a/src/internal/mod.rs +++ b/src/internal/mod.rs @@ -2,7 +2,6 @@ pub mod arena; pub mod frozen_copy_map; pub mod id; pub mod mapping; -pub mod small_vec; mod unwrap_unchecked; pub use unwrap_unchecked::debug_expect_unchecked; diff --git a/src/lib.rs b/src/lib.rs index ff98b2a7..3c804237 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ #![deny(missing_docs)] +mod conditional_requirement; pub mod conflict; pub(crate) mod internal; mod requirement; @@ -23,6 +24,7 @@ use std::{ fmt::{Debug, Display}, }; +pub use conditional_requirement::{Condition, ConditionalRequirement, LogicalOperator}; pub use internal::{ id::{NameId, SolvableId, StringId, VersionSetId, VersionSetUnionId}, mapping::Mapping, @@ -31,6 +33,8 @@ use itertools::Itertools; pub use requirement::Requirement; pub use solver::{Problem, Solver, SolverCache, UnsolvableOrCancelled}; +use crate::internal::id::ConditionId; + /// An object that is used by the solver to query certain properties of /// different internalized objects. pub trait Interner { @@ -99,6 +103,13 @@ pub trait Interner { &self, version_set_union: VersionSetUnionId, ) -> impl Iterator; + + /// Resolves how a condition should be represented in the solver. + /// + /// Internally, the solver uses `ConditionId` to represent conditions. This + /// allows implementers to have a custom representation for conditions that + /// differ from the representation of the solver. + fn resolve_condition(&self, condition: ConditionId) -> Condition; } /// Defines implementation specific behavior for the solver and a way for the @@ -226,7 +237,7 @@ pub struct KnownDependencies { feature = "serde", serde(default, skip_serializing_if = "Vec::is_empty") )] - pub requirements: Vec, + pub requirements: Vec, /// Defines additional constraints on packages that may or may not be part /// of the solution. Different from `requirements`, packages in this set diff --git a/src/requirement.rs b/src/requirement.rs index 610ae22a..481518c9 100644 --- a/src/requirement.rs +++ b/src/requirement.rs @@ -1,20 +1,37 @@ -use crate::{Interner, VersionSetId, VersionSetUnionId}; -use itertools::Itertools; use std::fmt::Display; +use itertools::Itertools; + +use crate::{ + ConditionalRequirement, Interner, VersionSetId, VersionSetUnionId, + conditional_requirement::Condition, +}; + /// Specifies the dependency of a solvable on a set of version sets. #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Requirement { /// Specifies a dependency on a single version set. 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. Union(VersionSetUnionId), } +impl Requirement { + /// Constructs a `ConditionalRequirement` from this `Requirement` and a + /// condition. + pub fn with_condition(self, condition: Condition) -> ConditionalRequirement { + ConditionalRequirement { + condition: Some(condition), + requirement: self, + } + } +} + impl Default for Requirement { fn default() -> Self { Self::Single(Default::default()) diff --git a/tests/solver.rs b/tests/solver.rs index aea71d10..ee1951c1 100644 --- a/tests/solver.rs +++ b/tests/solver.rs @@ -19,13 +19,7 @@ use ahash::HashMap; use indexmap::IndexMap; use insta::assert_snapshot; use itertools::Itertools; -use resolvo::{ - Candidates, Dependencies, DependencyProvider, Interner, KnownDependencies, NameId, Problem, - Requirement, SolvableId, Solver, SolverCache, StringId, UnsolvableOrCancelled, VersionSetId, - VersionSetUnionId, - snapshot::{DependencySnapshot, SnapshotProvider}, - utils::Pool, -}; +use resolvo::{Candidates, Dependencies, DependencyProvider, Interner, KnownDependencies, NameId, Problem, Requirement, SolvableId, Solver, SolverCache, StringId, UnsolvableOrCancelled, VersionSetId, VersionSetUnionId, snapshot::{DependencySnapshot, SnapshotProvider}, utils::Pool, Condition, LogicalOperator}; use tracing_test::traced_test; use version_ranges::Ranges; @@ -128,37 +122,74 @@ impl Spec { .map(|dep| Spec::from_str(dep)) } } +fn parse_version_range(s: &str) -> Ranges { + let (start, end) = s + .split_once("..") + .map_or((s, None), |(start, end)| (start, Some(end))); + let start: Pack = start.parse().unwrap(); + let end = end + .map(FromStr::from_str) + .transpose() + .unwrap() + .unwrap_or(start.offset(1)); + Ranges::between(start, end) +} -impl FromStr for Spec { +struct ConditionalSpec { + condition: Option, + spec: Spec, +} + +enum SpecCondition { + Binary(LogicalOperator, Box<[Condition; 2]>), + Requirement(Spec), +} + +impl FromStr for ConditionalSpec { type Err = (); fn from_str(s: &str) -> Result { - let split = s.split(' ').collect::>(); - let name = split - .first() - .expect("spec does not have a name") - .to_string(); - - fn version_range(s: Option<&&str>) -> Ranges { - if let Some(s) = s { - let (start, end) = s - .split_once("..") - .map_or((*s, None), |(start, end)| (start, Some(end))); - let start: Pack = start.parse().unwrap(); - let end = end - .map(FromStr::from_str) - .transpose() - .unwrap() - .unwrap_or(start.offset(1)); - Ranges::between(start, end) - } else { - Ranges::full() - } - } + // Split on the condition + let (s, condition) = s + .split_once("; if ") + .map_or_else(|| (s, None), |(left, right)| (left, Some(right))); + + // Parse the condition + let condition = condition.map(|condition| Condition::from_str(condition)).transpose().unwrap(); - let versions = version_range(split.get(1)); + // Parse the spec + let spec = Spec::from_str(s).unwrap(); + + Ok(Self { + condition, + spec, + }) + } +} + +impl FromStr for Condition { + type Err = (); + + fn from_str(s: &str) -> Result { + + } +} + + +impl FromStr for Spec { + type Err = (); + + fn from_str(s: &str) -> Result { + // Split the name and the version + let (Some(name), versions) = s.split_once(' ').map_or_else( + || (Some(s), None), + |(left, right)| (Some(left), Some(right)), + ) else { + panic!("spec does not have a name") + }; - Ok(Spec::new(name, versions)) + let versions = versions.map(parse_version_range).unwrap_or(Ranges::full()); + Ok(Spec::new(name.to_string(), versions)) } } From 39e2b18e659e8497c26f52be0a4b92f3e58d90f3 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Mon, 5 May 2025 22:12:24 +0200 Subject: [PATCH 02/28] finish parsing --- Cargo.lock | 92 ++++ Cargo.toml | 1 + cpp/src/lib.rs | 6 +- src/conditional_requirement.rs | 3 +- src/internal/mod.rs | 1 + src/lib.rs | 4 +- src/requirement.rs | 3 +- src/snapshot.rs | 15 +- src/solver/encoding.rs | 6 +- src/solver/mod.rs | 20 +- tests/.solver.rs.pending-snap | 73 +++ .../solver__conditional_requirements.snap.new | 9 + tests/snapshots/solver__root_constraints.snap | 6 +- tests/solver.rs | 414 +++++++++++------- 14 files changed, 459 insertions(+), 194 deletions(-) create mode 100644 tests/.solver.rs.pending-snap create mode 100644 tests/snapshots/solver__conditional_requirements.snap.new diff --git a/Cargo.lock b/Cargo.lock index 46ba5110..fb87e75c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,6 +39,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anstream" version = "0.6.18" @@ -311,12 +317,35 @@ dependencies = [ "toml", ] +[[package]] +name = "cc" +version = "1.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chumsky" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14377e276b2c8300513dff55ba4cc4142b44e5d6de6d00eb5b2307d650bb4ec1" +dependencies = [ + "hashbrown", + "regex-automata 0.3.9", + "serde", + "stacker", + "unicode-ident", + "unicode-segmentation", +] + [[package]] name = "clap" version = "4.5.24" @@ -494,6 +523,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "funty" version = "2.0.0" @@ -608,6 +643,11 @@ name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] [[package]] name = "heck" @@ -878,6 +918,15 @@ dependencies = [ "unarray", ] +[[package]] +name = "psm" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e944464ec8536cd1beb0bbfd96987eb5e3b72f2ecdafdc5c769a37f1fa2ae1f" +dependencies = [ + "cc", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -959,6 +1008,17 @@ dependencies = [ "regex-syntax 0.6.29", ] +[[package]] +name = "regex-automata" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.5", +] + [[package]] name = "regex-automata" version = "0.4.9" @@ -976,6 +1036,12 @@ version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + [[package]] name = "regex-syntax" version = "0.8.5" @@ -989,6 +1055,7 @@ dependencies = [ "ahash", "async-std", "bitvec", + "chumsky", "elsa", "event-listener 5.4.0", "futures", @@ -1102,6 +1169,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "similar" version = "2.6.0" @@ -1143,6 +1216,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "stacker" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys", +] + [[package]] name = "strsim" version = "0.11.1" @@ -1328,6 +1414,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-width" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 2b9587e5..76314ed2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,3 +48,4 @@ tracing-test = { version = "0.2.5", features = ["no-env-filter"] } tokio = { version = "1.42.0", features = ["time", "rt"] } resolvo = { path = ".", features = ["tokio", "version-ranges"] } serde_json = "1.0" +chumsky = { version = "0.10.1" , features = ["pratt"]} diff --git a/cpp/src/lib.rs b/cpp/src/lib.rs index 194ee01d..f00247e8 100644 --- a/cpp/src/lib.rs +++ b/cpp/src/lib.rs @@ -4,7 +4,7 @@ mod vector; use std::{ffi::c_void, fmt::Display, ptr::NonNull}; -use resolvo::{HintDependenciesAvailable, KnownDependencies, SolverCache}; +use resolvo::{Condition, ConditionId, HintDependenciesAvailable, KnownDependencies, SolverCache}; use crate::{slice::Slice, string::String, vector::Vector}; @@ -393,6 +393,10 @@ impl resolvo::Interner for &DependencyProvider { .copied() .map(Into::into) } + + fn resolve_condition(&self, condition: ConditionId) -> Condition { + todo!() + } } impl resolvo::DependencyProvider for &DependencyProvider { diff --git a/src/conditional_requirement.rs b/src/conditional_requirement.rs index 30998afd..8265eb93 100644 --- a/src/conditional_requirement.rs +++ b/src/conditional_requirement.rs @@ -1,5 +1,5 @@ -use crate::{Requirement, VersionSetId}; use crate::internal::id::ConditionId; +use crate::{Requirement, VersionSetId}; /// A [`ConditionalRequirement`] is a requirement that is only enforced when a /// certain condition holds. @@ -25,6 +25,7 @@ pub enum Condition { Requirement(VersionSetId), } +/// A [`LogicalOperator`] defines how multiple conditions are compared to each other. #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum LogicalOperator { diff --git a/src/internal/mod.rs b/src/internal/mod.rs index a28ff2e0..08a55f42 100644 --- a/src/internal/mod.rs +++ b/src/internal/mod.rs @@ -2,6 +2,7 @@ pub mod arena; pub mod frozen_copy_map; pub mod id; pub mod mapping; +pub mod small_vec; mod unwrap_unchecked; pub use unwrap_unchecked::debug_expect_unchecked; diff --git a/src/lib.rs b/src/lib.rs index 3c804237..acd6bab1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,15 +26,13 @@ use std::{ pub use conditional_requirement::{Condition, ConditionalRequirement, LogicalOperator}; pub use internal::{ - id::{NameId, SolvableId, StringId, VersionSetId, VersionSetUnionId}, + id::{ConditionId, NameId, SolvableId, StringId, VersionSetId, VersionSetUnionId}, mapping::Mapping, }; use itertools::Itertools; pub use requirement::Requirement; pub use solver::{Problem, Solver, SolverCache, UnsolvableOrCancelled}; -use crate::internal::id::ConditionId; - /// An object that is used by the solver to query certain properties of /// different internalized objects. pub trait Interner { diff --git a/src/requirement.rs b/src/requirement.rs index 481518c9..303ce1d8 100644 --- a/src/requirement.rs +++ b/src/requirement.rs @@ -6,6 +6,7 @@ use crate::{ ConditionalRequirement, Interner, VersionSetId, VersionSetUnionId, conditional_requirement::Condition, }; +use crate::internal::id::ConditionId; /// Specifies the dependency of a solvable on a set of version sets. #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] @@ -24,7 +25,7 @@ pub enum Requirement { impl Requirement { /// Constructs a `ConditionalRequirement` from this `Requirement` and a /// condition. - pub fn with_condition(self, condition: Condition) -> ConditionalRequirement { + pub fn with_condition(self, condition: ConditionId) -> ConditionalRequirement { ConditionalRequirement { condition: Some(condition), requirement: self, diff --git a/src/snapshot.rs b/src/snapshot.rs index 8f071d54..d8716713 100644 --- a/src/snapshot.rs +++ b/src/snapshot.rs @@ -14,11 +14,8 @@ use std::{any::Any, collections::VecDeque, fmt::Display, time::SystemTime}; use ahash::HashSet; use futures::FutureExt; -use crate::{ - Candidates, Dependencies, DependencyProvider, HintDependenciesAvailable, Interner, Mapping, - NameId, Requirement, SolvableId, SolverCache, StringId, VersionSetId, VersionSetUnionId, - internal::arena::ArenaId, -}; +use crate::{Candidates, Dependencies, DependencyProvider, HintDependenciesAvailable, Interner, Mapping, NameId, Requirement, SolvableId, SolverCache, StringId, VersionSetId, VersionSetUnionId, internal::arena::ArenaId, Condition}; +use crate::internal::id::ConditionId; /// A single solvable in a [`DependencySnapshot`]. #[derive(Clone, Debug)] @@ -228,8 +225,8 @@ impl DependencySnapshot { } } - for &requirement in deps.requirements.iter() { - match requirement { + for requirement in deps.requirements.iter() { + match requirement.requirement { Requirement::Single(version_set) => { if seen.insert(Element::VersionSet(version_set)) { queue.push_back(Element::VersionSet(version_set)); @@ -456,6 +453,10 @@ impl Interner for SnapshotProvider<'_> { .iter() .copied() } + + fn resolve_condition(&self, condition: ConditionId) -> Condition { + unimplemented!(); + } } impl DependencyProvider for SnapshotProvider<'_> { diff --git a/src/solver/encoding.rs b/src/solver/encoding.rs index 1340c169..a4aa1012 100644 --- a/src/solver/encoding.rs +++ b/src/solver/encoding.rs @@ -147,7 +147,7 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { // refer. Make sure we have all candidates for a particular package. for version_set_id in requirements .iter() - .flat_map(|requirement| requirement.version_sets(self.cache.provider())) + .flat_map(|requirement| requirement.requirement.version_sets(self.cache.provider())) .chain(constraints.iter().copied()) { let package_name = self.cache.provider().version_set_name(version_set_id); @@ -155,8 +155,8 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { } // For each requirement request the matching candidates. - for &requirement in requirements { - self.queue_requirement(solvable_id, requirement); + for requirement in requirements { + self.queue_requirement(solvable_id, requirement.requirement); } // For each constraint, request the candidates that are non-matching diff --git a/src/solver/mod.rs b/src/solver/mod.rs index 65ab2c39..5ea3f3a8 100644 --- a/src/solver/mod.rs +++ b/src/solver/mod.rs @@ -11,17 +11,11 @@ use std::{any::Any, fmt::Display, ops::ControlFlow}; use variable_map::VariableMap; use watch_map::WatchMap; -use crate::{ - Dependencies, DependencyProvider, KnownDependencies, Requirement, VersionSetId, - conflict::Conflict, - internal::{ - arena::{Arena, ArenaId}, - id::{ClauseId, LearntClauseId, NameId, SolvableId, SolvableOrRootId, VariableId}, - mapping::Mapping, - }, - runtime::{AsyncRuntime, NowOrNeverRuntime}, - solver::binary_encoding::AtMostOnceTracker, -}; +use crate::{Dependencies, DependencyProvider, KnownDependencies, Requirement, VersionSetId, conflict::Conflict, internal::{ + arena::{Arena, ArenaId}, + id::{ClauseId, LearntClauseId, NameId, SolvableId, SolvableOrRootId, VariableId}, + mapping::Mapping, +}, runtime::{AsyncRuntime, NowOrNeverRuntime}, solver::binary_encoding::AtMostOnceTracker, ConditionalRequirement}; mod binary_encoding; mod cache; @@ -42,7 +36,7 @@ mod watch_map; /// This struct follows the builder pattern and can have its fields set by one /// of the available setter methods. pub struct Problem { - requirements: Vec, + requirements: Vec, constraints: Vec, soft_requirements: S, } @@ -71,7 +65,7 @@ impl> Problem { /// /// Returns the [`Problem`] for further mutation or to pass to /// [`Solver::solve`]. - pub fn requirements(self, requirements: Vec) -> Self { + pub fn requirements(self, requirements: Vec) -> Self { Self { requirements, ..self diff --git a/tests/.solver.rs.pending-snap b/tests/.solver.rs.pending-snap new file mode 100644 index 00000000..369db7ca --- /dev/null +++ b/tests/.solver.rs.pending-snap @@ -0,0 +1,73 @@ +{"run_id":"1746475518-883885600","line":1069,"new":{"module_name":"solver","snapshot_name":"resolve_union_requirements","metadata":{"source":"tests/solver.rs","assertion_line":1069,"expression":"result"},"snapshot":"The following packages are incompatible\n├─ e * can be installed with any of the following options:\n│ └─ e 1 would require\n│ └─ a *, which can be installed with any of the following options:\n│ └─ a 1\n└─ f * cannot be installed because there are no viable options:\n └─ f 1 would constrain\n └─ a >=2, <3, which conflicts with any installable versions previously reported"},"old":{"module_name":"solver","metadata":{},"snapshot":"b=1\nd=1\ne=1\nf=1"}} +{"run_id":"1746475548-406396600","line":1049,"new":null,"old":null} +{"run_id":"1746475548-406396600","line":1530,"new":null,"old":null} +{"run_id":"1746475548-406396600","line":1337,"new":null,"old":null} +{"run_id":"1746475548-406396600","line":1011,"new":null,"old":null} +{"run_id":"1746475548-406396600","line":1380,"new":null,"old":null} +{"run_id":"1746475548-406396600","line":1432,"new":null,"old":null} +{"run_id":"1746475548-406396600","line":1032,"new":null,"old":null} +{"run_id":"1746475548-406396600","line":1488,"new":null,"old":null} +{"run_id":"1746475548-406396600","line":1069,"new":null,"old":null} +{"run_id":"1746475588-187467800","line":1337,"new":null,"old":null} +{"run_id":"1746475588-187467800","line":1530,"new":null,"old":null} +{"run_id":"1746475588-187467800","line":1049,"new":null,"old":null} +{"run_id":"1746475588-187467800","line":1380,"new":null,"old":null} +{"run_id":"1746475588-187467800","line":1432,"new":null,"old":null} +{"run_id":"1746475588-187467800","line":1011,"new":null,"old":null} +{"run_id":"1746475588-187467800","line":1032,"new":null,"old":null} +{"run_id":"1746475588-187467800","line":1069,"new":null,"old":null} +{"run_id":"1746475588-187467800","line":1488,"new":null,"old":null} +{"run_id":"1746475721-295409200","line":1049,"new":null,"old":null} +{"run_id":"1746475721-295409200","line":1530,"new":null,"old":null} +{"run_id":"1746475721-295409200","line":1337,"new":null,"old":null} +{"run_id":"1746475721-295409200","line":1380,"new":null,"old":null} +{"run_id":"1746475721-295409200","line":1432,"new":null,"old":null} +{"run_id":"1746475721-295409200","line":1032,"new":null,"old":null} +{"run_id":"1746475721-295409200","line":1011,"new":null,"old":null} +{"run_id":"1746475721-295409200","line":1069,"new":null,"old":null} +{"run_id":"1746475721-295409200","line":1488,"new":null,"old":null} +{"run_id":"1746475763-487748000","line":1049,"new":null,"old":null} +{"run_id":"1746475763-487748000","line":1337,"new":null,"old":null} +{"run_id":"1746475763-487748000","line":1530,"new":null,"old":null} +{"run_id":"1746475763-487748000","line":1380,"new":null,"old":null} +{"run_id":"1746475763-487748000","line":1432,"new":null,"old":null} +{"run_id":"1746475763-487748000","line":1032,"new":null,"old":null} +{"run_id":"1746475763-487748000","line":1011,"new":null,"old":null} +{"run_id":"1746475763-487748000","line":1069,"new":null,"old":null} +{"run_id":"1746475763-487748000","line":1488,"new":null,"old":null} +{"run_id":"1746475774-373191100","line":1337,"new":null,"old":null} +{"run_id":"1746475774-373191100","line":1530,"new":null,"old":null} +{"run_id":"1746475774-373191100","line":1049,"new":null,"old":null} +{"run_id":"1746475774-373191100","line":1380,"new":null,"old":null} +{"run_id":"1746475774-373191100","line":1432,"new":null,"old":null} +{"run_id":"1746475774-373191100","line":1032,"new":null,"old":null} +{"run_id":"1746475774-373191100","line":1011,"new":null,"old":null} +{"run_id":"1746475774-373191100","line":1488,"new":null,"old":null} +{"run_id":"1746475774-373191100","line":1069,"new":null,"old":null} +{"run_id":"1746475793-356201900","line":1337,"new":null,"old":null} +{"run_id":"1746475793-356201900","line":1530,"new":null,"old":null} +{"run_id":"1746475793-356201900","line":1049,"new":null,"old":null} +{"run_id":"1746475793-356201900","line":1380,"new":null,"old":null} +{"run_id":"1746475793-356201900","line":1432,"new":null,"old":null} +{"run_id":"1746475793-356201900","line":1032,"new":null,"old":null} +{"run_id":"1746475793-356201900","line":1011,"new":null,"old":null} +{"run_id":"1746475793-356201900","line":1069,"new":null,"old":null} +{"run_id":"1746475793-356201900","line":1488,"new":null,"old":null} +{"run_id":"1746475810-423217200","line":1530,"new":null,"old":null} +{"run_id":"1746475810-423217200","line":1337,"new":null,"old":null} +{"run_id":"1746475810-423217200","line":1049,"new":null,"old":null} +{"run_id":"1746475810-423217200","line":1032,"new":null,"old":null} +{"run_id":"1746475810-423217200","line":1011,"new":null,"old":null} +{"run_id":"1746475810-423217200","line":1380,"new":null,"old":null} +{"run_id":"1746475810-423217200","line":1432,"new":null,"old":null} +{"run_id":"1746475810-423217200","line":1069,"new":null,"old":null} +{"run_id":"1746475810-423217200","line":1488,"new":null,"old":null} +{"run_id":"1746475919-456405700","line":1049,"new":null,"old":null} +{"run_id":"1746475919-456405700","line":1530,"new":null,"old":null} +{"run_id":"1746475919-456405700","line":1337,"new":null,"old":null} +{"run_id":"1746475919-456405700","line":1380,"new":null,"old":null} +{"run_id":"1746475919-456405700","line":1432,"new":null,"old":null} +{"run_id":"1746475919-456405700","line":1032,"new":null,"old":null} +{"run_id":"1746475919-456405700","line":1011,"new":null,"old":null} +{"run_id":"1746475919-456405700","line":1069,"new":null,"old":null} +{"run_id":"1746475919-456405700","line":1488,"new":null,"old":null} diff --git a/tests/snapshots/solver__conditional_requirements.snap.new b/tests/snapshots/solver__conditional_requirements.snap.new new file mode 100644 index 00000000..774ec0da --- /dev/null +++ b/tests/snapshots/solver__conditional_requirements.snap.new @@ -0,0 +1,9 @@ +--- +source: tests/solver.rs +assertion_line: 1550 +expression: "solve_for_snapshot(provider, &requirements, &[])" +--- +bar=1 +foo=1 +icon=1 +menu=1 diff --git a/tests/snapshots/solver__root_constraints.snap b/tests/snapshots/solver__root_constraints.snap index 160d78c9..0dcdcc5e 100644 --- a/tests/snapshots/solver__root_constraints.snap +++ b/tests/snapshots/solver__root_constraints.snap @@ -1,8 +1,8 @@ --- source: tests/solver.rs -expression: "solve_for_snapshot(snapshot_provider, &[union_req], &[union_constraint])" +expression: "solve_for_snapshot(provider, &requirements, &constraints)" --- The following packages are incompatible └─ union * can be installed with any of the following options: - └─ union union=1 -├─ the constraint union 5 cannot be fulfilled + └─ union 1 +├─ the constraint union >=5, <6 cannot be fulfilled diff --git a/tests/solver.rs b/tests/solver.rs index ee1951c1..2655dc3e 100644 --- a/tests/solver.rs +++ b/tests/solver.rs @@ -1,3 +1,4 @@ +use chumsky::{IterParser, error}; use std::{ any::Any, borrow::Borrow, @@ -16,10 +17,18 @@ use std::{ }; use ahash::HashMap; +use chumsky::prelude::{EmptyErr, just}; +use chumsky::{Parser, extra, text}; use indexmap::IndexMap; use insta::assert_snapshot; use itertools::Itertools; -use resolvo::{Candidates, Dependencies, DependencyProvider, Interner, KnownDependencies, NameId, Problem, Requirement, SolvableId, Solver, SolverCache, StringId, UnsolvableOrCancelled, VersionSetId, VersionSetUnionId, snapshot::{DependencySnapshot, SnapshotProvider}, utils::Pool, Condition, LogicalOperator}; +use resolvo::{ + Candidates, Condition, ConditionId, ConditionalRequirement, Dependencies, DependencyProvider, + Interner, KnownDependencies, LogicalOperator, NameId, Problem, Requirement, SolvableId, Solver, + SolverCache, StringId, UnsolvableOrCancelled, VersionSetId, VersionSetUnionId, + snapshot::{DependencySnapshot, SnapshotProvider}, + utils::Pool, +}; use tracing_test::traced_test; use version_ranges::Ranges; @@ -114,82 +123,150 @@ impl Spec { Self { name, versions } } - pub fn parse_union( - spec: &str, - ) -> impl Iterator::Err>> + '_ { - spec.split('|') - .map(str::trim) - .map(|dep| Spec::from_str(dep)) + pub fn parse_union(spec: &str) -> Result, Vec>> { + parser::union_spec().parse(spec).into_result() } } -fn parse_version_range(s: &str) -> Ranges { - let (start, end) = s - .split_once("..") - .map_or((s, None), |(start, end)| (start, Some(end))); - let start: Pack = start.parse().unwrap(); - let end = end - .map(FromStr::from_str) - .transpose() - .unwrap() - .unwrap_or(start.offset(1)); - Ranges::between(start, end) + +impl Spec { + fn from_str(s: &str) -> Result>> { + parser::spec().parse(s).into_result() + } } +#[derive(Debug, Clone)] struct ConditionalSpec { - condition: Option, - spec: Spec, + condition: Option, + specs: Vec, } +impl ConditionalSpec { + fn from_str(s: &str) -> Result>> { + parser::conditional_spec().parse(s).into_result() + } +} + +#[derive(Debug, Clone)] enum SpecCondition { - Binary(LogicalOperator, Box<[Condition; 2]>), + Binary(LogicalOperator, Box<[SpecCondition; 2]>), Requirement(Spec), } -impl FromStr for ConditionalSpec { - type Err = (); - - fn from_str(s: &str) -> Result { - // Split on the condition - let (s, condition) = s - .split_once("; if ") - .map_or_else(|| (s, None), |(left, right)| (left, Some(right))); - - // Parse the condition - let condition = condition.map(|condition| Condition::from_str(condition)).transpose().unwrap(); - - // Parse the spec - let spec = Spec::from_str(s).unwrap(); - - Ok(Self { - condition, - spec, - }) +mod parser { + use super::{ConditionalSpec, Pack, Spec, SpecCondition, parser}; + use chumsky::{ + error, + error::LabelError, + extra::ParserExtra, + input::{SliceInput, StrInput}, + pratt::*, + prelude::*, + text, + text::{Char, TextExpected}, + util::MaybeRef, + }; + use resolvo::LogicalOperator; + use version_ranges::Ranges; + + /// Parses a package name identifier. + pub fn name<'src, I, E>() -> impl Parser<'src, I, >::Slice, E> + Copy + where + I: StrInput<'src>, + I::Token: Char + 'src, + E: ParserExtra<'src, I>, + E::Error: LabelError<'src, I, TextExpected<'src, I>>, + { + any() + .try_map(|c: I::Token, span| { + if c.to_ascii() + .map(|i| i.is_ascii_alphabetic() || i == b'_') + .unwrap_or(false) + { + Ok(c) + } else { + Err(LabelError::expected_found( + [TextExpected::IdentifierPart], + Some(MaybeRef::Val(c)), + span, + )) + } + }) + .then( + any() + .try_map(|c: I::Token, span| { + if c.to_ascii().map_or(false, |i| { + i.is_ascii_alphanumeric() || i == b'_' || i == b'-' + }) { + Ok(()) + } else { + Err(LabelError::expected_found( + [TextExpected::IdentifierPart], + Some(MaybeRef::Val(c)), + span, + )) + } + }) + .repeated(), + ) + .to_slice() } -} -impl FromStr for Condition { - type Err = (); - - fn from_str(s: &str) -> Result { - + /// Parses a range of package versions. E.g. `5` or `1..5`. + fn ranges<'src>() + -> impl Parser<'src, &'src str, Ranges, extra::Err>> { + text::int(10) + .map(|s: &str| s.parse().unwrap()) + .then( + just("..") + .padded() + .ignore_then(text::int(10).map(|s: &str| s.parse().unwrap()).padded()) + .or_not(), + ) + .map(|(left, right)| { + let right = Pack::new(right.unwrap_or_else(|| left + 1)); + Ranges::between(Pack::new(left), right) + }) } -} + /// Parses a single [`Spec`]. E.g. `foo 1..2` or `bar 3` or `baz`. + pub(crate) fn spec<'src>() + -> impl Parser<'src, &'src str, Spec, extra::Err>> { + name() + .padded() + .then(ranges().or_not()) + .map(|(name, range)| Spec::new(name.to_string(), range.unwrap_or(Ranges::full()))) + } -impl FromStr for Spec { - type Err = (); + fn condition<'src>() + -> impl Parser<'src, &'src str, SpecCondition, extra::Err>> { + let and = just("and").padded().map(|_| LogicalOperator::And); + let or = just("or").padded().map(|_| LogicalOperator::Or); + + let single = spec().map(SpecCondition::Requirement); + + single.pratt(( + infix(left(1), and, |lhs, op, rhs, _| { + SpecCondition::Binary(op, Box::new([lhs, rhs])) + }), + infix(left(1), or, |lhs, op, rhs, _| { + SpecCondition::Binary(op, Box::new([lhs, rhs])) + }), + )) + } - fn from_str(s: &str) -> Result { - // Split the name and the version - let (Some(name), versions) = s.split_once(' ').map_or_else( - || (Some(s), None), - |(left, right)| (Some(left), Some(right)), - ) else { - panic!("spec does not have a name") - }; + pub(crate) fn union_spec<'src>() + -> impl Parser<'src, &'src str, Vec, extra::Err>> { + spec().separated_by(just("|").padded()).at_least(1).collect() + } - let versions = versions.map(parse_version_range).unwrap_or(Ranges::full()); - Ok(Spec::new(name.to_string(), versions)) + pub(crate) fn conditional_spec<'src>() + -> impl Parser<'src, &'src str, ConditionalSpec, extra::Err>> { + union_spec() + .then(just("; if").padded().ignore_then(condition()).or_not()) + .map(|(spec, condition)| ConditionalSpec { + condition, + specs: spec, + }) } } @@ -216,7 +293,7 @@ struct BundleBoxProvider { #[derive(Debug, Clone)] struct BundleBoxPackageDependencies { - dependencies: Vec>, + dependencies: Vec, constrains: Vec, } @@ -231,21 +308,40 @@ impl BundleBoxProvider { .expect("package missing") } - pub fn requirements>(&self, requirements: &[&str]) -> Vec { + pub fn requirements(&mut self, requirements: &[&str]) -> Vec { requirements .iter() - .map(|dep| Spec::from_str(dep).unwrap()) - .map(|spec| self.intern_version_set(&spec)) - .map(From::from) + .map(|dep| ConditionalSpec::from_str(*dep).unwrap()) + .map(|spec| { + let mut iter = spec + .specs + .into_iter() + .map(|spec| { + let name = self.pool.intern_package_name(&spec.name); + self.pool.intern_version_set(name, spec.versions) + }) + .peekable(); + let first = iter.next().unwrap(); + let requirement = if iter.peek().is_some() { + self.pool.intern_version_set_union(first, iter).into() + } else { + first.into() + }; + ConditionalRequirement { + condition: None, + requirement, + } + }) .collect() } - pub fn parse_requirements(&self, requirements: &[&str]) -> Vec { + pub fn version_sets(&mut self, requirements: &[&str]) -> Vec { requirements .iter() - .map(|deps| { - let specs = Spec::parse_union(deps).map(Result::unwrap); - self.intern_version_set_union(specs).into() + .map(|dep| Spec::from_str(*dep).unwrap()) + .map(|spec| { + let name = self.pool.intern_package_name(&spec.name); + self.pool.intern_version_set(name, spec.versions) }) .collect() } @@ -303,7 +399,7 @@ impl BundleBoxProvider { let dependencies = dependencies .iter() - .map(|dep| Spec::parse_union(dep).collect()) + .map(|dep| ConditionalSpec::from_str(dep)) .collect::, _>>() .unwrap(); @@ -404,6 +500,10 @@ impl Interner for BundleBoxProvider { ) -> impl Iterator { self.pool.resolve_version_set_union(version_set_union) } + + fn resolve_condition(&self, condition: ConditionId) -> Condition { + todo!() + } } impl DependencyProvider for BundleBoxProvider { @@ -521,33 +621,31 @@ impl DependencyProvider for BundleBoxProvider { constrains: Vec::with_capacity(deps.constrains.len()), }; for req in &deps.dependencies { - let mut remaining_req_specs = req.iter(); - - let first = remaining_req_specs + let mut version_sets = req + .specs + .iter() + .map(|spec| { + let name = self.pool.intern_package_name(&spec.name); + self.pool.intern_version_set(name, spec.versions.clone()) + }) + .peekable(); + + let first = version_sets .next() .expect("Dependency spec must have at least one constraint"); - let first_name = self.pool.intern_package_name(&first.name); - let first_version_set = self - .pool - .intern_version_set(first_name, first.versions.clone()); - - let requirement = if remaining_req_specs.len() == 0 { - first_version_set.into() + let requirement = if version_sets.peek().is_none() { + Requirement::Single(first) } else { - let other_version_sets = remaining_req_specs.map(|spec| { - self.pool.intern_version_set( - self.pool.intern_package_name(&spec.name), - spec.versions.clone(), - ) - }); - - self.pool - .intern_version_set_union(first_version_set, other_version_sets) - .into() + Requirement::Union(self.pool.intern_version_set_union(first, version_sets)) + }; + + let conditooal_requirement = ConditionalRequirement { + condition: None, + requirement, }; - result.requirements.push(requirement); + result.requirements.push(conditooal_requirement); } for req in &deps.constrains { @@ -585,7 +683,7 @@ fn transaction_to_string(interner: &impl Interner, solvables: &Vec) } /// Unsat so that we can view the conflict -fn solve_unsat(provider: BundleBoxProvider, specs: &[&str]) -> String { +fn solve_unsat(mut provider: BundleBoxProvider, specs: &[&str]) -> String { let requirements = provider.requirements(specs); let mut solver = Solver::new(provider); let problem = Problem::new().requirements(requirements); @@ -619,7 +717,7 @@ fn solve_snapshot(mut provider: BundleBoxProvider, specs: &[&str]) -> String { provider.sleep_before_return = true; - let requirements = provider.parse_requirements(specs); + let requirements = provider.requirements(specs); let mut solver = Solver::new(provider).with_runtime(runtime); let problem = Problem::new().requirements(requirements); match solver.solve(problem) { @@ -644,7 +742,7 @@ fn solve_snapshot(mut provider: BundleBoxProvider, specs: &[&str]) -> String { /// Test whether we can select a version, this is the most basic operation #[test] fn test_unit_propagation_1() { - let provider = BundleBoxProvider::from_packages(&[("asdf", 1, vec![])]); + let mut provider = BundleBoxProvider::from_packages(&[("asdf", 1, vec![])]); let requirements = provider.requirements(&["asdf"]); let mut solver = Solver::new(provider); let problem = Problem::new().requirements(requirements); @@ -661,7 +759,7 @@ fn test_unit_propagation_1() { /// Test if we can also select a nested version #[test] fn test_unit_propagation_nested() { - let provider = BundleBoxProvider::from_packages(&[ + let mut provider = BundleBoxProvider::from_packages(&[ ("asdf", 1u32, vec!["efgh"]), ("efgh", 4u32, vec![]), ("dummy", 6u32, vec![]), @@ -688,7 +786,7 @@ fn test_unit_propagation_nested() { /// Test if we can resolve multiple versions at once #[test] fn test_resolve_multiple() { - let provider = BundleBoxProvider::from_packages(&[ + let mut provider = BundleBoxProvider::from_packages(&[ ("asdf", 1, vec![]), ("asdf", 2, vec![]), ("efgh", 4, vec![]), @@ -749,7 +847,7 @@ fn test_resolve_with_conflict() { #[test] #[traced_test] fn test_resolve_with_nonexisting() { - let provider = BundleBoxProvider::from_packages(&[ + let mut provider = BundleBoxProvider::from_packages(&[ ("asdf", 4, vec!["b"]), ("asdf", 3, vec![]), ("b", 1, vec!["idontexist"]), @@ -771,7 +869,7 @@ fn test_resolve_with_nonexisting() { #[test] #[traced_test] fn test_resolve_with_nested_deps() { - let provider = BundleBoxProvider::from_packages(&[ + let mut provider = BundleBoxProvider::from_packages(&[ ( "apache-airflow", 3, @@ -940,7 +1038,7 @@ fn test_resolve_favor_with_conflict() { #[test] fn test_resolve_cyclic() { - let provider = + let mut provider = BundleBoxProvider::from_packages(&[("a", 2, vec!["b 0..10"]), ("b", 5, vec!["a 2..4"])]); let requirements = provider.requirements(&["a 0..100"]); let mut solver = Solver::new(provider); @@ -1221,14 +1319,14 @@ fn test_root_excluded() { #[test] fn test_constraints() { - let provider = BundleBoxProvider::from_packages(&[ + let mut provider = BundleBoxProvider::from_packages(&[ ("a", 1, vec!["b 0..10"]), ("b", 1, vec![]), ("b", 2, vec![]), ("c", 1, vec![]), ]); let requirements = provider.requirements(&["a 0..10"]); - let constraints = provider.requirements(&["b 1..2", "c"]); + let constraints = provider.version_sets(&["b 1..2", "c"]); let mut solver = Solver::new(provider); let problem = Problem::new() .requirements(requirements) @@ -1258,7 +1356,7 @@ fn test_solve_with_additional() { provider.set_locked("locked", 2); let requirements = provider.requirements(&["a 0..10"]); - let constraints = provider.requirements(&["b 1..2", "c"]); + let constraints = provider.version_sets(&["b 1..2", "c"]); let extra_solvables = [ provider.solvable_id("b", 2), @@ -1310,7 +1408,7 @@ fn test_solve_with_additional_with_constrains() { provider.add_package("l", 1.into(), &["j", "k"], &[]); let requirements = provider.requirements(&["a 0..10", "e"]); - let constraints = provider.requirements(&["b 1..2", "c", "k 2..3"]); + let constraints = provider.version_sets(&["b 1..2", "c", "k 2..3"]); let extra_solvables = [ provider.solvable_id("d", 1), @@ -1342,36 +1440,34 @@ fn test_solve_with_additional_with_constrains() { "###); } -#[test] -fn test_snapshot() { - let provider = BundleBoxProvider::from_packages(&[ - ("menu", 15, vec!["dropdown 2..3"]), - ("menu", 10, vec!["dropdown 1..2"]), - ("dropdown", 2, vec!["icons 2"]), - ("dropdown", 1, vec!["intl 3"]), - ("icons", 2, vec![]), - ("icons", 1, vec![]), - ("intl", 5, vec![]), - ("intl", 3, vec![]), - ]); - - let menu_name_id = provider.package_name("menu"); - - let snapshot = provider.into_snapshot(); - - #[cfg(feature = "serde")] - serialize_snapshot(&snapshot, "snapshot_pubgrub_menu.json"); - - let mut snapshot_provider = snapshot.provider(); - - let menu_req = snapshot_provider.add_package_requirement(menu_name_id, "*"); - - assert_snapshot!(solve_for_snapshot(snapshot_provider, &[menu_req], &[])); -} +// #[test] +// fn test_snapshot() { +// let provider = BundleBoxProvider::from_packages(&[ +// ("menu", 15, vec!["dropdown 2..3"]), +// ("menu", 10, vec!["dropdown 1..2"]), +// ("dropdown", 2, vec!["icons 2"]), +// ("dropdown", 1, vec!["intl 3"]), +// ("icons", 2, vec![]), +// ("icons", 1, vec![]), +// ("intl", 5, vec![]), +// ("intl", 3, vec![]), +// ]); +// +// let menu_name_id = provider.package_name("menu"); +// +// let snapshot = provider.into_snapshot(); +// +// #[cfg(feature = "serde")] +// serialize_snapshot(&snapshot, "snapshot_pubgrub_menu.json"); +// +// let mut snapshot_provider = snapshot.provider(); +// +// assert_snapshot!(solve_for_snapshot(snapshot_provider, &[menu_req], &[])); +// } #[test] fn test_snapshot_union_requirements() { - let provider = BundleBoxProvider::from_packages(&[ + let mut provider = BundleBoxProvider::from_packages(&[ ("icons", 2, vec![]), ("icons", 1, vec![]), ("intl", 5, vec![]), @@ -1379,21 +1475,9 @@ fn test_snapshot_union_requirements() { ("union", 1, vec!["icons 2 | intl"]), ]); - let intl_name_id = provider.package_name("intl"); - let union_name_id = provider.package_name("union"); - - let snapshot = provider.into_snapshot(); - - let mut snapshot_provider = snapshot.provider(); + let requirements = provider.requirements(&["intl", "union"]); - let intl_req = snapshot_provider.add_package_requirement(intl_name_id, "*"); - let union_req = snapshot_provider.add_package_requirement(union_name_id, "*"); - - assert_snapshot!(solve_for_snapshot( - snapshot_provider, - &[intl_req, union_req], - &[] - )); + assert_snapshot!(solve_for_snapshot(provider, &requirements, &[])); } #[test] @@ -1409,28 +1493,18 @@ fn test_union_empty_requirements() { #[test] fn test_root_constraints() { - let provider = + let mut provider = BundleBoxProvider::from_packages(&[("icons", 1, vec![]), ("union", 1, vec!["icons"])]); - let union_name_id = provider.package_name("union"); - - let snapshot = provider.into_snapshot(); - - let mut snapshot_provider = snapshot.provider(); - - let union_req = snapshot_provider.add_package_requirement(union_name_id, "*"); - let union_constraint = snapshot_provider.add_package_requirement(union_name_id, "5"); + let requirements = provider.requirements(&["union"]); + let constraints = provider.version_sets(&["union 5"]); - assert_snapshot!(solve_for_snapshot( - snapshot_provider, - &[union_req], - &[union_constraint] - )); + assert_snapshot!(solve_for_snapshot(provider, &requirements, &constraints)); } #[test] fn test_explicit_root_requirements() { - let provider = BundleBoxProvider::from_packages(&[ + let mut provider = BundleBoxProvider::from_packages(&[ // `a` depends transitively on `b` ("a", 1, vec!["b"]), // `b` depends on `c`, but the highest version of `b` constrains `c` to `<2`. @@ -1460,20 +1534,36 @@ fn test_explicit_root_requirements() { "###); } +#[test] +fn test_conditional_requirements() { + let mut provider = BundleBoxProvider::from_packages(&[ + ("foo", 1, vec!["bar; if baz"]), + ("bar", 1, vec![]), + ("baz", 1, vec![]), + + ("menu", 1, vec!["icon; if foo"]), + ("icon", 1, vec![]), + ]); + + let requirements = provider.requirements(&["foo", "menu"]); + + assert_snapshot!(solve_for_snapshot(provider, &requirements, &[])); +} + #[cfg(feature = "serde")] fn serialize_snapshot(snapshot: &DependencySnapshot, destination: impl AsRef) { let file = std::io::BufWriter::new(std::fs::File::create(destination.as_ref()).unwrap()); serde_json::to_writer_pretty(file, snapshot).unwrap() } -fn solve_for_snapshot( - provider: SnapshotProvider, - root_reqs: &[VersionSetId], +fn solve_for_snapshot( + provider: D, + root_reqs: &[ConditionalRequirement], root_constraints: &[VersionSetId], ) -> String { let mut solver = Solver::new(provider); let problem = Problem::new() - .requirements(root_reqs.iter().copied().map(Into::into).collect()) + .requirements(root_reqs.iter().cloned().collect()) .constraints(root_constraints.iter().copied().map(Into::into).collect()); match solver.solve(problem) { Ok(solvables) => transaction_to_string(solver.provider(), &solvables), From 87b5c892b8a53fa1107881f099ac6f408760fa42 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Tue, 6 May 2025 09:32:34 +0200 Subject: [PATCH 03/28] add conditions to test suite --- src/internal/id.rs | 14 +++++- src/requirement.rs | 1 - src/snapshot.rs | 4 +- tests/solver.rs | 110 +++++++++++++++++++-------------------------- 4 files changed, 62 insertions(+), 67 deletions(-) diff --git a/src/internal/id.rs b/src/internal/id.rs index d7250c01..8912dbae 100644 --- a/src/internal/id.rs +++ b/src/internal/id.rs @@ -61,7 +61,19 @@ impl ArenaId for VersionSetId { #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(transparent))] -pub struct ConditionId(pub NonZero); +pub struct ConditionId(NonZero); + +impl ConditionId { + /// Creates a new `ConditionId` from a `u32`, panicking if the value is zero. + pub fn new(id: u32) -> Self { + Self::from_usize(id as usize) + } + + /// Returns the inner `u32` value of the `ConditionId`. + pub fn as_u32(self) -> u32 { + self.0.get() + } +} impl ArenaId for ConditionId { fn from_usize(x: usize) -> Self { diff --git a/src/requirement.rs b/src/requirement.rs index 303ce1d8..5feab43f 100644 --- a/src/requirement.rs +++ b/src/requirement.rs @@ -4,7 +4,6 @@ use itertools::Itertools; use crate::{ ConditionalRequirement, Interner, VersionSetId, VersionSetUnionId, - conditional_requirement::Condition, }; use crate::internal::id::ConditionId; diff --git a/src/snapshot.rs b/src/snapshot.rs index d8716713..2f491dc6 100644 --- a/src/snapshot.rs +++ b/src/snapshot.rs @@ -454,8 +454,8 @@ impl Interner for SnapshotProvider<'_> { .copied() } - fn resolve_condition(&self, condition: ConditionId) -> Condition { - unimplemented!(); + fn resolve_condition(&self, _condition: ConditionId) -> Condition { + todo!() } } diff --git a/tests/solver.rs b/tests/solver.rs index 2655dc3e..23284a42 100644 --- a/tests/solver.rs +++ b/tests/solver.rs @@ -1,7 +1,6 @@ -use chumsky::{IterParser, error}; +use chumsky::{error}; use std::{ any::Any, - borrow::Borrow, cell::{Cell, RefCell}, collections::HashSet, fmt::{Debug, Display, Formatter}, @@ -17,14 +16,13 @@ use std::{ }; use ahash::HashMap; -use chumsky::prelude::{EmptyErr, just}; -use chumsky::{Parser, extra, text}; +use chumsky::Parser; use indexmap::IndexMap; use insta::assert_snapshot; use itertools::Itertools; use resolvo::{ Candidates, Condition, ConditionId, ConditionalRequirement, Dependencies, DependencyProvider, - Interner, KnownDependencies, LogicalOperator, NameId, Problem, Requirement, SolvableId, Solver, + Interner, KnownDependencies, LogicalOperator, NameId, Problem, SolvableId, Solver, SolverCache, StringId, UnsolvableOrCancelled, VersionSetId, VersionSetUnionId, snapshot::{DependencySnapshot, SnapshotProvider}, utils::Pool, @@ -122,10 +120,6 @@ impl Spec { pub fn new(name: String, versions: Ranges) -> Self { Self { name, versions } } - - pub fn parse_union(spec: &str) -> Result, Vec>> { - parser::union_spec().parse(spec).into_result() - } } impl Spec { @@ -146,14 +140,14 @@ impl ConditionalSpec { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq, PartialEq, Hash)] enum SpecCondition { Binary(LogicalOperator, Box<[SpecCondition; 2]>), Requirement(Spec), } mod parser { - use super::{ConditionalSpec, Pack, Spec, SpecCondition, parser}; + use super::{ConditionalSpec, Pack, Spec, SpecCondition}; use chumsky::{ error, error::LabelError, @@ -256,7 +250,10 @@ mod parser { pub(crate) fn union_spec<'src>() -> impl Parser<'src, &'src str, Vec, extra::Err>> { - spec().separated_by(just("|").padded()).at_least(1).collect() + spec() + .separated_by(just("|").padded()) + .at_least(1) + .collect() } pub(crate) fn conditional_spec<'src>() @@ -274,6 +271,8 @@ mod parser { #[derive(Default)] struct BundleBoxProvider { pool: Pool>, + id_to_condition: Vec, + conditions: HashMap, packages: IndexMap>, favored: HashMap, locked: HashMap, @@ -293,7 +292,7 @@ struct BundleBoxProvider { #[derive(Debug, Clone)] struct BundleBoxPackageDependencies { - dependencies: Vec, + dependencies: Vec, constrains: Vec, } @@ -308,6 +307,22 @@ impl BundleBoxProvider { .expect("package missing") } + pub fn intern_condition(&mut self, condition: &SpecCondition) -> ConditionId { + if let Some(id) = self.conditions.get(&condition) { + return *id; + } + + if let SpecCondition::Binary(_op, sides) = condition { + self.intern_condition(&sides[0]); + self.intern_condition(&sides[1]); + } + + let id = ConditionId::new(self.id_to_condition.len() as u32); + self.id_to_condition.push(condition.clone()); + self.conditions.insert(condition.clone(), id); + id + } + pub fn requirements(&mut self, requirements: &[&str]) -> Vec { requirements .iter() @@ -316,10 +331,7 @@ impl BundleBoxProvider { let mut iter = spec .specs .into_iter() - .map(|spec| { - let name = self.pool.intern_package_name(&spec.name); - self.pool.intern_version_set(name, spec.versions) - }) + .map(|spec| self.intern_version_set(&spec)) .peekable(); let first = iter.next().unwrap(); let requirement = if iter.peek().is_some() { @@ -327,8 +339,11 @@ impl BundleBoxProvider { } else { first.into() }; + + let condition = spec.condition.map(|c| self.intern_condition(&c)); + ConditionalRequirement { - condition: None, + condition, requirement, } }) @@ -352,17 +367,6 @@ impl BundleBoxProvider { .intern_version_set(dep_name, spec.versions.clone()) } - pub fn intern_version_set_union( - &self, - specs: impl IntoIterator>, - ) -> VersionSetUnionId { - let mut specs = specs - .into_iter() - .map(|spec| self.intern_version_set(spec.borrow())); - self.pool - .intern_version_set_union(specs.next().unwrap(), specs) - } - pub fn from_packages(packages: &[(&str, u32, Vec<&str>)]) -> Self { let mut result = Self::new(); for (name, version, deps) in packages { @@ -397,11 +401,7 @@ impl BundleBoxProvider { ) { self.pool.intern_package_name(package_name); - let dependencies = dependencies - .iter() - .map(|dep| ConditionalSpec::from_str(dep)) - .collect::, _>>() - .unwrap(); + let dependencies = self.requirements(dependencies); let constrains = constrains .iter() @@ -502,7 +502,18 @@ impl Interner for BundleBoxProvider { } fn resolve_condition(&self, condition: ConditionId) -> Condition { - todo!() + let condition = condition.as_u32(); + let condition = &self.id_to_condition[condition as usize]; + match condition { + SpecCondition::Binary(op, items) => Condition::Binary( + *op, + *self.conditions.get(&items[0]).unwrap(), + *self.conditions.get(&items[1]).unwrap(), + ), + SpecCondition::Requirement(requirement) => { + Condition::Requirement(self.intern_version_set(requirement)) + } + } } } @@ -620,33 +631,7 @@ impl DependencyProvider for BundleBoxProvider { requirements: Vec::with_capacity(deps.dependencies.len()), constrains: Vec::with_capacity(deps.constrains.len()), }; - for req in &deps.dependencies { - let mut version_sets = req - .specs - .iter() - .map(|spec| { - let name = self.pool.intern_package_name(&spec.name); - self.pool.intern_version_set(name, spec.versions.clone()) - }) - .peekable(); - - let first = version_sets - .next() - .expect("Dependency spec must have at least one constraint"); - - let requirement = if version_sets.peek().is_none() { - Requirement::Single(first) - } else { - Requirement::Union(self.pool.intern_version_set_union(first, version_sets)) - }; - - let conditooal_requirement = ConditionalRequirement { - condition: None, - requirement, - }; - - result.requirements.push(conditooal_requirement); - } + result.requirements = deps.dependencies.clone(); for req in &deps.constrains { let dep_name = self.pool.intern_package_name(&req.name); @@ -1540,7 +1525,6 @@ fn test_conditional_requirements() { ("foo", 1, vec!["bar; if baz"]), ("bar", 1, vec![]), ("baz", 1, vec![]), - ("menu", 1, vec!["icon; if foo"]), ("icon", 1, vec![]), ]); From 21decc6557ab879eb0b8cb9de748fa4654b00fa0 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Tue, 6 May 2025 11:00:06 +0200 Subject: [PATCH 04/28] add condition to clause --- Cargo.lock | 12 ++ Cargo.toml | 2 +- src/internal/id.rs | 2 +- src/solver/clause.rs | 6 + src/solver/conditions.rs | 64 +++++++ src/solver/encoding.rs | 160 +++++++++++++----- src/solver/mod.rs | 28 ++- tests/.solver.rs.pending-snap | 18 ++ .../solver__conditional_requirements.snap.new | 2 +- 9 files changed, 239 insertions(+), 55 deletions(-) create mode 100644 src/solver/conditions.rs diff --git a/Cargo.lock b/Cargo.lock index fb87e75c..01b12e61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -584,6 +584,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -603,6 +614,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", + "futures-macro", "futures-sink", "futures-task", "pin-project-lite", diff --git a/Cargo.toml b/Cargo.toml index 76314ed2..fe117c3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ tracing = "0.1.41" elsa = "1.10.0" 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.42", features = ["rt"], optional = true } diff --git a/src/internal/id.rs b/src/internal/id.rs index 8912dbae..b551dd37 100644 --- a/src/internal/id.rs +++ b/src/internal/id.rs @@ -71,7 +71,7 @@ impl ConditionId { /// Returns the inner `u32` value of the `ConditionId`. pub fn as_u32(self) -> u32 { - self.0.get() + self.0.get() - 1 } } diff --git a/src/solver/clause.rs b/src/solver/clause.rs index 8a28d2ed..4b699ea0 100644 --- a/src/solver/clause.rs +++ b/src/solver/clause.rs @@ -18,6 +18,7 @@ use crate::{ variable_map::VariableMap, }, }; +use crate::solver::conditions::DisjunctionId; /// Represents a single clause in the SAT problem /// @@ -348,6 +349,7 @@ impl WatchedLiterals { candidate: VariableId, requirement: Requirement, matching_candidates: impl IntoIterator, + condition: Option<(DisjunctionId, &[VariableId])>, decision_tracker: &DecisionTracker, ) -> (Option, bool, Clause) { let (kind, watched_literals, conflict) = Clause::requires( @@ -664,6 +666,7 @@ mod test { parent, VersionSetId::from_usize(0).into(), [candidate1, candidate2], + None, &decisions, ); assert!(!conflict); @@ -687,6 +690,7 @@ mod test { parent, VersionSetId::from_usize(0).into(), [candidate1, candidate2], + None, &decisions, ); assert!(!conflict); @@ -710,6 +714,7 @@ mod test { parent, VersionSetId::from_usize(0).into(), [candidate1, candidate2], + None, &decisions, ); assert!(conflict); @@ -731,6 +736,7 @@ mod test { parent, VersionSetId::from_usize(0).into(), [candidate1, candidate2], + None, &decisions, ) }) diff --git a/src/solver/conditions.rs b/src/solver/conditions.rs new file mode 100644 index 00000000..4ed13986 --- /dev/null +++ b/src/solver/conditions.rs @@ -0,0 +1,64 @@ +use std::num::NonZero; + +use crate::{ + Condition, ConditionId, Interner, LogicalOperator, VersionSetId, + internal::{ + arena::{Arena, ArenaId}, + small_vec::SmallVec, + }, +}; + +/// An identifier that describes a group of version sets that are combined using +/// AND logical operators. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct DisjunctionId(NonZero); + +impl ArenaId for DisjunctionId { + fn from_usize(x: usize) -> Self { + // Safe because we are guaranteed that the id is non-zero by adding 1. + DisjunctionId(unsafe { NonZero::new_unchecked((x + 1) as u32) }) + } + + fn to_usize(self) -> usize { + (self.0.get() - 1) as usize + } +} + +pub struct Disjunction { + /// The top-level condition to which this disjunction belongs. + pub condition: ConditionId, +} + +/// Converts from a boolean expression tree as described by `condition` to a +/// boolean formula in disjunctive normal form (DNF). Each inner Vec represents +/// a conjunction (AND group) and the outer Vec represents the disjunction (OR +/// group). +pub fn convert_conditions_to_dnf( + condition: ConditionId, + interner: &I, +) -> Vec> { + match interner.resolve_condition(condition) { + Condition::Requirement(version_set) => vec![vec![version_set]], + Condition::Binary(LogicalOperator::Or, lhs, rhs) => { + let mut left_dnf = convert_conditions_to_dnf(lhs, interner); + let mut right_dnf = convert_conditions_to_dnf(rhs, interner); + left_dnf.append(&mut right_dnf); + left_dnf + } + Condition::Binary(LogicalOperator::And, lhs, rhs) => { + let left_dnf = convert_conditions_to_dnf(lhs, interner); + let right_dnf = convert_conditions_to_dnf(rhs, interner); + + // Distribute AND over OR + let mut result = Vec::new(); + for l in &left_dnf { + for r in &right_dnf { + let mut merged = l.clone(); + merged.extend(r.clone()); + result.push(merged); + } + } + result + } + } +} diff --git a/src/solver/encoding.rs b/src/solver/encoding.rs index a4aa1012..ab05c445 100644 --- a/src/solver/encoding.rs +++ b/src/solver/encoding.rs @@ -1,12 +1,19 @@ -use super::{SolverState, clause::WatchedLiterals}; +use std::{any::Any, future::ready}; + +use futures::{ + FutureExt, StreamExt, TryFutureExt, future::LocalBoxFuture, stream::FuturesUnordered, +}; + +use super::{SolverState, clause::WatchedLiterals, conditions}; use crate::{ - Candidates, Dependencies, DependencyProvider, NameId, Requirement, SolvableId, SolverCache, - StringId, VersionSetId, - internal::arena::ArenaId, - internal::id::{ClauseId, SolvableOrRootId, VariableId}, + Candidates, ConditionId, ConditionalRequirement, Dependencies, DependencyProvider, NameId, + SolvableId, SolverCache, StringId, VersionSetId, + internal::{ + arena::ArenaId, + id::{ClauseId, SolvableOrRootId, VariableId}, + }, + solver::conditions::Disjunction, }; -use futures::{FutureExt, StreamExt, future::LocalBoxFuture, stream::FuturesUnordered}; -use std::any::Any; /// An object that is responsible for encoding information from the dependency /// provider into rules and variables that are used by the solver. @@ -29,7 +36,8 @@ pub(crate) struct Encoder<'a, D: DependencyProvider> { /// The set of futures that are pending to be resolved. pending_futures: FuturesUnordered, Box>>>, - /// A list of clauses that were introduced that are conflicting with the current state. + /// A list of clauses that were introduced that are conflicting with the + /// current state. conflicting_clauses: Vec, } @@ -56,8 +64,9 @@ struct CandidatesAvailable<'a> { /// Result of querying candidates for a particular requirement. struct RequirementCandidatesAvailable<'a> { solvable_id: SolvableOrRootId, - requirement: Requirement, + requirement: ConditionalRequirement, candidates: Vec<&'a [SolvableId]>, + condition: Option<(ConditionId, Vec>)>, } /// Result of querying candidates for a particular constraint. @@ -137,7 +146,8 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { let (requirements, constraints) = match dependencies { Dependencies::Known(deps) => (&deps.requirements, &deps.constrains), Dependencies::Unknown(reason) => { - // If the dependencies are unknown, we add an exclusion clause and stop processing. + // If the dependencies are unknown, we add an exclusion clause and stop + // processing. self.add_exclusion_clause(solvable_id, *reason); return; } @@ -156,7 +166,7 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { // For each requirement request the matching candidates. for requirement in requirements { - self.queue_requirement(solvable_id, requirement.requirement); + self.queue_conditional_requirement(solvable_id, requirement.clone()); } // For each constraint, request the candidates that are non-matching @@ -206,11 +216,12 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { solvable_id, requirement, candidates, + condition, }: RequirementCandidatesAvailable<'a>, ) { tracing::trace!( "Sorted candidates available for {}", - requirement.display(self.cache.provider()), + requirement.requirement.display(self.cache.provider()), ); let variable = self.state.variable_map.intern_solvable_or_root(solvable_id); @@ -275,41 +286,74 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { ); } - // Add the requirements clause - let no_candidates = candidates.iter().all(|candidates| candidates.is_empty()); - let (watched_literals, conflict, kind) = WatchedLiterals::requires( - variable, - requirement, - version_set_variables.iter().flatten().copied(), - &self.state.decision_tracker, - ); - let clause_id = self.state.clauses.alloc(watched_literals, kind); + // Determine the disjunctions of all the conditions for this requirement. + let mut disjunctions = + Vec::with_capacity(condition.as_ref().map_or(0, |(_, dnf)| dnf.len())); + if let Some((condition, dnf)) = condition { + for disjunction in dnf { + let mut candidates = Vec::with_capacity( + disjunction + .iter() + .map(|candidates| candidates.len()) + .sum::(), + ); + for version_set_candidates in disjunction.into_iter() { + candidates.extend( + version_set_candidates + .into_iter() + .map(|&candidate| self.state.variable_map.intern_solvable(candidate)), + ); + } + let disjunction_id = self.state.disjunctions.alloc(Disjunction { condition }); + let candidates = self + .state + .disjunction_to_candidates + .insert(disjunction_id, candidates); + disjunctions.push(Some((disjunction_id, candidates))); + } + } else { + disjunctions.push(None); + } + + for condition in disjunctions { + // Add the requirements clause + let no_candidates = candidates.iter().all(|candidates| candidates.is_empty()); + let (watched_literals, conflict, kind) = WatchedLiterals::requires( + variable, + requirement.requirement, + version_set_variables.iter().flatten().copied(), + condition, + &self.state.decision_tracker, + ); + let clause_id = self.state.clauses.alloc(watched_literals, kind); - let watched_literals = self.state.clauses.watched_literals[clause_id.to_usize()].as_mut(); + let watched_literals = + self.state.clauses.watched_literals[clause_id.to_usize()].as_mut(); + + if let Some(watched_literals) = watched_literals { + self.state + .watches + .start_watching(watched_literals, clause_id); + } - if let Some(watched_literals) = watched_literals { self.state - .watches - .start_watching(watched_literals, clause_id); - } + .requires_clauses + .entry(variable) + .or_default() + .push((requirement.requirement, clause_id)); - self.state - .requires_clauses - .entry(variable) - .or_default() - .push((requirement, clause_id)); - - if conflict { - self.conflicting_clauses.push(clause_id); - } else if no_candidates { - // Add assertions for unit clauses (i.e. those with no matching candidates) - self.state.negative_assertions.push((variable, clause_id)); + if conflict { + self.conflicting_clauses.push(clause_id); + } else if no_candidates { + // Add assertions for unit clauses (i.e. those with no matching candidates) + self.state.negative_assertions.push((variable, clause_id)); + } } // Store resolved variables for later self.state .requirement_to_sorted_candidates - .insert(requirement, version_set_variables); + .insert(requirement.requirement, version_set_variables); } /// Called when the candidates for a particular constraint are available. @@ -358,7 +402,8 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { } } - /// Adds clauses to forbid any other clauses than the locked solvable to be installed. + /// Adds clauses to forbid any other clauses than the locked solvable to be + /// installed. fn add_locked_package_clauses( &mut self, locked_solvable_id: SolvableId, @@ -464,20 +509,45 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { /// Enqueues retrieving the candidates for a particular requirement. These /// candidates are already filtered and sorted. - fn queue_requirement(&mut self, solvable_id: SolvableOrRootId, requirement: Requirement) { + fn queue_conditional_requirement( + &mut self, + solvable_id: SolvableOrRootId, + requirement: ConditionalRequirement, + ) { let cache = self.cache; let query_requirements_candidates = async move { - let candidates = - futures::future::try_join_all(requirement.version_sets(cache.provider()).map( - |version_set| cache.get_or_cache_sorted_candidates_for_version_set(version_set), - )) - .await?; + let candidates = futures::future::try_join_all( + requirement + .requirement + .version_sets(cache.provider()) + .map(|version_set| { + cache.get_or_cache_sorted_candidates_for_version_set(version_set) + }), + ); + + let condition_candidates = match requirement.condition { + Some(condition) => futures::future::try_join_all( + conditions::convert_conditions_to_dnf(condition, cache.provider()) + .into_iter() + .map(|cnf| { + futures::future::try_join_all(cnf.into_iter().map(|version_set| { + cache.get_or_cache_matching_candidates(version_set) + })) + }), + ) + .map_ok(move |dnf| Some((condition, dnf))) + .left_future(), + None => ready(Ok(None)).right_future(), + }; + + let (candidates, condition) = futures::try_join!(candidates, condition_candidates)?; Ok(TaskResult::RequirementCandidates( RequirementCandidatesAvailable { solvable_id, requirement, candidates, + condition, }, )) }; diff --git a/src/solver/mod.rs b/src/solver/mod.rs index 5ea3f3a8..b9fc8464 100644 --- a/src/solver/mod.rs +++ b/src/solver/mod.rs @@ -1,3 +1,5 @@ +use std::{any::Any, fmt::Display, ops::ControlFlow}; + use ahash::{HashMap, HashSet}; pub use cache::SolverCache; use clause::{Clause, Literal, WatchedLiterals}; @@ -7,19 +9,27 @@ use elsa::FrozenMap; use encoding::Encoder; use indexmap::IndexMap; use itertools::Itertools; -use std::{any::Any, fmt::Display, ops::ControlFlow}; use variable_map::VariableMap; use watch_map::WatchMap; - -use crate::{Dependencies, DependencyProvider, KnownDependencies, Requirement, VersionSetId, conflict::Conflict, internal::{ - arena::{Arena, ArenaId}, - id::{ClauseId, LearntClauseId, NameId, SolvableId, SolvableOrRootId, VariableId}, - mapping::Mapping, -}, runtime::{AsyncRuntime, NowOrNeverRuntime}, solver::binary_encoding::AtMostOnceTracker, ConditionalRequirement}; +use conditions::{DisjunctionId, Disjunction}; + +use crate::{ + ConditionalRequirement, Dependencies, DependencyProvider, KnownDependencies, Requirement, + VersionSetId, + conflict::Conflict, + internal::{ + arena::{Arena, ArenaId}, + id::{ClauseId, LearntClauseId, NameId, SolvableId, SolvableOrRootId, VariableId}, + mapping::Mapping, + }, + runtime::{AsyncRuntime, NowOrNeverRuntime}, + solver::binary_encoding::AtMostOnceTracker, +}; mod binary_encoding; mod cache; pub(crate) mod clause; +mod conditions; mod decision; mod decision_map; mod decision_tracker; @@ -159,6 +169,8 @@ pub(crate) struct SolverState { /// candidates. requirement_to_sorted_candidates: FrozenMap, + disjunction_to_candidates: + FrozenMap, ahash::RandomState>, pub(crate) variable_map: VariableMap, @@ -168,6 +180,8 @@ pub(crate) struct SolverState { learnt_why: Mapping>, learnt_clause_ids: Vec, + disjunctions: Arena, + clauses_added_for_package: HashSet, clauses_added_for_solvable: HashSet, forbidden_clauses_added: HashMap>, diff --git a/tests/.solver.rs.pending-snap b/tests/.solver.rs.pending-snap index 369db7ca..8c9c6561 100644 --- a/tests/.solver.rs.pending-snap +++ b/tests/.solver.rs.pending-snap @@ -71,3 +71,21 @@ {"run_id":"1746475919-456405700","line":1011,"new":null,"old":null} {"run_id":"1746475919-456405700","line":1069,"new":null,"old":null} {"run_id":"1746475919-456405700","line":1488,"new":null,"old":null} +{"run_id":"1746521934-289213500","line":1034,"new":null,"old":null} +{"run_id":"1746521934-289213500","line":1515,"new":null,"old":null} +{"run_id":"1746521934-289213500","line":1322,"new":null,"old":null} +{"run_id":"1746521934-289213500","line":1017,"new":null,"old":null} +{"run_id":"1746521934-289213500","line":1365,"new":null,"old":null} +{"run_id":"1746521934-289213500","line":1417,"new":null,"old":null} +{"run_id":"1746521934-289213500","line":996,"new":null,"old":null} +{"run_id":"1746521934-289213500","line":1054,"new":null,"old":null} +{"run_id":"1746521934-289213500","line":1473,"new":null,"old":null} +{"run_id":"1746521959-537048400","line":1515,"new":null,"old":null} +{"run_id":"1746521959-537048400","line":1322,"new":null,"old":null} +{"run_id":"1746521959-537048400","line":1034,"new":null,"old":null} +{"run_id":"1746521959-537048400","line":1365,"new":null,"old":null} +{"run_id":"1746521959-537048400","line":1417,"new":null,"old":null} +{"run_id":"1746521959-537048400","line":1017,"new":null,"old":null} +{"run_id":"1746521959-537048400","line":996,"new":null,"old":null} +{"run_id":"1746521959-537048400","line":1473,"new":null,"old":null} +{"run_id":"1746521959-537048400","line":1054,"new":null,"old":null} diff --git a/tests/snapshots/solver__conditional_requirements.snap.new b/tests/snapshots/solver__conditional_requirements.snap.new index 774ec0da..351b3309 100644 --- a/tests/snapshots/solver__conditional_requirements.snap.new +++ b/tests/snapshots/solver__conditional_requirements.snap.new @@ -1,6 +1,6 @@ --- source: tests/solver.rs -assertion_line: 1550 +assertion_line: 1534 expression: "solve_for_snapshot(provider, &requirements, &[])" --- bar=1 From 9f595c8add609f77384c9dd14050fe4b74ada14b Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Tue, 6 May 2025 16:06:26 +0200 Subject: [PATCH 05/28] wip --- src/conflict.rs | 2 +- src/solver/clause.rs | 113 ++++++++++++------ src/solver/encoding.rs | 2 +- src/solver/mod.rs | 29 ++++- tests/.solver.rs.pending-snap | 90 ++++++++++++++ ...ver__conditional_optional_missing.snap.new | 8 ++ .../solver__conditional_requirements.snap.new | 9 +- tests/solver.rs | 17 ++- 8 files changed, 215 insertions(+), 55 deletions(-) create mode 100644 tests/snapshots/solver__conditional_optional_missing.snap.new diff --git a/src/conflict.rs b/src/conflict.rs index bfa20fdd..4bb43bc1 100644 --- a/src/conflict.rs +++ b/src/conflict.rs @@ -80,7 +80,7 @@ impl Conflict { ); } Clause::Learnt(..) => unreachable!(), - &Clause::Requires(package_id, version_set_id) => { + &Clause::Requires(package_id, _condition, version_set_id) => { let solvable = package_id .as_solvable_or_root(&state.variable_map) .expect("only solvables can be excluded"); diff --git a/src/solver/clause.rs b/src/solver/clause.rs index 4b699ea0..dbfe3ab1 100644 --- a/src/solver/clause.rs +++ b/src/solver/clause.rs @@ -8,17 +8,16 @@ use std::{ use elsa::FrozenMap; use crate::{ - Interner, NameId, Requirement, + Candidates, Interner, NameId, Requirement, internal::{ arena::{Arena, ArenaId}, id::{ClauseId, LearntClauseId, StringId, VersionSetId}, }, solver::{ - VariableId, decision_map::DecisionMap, decision_tracker::DecisionTracker, - variable_map::VariableMap, + VariableId, conditions::DisjunctionId, decision_map::DecisionMap, + decision_tracker::DecisionTracker, variable_map::VariableMap, }, }; -use crate::solver::conditions::DisjunctionId; /// Represents a single clause in the SAT problem /// @@ -56,9 +55,15 @@ pub(crate) enum Clause { /// Makes the solvable require the candidates associated with the /// [`Requirement`]. /// - /// In SAT terms: (¬A ∨ B1 ∨ B2 ∨ ... ∨ B99), where B1 to B99 represent the - /// possible candidates for the provided [`Requirement`]. - Requires(VariableId, Requirement), + /// Optionally the requirement can be associated with a condition in the + /// form of a disjunction. + /// + /// ~A v ~C1 ^ C2 ^ C3 ^ v R + /// + /// In SAT terms: (¬A ∨ ¬D1 v ¬D2 .. v ¬D99 v B1 ∨ B2 ∨ ... ∨ B99), where D1 + /// to D99 represent the candidates of the disjunction and B1 to B99 + /// represent the possible candidates for the provided [`Requirement`]. + Requires(VariableId, Option, Requirement), /// Ensures only a single version of a package is installed /// /// Usage: generate one [`Clause::ForbidMultipleInstances`] clause for each @@ -117,37 +122,55 @@ impl Clause { parent: VariableId, requirement: Requirement, candidates: impl IntoIterator, + condition: Option<(DisjunctionId, &[VariableId])>, decision_tracker: &DecisionTracker, ) -> (Self, Option<[Literal; 2]>, bool) { // It only makes sense to introduce a requires clause when the parent solvable // is undecided or going to be installed assert_ne!(decision_tracker.assigned_value(parent), Some(false)); - let kind = Clause::Requires(parent, requirement); - let mut candidates = candidates.into_iter().peekable(); - let first_candidate = candidates.peek().copied(); - if let Some(first_candidate) = first_candidate { - match candidates.find(|&c| decision_tracker.assigned_value(c) != Some(false)) { - // Watch any candidate that is not assigned to false - Some(watched_candidate) => ( + let kind = Clause::Requires(parent, condition.map(|d| d.0), requirement); + + // Construct literals to watch + let mut condition_literals = condition + .into_iter() + .flat_map(|(_, candidates)| candidates) + .map(|candidate| candidate.negative()) + .peekable(); + let mut candidate_literals = candidates + .into_iter() + .map(|candidate| candidate.positive()) + .peekable(); + + let mut literals = condition_literals.chain(candidate_literals).peekable(); + let Some(&first_literal) = literals.peek() else { + // If there are no candidates and there is no condition, then this clause is an + // assertion. + // There is also no conflict because we asserted above that the parent is not + // assigned to false yet. + return (kind, None, false); + }; + + match literals.find(|&c| c.eval(decision_tracker.map()) != Some(false)) { + // Watch any candidate that is not assigned to false + Some(watched_candidate) => ( + kind, + Some([parent.negative(), watched_candidate]), + false, + ), + + // All candidates are assigned to false! Therefore, the clause conflicts with the + // current decisions. There are no valid watches for it at the moment, but we will + // assign default ones nevertheless, because they will become valid after the solver + // restarts. + None => { + // Try to find a condition that is not assigned to false. + ( kind, - Some([parent.negative(), watched_candidate.positive()]), - false, - ), - - // All candidates are assigned to false! Therefore, the clause conflicts with the - // current decisions. There are no valid watches for it at the moment, but we will - // assign default ones nevertheless, because they will become valid after the solver - // restarts. - None => ( - kind, - Some([parent.negative(), first_candidate.positive()]), + Some([parent.negative(), first_literal]), true, - ), + ) } - } else { - // If there are no candidates there is no need to watch anything. - (kind, None, false) } } @@ -243,6 +266,7 @@ impl Clause { Vec>, ahash::RandomState, >, + disjunction_to_candidates: &FrozenMap, ahash::RandomState>, init: C, mut visit: F, ) -> ControlFlow @@ -256,14 +280,22 @@ impl Clause { .iter() .copied() .try_fold(init, visit), - Clause::Requires(solvable_id, match_spec_id) => iter::once(solvable_id.negative()) - .chain( - requirements_to_sorted_candidates[&match_spec_id] - .iter() - .flatten() - .map(|&s| s.positive()), - ) - .try_fold(init, visit), + Clause::Requires(solvable_id, disjunction, match_spec_id) => { + iter::once(solvable_id.negative()) + .chain( + disjunction + .into_iter() + .flat_map(|d| disjunction_to_candidates[&d].iter()) + .map(|var| var.negative()), + ) + .chain( + requirements_to_sorted_candidates[&match_spec_id] + .iter() + .flatten() + .map(|&s| s.positive()), + ) + .try_fold(init, visit) + } Clause::Constrains(s1, s2, _) => [s1.negative(), s2.negative()] .into_iter() .try_fold(init, visit), @@ -287,11 +319,13 @@ impl Clause { Vec>, ahash::RandomState, >, + disjunction_to_candidates: &FrozenMap, ahash::RandomState>, mut visit: impl FnMut(Literal), ) { self.try_fold_literals( learnt_clauses, requirements_to_sorted_candidates, + disjunction_to_candidates, (), |_, lit| { visit(lit); @@ -356,6 +390,7 @@ impl WatchedLiterals { candidate, requirement, matching_candidates, + condition, decision_tracker, ); @@ -439,6 +474,7 @@ impl WatchedLiterals { Vec>, ahash::RandomState, >, + disjunction_to_candidates: &FrozenMap, ahash::RandomState>, decision_map: &DecisionMap, for_watch_index: usize, ) -> Option { @@ -455,6 +491,7 @@ impl WatchedLiterals { let next = clause.try_fold_literals( learnt_clauses, requirement_to_sorted_candidates, + disjunction_to_candidates, (), |_, lit| { // The next unwatched variable (if available), is a variable that is: @@ -572,7 +609,7 @@ impl Display for ClauseDisplay<'_, I> { ) } Clause::Learnt(learnt_id) => write!(f, "Learnt({learnt_id:?})"), - Clause::Requires(variable, requirement) => { + Clause::Requires(variable, _condition, requirement) => { write!( f, "Requires({}({:?}), {})", diff --git a/src/solver/encoding.rs b/src/solver/encoding.rs index ab05c445..5417c4d6 100644 --- a/src/solver/encoding.rs +++ b/src/solver/encoding.rs @@ -340,7 +340,7 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { .requires_clauses .entry(variable) .or_default() - .push((requirement.requirement, clause_id)); + .push((requirement.requirement, condition.map(|cond| cond.0), clause_id)); if conflict { self.conflicting_clauses.push(clause_id); diff --git a/src/solver/mod.rs b/src/solver/mod.rs index b9fc8464..59e3a967 100644 --- a/src/solver/mod.rs +++ b/src/solver/mod.rs @@ -3,6 +3,7 @@ use std::{any::Any, fmt::Display, ops::ControlFlow}; use ahash::{HashMap, HashSet}; pub use cache::SolverCache; use clause::{Clause, Literal, WatchedLiterals}; +use conditions::{Disjunction, DisjunctionId}; use decision::Decision; use decision_tracker::DecisionTracker; use elsa::FrozenMap; @@ -11,7 +12,6 @@ use indexmap::IndexMap; use itertools::Itertools; use variable_map::VariableMap; use watch_map::WatchMap; -use conditions::{DisjunctionId, Disjunction}; use crate::{ ConditionalRequirement, Dependencies, DependencyProvider, KnownDependencies, Requirement, @@ -162,15 +162,18 @@ pub struct Solver { #[derive(Default)] pub(crate) struct SolverState { pub(crate) clauses: Clauses, - requires_clauses: IndexMap, ahash::RandomState>, + requires_clauses: IndexMap< + VariableId, + Vec<(Requirement, Option, ClauseId)>, + ahash::RandomState, + >, watches: WatchMap, /// A mapping from requirements to the variables that represent the /// candidates. requirement_to_sorted_candidates: FrozenMap, - disjunction_to_candidates: - FrozenMap, ahash::RandomState>, + disjunction_to_candidates: FrozenMap, ahash::RandomState>, pub(crate) variable_map: VariableMap, @@ -712,9 +715,21 @@ impl Solver { continue; } - for (deps, clause_id) in requirements.iter() { + for (deps, condition, clause_id) in requirements.iter() { let mut candidate = ControlFlow::Break(()); + // If the clause has a condition that is not yet satisfied we need to skip it. + if let Some(condition) = condition { + let candidates = &self.state.disjunction_to_candidates[condition]; + if !candidates + .iter() + .any(|c| self.state.decision_tracker.assigned_value(*c) == Some(true)) + { + // The condition is not satisfied, skip this clause. + continue; + } + } + // Get the candidates for the individual version sets. let version_set_candidates = &self.state.requirement_to_sorted_candidates[deps]; @@ -1078,6 +1093,7 @@ impl Solver { clause, &self.state.learnt_clauses, &self.state.requirement_to_sorted_candidates, + &self.state.disjunction_to_candidates, self.state.decision_tracker.map(), watch_index, ) { @@ -1240,6 +1256,7 @@ impl Solver { self.state.clauses.kinds[clause_id.to_usize()].visit_literals( &self.state.learnt_clauses, &self.state.requirement_to_sorted_candidates, + &self.state.disjunction_to_candidates, |literal| { involved.insert(literal.variable()); }, @@ -1278,6 +1295,7 @@ impl Solver { self.state.clauses.kinds[why.to_usize()].visit_literals( &self.state.learnt_clauses, &self.state.requirement_to_sorted_candidates, + &self.state.disjunction_to_candidates, |literal| { if literal.eval(self.state.decision_tracker.map()) == Some(true) { assert_eq!(literal.variable(), decision.variable); @@ -1325,6 +1343,7 @@ impl Solver { clause_kinds[clause_id.to_usize()].visit_literals( &self.state.learnt_clauses, &self.state.requirement_to_sorted_candidates, + &self.state.disjunction_to_candidates, |literal| { if !first_iteration && literal.variable() == conflicting_solvable { // We are only interested in the causes of the conflict, so we ignore the diff --git a/tests/.solver.rs.pending-snap b/tests/.solver.rs.pending-snap index 8c9c6561..a08ca9ab 100644 --- a/tests/.solver.rs.pending-snap +++ b/tests/.solver.rs.pending-snap @@ -89,3 +89,93 @@ {"run_id":"1746521959-537048400","line":996,"new":null,"old":null} {"run_id":"1746521959-537048400","line":1473,"new":null,"old":null} {"run_id":"1746521959-537048400","line":1054,"new":null,"old":null} +{"run_id":"1746522623-864633900","line":1322,"new":null,"old":null} +{"run_id":"1746522623-864633900","line":1034,"new":null,"old":null} +{"run_id":"1746522623-864633900","line":1515,"new":null,"old":null} +{"run_id":"1746522623-864633900","line":1365,"new":null,"old":null} +{"run_id":"1746522623-864633900","line":1417,"new":null,"old":null} +{"run_id":"1746522623-864633900","line":1017,"new":null,"old":null} +{"run_id":"1746522623-864633900","line":996,"new":null,"old":null} +{"run_id":"1746522623-864633900","line":1473,"new":null,"old":null} +{"run_id":"1746522623-864633900","line":1054,"new":null,"old":null} +{"run_id":"1746522885-708421500","line":1034,"new":null,"old":null} +{"run_id":"1746522885-708421500","line":1322,"new":null,"old":null} +{"run_id":"1746522885-708421500","line":1515,"new":null,"old":null} +{"run_id":"1746522885-708421500","line":1365,"new":null,"old":null} +{"run_id":"1746522885-708421500","line":1417,"new":null,"old":null} +{"run_id":"1746522885-708421500","line":1017,"new":null,"old":null} +{"run_id":"1746522885-708421500","line":996,"new":null,"old":null} +{"run_id":"1746522885-708421500","line":1054,"new":null,"old":null} +{"run_id":"1746522885-708421500","line":1473,"new":null,"old":null} +{"run_id":"1746523011-294181500","line":1322,"new":null,"old":null} +{"run_id":"1746523011-294181500","line":1515,"new":null,"old":null} +{"run_id":"1746523011-294181500","line":1034,"new":null,"old":null} +{"run_id":"1746523011-294181500","line":1017,"new":null,"old":null} +{"run_id":"1746523011-294181500","line":1365,"new":null,"old":null} +{"run_id":"1746523011-294181500","line":1417,"new":null,"old":null} +{"run_id":"1746523011-294181500","line":996,"new":null,"old":null} +{"run_id":"1746523011-294181500","line":1473,"new":null,"old":null} +{"run_id":"1746523011-294181500","line":1054,"new":null,"old":null} +{"run_id":"1746523195-143485300","line":1034,"new":null,"old":null} +{"run_id":"1746523195-143485300","line":1322,"new":null,"old":null} +{"run_id":"1746523195-143485300","line":1515,"new":null,"old":null} +{"run_id":"1746523195-143485300","line":1365,"new":null,"old":null} +{"run_id":"1746523195-143485300","line":1417,"new":null,"old":null} +{"run_id":"1746523195-143485300","line":996,"new":null,"old":null} +{"run_id":"1746523195-143485300","line":1017,"new":null,"old":null} +{"run_id":"1746523195-143485300","line":1054,"new":null,"old":null} +{"run_id":"1746523195-143485300","line":1473,"new":null,"old":null} +{"run_id":"1746523202-920217000","line":1034,"new":null,"old":null} +{"run_id":"1746523202-920217000","line":1322,"new":null,"old":null} +{"run_id":"1746523202-920217000","line":1515,"new":null,"old":null} +{"run_id":"1746523202-920217000","line":1017,"new":null,"old":null} +{"run_id":"1746523202-920217000","line":1365,"new":null,"old":null} +{"run_id":"1746523202-920217000","line":1417,"new":null,"old":null} +{"run_id":"1746523202-920217000","line":996,"new":null,"old":null} +{"run_id":"1746523202-920217000","line":1054,"new":null,"old":null} +{"run_id":"1746523202-920217000","line":1473,"new":null,"old":null} +{"run_id":"1746523259-7247400","line":1515,"new":null,"old":null} +{"run_id":"1746523259-7247400","line":1034,"new":null,"old":null} +{"run_id":"1746523259-7247400","line":1322,"new":null,"old":null} +{"run_id":"1746523259-7247400","line":1017,"new":null,"old":null} +{"run_id":"1746523259-7247400","line":1365,"new":null,"old":null} +{"run_id":"1746523259-7247400","line":1417,"new":null,"old":null} +{"run_id":"1746523259-7247400","line":996,"new":null,"old":null} +{"run_id":"1746523259-7247400","line":1054,"new":null,"old":null} +{"run_id":"1746523259-7247400","line":1473,"new":null,"old":null} +{"run_id":"1746523279-14738600","line":1515,"new":null,"old":null} +{"run_id":"1746523279-14738600","line":1322,"new":null,"old":null} +{"run_id":"1746523279-14738600","line":1034,"new":null,"old":null} +{"run_id":"1746523279-14738600","line":1365,"new":null,"old":null} +{"run_id":"1746523279-14738600","line":1417,"new":null,"old":null} +{"run_id":"1746523279-14738600","line":996,"new":null,"old":null} +{"run_id":"1746523279-14738600","line":1017,"new":null,"old":null} +{"run_id":"1746523279-14738600","line":1054,"new":null,"old":null} +{"run_id":"1746523279-14738600","line":1473,"new":null,"old":null} +{"run_id":"1746523375-955256700","line":1322,"new":null,"old":null} +{"run_id":"1746523375-955256700","line":1034,"new":null,"old":null} +{"run_id":"1746523375-955256700","line":1515,"new":null,"old":null} +{"run_id":"1746523375-955256700","line":1017,"new":null,"old":null} +{"run_id":"1746523375-955256700","line":1365,"new":null,"old":null} +{"run_id":"1746523375-955256700","line":1417,"new":null,"old":null} +{"run_id":"1746523375-955256700","line":996,"new":null,"old":null} +{"run_id":"1746523375-955256700","line":1054,"new":null,"old":null} +{"run_id":"1746523375-955256700","line":1473,"new":null,"old":null} +{"run_id":"1746523425-492121500","line":1322,"new":null,"old":null} +{"run_id":"1746523425-492121500","line":1515,"new":null,"old":null} +{"run_id":"1746523425-492121500","line":1034,"new":null,"old":null} +{"run_id":"1746523425-492121500","line":1365,"new":null,"old":null} +{"run_id":"1746523425-492121500","line":1417,"new":null,"old":null} +{"run_id":"1746523425-492121500","line":1017,"new":null,"old":null} +{"run_id":"1746523425-492121500","line":996,"new":null,"old":null} +{"run_id":"1746523425-492121500","line":1054,"new":null,"old":null} +{"run_id":"1746523425-492121500","line":1473,"new":null,"old":null} +{"run_id":"1746524567-305832800","line":1034,"new":null,"old":null} +{"run_id":"1746524567-305832800","line":1322,"new":null,"old":null} +{"run_id":"1746524567-305832800","line":1515,"new":null,"old":null} +{"run_id":"1746524567-305832800","line":1365,"new":null,"old":null} +{"run_id":"1746524567-305832800","line":1417,"new":null,"old":null} +{"run_id":"1746524567-305832800","line":996,"new":null,"old":null} +{"run_id":"1746524567-305832800","line":1054,"new":null,"old":null} +{"run_id":"1746524567-305832800","line":1017,"new":null,"old":null} +{"run_id":"1746524567-305832800","line":1473,"new":null,"old":null} diff --git a/tests/snapshots/solver__conditional_optional_missing.snap.new b/tests/snapshots/solver__conditional_optional_missing.snap.new new file mode 100644 index 00000000..3ffa7830 --- /dev/null +++ b/tests/snapshots/solver__conditional_optional_missing.snap.new @@ -0,0 +1,8 @@ +--- +source: tests/solver.rs +assertion_line: 1541 +expression: "solve_for_snapshot(provider, &requirements, &[])" +--- +menu * cannot be installed because there are no viable options: +└─ menu 2 would require + └─ icon *, for which no candidates were found. diff --git a/tests/snapshots/solver__conditional_requirements.snap.new b/tests/snapshots/solver__conditional_requirements.snap.new index 351b3309..86ce51ee 100644 --- a/tests/snapshots/solver__conditional_requirements.snap.new +++ b/tests/snapshots/solver__conditional_requirements.snap.new @@ -1,9 +1,8 @@ --- source: tests/solver.rs -assertion_line: 1534 +assertion_line: 1530 expression: "solve_for_snapshot(provider, &requirements, &[])" --- -bar=1 -foo=1 -icon=1 -menu=1 +foo * cannot be installed because there are no viable options: +└─ foo 2 would require + └─ baz *, for which no candidates were found. diff --git a/tests/solver.rs b/tests/solver.rs index 23284a42..4b386cb7 100644 --- a/tests/solver.rs +++ b/tests/solver.rs @@ -1522,15 +1522,22 @@ fn test_explicit_root_requirements() { #[test] fn test_conditional_requirements() { let mut provider = BundleBoxProvider::from_packages(&[ - ("foo", 1, vec!["bar; if baz"]), + ("foo", 2, vec!["baz; if bar"]), ("bar", 1, vec![]), - ("baz", 1, vec![]), - ("menu", 1, vec!["icon; if foo"]), - ("icon", 1, vec![]), ]); - let requirements = provider.requirements(&["foo", "menu"]); + let requirements = provider.requirements(&["foo"]); + assert_snapshot!(solve_for_snapshot(provider, &requirements, &[])); +} + +#[test] +fn test_conditional_optional_missing() { + let mut provider = BundleBoxProvider::from_packages(&[ + ("menu", 2, vec!["icon; if intl"]), + ("intl", 1, vec![]), + ]); + let requirements = provider.requirements(&["menu"]); assert_snapshot!(solve_for_snapshot(provider, &requirements, &[])); } From b45933007fb45a6d76036bfde4e92ecd4037502c Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Tue, 6 May 2025 18:43:26 +0200 Subject: [PATCH 06/28] only missing complement --- src/solver/clause.rs | 30 +-- src/solver/encoding.rs | 195 ++++++++++++------ src/solver/mod.rs | 15 +- src/solver/variable_map.rs | 7 + tests/.solver.rs.pending-snap | 45 ++++ ...ver__conditional_optional_missing.snap.new | 8 +- tests/solver.rs | 13 +- 7 files changed, 223 insertions(+), 90 deletions(-) diff --git a/src/solver/clause.rs b/src/solver/clause.rs index dbfe3ab1..b3a7c3d6 100644 --- a/src/solver/clause.rs +++ b/src/solver/clause.rs @@ -57,7 +57,7 @@ pub(crate) enum Clause { /// /// Optionally the requirement can be associated with a condition in the /// form of a disjunction. - /// + /// /// ~A v ~C1 ^ C2 ^ C3 ^ v R /// /// In SAT terms: (¬A ∨ ¬D1 v ¬D2 .. v ¬D99 v B1 ∨ B2 ∨ ... ∨ B99), where D1 @@ -122,7 +122,7 @@ impl Clause { parent: VariableId, requirement: Requirement, candidates: impl IntoIterator, - condition: Option<(DisjunctionId, &[VariableId])>, + condition: Option<(DisjunctionId, &[Literal])>, decision_tracker: &DecisionTracker, ) -> (Self, Option<[Literal; 2]>, bool) { // It only makes sense to introduce a requires clause when the parent solvable @@ -135,9 +135,9 @@ impl Clause { let mut condition_literals = condition .into_iter() .flat_map(|(_, candidates)| candidates) - .map(|candidate| candidate.negative()) + .copied() .peekable(); - let mut candidate_literals = candidates + let candidate_literals = candidates .into_iter() .map(|candidate| candidate.positive()) .peekable(); @@ -153,11 +153,7 @@ impl Clause { match literals.find(|&c| c.eval(decision_tracker.map()) != Some(false)) { // Watch any candidate that is not assigned to false - Some(watched_candidate) => ( - kind, - Some([parent.negative(), watched_candidate]), - false, - ), + Some(watched_candidate) => (kind, Some([parent.negative(), watched_candidate]), false), // All candidates are assigned to false! Therefore, the clause conflicts with the // current decisions. There are no valid watches for it at the moment, but we will @@ -165,11 +161,7 @@ impl Clause { // restarts. None => { // Try to find a condition that is not assigned to false. - ( - kind, - Some([parent.negative(), first_literal]), - true, - ) + (kind, Some([parent.negative(), first_literal]), true) } } } @@ -266,7 +258,7 @@ impl Clause { Vec>, ahash::RandomState, >, - disjunction_to_candidates: &FrozenMap, ahash::RandomState>, + disjunction_to_candidates: &FrozenMap, ahash::RandomState>, init: C, mut visit: F, ) -> ControlFlow @@ -286,7 +278,7 @@ impl Clause { disjunction .into_iter() .flat_map(|d| disjunction_to_candidates[&d].iter()) - .map(|var| var.negative()), + .copied() ) .chain( requirements_to_sorted_candidates[&match_spec_id] @@ -319,7 +311,7 @@ impl Clause { Vec>, ahash::RandomState, >, - disjunction_to_candidates: &FrozenMap, ahash::RandomState>, + disjunction_to_candidates: &FrozenMap, ahash::RandomState>, mut visit: impl FnMut(Literal), ) { self.try_fold_literals( @@ -383,7 +375,7 @@ impl WatchedLiterals { candidate: VariableId, requirement: Requirement, matching_candidates: impl IntoIterator, - condition: Option<(DisjunctionId, &[VariableId])>, + condition: Option<(DisjunctionId, &[Literal])>, decision_tracker: &DecisionTracker, ) -> (Option, bool, Clause) { let (kind, watched_literals, conflict) = Clause::requires( @@ -474,7 +466,7 @@ impl WatchedLiterals { Vec>, ahash::RandomState, >, - disjunction_to_candidates: &FrozenMap, ahash::RandomState>, + disjunction_to_candidates: &FrozenMap, ahash::RandomState>, decision_map: &DecisionMap, for_watch_index: usize, ) -> Option { diff --git a/src/solver/encoding.rs b/src/solver/encoding.rs index 5417c4d6..e2d9945c 100644 --- a/src/solver/encoding.rs +++ b/src/solver/encoding.rs @@ -12,7 +12,7 @@ use crate::{ arena::ArenaId, id::{ClauseId, SolvableOrRootId, VariableId}, }, - solver::conditions::Disjunction, + solver::{clause::Literal, conditions::Disjunction, decision::Decision}, }; /// An object that is responsible for encoding information from the dependency @@ -29,6 +29,7 @@ use crate::{ pub(crate) struct Encoder<'a, D: DependencyProvider> { state: &'a mut SolverState, cache: &'a SolverCache, + level: u32, /// The dependencies of the root solvable. root_dependencies: &'a Dependencies, @@ -66,7 +67,18 @@ struct RequirementCandidatesAvailable<'a> { solvable_id: SolvableOrRootId, requirement: ConditionalRequirement, candidates: Vec<&'a [SolvableId]>, - condition: Option<(ConditionId, Vec>)>, + condition: Option<(ConditionId, Vec>>)>, +} + +/// The complement of a solvables that match aVersionSet or an empty set. +enum DisjunctionComplement<'a> { + Solvables(&'a [SolvableId]), + Empty(VersionSetId), +} + +enum DisjunctionLiterals<'a> { + ComplementVariables(&'a [Literal]), + AnyOf(Literal), } /// Result of querying candidates for a particular constraint. @@ -81,6 +93,7 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { state: &'a mut SolverState, cache: &'a SolverCache, root_dependencies: &'a Dependencies, + level: u32, ) -> Self { Self { state, @@ -88,6 +101,7 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { root_dependencies, pending_futures: FuturesUnordered::new(), conflicting_clauses: Vec::new(), + level, } } @@ -237,79 +251,61 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { }) .collect::>(); - // Queue requesting the dependencies of the candidates as well if they are - // cheaply available from the dependency provider. - for (candidate, candidate_var) in candidates + // Keep a list of candidates that should be added to forbid clauses. + let mut potential_forbid_candidates = candidates .iter() .zip(version_set_variables.iter()) .flat_map(|(&candidates, variable)| { candidates.iter().copied().zip(variable.iter().copied()) }) - { + .collect::>(); + + // Queue requesting the dependencies of the candidates as well if they are + // cheaply available from the dependency provider. + for &candidate in candidates.iter().flat_map(|solvables| solvables.iter()) { // If the dependencies are already available for the // candidate, queue the candidate for processing. if self.cache.are_dependencies_available_for(candidate) { self.queue_solvable(candidate.into()) } - - // Add forbid constraints for this solvable on all other - // solvables that have been visited already for the same - // version set name. - let name_id = self.cache.provider().solvable_name(candidate); - let other_solvables = self - .state - .forbidden_clauses_added - .entry(name_id) - .or_default(); - other_solvables.add( - candidate_var, - |a, b, positive| { - let (watched_literals, kind) = WatchedLiterals::forbid_multiple( - a, - if positive { b.positive() } else { b.negative() }, - name_id, - ); - let clause_id = self.state.clauses.alloc(watched_literals, kind); - let watched_literals = self.state.clauses.watched_literals - [clause_id.to_usize()] - .as_mut() - .expect("forbid clause must have watched literals"); - self.state - .watches - .start_watching(watched_literals, clause_id); - }, - || { - self.state - .variable_map - .alloc_forbid_multiple_variable(name_id) - }, - ); } // Determine the disjunctions of all the conditions for this requirement. let mut disjunctions = Vec::with_capacity(condition.as_ref().map_or(0, |(_, dnf)| dnf.len())); if let Some((condition, dnf)) = condition { - for disjunction in dnf { - let mut candidates = Vec::with_capacity( - disjunction - .iter() - .map(|candidates| candidates.len()) - .sum::(), - ); - for version_set_candidates in disjunction.into_iter() { - candidates.extend( - version_set_candidates - .into_iter() - .map(|&candidate| self.state.variable_map.intern_solvable(candidate)), - ); - } + let disjunction_literals = dnf + .into_iter() + .map(|disjunction| { + let mut literals = Vec::new(); + for complement in disjunction { + match complement { + DisjunctionComplement::Solvables(solvables) => { + literals.reserve(solvables.len()); + potential_forbid_candidates.reserve(solvables.len()); + for &solvable in solvables { + let variable = + self.state.variable_map.intern_solvable(solvable); + potential_forbid_candidates.push((solvable, variable)); + literals.push(variable.positive()); + } + } + DisjunctionComplement::Empty(version_set) => { + todo!(); + } + } + } + literals + }) + .collect::>(); + + for literals in disjunction_literals { let disjunction_id = self.state.disjunctions.alloc(Disjunction { condition }); - let candidates = self + let literals = self .state .disjunction_to_candidates - .insert(disjunction_id, candidates); - disjunctions.push(Some((disjunction_id, candidates))); + .insert(disjunction_id, literals); + disjunctions.push(Some((disjunction_id, literals))); } } else { disjunctions.push(None); @@ -340,7 +336,11 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { .requires_clauses .entry(variable) .or_default() - .push((requirement.requirement, condition.map(|cond| cond.0), clause_id)); + .push(( + requirement.requirement, + condition.map(|cond| cond.0), + clause_id, + )); if conflict { self.conflicting_clauses.push(clause_id); @@ -354,6 +354,62 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { self.state .requirement_to_sorted_candidates .insert(requirement.requirement, version_set_variables); + + // Add forbid clauses + for (candidate, candidate_var) in potential_forbid_candidates { + // Add forbid constraints for this solvable on all other + // solvables that have been visited already for the same + // version set name. + let name_id = self.cache.provider().solvable_name(candidate); + let other_solvables = self + .state + .forbidden_clauses_added + .entry(name_id) + .or_default(); + other_solvables.add( + candidate_var, + |a, b, positive| { + let literal_b = if positive { b.positive() } else { b.negative() }; + let literal_a = a.negative(); + let (watched_literals, kind) = + WatchedLiterals::forbid_multiple(a, literal_b, name_id); + let clause_id = self.state.clauses.alloc(watched_literals, kind); + let watched_literals = self.state.clauses.watched_literals + [clause_id.to_usize()] + .as_mut() + .expect("forbid clause must have watched literals"); + self.state + .watches + .start_watching(watched_literals, clause_id); + let set_literal = match ( + literal_a.eval(self.state.decision_tracker.map()), + literal_b.eval(self.state.decision_tracker.map()), + ) { + (Some(false), None) => Some(literal_b), + (None, Some(false)) => Some(literal_a), + _ => None, + }; + if let Some(literal) = set_literal { + self.state + .decision_tracker + .try_add_decision( + Decision::new( + literal.variable(), + literal.satisfying_value(), + clause_id, + ), + self.level, + ) + .expect("we checked that there is no value yet"); + } + }, + || { + self.state + .variable_map + .alloc_forbid_multiple_variable(name_id) + }, + ); + } } /// Called when the candidates for a particular constraint are available. @@ -531,7 +587,15 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { .into_iter() .map(|cnf| { futures::future::try_join_all(cnf.into_iter().map(|version_set| { - cache.get_or_cache_matching_candidates(version_set) + cache + .get_or_cache_non_matching_candidates(version_set) + .map_ok(move |matching_candidates| { + if matching_candidates.len() == 0 { + DisjunctionComplement::Empty(version_set) + } else { + DisjunctionComplement::Solvables(matching_candidates) + } + }) })) }), ) @@ -576,4 +640,19 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { self.pending_futures .push(query_constraints_candidates.boxed_local()); } + + fn disjunction_literals( + &mut self, + disjunction_complement: DisjunctionComplement, + ) -> Vec { + match disjunction_complement { + DisjunctionComplement::Solvables(solvables) => solvables + .iter() + .map(|&solvable| self.state.variable_map.intern_solvable(solvable).positive()) + .collect(), + DisjunctionComplement::Empty(version_set) => { + todo!(); + } + } + } } diff --git a/src/solver/mod.rs b/src/solver/mod.rs index 59e3a967..c34a35b3 100644 --- a/src/solver/mod.rs +++ b/src/solver/mod.rs @@ -173,7 +173,7 @@ pub(crate) struct SolverState { /// candidates. requirement_to_sorted_candidates: FrozenMap, - disjunction_to_candidates: FrozenMap, ahash::RandomState>, + disjunction_to_candidates: FrozenMap, ahash::RandomState>, pub(crate) variable_map: VariableMap, @@ -440,7 +440,8 @@ impl Solver { // Add the clauses for the root solvable. let conflicting_clauses = self.async_runtime.block_on( - Encoder::new(&mut self.state, &self.cache, root_deps).encode([root_solvable]), + Encoder::new(&mut self.state, &self.cache, root_deps, level) + .encode([root_solvable]), )?; if let Some(clause_id) = conflicting_clauses.into_iter().next() { @@ -558,7 +559,7 @@ impl Solver { .collect::>(); let conflicting_clauses = self.async_runtime.block_on( - Encoder::new(&mut self.state, &self.cache, root_deps).encode(new_solvables), + Encoder::new(&mut self.state, &self.cache, root_deps, level).encode(new_solvables), )?; // Serially process the outputs, to reduce the need for synchronization @@ -721,10 +722,10 @@ impl Solver { // If the clause has a condition that is not yet satisfied we need to skip it. if let Some(condition) = condition { let candidates = &self.state.disjunction_to_candidates[condition]; - if !candidates - .iter() - .any(|c| self.state.decision_tracker.assigned_value(*c) == Some(true)) - { + if !candidates.iter().all(|c| { + let value = c.eval(self.state.decision_tracker.map()); + value == Some(false) + }) { // The condition is not satisfied, skip this clause. continue; } diff --git a/src/solver/variable_map.rs b/src/solver/variable_map.rs index e65280ab..a3afe9c7 100644 --- a/src/solver/variable_map.rs +++ b/src/solver/variable_map.rs @@ -38,6 +38,10 @@ pub enum VariableOrigin { /// A variable that helps encode an at most one constraint. ForbidMultiple(NameId), + + /// A variable that indicates that any solvable of a particular package is + /// part of the solution. + AnyOf(NameId), } impl Default for VariableMap { @@ -136,6 +140,9 @@ impl Display for VariableDisplay<'_, I> { VariableOrigin::ForbidMultiple(name) => { write!(f, "forbid-multiple({})", self.interner.display_name(name)) } + VariableOrigin::AnyOf(name) => { + write!(f, "any-of({})", self.interner.display_name(name)) + } } } } diff --git a/tests/.solver.rs.pending-snap b/tests/.solver.rs.pending-snap index a08ca9ab..9a8bcedd 100644 --- a/tests/.solver.rs.pending-snap +++ b/tests/.solver.rs.pending-snap @@ -179,3 +179,48 @@ {"run_id":"1746524567-305832800","line":1054,"new":null,"old":null} {"run_id":"1746524567-305832800","line":1017,"new":null,"old":null} {"run_id":"1746524567-305832800","line":1473,"new":null,"old":null} +{"run_id":"1746544766-929604400","line":1034,"new":null,"old":null} +{"run_id":"1746544766-929604400","line":1515,"new":null,"old":null} +{"run_id":"1746544766-929604400","line":1322,"new":null,"old":null} +{"run_id":"1746544766-929604400","line":1365,"new":null,"old":null} +{"run_id":"1746544766-929604400","line":1417,"new":null,"old":null} +{"run_id":"1746544766-929604400","line":996,"new":null,"old":null} +{"run_id":"1746544766-929604400","line":1054,"new":null,"old":null} +{"run_id":"1746544766-929604400","line":1017,"new":null,"old":null} +{"run_id":"1746544766-929604400","line":1473,"new":null,"old":null} +{"run_id":"1746546447-335828800","line":1515,"new":null,"old":null} +{"run_id":"1746546447-335828800","line":1322,"new":null,"old":null} +{"run_id":"1746546447-335828800","line":1034,"new":null,"old":null} +{"run_id":"1746546447-335828800","line":1365,"new":null,"old":null} +{"run_id":"1746546447-335828800","line":1417,"new":null,"old":null} +{"run_id":"1746546447-335828800","line":996,"new":null,"old":null} +{"run_id":"1746546447-335828800","line":1017,"new":null,"old":null} +{"run_id":"1746546447-335828800","line":1054,"new":null,"old":null} +{"run_id":"1746546447-335828800","line":1473,"new":null,"old":null} +{"run_id":"1746546483-93806700","line":1515,"new":null,"old":null} +{"run_id":"1746546483-93806700","line":1322,"new":null,"old":null} +{"run_id":"1746546483-93806700","line":1034,"new":null,"old":null} +{"run_id":"1746546483-93806700","line":1017,"new":null,"old":null} +{"run_id":"1746546483-93806700","line":996,"new":null,"old":null} +{"run_id":"1746546483-93806700","line":1365,"new":null,"old":null} +{"run_id":"1746546483-93806700","line":1417,"new":null,"old":null} +{"run_id":"1746546483-93806700","line":1054,"new":null,"old":null} +{"run_id":"1746546483-93806700","line":1473,"new":null,"old":null} +{"run_id":"1746546515-573078600","line":1034,"new":null,"old":null} +{"run_id":"1746546515-573078600","line":1515,"new":null,"old":null} +{"run_id":"1746546515-573078600","line":1322,"new":null,"old":null} +{"run_id":"1746546515-573078600","line":1365,"new":null,"old":null} +{"run_id":"1746546515-573078600","line":1417,"new":null,"old":null} +{"run_id":"1746546515-573078600","line":996,"new":null,"old":null} +{"run_id":"1746546515-573078600","line":1017,"new":null,"old":null} +{"run_id":"1746546515-573078600","line":1054,"new":null,"old":null} +{"run_id":"1746546515-573078600","line":1473,"new":null,"old":null} +{"run_id":"1746546555-408905300","line":1515,"new":null,"old":null} +{"run_id":"1746546555-408905300","line":1322,"new":null,"old":null} +{"run_id":"1746546555-408905300","line":1034,"new":null,"old":null} +{"run_id":"1746546555-408905300","line":996,"new":null,"old":null} +{"run_id":"1746546555-408905300","line":1365,"new":null,"old":null} +{"run_id":"1746546555-408905300","line":1017,"new":null,"old":null} +{"run_id":"1746546555-408905300","line":1054,"new":null,"old":null} +{"run_id":"1746546555-408905300","line":1417,"new":null,"old":null} +{"run_id":"1746546555-408905300","line":1473,"new":null,"old":null} diff --git a/tests/snapshots/solver__conditional_optional_missing.snap.new b/tests/snapshots/solver__conditional_optional_missing.snap.new index 3ffa7830..d3993af3 100644 --- a/tests/snapshots/solver__conditional_optional_missing.snap.new +++ b/tests/snapshots/solver__conditional_optional_missing.snap.new @@ -1,8 +1,8 @@ --- source: tests/solver.rs -assertion_line: 1541 +assertion_line: 1550 expression: "solve_for_snapshot(provider, &requirements, &[])" --- -menu * cannot be installed because there are no viable options: -└─ menu 2 would require - └─ icon *, for which no candidates were found. +baz=2 +icon=2 +menu=1 diff --git a/tests/solver.rs b/tests/solver.rs index 4b386cb7..5c0d2cf4 100644 --- a/tests/solver.rs +++ b/tests/solver.rs @@ -1531,13 +1531,22 @@ fn test_conditional_requirements() { } #[test] +#[traced_test] fn test_conditional_optional_missing() { let mut provider = BundleBoxProvider::from_packages(&[ - ("menu", 2, vec!["icon; if intl"]), + ("menu", 1, vec!["icon 1; if (intl 1..3 or baz 1..2) or intl 1..2"]), + ("icon", 1, vec![]), + ("icon", 2, vec![]), + ("baz", 1, vec![]), + ("baz", 2, vec![]), ("intl", 1, vec![]), + ("intl", 2, vec![]), + ("intl", 3, vec![]), ]); - let requirements = provider.requirements(&["menu"]); + dbg!(&provider.conditions); + + let requirements = provider.requirements(&["menu", "icon", "baz 1"]); assert_snapshot!(solve_for_snapshot(provider, &requirements, &[])); } From 2c2a4b49a483e14c512dc0da7a583cf23bd66ad0 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Wed, 7 May 2025 12:19:25 +0200 Subject: [PATCH 07/28] refactored --- src/solver/clause.rs | 4 +- src/solver/conditions.rs | 3 +- src/solver/encoding.rs | 242 ++++++++++-------- tests/.solver.rs.pending-snap | 9 + ...ver__condition_limits_requirement.snap.new | 8 + ...ver__conditional_optional_missing.snap.new | 6 +- ...__unsat_applies_graph_compression.snap.new | 14 + tests/solver.rs | 11 +- 8 files changed, 169 insertions(+), 128 deletions(-) create mode 100644 tests/snapshots/solver__condition_limits_requirement.snap.new create mode 100644 tests/snapshots/solver__unsat_applies_graph_compression.snap.new diff --git a/src/solver/clause.rs b/src/solver/clause.rs index b3a7c3d6..994fd6f4 100644 --- a/src/solver/clause.rs +++ b/src/solver/clause.rs @@ -8,7 +8,7 @@ use std::{ use elsa::FrozenMap; use crate::{ - Candidates, Interner, NameId, Requirement, + Interner, NameId, Requirement, internal::{ arena::{Arena, ArenaId}, id::{ClauseId, LearntClauseId, StringId, VersionSetId}, @@ -132,7 +132,7 @@ impl Clause { let kind = Clause::Requires(parent, condition.map(|d| d.0), requirement); // Construct literals to watch - let mut condition_literals = condition + let condition_literals = condition .into_iter() .flat_map(|(_, candidates)| candidates) .copied() diff --git a/src/solver/conditions.rs b/src/solver/conditions.rs index 4ed13986..5539c796 100644 --- a/src/solver/conditions.rs +++ b/src/solver/conditions.rs @@ -3,8 +3,7 @@ use std::num::NonZero; use crate::{ Condition, ConditionId, Interner, LogicalOperator, VersionSetId, internal::{ - arena::{Arena, ArenaId}, - small_vec::SmallVec, + arena::{ ArenaId}, }, }; diff --git a/src/solver/encoding.rs b/src/solver/encoding.rs index e2d9945c..47ff2864 100644 --- a/src/solver/encoding.rs +++ b/src/solver/encoding.rs @@ -3,6 +3,7 @@ use std::{any::Any, future::ready}; use futures::{ FutureExt, StreamExt, TryFutureExt, future::LocalBoxFuture, stream::FuturesUnordered, }; +use indexmap::IndexMap; use super::{SolverState, clause::WatchedLiterals, conditions}; use crate::{ @@ -26,20 +27,24 @@ use crate::{ /// /// The encoder itself is completely single threaded (and not `Send`) but the /// dependency provider is free to spawn tasks on other threads. -pub(crate) struct Encoder<'a, D: DependencyProvider> { +pub(crate) struct Encoder<'a, 'cache, D: DependencyProvider> { state: &'a mut SolverState, - cache: &'a SolverCache, + cache: &'cache SolverCache, level: u32, /// The dependencies of the root solvable. - root_dependencies: &'a Dependencies, + root_dependencies: &'cache Dependencies, /// The set of futures that are pending to be resolved. - pending_futures: FuturesUnordered, Box>>>, + pending_futures: + FuturesUnordered, Box>>>, /// A list of clauses that were introduced that are conflicting with the /// current state. conflicting_clauses: Vec, + + /// Stores for which packages and solvables we want to add forbid clauses. + pending_forbid_clauses: IndexMap>, } /// The result of a future that was queued for processing. @@ -72,7 +77,7 @@ struct RequirementCandidatesAvailable<'a> { /// The complement of a solvables that match aVersionSet or an empty set. enum DisjunctionComplement<'a> { - Solvables(&'a [SolvableId]), + Solvables(VersionSetId, &'a [SolvableId]), Empty(VersionSetId), } @@ -88,11 +93,11 @@ struct ConstraintCandidatesAvailable<'a> { candidates: &'a [SolvableId], } -impl<'a, D: DependencyProvider> Encoder<'a, D> { +impl<'a, 'cache, D: DependencyProvider> Encoder<'a, 'cache, D> { pub fn new( state: &'a mut SolverState, - cache: &'a SolverCache, - root_dependencies: &'a Dependencies, + cache: &'cache SolverCache, + root_dependencies: &'cache Dependencies, level: u32, ) -> Self { Self { @@ -101,6 +106,7 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { root_dependencies, pending_futures: FuturesUnordered::new(), conflicting_clauses: Vec::new(), + pending_forbid_clauses: IndexMap::default(), level, } } @@ -120,11 +126,13 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { self.on_task_result(future_result?); } + self.add_pending_forbid_clauses(); + Ok(self.conflicting_clauses) } /// Called when the result of a future is available. - fn on_task_result(&mut self, result: TaskResult<'a>) { + fn on_task_result(&mut self, result: TaskResult<'cache>) { match result { TaskResult::Dependencies(dependencies) => self.on_dependencies_available(dependencies), TaskResult::Candidates(candidates) => self.on_candidates_available(candidates), @@ -149,7 +157,7 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { DependenciesAvailable { solvable_id, dependencies, - }: DependenciesAvailable<'a>, + }: DependenciesAvailable<'cache>, ) { tracing::trace!( "dependencies available for {}", @@ -196,7 +204,7 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { CandidatesAvailable { name_id, package_candidates, - }: CandidatesAvailable<'a>, + }: CandidatesAvailable<'cache>, ) { tracing::trace!( "Package candidates available for {}", @@ -231,7 +239,7 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { requirement, candidates, condition, - }: RequirementCandidatesAvailable<'a>, + }: RequirementCandidatesAvailable<'cache>, ) { tracing::trace!( "Sorted candidates available for {}", @@ -251,14 +259,24 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { }) .collect::>(); - // Keep a list of candidates that should be added to forbid clauses. - let mut potential_forbid_candidates = candidates + // Make sure that for every candidate that we require we also have a forbid + // clause to force one solvable per package name. + // + // We only add these clauses for packages that can actually be selected to + // reduce the overall number of clauses. + for (solvable, variable_id) in candidates .iter() .zip(version_set_variables.iter()) .flat_map(|(&candidates, variable)| { candidates.iter().copied().zip(variable.iter().copied()) }) - .collect::>(); + { + let name_id = self.cache.provider().solvable_name(solvable); + self.pending_forbid_clauses + .entry(name_id) + .or_default() + .push(variable_id); + } // Queue requesting the dependencies of the candidates as well if they are // cheaply available from the dependency provider. @@ -271,47 +289,42 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { } // Determine the disjunctions of all the conditions for this requirement. - let mut disjunctions = - Vec::with_capacity(condition.as_ref().map_or(0, |(_, dnf)| dnf.len())); + let mut conditions = Vec::with_capacity(condition.as_ref().map_or(0, |(_, dnf)| dnf.len())); if let Some((condition, dnf)) = condition { - let disjunction_literals = dnf - .into_iter() - .map(|disjunction| { - let mut literals = Vec::new(); - for complement in disjunction { - match complement { - DisjunctionComplement::Solvables(solvables) => { - literals.reserve(solvables.len()); - potential_forbid_candidates.reserve(solvables.len()); - for &solvable in solvables { - let variable = - self.state.variable_map.intern_solvable(solvable); - potential_forbid_candidates.push((solvable, variable)); - literals.push(variable.positive()); - } - } - DisjunctionComplement::Empty(version_set) => { - todo!(); + for disjunctions in dnf { + let mut disjunction_literals = Vec::new(); + for disjunction_complement in disjunctions { + match disjunction_complement { + DisjunctionComplement::Solvables(version_set, solvables) => { + let name_id = self.cache.provider().version_set_name(version_set); + let pending_forbid_clauses = + self.pending_forbid_clauses.entry(name_id).or_default(); + disjunction_literals.reserve(solvables.len()); + pending_forbid_clauses.reserve(solvables.len()); + for &solvable in solvables { + let variable = self.state.variable_map.intern_solvable(solvable); + disjunction_literals.push(variable.positive()); + pending_forbid_clauses.push(variable); } } + DisjunctionComplement::Empty(version_set) => { + todo!() + } } - literals - }) - .collect::>(); + } - for literals in disjunction_literals { let disjunction_id = self.state.disjunctions.alloc(Disjunction { condition }); let literals = self .state .disjunction_to_candidates - .insert(disjunction_id, literals); - disjunctions.push(Some((disjunction_id, literals))); + .insert(disjunction_id, disjunction_literals); + conditions.push(Some((disjunction_id, literals))); } } else { - disjunctions.push(None); + conditions.push(None); } - for condition in disjunctions { + for condition in conditions { // Add the requirements clause let no_candidates = candidates.iter().all(|candidates| candidates.is_empty()); let (watched_literals, conflict, kind) = WatchedLiterals::requires( @@ -354,62 +367,6 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { self.state .requirement_to_sorted_candidates .insert(requirement.requirement, version_set_variables); - - // Add forbid clauses - for (candidate, candidate_var) in potential_forbid_candidates { - // Add forbid constraints for this solvable on all other - // solvables that have been visited already for the same - // version set name. - let name_id = self.cache.provider().solvable_name(candidate); - let other_solvables = self - .state - .forbidden_clauses_added - .entry(name_id) - .or_default(); - other_solvables.add( - candidate_var, - |a, b, positive| { - let literal_b = if positive { b.positive() } else { b.negative() }; - let literal_a = a.negative(); - let (watched_literals, kind) = - WatchedLiterals::forbid_multiple(a, literal_b, name_id); - let clause_id = self.state.clauses.alloc(watched_literals, kind); - let watched_literals = self.state.clauses.watched_literals - [clause_id.to_usize()] - .as_mut() - .expect("forbid clause must have watched literals"); - self.state - .watches - .start_watching(watched_literals, clause_id); - let set_literal = match ( - literal_a.eval(self.state.decision_tracker.map()), - literal_b.eval(self.state.decision_tracker.map()), - ) { - (Some(false), None) => Some(literal_b), - (None, Some(false)) => Some(literal_a), - _ => None, - }; - if let Some(literal) = set_literal { - self.state - .decision_tracker - .try_add_decision( - Decision::new( - literal.variable(), - literal.satisfying_value(), - clause_id, - ), - self.level, - ) - .expect("we checked that there is no value yet"); - } - }, - || { - self.state - .variable_map - .alloc_forbid_multiple_variable(name_id) - }, - ); - } } /// Called when the candidates for a particular constraint are available. @@ -419,7 +376,7 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { solvable_id, constraint, candidates, - }: ConstraintCandidatesAvailable<'a>, + }: ConstraintCandidatesAvailable<'cache>, ) { tracing::trace!( "non matching candidates available for {} {}", @@ -593,7 +550,10 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { if matching_candidates.len() == 0 { DisjunctionComplement::Empty(version_set) } else { - DisjunctionComplement::Solvables(matching_candidates) + DisjunctionComplement::Solvables( + version_set, + matching_candidates, + ) } }) })) @@ -641,18 +601,74 @@ impl<'a, D: DependencyProvider> Encoder<'a, D> { .push(query_constraints_candidates.boxed_local()); } - fn disjunction_literals( - &mut self, - disjunction_complement: DisjunctionComplement, - ) -> Vec { - match disjunction_complement { - DisjunctionComplement::Solvables(solvables) => solvables - .iter() - .map(|&solvable| self.state.variable_map.intern_solvable(solvable).positive()) - .collect(), - DisjunctionComplement::Empty(version_set) => { - todo!(); - } + /// Add forbid clauses for solvables that have not been added to the set of + /// forbid clases. + fn add_pending_forbid_clauses(&mut self) { + for (name_id, candidate_var) in + self.pending_forbid_clauses + .drain(..) + .flat_map(|(name_id, candidate_vars)| { + candidate_vars + .into_iter() + .map(move |candidate_var| (name_id, candidate_var)) + }) + { + // Add forbid constraints for this solvable on all other + // solvables that have been visited already for the same + // version set name. + let other_solvables = self + .state + .forbidden_clauses_added + .entry(name_id) + .or_default(); + other_solvables.add( + candidate_var, + |a, b, positive| { + let literal_b = if positive { b.positive() } else { b.negative() }; + let literal_a = a.negative(); + let (watched_literals, kind) = + WatchedLiterals::forbid_multiple(a, literal_b, name_id); + let clause_id = self.state.clauses.alloc(watched_literals, kind); + let watched_literals = self.state.clauses.watched_literals + [clause_id.to_usize()] + .as_mut() + .expect("forbid clause must have watched literals"); + self.state + .watches + .start_watching(watched_literals, clause_id); + + // Add a decision if a decision has already been made for one of the literals. + let set_literal = match ( + literal_a.eval(self.state.decision_tracker.map()), + literal_b.eval(self.state.decision_tracker.map()), + ) { + (Some(false), None) => Some(literal_b), + (None, Some(false)) => Some(literal_a), + (Some(false), Some(false)) => unreachable!( + "both literals cannot be false as that would be a conflict" + ), + _ => None, + }; + if let Some(literal) = set_literal { + self.state + .decision_tracker + .try_add_decision( + Decision::new( + literal.variable(), + literal.satisfying_value(), + clause_id, + ), + self.level, + ) + .expect("we checked that there is no value yet"); + } + }, + || { + self.state + .variable_map + .alloc_forbid_multiple_variable(name_id) + }, + ); } } } diff --git a/tests/.solver.rs.pending-snap b/tests/.solver.rs.pending-snap index 9a8bcedd..20aba851 100644 --- a/tests/.solver.rs.pending-snap +++ b/tests/.solver.rs.pending-snap @@ -224,3 +224,12 @@ {"run_id":"1746546555-408905300","line":1054,"new":null,"old":null} {"run_id":"1746546555-408905300","line":1417,"new":null,"old":null} {"run_id":"1746546555-408905300","line":1473,"new":null,"old":null} +{"run_id":"1746612890-497675200","line":1034,"new":null,"old":null} +{"run_id":"1746612890-497675200","line":1515,"new":null,"old":null} +{"run_id":"1746612890-497675200","line":1322,"new":null,"old":null} +{"run_id":"1746612890-497675200","line":996,"new":null,"old":null} +{"run_id":"1746612890-497675200","line":1017,"new":null,"old":null} +{"run_id":"1746612890-497675200","line":1054,"new":null,"old":null} +{"run_id":"1746612890-497675200","line":1365,"new":null,"old":null} +{"run_id":"1746612890-497675200","line":1417,"new":null,"old":null} +{"run_id":"1746612890-497675200","line":1473,"new":null,"old":null} diff --git a/tests/snapshots/solver__condition_limits_requirement.snap.new b/tests/snapshots/solver__condition_limits_requirement.snap.new new file mode 100644 index 00000000..bc846c1f --- /dev/null +++ b/tests/snapshots/solver__condition_limits_requirement.snap.new @@ -0,0 +1,8 @@ +--- +source: tests/solver.rs +assertion_line: 1545 +expression: "solve_for_snapshot(provider, &requirements, &[])" +--- +icon=1 +intl=1 +menu=1 diff --git a/tests/snapshots/solver__conditional_optional_missing.snap.new b/tests/snapshots/solver__conditional_optional_missing.snap.new index d3993af3..80e34ea7 100644 --- a/tests/snapshots/solver__conditional_optional_missing.snap.new +++ b/tests/snapshots/solver__conditional_optional_missing.snap.new @@ -1,8 +1,8 @@ --- source: tests/solver.rs -assertion_line: 1550 +assertion_line: 1548 expression: "solve_for_snapshot(provider, &requirements, &[])" --- -baz=2 -icon=2 +baz=1 +icon=1 menu=1 diff --git a/tests/snapshots/solver__unsat_applies_graph_compression.snap.new b/tests/snapshots/solver__unsat_applies_graph_compression.snap.new new file mode 100644 index 00000000..35db3f9e --- /dev/null +++ b/tests/snapshots/solver__unsat_applies_graph_compression.snap.new @@ -0,0 +1,14 @@ +--- +source: tests/solver.rs +assertion_line: 1184 +expression: error +--- +The following packages are incompatible +├─ c >=101, <104 can be installed with any of the following options: +│ └─ c 101 +└─ a * cannot be installed because there are no viable options: + └─ a 9 | 10 would require + └─ b *, which cannot be installed because there are no viable options: + └─ b 42 | 100 would require + └─ c >=0, <100, which cannot be installed because there are no viable options: + └─ c 99, which conflicts with the versions reported above. diff --git a/tests/solver.rs b/tests/solver.rs index 5c0d2cf4..4d6d5ab2 100644 --- a/tests/solver.rs +++ b/tests/solver.rs @@ -1532,21 +1532,16 @@ fn test_conditional_requirements() { #[test] #[traced_test] -fn test_conditional_optional_missing() { +fn test_condition_limits_requirement() { let mut provider = BundleBoxProvider::from_packages(&[ - ("menu", 1, vec!["icon 1; if (intl 1..3 or baz 1..2) or intl 1..2"]), + ("menu", 1, vec!["icon 1; if intl 1"]), ("icon", 1, vec![]), ("icon", 2, vec![]), - ("baz", 1, vec![]), - ("baz", 2, vec![]), ("intl", 1, vec![]), ("intl", 2, vec![]), - ("intl", 3, vec![]), ]); - dbg!(&provider.conditions); - - let requirements = provider.requirements(&["menu", "icon", "baz 1"]); + let requirements = provider.requirements(&["menu", "icon", "intl 1"]); assert_snapshot!(solve_for_snapshot(provider, &requirements, &[])); } From 6f0397d2e42c4f8ced12133ebadb7adc1175c93e Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Wed, 7 May 2025 17:46:32 +0200 Subject: [PATCH 08/28] wip --- src/conflict.rs | 8 +- src/solver/binary_encoding.rs | 12 +- src/solver/clause.rs | 38 +++++- src/solver/conditions.rs | 65 +++++++++- src/solver/encoding.rs | 112 +++++++++++++++--- src/solver/mod.rs | 11 +- src/solver/variable_map.rs | 15 ++- .../solver__condition_is_disabled.snap.new | 9 ++ ...ver__condition_limits_requirement.snap.new | 2 - tests/solver.rs | 21 +++- 10 files changed, 255 insertions(+), 38 deletions(-) create mode 100644 tests/snapshots/solver__condition_is_disabled.snap.new diff --git a/src/conflict.rs b/src/conflict.rs index 4bb43bc1..aa79283f 100644 --- a/src/conflict.rs +++ b/src/conflict.rs @@ -11,7 +11,6 @@ use petgraph::{ visit::{Bfs, DfsPostOrder, EdgeRef}, }; -use crate::solver::variable_map::VariableOrigin; use crate::{ DependencyProvider, Interner, Requirement, internal::{ @@ -19,7 +18,7 @@ use crate::{ id::{ClauseId, SolvableId, SolvableOrRootId, StringId, VersionSetId}, }, runtime::AsyncRuntime, - solver::{Solver, clause::Clause}, + solver::{Solver, clause::Clause, variable_map::VariableOrigin}, }; /// Represents the cause of the solver being unable to find a solution @@ -162,6 +161,11 @@ impl Conflict { ConflictEdge::Conflict(ConflictCause::Constrains(version_set_id)), ); } + Clause::AnyOf(_, _) => { + unreachable!( + "an assumption is violated: AnyOf clauses can never make the auxiliary variable turn false, so they can never be part of a conflict." + ); + } } } diff --git a/src/solver/binary_encoding.rs b/src/solver/binary_encoding.rs index 144d93c2..092794bf 100644 --- a/src/solver/binary_encoding.rs +++ b/src/solver/binary_encoding.rs @@ -6,8 +6,8 @@ use indexmap::IndexSet; /// that at most one of a set of variables can be true. pub(crate) struct AtMostOnceTracker { /// The set of variables of which at most one can be assigned true. - variables: IndexSet, - helpers: Vec, + pub(crate) variables: IndexSet, + pub(crate) helpers: Vec, } impl Default for AtMostOnceTracker { @@ -31,10 +31,10 @@ impl AtMostOnceTracker { variable: V, mut alloc_clause: impl FnMut(V, V, bool), mut alloc_var: impl FnMut() -> V, - ) { + ) -> bool { // If the variable is already tracked, we don't need to do anything. if self.variables.contains(&variable) { - return; + return false; } // If there are no variables yet, it means that this is the first variable that @@ -42,7 +42,7 @@ impl AtMostOnceTracker { // need to add any clauses. if self.variables.is_empty() { self.variables.insert(variable.clone()); - return; + return true; } // Allocate additional helper variables as needed and create clauses for the @@ -69,6 +69,8 @@ impl AtMostOnceTracker { ((var_idx >> bit_idx) & 1) == 1, ); } + + true } } diff --git a/src/solver/clause.rs b/src/solver/clause.rs index 994fd6f4..fb00e56d 100644 --- a/src/solver/clause.rs +++ b/src/solver/clause.rs @@ -14,8 +14,8 @@ use crate::{ id::{ClauseId, LearntClauseId, StringId, VersionSetId}, }, solver::{ - VariableId, conditions::DisjunctionId, decision_map::DecisionMap, - decision_tracker::DecisionTracker, variable_map::VariableMap, + VariableId, conditions::DisjunctionId, + decision_map::DecisionMap, decision_tracker::DecisionTracker, variable_map::VariableMap, }, }; @@ -103,6 +103,10 @@ pub(crate) enum Clause { /// A clause that forbids a package from being installed for an external /// reason. Excluded(VariableId, StringId), + + /// A clause that indicates that any version of a package C is selected. + /// In SAT terms: (C_selected v ¬Cj) + AnyOf(VariableId, VariableId), } impl Clause { @@ -246,6 +250,11 @@ impl Clause { ) } + fn any_of(selected_var: VariableId, other_var: VariableId) -> (Self, Option<[Literal; 2]>) { + let kind = Clause::AnyOf(selected_var, other_var); + (kind, Some([selected_var.positive(), other_var.negative()])) + } + /// Tries to fold over all the literals in the clause. /// /// This function is useful to iterate, find, or filter the literals in a @@ -278,7 +287,7 @@ impl Clause { disjunction .into_iter() .flat_map(|d| disjunction_to_candidates[&d].iter()) - .copied() + .copied(), ) .chain( requirements_to_sorted_candidates[&match_spec_id] @@ -297,6 +306,9 @@ impl Clause { Clause::Lock(_, s) => [s.negative(), VariableId::root().negative()] .into_iter() .try_fold(init, visit), + Clause::AnyOf(selected, variable) => [selected.positive(), variable.negative()] + .into_iter() + .try_fold(init, visit), } } @@ -448,6 +460,11 @@ impl WatchedLiterals { (Self::from_kind_and_initial_watches(watched_literals), kind) } + pub fn any_of(selected_var: VariableId, other_var: VariableId) -> (Option, Clause) { + let (kind, watched_literals) = Clause::any_of(selected_var, other_var); + (Self::from_kind_and_initial_watches(watched_literals), kind) + } + fn from_kind_and_initial_watches(watched_literals: Option<[Literal; 2]>) -> Option { let watched_literals = watched_literals?; debug_assert!(watched_literals[0] != watched_literals[1]); @@ -601,13 +618,14 @@ impl Display for ClauseDisplay<'_, I> { ) } Clause::Learnt(learnt_id) => write!(f, "Learnt({learnt_id:?})"), - Clause::Requires(variable, _condition, requirement) => { + Clause::Requires(variable, condition, requirement) => { write!( f, - "Requires({}({:?}), {})", + "Requires({}({:?}), {}, condition={:?})", variable.display(self.variable_map, self.interner), variable, requirement.display(self.interner), + condition ) } Clause::Constrains(v1, v2, version_set_id) => { @@ -642,6 +660,16 @@ impl Display for ClauseDisplay<'_, I> { other, ) } + Clause::AnyOf(variable, other) => { + write!( + f, + "AnyOf({}({:?}), {}({:?}))", + variable.display(self.variable_map, self.interner), + variable, + other.display(self.variable_map, self.interner), + other, + ) + } } } } diff --git a/src/solver/conditions.rs b/src/solver/conditions.rs index 5539c796..c259084a 100644 --- a/src/solver/conditions.rs +++ b/src/solver/conditions.rs @@ -1,10 +1,67 @@ +//! Resolvo supports conditional dependencies. E.g. `foo REQUIRES bar IF baz is +//! selected`. +//! +//! In SAT terms we express the requirement `A requires B` as `¬A1 v B1 .. v +//! B99` where A1 is a candidate of package A and B1 through B99 are candidates +//! that match the requirement B. In logical terms we say, either we do not +//! select A1 or we select one of matching Bs. +//! +//! If we add a condition C to the requirement, e.g. `A requires B if C` we can +//! modify the requirement clause to `¬A1 v ¬(C) v B1 .. v B2`. In logical terms +//! we say, either we do not select A1 or we do not match the condition or we +//! select one of the matching Bs. +//! +//! The condition `C` however expands to another set of matching candidates +//! (e.g. `C1 v C2 v C3`). If we insert that into the formula we get, +//! +//! `¬A1 v ¬(C1 v C2 v C3) v B1 .. v B2` +//! +//! which expands to +//! +//! `¬A1 v ¬C1 ^ ¬C2 ^ ¬C3 v B1 .. v B2` +//! +//! This is not in CNF form (required for SAT clauses) so we cannot use this +//! directly. To work around that problem we instead represent the condition +//! `¬(C)` as the complement of the version set C. E.g. if C would represent +//! `package >=1` then the complement would represent the candidates that match +//! `package <1`, or the candidates that do NOT match C. So if we represent the +//! complement of C as C! the final form of clause becomes: +//! +//! `¬A1 v C!1 .. v C!99 v B1 .. v B2` +//! +//! This introduces another edge case though. What if the complement is empty? +//! The final format would be void of `C!n` variables so it would become `¬A1 v +//! B1 .. v B2`, e.g. A unconditionally requires B. To fix that issue we +//! introduce an auxiliary variable that encodes if at-least-one of the package +//! C is selected (notated as `C_selected`). For each candidate of C we add the +//! clause +//! +//! `¬Cn v C_selected` +//! +//! This forces `C_selected` to become true if any `Cn` is set to true. We then +//! modify the requirement clause to be +//! +//! `¬A1 v ¬C_selected v B1 .. v B2` +//! +//! Note that we do not encode any clauses to force `C_selected` to be false. +//! We argue that this state is not required to function properly. If +//! `C_selected` would be set to false it would mean that all candidates of +//! package C are unselectable. This would disable the requirement, e.g. B +//! shouldnt be selected for A1. But it doesnt prevent A1 from being selected. +//! +//! Conditions are expressed as boolean expression trees. Internally they are +//! converted to Disjunctive Normal Form (DNF). A boolean expression is +//! in DNF if it is a **disjunction (OR)** of one or more **conjunctive clauses +//! (AND)**. +//! +//! We add a requires clause for each disjunction in the boolean expression. So +//! if we have the requirement `A requires B if C or D` we add requires clause +//! for `A requires B if C` and for `A requires B if D`. + use std::num::NonZero; use crate::{ - Condition, ConditionId, Interner, LogicalOperator, VersionSetId, - internal::{ - arena::{ ArenaId}, - }, + Condition, ConditionId, Interner, LogicalOperator, VersionSetId, internal::arena::ArenaId, }; /// An identifier that describes a group of version sets that are combined using diff --git a/src/solver/encoding.rs b/src/solver/encoding.rs index 47ff2864..3650c391 100644 --- a/src/solver/encoding.rs +++ b/src/solver/encoding.rs @@ -13,7 +13,7 @@ use crate::{ arena::ArenaId, id::{ClauseId, SolvableOrRootId, VariableId}, }, - solver::{clause::Literal, conditions::Disjunction, decision::Decision}, + solver::{conditions::Disjunction, decision::Decision}, }; /// An object that is responsible for encoding information from the dependency @@ -45,6 +45,9 @@ pub(crate) struct Encoder<'a, 'cache, D: DependencyProvider> { /// Stores for which packages and solvables we want to add forbid clauses. pending_forbid_clauses: IndexMap>, + + /// A set of packages that should have an at-least-once tracker. + new_at_least_one_packages: IndexMap, } /// The result of a future that was queued for processing. @@ -81,11 +84,6 @@ enum DisjunctionComplement<'a> { Empty(VersionSetId), } -enum DisjunctionLiterals<'a> { - ComplementVariables(&'a [Literal]), - AnyOf(Literal), -} - /// Result of querying candidates for a particular constraint. struct ConstraintCandidatesAvailable<'a> { solvable_id: SolvableOrRootId, @@ -108,6 +106,7 @@ impl<'a, 'cache, D: DependencyProvider> Encoder<'a, 'cache, D> { conflicting_clauses: Vec::new(), pending_forbid_clauses: IndexMap::default(), level, + new_at_least_one_packages: IndexMap::new(), } } @@ -127,6 +126,7 @@ impl<'a, 'cache, D: DependencyProvider> Encoder<'a, 'cache, D> { } self.add_pending_forbid_clauses(); + self.add_pending_at_least_one_clauses(); Ok(self.conflicting_clauses) } @@ -308,7 +308,25 @@ impl<'a, 'cache, D: DependencyProvider> Encoder<'a, 'cache, D> { } } DisjunctionComplement::Empty(version_set) => { - todo!() + let name_id = self.cache.provider().version_set_name(version_set); + let at_least_one_of_var = match self + .state + .at_last_once_tracker + .get(&name_id) + .copied() + .or_else(|| self.new_at_least_one_packages.get(&name_id).copied()) + { + Some(variable) => variable, + None => { + let variable = self + .state + .variable_map + .alloc_at_least_one_variable(name_id); + self.new_at_least_one_packages.insert(name_id, variable); + variable + } + }; + disjunction_literals.push(at_least_one_of_var.negative()); } } } @@ -357,7 +375,7 @@ impl<'a, 'cache, D: DependencyProvider> Encoder<'a, 'cache, D> { if conflict { self.conflicting_clauses.push(clause_id); - } else if no_candidates { + } else if no_candidates && condition.is_none() { // Add assertions for unit clauses (i.e. those with no matching candidates) self.state.negative_assertions.push((variable, clause_id)); } @@ -616,12 +634,8 @@ impl<'a, 'cache, D: DependencyProvider> Encoder<'a, 'cache, D> { // Add forbid constraints for this solvable on all other // solvables that have been visited already for the same // version set name. - let other_solvables = self - .state - .forbidden_clauses_added - .entry(name_id) - .or_default(); - other_solvables.add( + let other_solvables = self.state.at_most_one_trackers.entry(name_id).or_default(); + let variable_is_new = other_solvables.add( candidate_var, |a, b, positive| { let literal_b = if positive { b.positive() } else { b.negative() }; @@ -669,6 +683,76 @@ impl<'a, 'cache, D: DependencyProvider> Encoder<'a, 'cache, D> { .alloc_forbid_multiple_variable(name_id) }, ); + + if variable_is_new { + if let Some(&at_least_one_variable) = self.state.at_last_once_tracker.get(&name_id) + { + let (watched_literals, kind) = + WatchedLiterals::any_of(at_least_one_variable, candidate_var); + let clause_id = self.state.clauses.alloc(watched_literals, kind); + let watched_literals = self.state.clauses.watched_literals + [clause_id.to_usize()] + .as_mut() + .expect("forbid clause must have watched literals"); + self.state + .watches + .start_watching(watched_literals, clause_id); + } + } + } + } + + /// Adds clauses to track if at least one solvable for a particular package + /// is selected. An auxiliary variable is introduced and for each solvable a + /// clause is added that forces the auxiliary variable to turn true if any + /// solvable is selected. + /// + /// This function only looks at solvables that are added to the at most once + /// tracker. The encoder has an optimization that it only creates clauses + /// for packages that are references from a requires clause. Any other + /// solvable will never be selected anyway so we can completely ignore it. + /// + /// No clause is added to force the auxiliary variable to turn false. This + /// is on purpose because we dont not need this state to compute a proper + /// solution. + fn add_pending_at_least_one_clauses(&mut self) { + for (name_id, at_least_one_variable) in self.new_at_least_one_packages.drain(..) { + // Find the at-most-one tracker for the package. We want to reuse the same + // variables. + let variables = self + .state + .at_most_one_trackers + .get(&name_id) + .map(|tracker| &tracker.variables); + + // Add clauses for the existing variables. + for &helper_var in variables.into_iter().flatten() { + let (watched_literals, kind) = + WatchedLiterals::any_of(at_least_one_variable, helper_var); + let clause_id = self.state.clauses.alloc(watched_literals, kind); + let watched_literals = self.state.clauses.watched_literals[clause_id.to_usize()] + .as_mut() + .expect("forbid clause must have watched literals"); + self.state + .watches + .start_watching(watched_literals, clause_id); + + // Assign true if any of the variables is true. + if self.state.decision_tracker.assigned_value(helper_var) == Some(true) { + self.state + .decision_tracker + .try_add_decision( + Decision::new(at_least_one_variable, true, clause_id), + self.level, + ) + .expect("the at least one variable must be undecided"); + } + } + + // Record that we have a variable for this package. + self.state + .at_last_once_tracker + .insert(name_id, at_least_one_variable); } } } diff --git a/src/solver/mod.rs b/src/solver/mod.rs index c34a35b3..09a9a0da 100644 --- a/src/solver/mod.rs +++ b/src/solver/mod.rs @@ -187,7 +187,11 @@ pub(crate) struct SolverState { clauses_added_for_package: HashSet, clauses_added_for_solvable: HashSet, - forbidden_clauses_added: HashMap>, + at_most_one_trackers: HashMap>, + + /// Keeps track of auxiliary variables that are used to encode at-least-one + /// solvable for a package. + at_last_once_tracker: HashMap, decision_tracker: DecisionTracker, @@ -592,7 +596,10 @@ impl Solver { clause_id: ClauseId, ) -> Result { if starting_level == 0 { - tracing::trace!("Unsolvable: {:?}", clause_id); + tracing::trace!("Unsolvable: {}", self.state.clauses.kinds[clause_id.to_usize()].display( + &self.state.variable_map, + self.provider(), + )); Err(UnsolvableOrCancelled::Unsolvable( self.analyze_unsolvable(clause_id), )) diff --git a/src/solver/variable_map.rs b/src/solver/variable_map.rs index a3afe9c7..ca417018 100644 --- a/src/solver/variable_map.rs +++ b/src/solver/variable_map.rs @@ -41,7 +41,7 @@ pub enum VariableOrigin { /// A variable that indicates that any solvable of a particular package is /// part of the solution. - AnyOf(NameId), + AtLeastOne(NameId), } impl Default for VariableMap { @@ -92,6 +92,17 @@ impl VariableMap { variable_id } + /// Allocate a variable helps encode whether at least one solvable for a + /// particular package is selected. + pub fn alloc_at_least_one_variable(&mut self, name: NameId) -> VariableId { + let id = self.next_id; + self.next_id += 1; + let variable_id = VariableId::from_usize(id); + self.origins + .insert(variable_id, VariableOrigin::AtLeastOne(name)); + variable_id + } + /// Returns the origin of a variable. The origin describes the semantics of /// a variable. pub fn origin(&self, variable_id: VariableId) -> VariableOrigin { @@ -140,7 +151,7 @@ impl Display for VariableDisplay<'_, I> { VariableOrigin::ForbidMultiple(name) => { write!(f, "forbid-multiple({})", self.interner.display_name(name)) } - VariableOrigin::AnyOf(name) => { + VariableOrigin::AtLeastOne(name) => { write!(f, "any-of({})", self.interner.display_name(name)) } } diff --git a/tests/snapshots/solver__condition_is_disabled.snap.new b/tests/snapshots/solver__condition_is_disabled.snap.new new file mode 100644 index 00000000..93c2b87d --- /dev/null +++ b/tests/snapshots/solver__condition_is_disabled.snap.new @@ -0,0 +1,9 @@ +--- +source: tests/solver.rs +assertion_line: 1562 +expression: "solve_for_snapshot(provider, &requirements, &[])" +--- +disabled=2 +icon=1 +intl=2 +menu=2 diff --git a/tests/snapshots/solver__condition_limits_requirement.snap.new b/tests/snapshots/solver__condition_limits_requirement.snap.new index bc846c1f..2455911c 100644 --- a/tests/snapshots/solver__condition_limits_requirement.snap.new +++ b/tests/snapshots/solver__condition_limits_requirement.snap.new @@ -3,6 +3,4 @@ source: tests/solver.rs assertion_line: 1545 expression: "solve_for_snapshot(provider, &requirements, &[])" --- -icon=1 -intl=1 menu=1 diff --git a/tests/solver.rs b/tests/solver.rs index 4d6d5ab2..1b7a2656 100644 --- a/tests/solver.rs +++ b/tests/solver.rs @@ -1534,14 +1534,31 @@ fn test_conditional_requirements() { #[traced_test] fn test_condition_limits_requirement() { let mut provider = BundleBoxProvider::from_packages(&[ - ("menu", 1, vec!["icon 1; if intl 1"]), + ("menu", 1, vec!["bla; if intl"]), ("icon", 1, vec![]), ("icon", 2, vec![]), ("intl", 1, vec![]), ("intl", 2, vec![]), ]); - let requirements = provider.requirements(&["menu", "icon", "intl 1"]); + let requirements = provider.requirements(&["menu"]); + assert_snapshot!(solve_for_snapshot(provider, &requirements, &[])); +} + +#[test] +#[traced_test] +fn test_condition_is_disabled() { + let mut provider = BundleBoxProvider::from_packages(&[ + ("menu", 1, vec![]), + ("menu", 2, vec!["icon; if disabled", "intl"]), + ("disabled", 1, vec!["missing"]), + ("disabled", 2, vec![]), + ("intl", 1, vec![]), + ("intl", 2, vec!["disabled"]), + ("icon", 1, vec![]), + ]); + + let requirements = provider.requirements(&["menu"]); assert_snapshot!(solve_for_snapshot(provider, &requirements, &[])); } From cef4b799dd9a36421b7058e0e50c8c154b41bdf8 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Thu, 8 May 2025 15:44:56 +0200 Subject: [PATCH 09/28] add more tests --- src/conflict.rs | 9 +- src/solver/encoding.rs | 10 +- src/solver/mod.rs | 2 +- tests/.solver.rs.pending-snap | 235 ------------------ ...new => solver__condition_is_disabled.snap} | 1 - ...olver__condition_missing_requirement.snap} | 1 - ... => solver__conditional_requirements.snap} | 5 +- .../solver__conditional_requirements.snap.new | 8 - ..._conditional_requirements_version_set.snap | 6 + .../solver__conditional_unsolvable.snap | 10 + ...ditional_unsolvable_without_condition.snap | 7 + ...lver__unsat_applies_graph_compression.snap | 3 +- ...__unsat_applies_graph_compression.snap.new | 14 -- tests/solver.rs | 71 ++++-- 14 files changed, 85 insertions(+), 297 deletions(-) delete mode 100644 tests/.solver.rs.pending-snap rename tests/snapshots/{solver__condition_is_disabled.snap.new => solver__condition_is_disabled.snap} (85%) rename tests/snapshots/{solver__condition_limits_requirement.snap.new => solver__condition_missing_requirement.snap} (82%) rename tests/snapshots/{solver__conditional_optional_missing.snap.new => solver__conditional_requirements.snap} (74%) delete mode 100644 tests/snapshots/solver__conditional_requirements.snap.new create mode 100644 tests/snapshots/solver__conditional_requirements_version_set.snap create mode 100644 tests/snapshots/solver__conditional_unsolvable.snap create mode 100644 tests/snapshots/solver__conditional_unsolvable_without_condition.snap delete mode 100644 tests/snapshots/solver__unsat_applies_graph_compression.snap.new diff --git a/src/conflict.rs b/src/conflict.rs index aa79283f..00e6a311 100644 --- a/src/conflict.rs +++ b/src/conflict.rs @@ -161,10 +161,11 @@ impl Conflict { ConflictEdge::Conflict(ConflictCause::Constrains(version_set_id)), ); } - Clause::AnyOf(_, _) => { - unreachable!( - "an assumption is violated: AnyOf clauses can never make the auxiliary variable turn false, so they can never be part of a conflict." - ); + Clause::AnyOf(selected, variable) => { + // Assumption: since `AnyOf` of clause can never be false, we dont add an edge + // for it. + let decision_map = solver.state.decision_tracker.map(); + debug_assert_ne!(selected.positive().eval(decision_map), Some(false)); } } } diff --git a/src/solver/encoding.rs b/src/solver/encoding.rs index 3650c391..57d4b5c7 100644 --- a/src/solver/encoding.rs +++ b/src/solver/encoding.rs @@ -619,8 +619,12 @@ impl<'a, 'cache, D: DependencyProvider> Encoder<'a, 'cache, D> { .push(query_constraints_candidates.boxed_local()); } - /// Add forbid clauses for solvables that have not been added to the set of - /// forbid clases. + /// Add forbid clauses for solvables that we discovered as reachable from a + /// requires clause. + /// + /// The number of forbid clauses for a package is O(n log n) so we only add + /// clauses for the packages that are reachable from a requirement as an + /// optimization. fn add_pending_forbid_clauses(&mut self) { for (name_id, candidate_var) in self.pending_forbid_clauses @@ -715,6 +719,8 @@ impl<'a, 'cache, D: DependencyProvider> Encoder<'a, 'cache, D> { /// No clause is added to force the auxiliary variable to turn false. This /// is on purpose because we dont not need this state to compute a proper /// solution. + /// + /// See [`super::conditions`] for more information about conditions. fn add_pending_at_least_one_clauses(&mut self) { for (name_id, at_least_one_variable) in self.new_at_least_one_packages.drain(..) { // Find the at-most-one tracker for the package. We want to reuse the same diff --git a/src/solver/mod.rs b/src/solver/mod.rs index 09a9a0da..f2771c8a 100644 --- a/src/solver/mod.rs +++ b/src/solver/mod.rs @@ -193,7 +193,7 @@ pub(crate) struct SolverState { /// solvable for a package. at_last_once_tracker: HashMap, - decision_tracker: DecisionTracker, + pub(crate) decision_tracker: DecisionTracker, /// Activity score per package. name_activity: Vec, diff --git a/tests/.solver.rs.pending-snap b/tests/.solver.rs.pending-snap deleted file mode 100644 index 20aba851..00000000 --- a/tests/.solver.rs.pending-snap +++ /dev/null @@ -1,235 +0,0 @@ -{"run_id":"1746475518-883885600","line":1069,"new":{"module_name":"solver","snapshot_name":"resolve_union_requirements","metadata":{"source":"tests/solver.rs","assertion_line":1069,"expression":"result"},"snapshot":"The following packages are incompatible\n├─ e * can be installed with any of the following options:\n│ └─ e 1 would require\n│ └─ a *, which can be installed with any of the following options:\n│ └─ a 1\n└─ f * cannot be installed because there are no viable options:\n └─ f 1 would constrain\n └─ a >=2, <3, which conflicts with any installable versions previously reported"},"old":{"module_name":"solver","metadata":{},"snapshot":"b=1\nd=1\ne=1\nf=1"}} -{"run_id":"1746475548-406396600","line":1049,"new":null,"old":null} -{"run_id":"1746475548-406396600","line":1530,"new":null,"old":null} -{"run_id":"1746475548-406396600","line":1337,"new":null,"old":null} -{"run_id":"1746475548-406396600","line":1011,"new":null,"old":null} -{"run_id":"1746475548-406396600","line":1380,"new":null,"old":null} -{"run_id":"1746475548-406396600","line":1432,"new":null,"old":null} -{"run_id":"1746475548-406396600","line":1032,"new":null,"old":null} -{"run_id":"1746475548-406396600","line":1488,"new":null,"old":null} -{"run_id":"1746475548-406396600","line":1069,"new":null,"old":null} -{"run_id":"1746475588-187467800","line":1337,"new":null,"old":null} -{"run_id":"1746475588-187467800","line":1530,"new":null,"old":null} -{"run_id":"1746475588-187467800","line":1049,"new":null,"old":null} -{"run_id":"1746475588-187467800","line":1380,"new":null,"old":null} -{"run_id":"1746475588-187467800","line":1432,"new":null,"old":null} -{"run_id":"1746475588-187467800","line":1011,"new":null,"old":null} -{"run_id":"1746475588-187467800","line":1032,"new":null,"old":null} -{"run_id":"1746475588-187467800","line":1069,"new":null,"old":null} -{"run_id":"1746475588-187467800","line":1488,"new":null,"old":null} -{"run_id":"1746475721-295409200","line":1049,"new":null,"old":null} -{"run_id":"1746475721-295409200","line":1530,"new":null,"old":null} -{"run_id":"1746475721-295409200","line":1337,"new":null,"old":null} -{"run_id":"1746475721-295409200","line":1380,"new":null,"old":null} -{"run_id":"1746475721-295409200","line":1432,"new":null,"old":null} -{"run_id":"1746475721-295409200","line":1032,"new":null,"old":null} -{"run_id":"1746475721-295409200","line":1011,"new":null,"old":null} -{"run_id":"1746475721-295409200","line":1069,"new":null,"old":null} -{"run_id":"1746475721-295409200","line":1488,"new":null,"old":null} -{"run_id":"1746475763-487748000","line":1049,"new":null,"old":null} -{"run_id":"1746475763-487748000","line":1337,"new":null,"old":null} -{"run_id":"1746475763-487748000","line":1530,"new":null,"old":null} -{"run_id":"1746475763-487748000","line":1380,"new":null,"old":null} -{"run_id":"1746475763-487748000","line":1432,"new":null,"old":null} -{"run_id":"1746475763-487748000","line":1032,"new":null,"old":null} -{"run_id":"1746475763-487748000","line":1011,"new":null,"old":null} -{"run_id":"1746475763-487748000","line":1069,"new":null,"old":null} -{"run_id":"1746475763-487748000","line":1488,"new":null,"old":null} -{"run_id":"1746475774-373191100","line":1337,"new":null,"old":null} -{"run_id":"1746475774-373191100","line":1530,"new":null,"old":null} -{"run_id":"1746475774-373191100","line":1049,"new":null,"old":null} -{"run_id":"1746475774-373191100","line":1380,"new":null,"old":null} -{"run_id":"1746475774-373191100","line":1432,"new":null,"old":null} -{"run_id":"1746475774-373191100","line":1032,"new":null,"old":null} -{"run_id":"1746475774-373191100","line":1011,"new":null,"old":null} -{"run_id":"1746475774-373191100","line":1488,"new":null,"old":null} -{"run_id":"1746475774-373191100","line":1069,"new":null,"old":null} -{"run_id":"1746475793-356201900","line":1337,"new":null,"old":null} -{"run_id":"1746475793-356201900","line":1530,"new":null,"old":null} -{"run_id":"1746475793-356201900","line":1049,"new":null,"old":null} -{"run_id":"1746475793-356201900","line":1380,"new":null,"old":null} -{"run_id":"1746475793-356201900","line":1432,"new":null,"old":null} -{"run_id":"1746475793-356201900","line":1032,"new":null,"old":null} -{"run_id":"1746475793-356201900","line":1011,"new":null,"old":null} -{"run_id":"1746475793-356201900","line":1069,"new":null,"old":null} -{"run_id":"1746475793-356201900","line":1488,"new":null,"old":null} -{"run_id":"1746475810-423217200","line":1530,"new":null,"old":null} -{"run_id":"1746475810-423217200","line":1337,"new":null,"old":null} -{"run_id":"1746475810-423217200","line":1049,"new":null,"old":null} -{"run_id":"1746475810-423217200","line":1032,"new":null,"old":null} -{"run_id":"1746475810-423217200","line":1011,"new":null,"old":null} -{"run_id":"1746475810-423217200","line":1380,"new":null,"old":null} -{"run_id":"1746475810-423217200","line":1432,"new":null,"old":null} -{"run_id":"1746475810-423217200","line":1069,"new":null,"old":null} -{"run_id":"1746475810-423217200","line":1488,"new":null,"old":null} -{"run_id":"1746475919-456405700","line":1049,"new":null,"old":null} -{"run_id":"1746475919-456405700","line":1530,"new":null,"old":null} -{"run_id":"1746475919-456405700","line":1337,"new":null,"old":null} -{"run_id":"1746475919-456405700","line":1380,"new":null,"old":null} -{"run_id":"1746475919-456405700","line":1432,"new":null,"old":null} -{"run_id":"1746475919-456405700","line":1032,"new":null,"old":null} -{"run_id":"1746475919-456405700","line":1011,"new":null,"old":null} -{"run_id":"1746475919-456405700","line":1069,"new":null,"old":null} -{"run_id":"1746475919-456405700","line":1488,"new":null,"old":null} -{"run_id":"1746521934-289213500","line":1034,"new":null,"old":null} -{"run_id":"1746521934-289213500","line":1515,"new":null,"old":null} -{"run_id":"1746521934-289213500","line":1322,"new":null,"old":null} -{"run_id":"1746521934-289213500","line":1017,"new":null,"old":null} -{"run_id":"1746521934-289213500","line":1365,"new":null,"old":null} -{"run_id":"1746521934-289213500","line":1417,"new":null,"old":null} -{"run_id":"1746521934-289213500","line":996,"new":null,"old":null} -{"run_id":"1746521934-289213500","line":1054,"new":null,"old":null} -{"run_id":"1746521934-289213500","line":1473,"new":null,"old":null} -{"run_id":"1746521959-537048400","line":1515,"new":null,"old":null} -{"run_id":"1746521959-537048400","line":1322,"new":null,"old":null} -{"run_id":"1746521959-537048400","line":1034,"new":null,"old":null} -{"run_id":"1746521959-537048400","line":1365,"new":null,"old":null} -{"run_id":"1746521959-537048400","line":1417,"new":null,"old":null} -{"run_id":"1746521959-537048400","line":1017,"new":null,"old":null} -{"run_id":"1746521959-537048400","line":996,"new":null,"old":null} -{"run_id":"1746521959-537048400","line":1473,"new":null,"old":null} -{"run_id":"1746521959-537048400","line":1054,"new":null,"old":null} -{"run_id":"1746522623-864633900","line":1322,"new":null,"old":null} -{"run_id":"1746522623-864633900","line":1034,"new":null,"old":null} -{"run_id":"1746522623-864633900","line":1515,"new":null,"old":null} -{"run_id":"1746522623-864633900","line":1365,"new":null,"old":null} -{"run_id":"1746522623-864633900","line":1417,"new":null,"old":null} -{"run_id":"1746522623-864633900","line":1017,"new":null,"old":null} -{"run_id":"1746522623-864633900","line":996,"new":null,"old":null} -{"run_id":"1746522623-864633900","line":1473,"new":null,"old":null} -{"run_id":"1746522623-864633900","line":1054,"new":null,"old":null} -{"run_id":"1746522885-708421500","line":1034,"new":null,"old":null} -{"run_id":"1746522885-708421500","line":1322,"new":null,"old":null} -{"run_id":"1746522885-708421500","line":1515,"new":null,"old":null} -{"run_id":"1746522885-708421500","line":1365,"new":null,"old":null} -{"run_id":"1746522885-708421500","line":1417,"new":null,"old":null} -{"run_id":"1746522885-708421500","line":1017,"new":null,"old":null} -{"run_id":"1746522885-708421500","line":996,"new":null,"old":null} -{"run_id":"1746522885-708421500","line":1054,"new":null,"old":null} -{"run_id":"1746522885-708421500","line":1473,"new":null,"old":null} -{"run_id":"1746523011-294181500","line":1322,"new":null,"old":null} -{"run_id":"1746523011-294181500","line":1515,"new":null,"old":null} -{"run_id":"1746523011-294181500","line":1034,"new":null,"old":null} -{"run_id":"1746523011-294181500","line":1017,"new":null,"old":null} -{"run_id":"1746523011-294181500","line":1365,"new":null,"old":null} -{"run_id":"1746523011-294181500","line":1417,"new":null,"old":null} -{"run_id":"1746523011-294181500","line":996,"new":null,"old":null} -{"run_id":"1746523011-294181500","line":1473,"new":null,"old":null} -{"run_id":"1746523011-294181500","line":1054,"new":null,"old":null} -{"run_id":"1746523195-143485300","line":1034,"new":null,"old":null} -{"run_id":"1746523195-143485300","line":1322,"new":null,"old":null} -{"run_id":"1746523195-143485300","line":1515,"new":null,"old":null} -{"run_id":"1746523195-143485300","line":1365,"new":null,"old":null} -{"run_id":"1746523195-143485300","line":1417,"new":null,"old":null} -{"run_id":"1746523195-143485300","line":996,"new":null,"old":null} -{"run_id":"1746523195-143485300","line":1017,"new":null,"old":null} -{"run_id":"1746523195-143485300","line":1054,"new":null,"old":null} -{"run_id":"1746523195-143485300","line":1473,"new":null,"old":null} -{"run_id":"1746523202-920217000","line":1034,"new":null,"old":null} -{"run_id":"1746523202-920217000","line":1322,"new":null,"old":null} -{"run_id":"1746523202-920217000","line":1515,"new":null,"old":null} -{"run_id":"1746523202-920217000","line":1017,"new":null,"old":null} -{"run_id":"1746523202-920217000","line":1365,"new":null,"old":null} -{"run_id":"1746523202-920217000","line":1417,"new":null,"old":null} -{"run_id":"1746523202-920217000","line":996,"new":null,"old":null} -{"run_id":"1746523202-920217000","line":1054,"new":null,"old":null} -{"run_id":"1746523202-920217000","line":1473,"new":null,"old":null} -{"run_id":"1746523259-7247400","line":1515,"new":null,"old":null} -{"run_id":"1746523259-7247400","line":1034,"new":null,"old":null} -{"run_id":"1746523259-7247400","line":1322,"new":null,"old":null} -{"run_id":"1746523259-7247400","line":1017,"new":null,"old":null} -{"run_id":"1746523259-7247400","line":1365,"new":null,"old":null} -{"run_id":"1746523259-7247400","line":1417,"new":null,"old":null} -{"run_id":"1746523259-7247400","line":996,"new":null,"old":null} -{"run_id":"1746523259-7247400","line":1054,"new":null,"old":null} -{"run_id":"1746523259-7247400","line":1473,"new":null,"old":null} -{"run_id":"1746523279-14738600","line":1515,"new":null,"old":null} -{"run_id":"1746523279-14738600","line":1322,"new":null,"old":null} -{"run_id":"1746523279-14738600","line":1034,"new":null,"old":null} -{"run_id":"1746523279-14738600","line":1365,"new":null,"old":null} -{"run_id":"1746523279-14738600","line":1417,"new":null,"old":null} -{"run_id":"1746523279-14738600","line":996,"new":null,"old":null} -{"run_id":"1746523279-14738600","line":1017,"new":null,"old":null} -{"run_id":"1746523279-14738600","line":1054,"new":null,"old":null} -{"run_id":"1746523279-14738600","line":1473,"new":null,"old":null} -{"run_id":"1746523375-955256700","line":1322,"new":null,"old":null} -{"run_id":"1746523375-955256700","line":1034,"new":null,"old":null} -{"run_id":"1746523375-955256700","line":1515,"new":null,"old":null} -{"run_id":"1746523375-955256700","line":1017,"new":null,"old":null} -{"run_id":"1746523375-955256700","line":1365,"new":null,"old":null} -{"run_id":"1746523375-955256700","line":1417,"new":null,"old":null} -{"run_id":"1746523375-955256700","line":996,"new":null,"old":null} -{"run_id":"1746523375-955256700","line":1054,"new":null,"old":null} -{"run_id":"1746523375-955256700","line":1473,"new":null,"old":null} -{"run_id":"1746523425-492121500","line":1322,"new":null,"old":null} -{"run_id":"1746523425-492121500","line":1515,"new":null,"old":null} -{"run_id":"1746523425-492121500","line":1034,"new":null,"old":null} -{"run_id":"1746523425-492121500","line":1365,"new":null,"old":null} -{"run_id":"1746523425-492121500","line":1417,"new":null,"old":null} -{"run_id":"1746523425-492121500","line":1017,"new":null,"old":null} -{"run_id":"1746523425-492121500","line":996,"new":null,"old":null} -{"run_id":"1746523425-492121500","line":1054,"new":null,"old":null} -{"run_id":"1746523425-492121500","line":1473,"new":null,"old":null} -{"run_id":"1746524567-305832800","line":1034,"new":null,"old":null} -{"run_id":"1746524567-305832800","line":1322,"new":null,"old":null} -{"run_id":"1746524567-305832800","line":1515,"new":null,"old":null} -{"run_id":"1746524567-305832800","line":1365,"new":null,"old":null} -{"run_id":"1746524567-305832800","line":1417,"new":null,"old":null} -{"run_id":"1746524567-305832800","line":996,"new":null,"old":null} -{"run_id":"1746524567-305832800","line":1054,"new":null,"old":null} -{"run_id":"1746524567-305832800","line":1017,"new":null,"old":null} -{"run_id":"1746524567-305832800","line":1473,"new":null,"old":null} -{"run_id":"1746544766-929604400","line":1034,"new":null,"old":null} -{"run_id":"1746544766-929604400","line":1515,"new":null,"old":null} -{"run_id":"1746544766-929604400","line":1322,"new":null,"old":null} -{"run_id":"1746544766-929604400","line":1365,"new":null,"old":null} -{"run_id":"1746544766-929604400","line":1417,"new":null,"old":null} -{"run_id":"1746544766-929604400","line":996,"new":null,"old":null} -{"run_id":"1746544766-929604400","line":1054,"new":null,"old":null} -{"run_id":"1746544766-929604400","line":1017,"new":null,"old":null} -{"run_id":"1746544766-929604400","line":1473,"new":null,"old":null} -{"run_id":"1746546447-335828800","line":1515,"new":null,"old":null} -{"run_id":"1746546447-335828800","line":1322,"new":null,"old":null} -{"run_id":"1746546447-335828800","line":1034,"new":null,"old":null} -{"run_id":"1746546447-335828800","line":1365,"new":null,"old":null} -{"run_id":"1746546447-335828800","line":1417,"new":null,"old":null} -{"run_id":"1746546447-335828800","line":996,"new":null,"old":null} -{"run_id":"1746546447-335828800","line":1017,"new":null,"old":null} -{"run_id":"1746546447-335828800","line":1054,"new":null,"old":null} -{"run_id":"1746546447-335828800","line":1473,"new":null,"old":null} -{"run_id":"1746546483-93806700","line":1515,"new":null,"old":null} -{"run_id":"1746546483-93806700","line":1322,"new":null,"old":null} -{"run_id":"1746546483-93806700","line":1034,"new":null,"old":null} -{"run_id":"1746546483-93806700","line":1017,"new":null,"old":null} -{"run_id":"1746546483-93806700","line":996,"new":null,"old":null} -{"run_id":"1746546483-93806700","line":1365,"new":null,"old":null} -{"run_id":"1746546483-93806700","line":1417,"new":null,"old":null} -{"run_id":"1746546483-93806700","line":1054,"new":null,"old":null} -{"run_id":"1746546483-93806700","line":1473,"new":null,"old":null} -{"run_id":"1746546515-573078600","line":1034,"new":null,"old":null} -{"run_id":"1746546515-573078600","line":1515,"new":null,"old":null} -{"run_id":"1746546515-573078600","line":1322,"new":null,"old":null} -{"run_id":"1746546515-573078600","line":1365,"new":null,"old":null} -{"run_id":"1746546515-573078600","line":1417,"new":null,"old":null} -{"run_id":"1746546515-573078600","line":996,"new":null,"old":null} -{"run_id":"1746546515-573078600","line":1017,"new":null,"old":null} -{"run_id":"1746546515-573078600","line":1054,"new":null,"old":null} -{"run_id":"1746546515-573078600","line":1473,"new":null,"old":null} -{"run_id":"1746546555-408905300","line":1515,"new":null,"old":null} -{"run_id":"1746546555-408905300","line":1322,"new":null,"old":null} -{"run_id":"1746546555-408905300","line":1034,"new":null,"old":null} -{"run_id":"1746546555-408905300","line":996,"new":null,"old":null} -{"run_id":"1746546555-408905300","line":1365,"new":null,"old":null} -{"run_id":"1746546555-408905300","line":1017,"new":null,"old":null} -{"run_id":"1746546555-408905300","line":1054,"new":null,"old":null} -{"run_id":"1746546555-408905300","line":1417,"new":null,"old":null} -{"run_id":"1746546555-408905300","line":1473,"new":null,"old":null} -{"run_id":"1746612890-497675200","line":1034,"new":null,"old":null} -{"run_id":"1746612890-497675200","line":1515,"new":null,"old":null} -{"run_id":"1746612890-497675200","line":1322,"new":null,"old":null} -{"run_id":"1746612890-497675200","line":996,"new":null,"old":null} -{"run_id":"1746612890-497675200","line":1017,"new":null,"old":null} -{"run_id":"1746612890-497675200","line":1054,"new":null,"old":null} -{"run_id":"1746612890-497675200","line":1365,"new":null,"old":null} -{"run_id":"1746612890-497675200","line":1417,"new":null,"old":null} -{"run_id":"1746612890-497675200","line":1473,"new":null,"old":null} diff --git a/tests/snapshots/solver__condition_is_disabled.snap.new b/tests/snapshots/solver__condition_is_disabled.snap similarity index 85% rename from tests/snapshots/solver__condition_is_disabled.snap.new rename to tests/snapshots/solver__condition_is_disabled.snap index 93c2b87d..ab3c1b93 100644 --- a/tests/snapshots/solver__condition_is_disabled.snap.new +++ b/tests/snapshots/solver__condition_is_disabled.snap @@ -1,6 +1,5 @@ --- source: tests/solver.rs -assertion_line: 1562 expression: "solve_for_snapshot(provider, &requirements, &[])" --- disabled=2 diff --git a/tests/snapshots/solver__condition_limits_requirement.snap.new b/tests/snapshots/solver__condition_missing_requirement.snap similarity index 82% rename from tests/snapshots/solver__condition_limits_requirement.snap.new rename to tests/snapshots/solver__condition_missing_requirement.snap index 2455911c..2ab13fca 100644 --- a/tests/snapshots/solver__condition_limits_requirement.snap.new +++ b/tests/snapshots/solver__condition_missing_requirement.snap @@ -1,6 +1,5 @@ --- source: tests/solver.rs -assertion_line: 1545 expression: "solve_for_snapshot(provider, &requirements, &[])" --- menu=1 diff --git a/tests/snapshots/solver__conditional_optional_missing.snap.new b/tests/snapshots/solver__conditional_requirements.snap similarity index 74% rename from tests/snapshots/solver__conditional_optional_missing.snap.new rename to tests/snapshots/solver__conditional_requirements.snap index 80e34ea7..25c8c915 100644 --- a/tests/snapshots/solver__conditional_optional_missing.snap.new +++ b/tests/snapshots/solver__conditional_requirements.snap @@ -1,8 +1,7 @@ --- source: tests/solver.rs -assertion_line: 1548 expression: "solve_for_snapshot(provider, &requirements, &[])" --- +bar=1 baz=1 -icon=1 -menu=1 +foo=1 diff --git a/tests/snapshots/solver__conditional_requirements.snap.new b/tests/snapshots/solver__conditional_requirements.snap.new deleted file mode 100644 index 86ce51ee..00000000 --- a/tests/snapshots/solver__conditional_requirements.snap.new +++ /dev/null @@ -1,8 +0,0 @@ ---- -source: tests/solver.rs -assertion_line: 1530 -expression: "solve_for_snapshot(provider, &requirements, &[])" ---- -foo * cannot be installed because there are no viable options: -└─ foo 2 would require - └─ baz *, for which no candidates were found. diff --git a/tests/snapshots/solver__conditional_requirements_version_set.snap b/tests/snapshots/solver__conditional_requirements_version_set.snap new file mode 100644 index 00000000..bb1e78a0 --- /dev/null +++ b/tests/snapshots/solver__conditional_requirements_version_set.snap @@ -0,0 +1,6 @@ +--- +source: tests/solver.rs +expression: "solve_for_snapshot(provider, &requirements, &[])" +--- +bar=2 +foo=1 diff --git a/tests/snapshots/solver__conditional_unsolvable.snap b/tests/snapshots/solver__conditional_unsolvable.snap new file mode 100644 index 00000000..ef1a50a5 --- /dev/null +++ b/tests/snapshots/solver__conditional_unsolvable.snap @@ -0,0 +1,10 @@ +--- +source: tests/solver.rs +expression: "solve_for_snapshot(provider, &requirements, &[])" +--- +foo * cannot be installed because there are no viable options: +└─ foo 1 would require + └─ baz >=2, <3, for which no candidates were found. +The following packages are incompatible +└─ bar * can be installed with any of the following options: + └─ bar 1 diff --git a/tests/snapshots/solver__conditional_unsolvable_without_condition.snap b/tests/snapshots/solver__conditional_unsolvable_without_condition.snap new file mode 100644 index 00000000..25c8c915 --- /dev/null +++ b/tests/snapshots/solver__conditional_unsolvable_without_condition.snap @@ -0,0 +1,7 @@ +--- +source: tests/solver.rs +expression: "solve_for_snapshot(provider, &requirements, &[])" +--- +bar=1 +baz=1 +foo=1 diff --git a/tests/snapshots/solver__unsat_applies_graph_compression.snap b/tests/snapshots/solver__unsat_applies_graph_compression.snap index c1fb2f3e..4f43875d 100644 --- a/tests/snapshots/solver__unsat_applies_graph_compression.snap +++ b/tests/snapshots/solver__unsat_applies_graph_compression.snap @@ -1,11 +1,10 @@ --- source: tests/solver.rs expression: error -snapshot_kind: text --- The following packages are incompatible ├─ c >=101, <104 can be installed with any of the following options: -│ └─ c 103 +│ └─ c 101 └─ a * cannot be installed because there are no viable options: └─ a 9 | 10 would require └─ b *, which cannot be installed because there are no viable options: diff --git a/tests/snapshots/solver__unsat_applies_graph_compression.snap.new b/tests/snapshots/solver__unsat_applies_graph_compression.snap.new deleted file mode 100644 index 35db3f9e..00000000 --- a/tests/snapshots/solver__unsat_applies_graph_compression.snap.new +++ /dev/null @@ -1,14 +0,0 @@ ---- -source: tests/solver.rs -assertion_line: 1184 -expression: error ---- -The following packages are incompatible -├─ c >=101, <104 can be installed with any of the following options: -│ └─ c 101 -└─ a * cannot be installed because there are no viable options: - └─ a 9 | 10 would require - └─ b *, which cannot be installed because there are no viable options: - └─ b 42 | 100 would require - └─ c >=0, <100, which cannot be installed because there are no viable options: - └─ c 99, which conflicts with the versions reported above. diff --git a/tests/solver.rs b/tests/solver.rs index 1b7a2656..6406f087 100644 --- a/tests/solver.rs +++ b/tests/solver.rs @@ -1,4 +1,3 @@ -use chumsky::{error}; use std::{ any::Any, cell::{Cell, RefCell}, @@ -16,14 +15,14 @@ use std::{ }; use ahash::HashMap; -use chumsky::Parser; +use chumsky::{Parser, error}; use indexmap::IndexMap; use insta::assert_snapshot; use itertools::Itertools; use resolvo::{ Candidates, Condition, ConditionId, ConditionalRequirement, Dependencies, DependencyProvider, - Interner, KnownDependencies, LogicalOperator, NameId, Problem, SolvableId, Solver, - SolverCache, StringId, UnsolvableOrCancelled, VersionSetId, VersionSetUnionId, + Interner, KnownDependencies, LogicalOperator, NameId, Problem, SolvableId, Solver, SolverCache, + StringId, UnsolvableOrCancelled, VersionSetId, VersionSetUnionId, snapshot::{DependencySnapshot, SnapshotProvider}, utils::Pool, }; @@ -147,7 +146,6 @@ enum SpecCondition { } mod parser { - use super::{ConditionalSpec, Pack, Spec, SpecCondition}; use chumsky::{ error, error::LabelError, @@ -162,6 +160,8 @@ mod parser { use resolvo::LogicalOperator; use version_ranges::Ranges; + use super::{ConditionalSpec, Pack, Spec, SpecCondition}; + /// Parses a package name identifier. pub fn name<'src, I, E>() -> impl Parser<'src, I, >::Slice, E> + Copy where @@ -1447,8 +1447,8 @@ fn test_solve_with_additional_with_constrains() { // // let mut snapshot_provider = snapshot.provider(); // -// assert_snapshot!(solve_for_snapshot(snapshot_provider, &[menu_req], &[])); -// } +// assert_snapshot!(solve_for_snapshot(snapshot_provider, &[menu_req], +// &[])); } #[test] fn test_snapshot_union_requirements() { @@ -1522,42 +1522,61 @@ fn test_explicit_root_requirements() { #[test] fn test_conditional_requirements() { let mut provider = BundleBoxProvider::from_packages(&[ - ("foo", 2, vec!["baz; if bar"]), + ("foo", 1, vec!["baz; if bar"]), ("bar", 1, vec![]), + ("baz", 1, vec![]), ]); - let requirements = provider.requirements(&["foo"]); + let requirements = provider.requirements(&["foo", "bar"]); assert_snapshot!(solve_for_snapshot(provider, &requirements, &[])); } #[test] -#[traced_test] -fn test_condition_limits_requirement() { +fn test_conditional_unsolvable() { let mut provider = BundleBoxProvider::from_packages(&[ - ("menu", 1, vec!["bla; if intl"]), - ("icon", 1, vec![]), - ("icon", 2, vec![]), - ("intl", 1, vec![]), - ("intl", 2, vec![]), + ("foo", 1, vec!["baz 2; if bar"]), + ("bar", 1, vec![]), + ("baz", 1, vec![]), ]); - let requirements = provider.requirements(&["menu"]); + let requirements = provider.requirements(&["foo", "bar"]); assert_snapshot!(solve_for_snapshot(provider, &requirements, &[])); } #[test] -#[traced_test] -fn test_condition_is_disabled() { +fn test_conditional_unsolvable_without_condition() { let mut provider = BundleBoxProvider::from_packages(&[ - ("menu", 1, vec![]), - ("menu", 2, vec!["icon; if disabled", "intl"]), - ("disabled", 1, vec!["missing"]), - ("disabled", 2, vec![]), - ("intl", 1, vec![]), - ("intl", 2, vec!["disabled"]), - ("icon", 1, vec![]), + ("foo", 1, vec![]), + ("foo", 2, vec!["baz 2; if bar"]), /* This will not be selected because baz 2 conflicts + * with the requirement. */ + ("bar", 1, vec![]), + ("baz", 1, vec![]), + ("baz", 2, vec![]), ]); + let requirements = provider.requirements(&["foo", "bar", "baz 1"]); + assert_snapshot!(solve_for_snapshot(provider, &requirements, &[])); +} + +#[test] +fn test_conditional_requirements_version_set() { + let mut provider = BundleBoxProvider::from_packages(&[ + ("foo", 1, vec!["baz; if bar 1"]), + ("bar", 1, vec![]), + ("bar", 2, vec![]), + ("baz", 1, vec![]), + ]); + + let requirements = provider.requirements(&["foo", "bar"]); + assert_snapshot!(solve_for_snapshot(provider, &requirements, &[])); +} + +#[test] +#[traced_test] +fn test_condition_missing_requirement() { + let mut provider = + BundleBoxProvider::from_packages(&[("menu", 1, vec!["bla; if intl"]), ("intl", 1, vec![])]); + let requirements = provider.requirements(&["menu"]); assert_snapshot!(solve_for_snapshot(provider, &requirements, &[])); } From ab891cb87e7dc1f3b95f68bd6a8713cdf08946e0 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Thu, 8 May 2025 16:48:55 +0200 Subject: [PATCH 10/28] cleanup and split test --- src/conditional_requirement.rs | 21 +- src/conflict.rs | 2 +- src/requirement.rs | 5 +- src/snapshot.rs | 48 +- src/solver/clause.rs | 13 +- src/solver/conditions.rs | 16 +- src/solver/encoding.rs | 23 +- src/solver/mod.rs | 32 +- tests/solver/bundle_box/conditional_spec.rs | 21 + tests/solver/bundle_box/mod.rs | 431 +++++++++++ tests/solver/bundle_box/pack.rs | 57 ++ tests/solver/bundle_box/parser.rs | 119 +++ tests/solver/bundle_box/spec.rs | 23 + tests/{solver.rs => solver/main.rs} | 731 ++---------------- .../solver__condition_is_disabled.snap | 0 ...solver__condition_missing_requirement.snap | 0 .../solver__conditional_requirements.snap | 0 ..._conditional_requirements_version_set.snap | 0 .../solver__conditional_unsolvable.snap | 0 ...ditional_unsolvable_without_condition.snap | 0 .../snapshots/solver__excluded.snap | 0 .../snapshots/solver__incremental_crash.snap | 0 .../snapshots/solver__merge_excluded.snap | 0 .../snapshots/solver__merge_installable.snap | 0 ...erge_installable_non_continuous_range.snap | 0 .../snapshots/solver__missing_dep.snap | 0 .../snapshots/solver__no_backtracking.snap | 0 .../snapshots/solver__resolve_and_cancel.snap | 0 ...lve_with_concurrent_metadata_fetching.snap | 0 .../solver__resolve_with_conflict.snap | 0 .../snapshots/solver__root_constraints.snap | 0 .../snapshots/solver__root_excluded.snap | 0 .../snapshots/solver__snapshot.snap | 0 .../solver__snapshot_union_requirements.snap | 0 .../solver__unsat_after_backtracking.snap | 0 ...lver__unsat_applies_graph_compression.snap | 0 .../solver__unsat_bluesky_conflict.snap | 0 .../snapshots/solver__unsat_constrains.snap | 0 .../snapshots/solver__unsat_constrains_2.snap | 0 ..._unsat_incompatible_root_requirements.snap | 0 .../solver__unsat_locked_and_excluded.snap | 0 ...solver__unsat_missing_top_level_dep_1.snap | 0 ...solver__unsat_missing_top_level_dep_2.snap | 0 ...lver__unsat_no_candidates_for_child_1.snap | 0 ...lver__unsat_no_candidates_for_child_2.snap | 0 .../solver__unsat_pubgrub_article.snap | 0 46 files changed, 814 insertions(+), 728 deletions(-) create mode 100644 tests/solver/bundle_box/conditional_spec.rs create mode 100644 tests/solver/bundle_box/mod.rs create mode 100644 tests/solver/bundle_box/pack.rs create mode 100644 tests/solver/bundle_box/parser.rs create mode 100644 tests/solver/bundle_box/spec.rs rename tests/{solver.rs => solver/main.rs} (57%) rename tests/{ => solver}/snapshots/solver__condition_is_disabled.snap (100%) rename tests/{ => solver}/snapshots/solver__condition_missing_requirement.snap (100%) rename tests/{ => solver}/snapshots/solver__conditional_requirements.snap (100%) rename tests/{ => solver}/snapshots/solver__conditional_requirements_version_set.snap (100%) rename tests/{ => solver}/snapshots/solver__conditional_unsolvable.snap (100%) rename tests/{ => solver}/snapshots/solver__conditional_unsolvable_without_condition.snap (100%) rename tests/{ => solver}/snapshots/solver__excluded.snap (100%) rename tests/{ => solver}/snapshots/solver__incremental_crash.snap (100%) rename tests/{ => solver}/snapshots/solver__merge_excluded.snap (100%) rename tests/{ => solver}/snapshots/solver__merge_installable.snap (100%) rename tests/{ => solver}/snapshots/solver__merge_installable_non_continuous_range.snap (100%) rename tests/{ => solver}/snapshots/solver__missing_dep.snap (100%) rename tests/{ => solver}/snapshots/solver__no_backtracking.snap (100%) rename tests/{ => solver}/snapshots/solver__resolve_and_cancel.snap (100%) rename tests/{ => solver}/snapshots/solver__resolve_with_concurrent_metadata_fetching.snap (100%) rename tests/{ => solver}/snapshots/solver__resolve_with_conflict.snap (100%) rename tests/{ => solver}/snapshots/solver__root_constraints.snap (100%) rename tests/{ => solver}/snapshots/solver__root_excluded.snap (100%) rename tests/{ => solver}/snapshots/solver__snapshot.snap (100%) rename tests/{ => solver}/snapshots/solver__snapshot_union_requirements.snap (100%) rename tests/{ => solver}/snapshots/solver__unsat_after_backtracking.snap (100%) rename tests/{ => solver}/snapshots/solver__unsat_applies_graph_compression.snap (100%) rename tests/{ => solver}/snapshots/solver__unsat_bluesky_conflict.snap (100%) rename tests/{ => solver}/snapshots/solver__unsat_constrains.snap (100%) rename tests/{ => solver}/snapshots/solver__unsat_constrains_2.snap (100%) rename tests/{ => solver}/snapshots/solver__unsat_incompatible_root_requirements.snap (100%) rename tests/{ => solver}/snapshots/solver__unsat_locked_and_excluded.snap (100%) rename tests/{ => solver}/snapshots/solver__unsat_missing_top_level_dep_1.snap (100%) rename tests/{ => solver}/snapshots/solver__unsat_missing_top_level_dep_2.snap (100%) rename tests/{ => solver}/snapshots/solver__unsat_no_candidates_for_child_1.snap (100%) rename tests/{ => solver}/snapshots/solver__unsat_no_candidates_for_child_2.snap (100%) rename tests/{ => solver}/snapshots/solver__unsat_pubgrub_article.snap (100%) diff --git a/src/conditional_requirement.rs b/src/conditional_requirement.rs index 8265eb93..0477a4b5 100644 --- a/src/conditional_requirement.rs +++ b/src/conditional_requirement.rs @@ -1,5 +1,4 @@ -use crate::internal::id::ConditionId; -use crate::{Requirement, VersionSetId}; +use crate::{Requirement, VersionSetId, VersionSetUnionId, internal::id::ConditionId}; /// A [`ConditionalRequirement`] is a requirement that is only enforced when a /// certain condition holds. @@ -7,6 +6,7 @@ use crate::{Requirement, VersionSetId}; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ConditionalRequirement { /// The requirement is enforced only when the condition evaluates to true. + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub condition: Option, /// A requirement on another package. @@ -17,6 +17,7 @@ pub struct ConditionalRequirement { /// based on whether one or more other requirements are true or false. #[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] pub enum Condition { /// Defines a combination of conditions using logical operators. Binary(LogicalOperator, ConditionId, ConditionId), @@ -25,9 +26,11 @@ pub enum Condition { Requirement(VersionSetId), } -/// A [`LogicalOperator`] defines how multiple conditions are compared to each other. +/// A [`LogicalOperator`] defines how multiple conditions are compared to each +/// other. #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] pub enum LogicalOperator { /// The condition is true if both operands are true. And, @@ -46,3 +49,15 @@ impl From for ConditionalRequirement { } } } + +impl From for ConditionalRequirement { + fn from(value: VersionSetId) -> Self { + Requirement::Single(value).into() + } +} + +impl From for ConditionalRequirement { + fn from(value: VersionSetUnionId) -> Self { + Requirement::Union(value).into() + } +} diff --git a/src/conflict.rs b/src/conflict.rs index 00e6a311..7e49daff 100644 --- a/src/conflict.rs +++ b/src/conflict.rs @@ -161,7 +161,7 @@ impl Conflict { ConflictEdge::Conflict(ConflictCause::Constrains(version_set_id)), ); } - Clause::AnyOf(selected, variable) => { + Clause::AnyOf(selected, _variable) => { // Assumption: since `AnyOf` of clause can never be false, we dont add an edge // for it. let decision_map = solver.state.decision_tracker.map(); diff --git a/src/requirement.rs b/src/requirement.rs index 5feab43f..8c5c9ec0 100644 --- a/src/requirement.rs +++ b/src/requirement.rs @@ -2,14 +2,13 @@ use std::fmt::Display; use itertools::Itertools; -use crate::{ - ConditionalRequirement, Interner, VersionSetId, VersionSetUnionId, -}; use crate::internal::id::ConditionId; +use crate::{ConditionalRequirement, Interner, VersionSetId, VersionSetUnionId}; /// Specifies the dependency of a solvable on a set of version sets. #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] pub enum Requirement { /// Specifies a dependency on a single version set. Single(VersionSetId), diff --git a/src/snapshot.rs b/src/snapshot.rs index 2f491dc6..882e5c1e 100644 --- a/src/snapshot.rs +++ b/src/snapshot.rs @@ -14,8 +14,12 @@ use std::{any::Any, collections::VecDeque, fmt::Display, time::SystemTime}; use ahash::HashSet; use futures::FutureExt; -use crate::{Candidates, Dependencies, DependencyProvider, HintDependenciesAvailable, Interner, Mapping, NameId, Requirement, SolvableId, SolverCache, StringId, VersionSetId, VersionSetUnionId, internal::arena::ArenaId, Condition}; -use crate::internal::id::ConditionId; +use crate::{ + Candidates, Condition, Dependencies, DependencyProvider, HintDependenciesAvailable, Interner, + Mapping, NameId, Requirement, SolvableId, SolverCache, StringId, VersionSetId, + VersionSetUnionId, + internal::{arena::ArenaId, id::ConditionId}, +}; /// A single solvable in a [`DependencySnapshot`]. #[derive(Clone, Debug)] @@ -109,6 +113,13 @@ pub struct DependencySnapshot { serde(default, skip_serializing_if = "Mapping::is_empty") )] pub strings: Mapping, + + /// All the conditions + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "Mapping::is_empty") + )] + pub conditions: Mapping, } impl DependencySnapshot { @@ -150,6 +161,7 @@ impl DependencySnapshot { VersionSet(VersionSetId), Package(NameId), String(StringId), + Condition(ConditionId), } let cache = SolverCache::new(provider); @@ -160,6 +172,7 @@ impl DependencySnapshot { version_sets: Mapping::new(), packages: Mapping::new(), strings: Mapping::new(), + conditions: Mapping::new(), }; let mut queue = names @@ -226,6 +239,11 @@ impl DependencySnapshot { } for requirement in deps.requirements.iter() { + if let Some(condition) = requirement.condition { + if seen.insert(Element::Condition(condition)) { + queue.push_back(Element::Condition(condition)) + } + } match requirement.requirement { Requirement::Single(version_set) => { if seen.insert(Element::VersionSet(version_set)) { @@ -296,6 +314,24 @@ impl DependencySnapshot { result.version_sets.insert(version_set_id, version_set); } + Element::Condition(condition_id) => { + let condition = cache.provider().resolve_condition(condition_id); + match condition { + Condition::Requirement(version_set) => { + if seen.insert(Element::VersionSet(version_set)) { + queue.push_back(Element::VersionSet(version_set)) + } + } + Condition::Binary(_, lhs, rhs) => { + for cond in [lhs, rhs] { + if seen.insert(Element::Condition(cond)) { + queue.push_back(Element::Condition(cond)) + } + } + } + } + result.conditions.insert(condition_id, condition); + } } } @@ -454,8 +490,12 @@ impl Interner for SnapshotProvider<'_> { .copied() } - fn resolve_condition(&self, _condition: ConditionId) -> Condition { - todo!() + fn resolve_condition(&self, condition: ConditionId) -> Condition { + self.snapshot + .conditions + .get(condition) + .expect("missing condition") + .clone() } } diff --git a/src/solver/clause.rs b/src/solver/clause.rs index fb00e56d..f93291f4 100644 --- a/src/solver/clause.rs +++ b/src/solver/clause.rs @@ -7,6 +7,7 @@ use std::{ use elsa::FrozenMap; +use crate::solver::conditions::Disjunction; use crate::{ Interner, NameId, Requirement, internal::{ @@ -14,8 +15,8 @@ use crate::{ id::{ClauseId, LearntClauseId, StringId, VersionSetId}, }, solver::{ - VariableId, conditions::DisjunctionId, - decision_map::DecisionMap, decision_tracker::DecisionTracker, variable_map::VariableMap, + VariableId, conditions::DisjunctionId, decision_map::DecisionMap, + decision_tracker::DecisionTracker, variable_map::VariableMap, }, }; @@ -267,7 +268,7 @@ impl Clause { Vec>, ahash::RandomState, >, - disjunction_to_candidates: &FrozenMap, ahash::RandomState>, + disjunction_to_candidates: &Arena, init: C, mut visit: F, ) -> ControlFlow @@ -286,7 +287,7 @@ impl Clause { .chain( disjunction .into_iter() - .flat_map(|d| disjunction_to_candidates[&d].iter()) + .flat_map(|d| disjunction_to_candidates[d].literals.iter()) .copied(), ) .chain( @@ -323,7 +324,7 @@ impl Clause { Vec>, ahash::RandomState, >, - disjunction_to_candidates: &FrozenMap, ahash::RandomState>, + disjunction_to_candidates: &Arena, mut visit: impl FnMut(Literal), ) { self.try_fold_literals( @@ -483,7 +484,7 @@ impl WatchedLiterals { Vec>, ahash::RandomState, >, - disjunction_to_candidates: &FrozenMap, ahash::RandomState>, + disjunction_to_candidates: &Arena, decision_map: &DecisionMap, for_watch_index: usize, ) -> Option { diff --git a/src/solver/conditions.rs b/src/solver/conditions.rs index c259084a..e131e719 100644 --- a/src/solver/conditions.rs +++ b/src/solver/conditions.rs @@ -14,11 +14,11 @@ //! The condition `C` however expands to another set of matching candidates //! (e.g. `C1 v C2 v C3`). If we insert that into the formula we get, //! -//! `¬A1 v ¬(C1 v C2 v C3) v B1 .. v B2` +//! `¬A1 v ¬(C1 v C2 v C3) v B1 .. v B2` //! //! which expands to //! -//! `¬A1 v ¬C1 ^ ¬C2 ^ ¬C3 v B1 .. v B2` +//! `¬A1 v ¬C1 ^ ¬C2 ^ ¬C3 v B1 .. v B2` //! //! This is not in CNF form (required for SAT clauses) so we cannot use this //! directly. To work around that problem we instead represent the condition @@ -27,7 +27,7 @@ //! `package <1`, or the candidates that do NOT match C. So if we represent the //! complement of C as C! the final form of clause becomes: //! -//! `¬A1 v C!1 .. v C!99 v B1 .. v B2` +//! `¬A1 v C!1 .. v C!99 v B1 .. v B2` //! //! This introduces another edge case though. What if the complement is empty? //! The final format would be void of `C!n` variables so it would become `¬A1 v @@ -36,12 +36,12 @@ //! C is selected (notated as `C_selected`). For each candidate of C we add the //! clause //! -//! `¬Cn v C_selected` +//! `¬Cn v C_selected` //! //! This forces `C_selected` to become true if any `Cn` is set to true. We then //! modify the requirement clause to be //! -//! `¬A1 v ¬C_selected v B1 .. v B2` +//! `¬A1 v ¬C_selected v B1 .. v B2` //! //! Note that we do not encode any clauses to force `C_selected` to be false. //! We argue that this state is not required to function properly. If @@ -60,6 +60,7 @@ use std::num::NonZero; +use crate::solver::clause::Literal; use crate::{ Condition, ConditionId, Interner, LogicalOperator, VersionSetId, internal::arena::ArenaId, }; @@ -81,8 +82,11 @@ impl ArenaId for DisjunctionId { } pub struct Disjunction { + /// The literals associated with this particular disjunction + pub literals: Vec, + /// The top-level condition to which this disjunction belongs. - pub condition: ConditionId, + pub _condition: ConditionId, } /// Converts from a boolean expression tree as described by `condition` to a diff --git a/src/solver/encoding.rs b/src/solver/encoding.rs index 57d4b5c7..436e56d6 100644 --- a/src/solver/encoding.rs +++ b/src/solver/encoding.rs @@ -331,12 +331,11 @@ impl<'a, 'cache, D: DependencyProvider> Encoder<'a, 'cache, D> { } } - let disjunction_id = self.state.disjunctions.alloc(Disjunction { condition }); - let literals = self - .state - .disjunction_to_candidates - .insert(disjunction_id, disjunction_literals); - conditions.push(Some((disjunction_id, literals))); + let disjunction_id = self.state.disjunctions.alloc(Disjunction { + literals: disjunction_literals, + _condition: condition, + }); + conditions.push(Some(disjunction_id)); } } else { conditions.push(None); @@ -345,11 +344,13 @@ impl<'a, 'cache, D: DependencyProvider> Encoder<'a, 'cache, D> { for condition in conditions { // Add the requirements clause let no_candidates = candidates.iter().all(|candidates| candidates.is_empty()); + let condition_literals = + condition.map(|id| self.state.disjunctions[id].literals.as_slice()); let (watched_literals, conflict, kind) = WatchedLiterals::requires( variable, requirement.requirement, version_set_variables.iter().flatten().copied(), - condition, + condition.zip(condition_literals), &self.state.decision_tracker, ); let clause_id = self.state.clauses.alloc(watched_literals, kind); @@ -367,11 +368,7 @@ impl<'a, 'cache, D: DependencyProvider> Encoder<'a, 'cache, D> { .requires_clauses .entry(variable) .or_default() - .push(( - requirement.requirement, - condition.map(|cond| cond.0), - clause_id, - )); + .push((requirement.requirement, condition, clause_id)); if conflict { self.conflicting_clauses.push(clause_id); @@ -565,7 +562,7 @@ impl<'a, 'cache, D: DependencyProvider> Encoder<'a, 'cache, D> { cache .get_or_cache_non_matching_candidates(version_set) .map_ok(move |matching_candidates| { - if matching_candidates.len() == 0 { + if matching_candidates.is_empty() { DisjunctionComplement::Empty(version_set) } else { DisjunctionComplement::Solvables( diff --git a/src/solver/mod.rs b/src/solver/mod.rs index f2771c8a..893d0c7c 100644 --- a/src/solver/mod.rs +++ b/src/solver/mod.rs @@ -159,21 +159,18 @@ pub struct Solver { activity_decay: f32, } +type RequiresClause = (Requirement, Option, ClauseId); + #[derive(Default)] pub(crate) struct SolverState { pub(crate) clauses: Clauses, - requires_clauses: IndexMap< - VariableId, - Vec<(Requirement, Option, ClauseId)>, - ahash::RandomState, - >, + requires_clauses: IndexMap, ahash::RandomState>, watches: WatchMap, /// A mapping from requirements to the variables that represent the /// candidates. requirement_to_sorted_candidates: FrozenMap, - disjunction_to_candidates: FrozenMap, ahash::RandomState>, pub(crate) variable_map: VariableMap, @@ -596,10 +593,11 @@ impl Solver { clause_id: ClauseId, ) -> Result { if starting_level == 0 { - tracing::trace!("Unsolvable: {}", self.state.clauses.kinds[clause_id.to_usize()].display( - &self.state.variable_map, - self.provider(), - )); + tracing::trace!( + "Unsolvable: {}", + self.state.clauses.kinds[clause_id.to_usize()] + .display(&self.state.variable_map, self.provider(),) + ); Err(UnsolvableOrCancelled::Unsolvable( self.analyze_unsolvable(clause_id), )) @@ -727,9 +725,9 @@ impl Solver { let mut candidate = ControlFlow::Break(()); // If the clause has a condition that is not yet satisfied we need to skip it. - if let Some(condition) = condition { - let candidates = &self.state.disjunction_to_candidates[condition]; - if !candidates.iter().all(|c| { + if let Some(condition) = *condition { + let literals = &self.state.disjunctions[condition].literals; + if !literals.iter().all(|c| { let value = c.eval(self.state.decision_tracker.map()); value == Some(false) }) { @@ -1101,7 +1099,7 @@ impl Solver { clause, &self.state.learnt_clauses, &self.state.requirement_to_sorted_candidates, - &self.state.disjunction_to_candidates, + &self.state.disjunctions, self.state.decision_tracker.map(), watch_index, ) { @@ -1264,7 +1262,7 @@ impl Solver { self.state.clauses.kinds[clause_id.to_usize()].visit_literals( &self.state.learnt_clauses, &self.state.requirement_to_sorted_candidates, - &self.state.disjunction_to_candidates, + &self.state.disjunctions, |literal| { involved.insert(literal.variable()); }, @@ -1303,7 +1301,7 @@ impl Solver { self.state.clauses.kinds[why.to_usize()].visit_literals( &self.state.learnt_clauses, &self.state.requirement_to_sorted_candidates, - &self.state.disjunction_to_candidates, + &self.state.disjunctions, |literal| { if literal.eval(self.state.decision_tracker.map()) == Some(true) { assert_eq!(literal.variable(), decision.variable); @@ -1351,7 +1349,7 @@ impl Solver { clause_kinds[clause_id.to_usize()].visit_literals( &self.state.learnt_clauses, &self.state.requirement_to_sorted_candidates, - &self.state.disjunction_to_candidates, + &self.state.disjunctions, |literal| { if !first_iteration && literal.variable() == conflicting_solvable { // We are only interested in the causes of the conflict, so we ignore the diff --git a/tests/solver/bundle_box/conditional_spec.rs b/tests/solver/bundle_box/conditional_spec.rs new file mode 100644 index 00000000..00b1e00d --- /dev/null +++ b/tests/solver/bundle_box/conditional_spec.rs @@ -0,0 +1,21 @@ +use super::Spec; +use chumsky::{Parser, error}; +use resolvo::LogicalOperator; + +#[derive(Debug, Clone)] +pub struct ConditionalSpec { + pub condition: Option, + pub specs: Vec, +} + +impl ConditionalSpec { + pub fn from_str(s: &str) -> Result>> { + super::parser::conditional_spec().parse(s).into_result() + } +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum SpecCondition { + Binary(LogicalOperator, Box<[SpecCondition; 2]>), + Requirement(Spec), +} diff --git a/tests/solver/bundle_box/mod.rs b/tests/solver/bundle_box/mod.rs new file mode 100644 index 00000000..33ccd6b4 --- /dev/null +++ b/tests/solver/bundle_box/mod.rs @@ -0,0 +1,431 @@ +// Let's define our own packaging version system and dependency specification. +// This is a very simple version system, where a package is identified by a name +// and a version in which the version is just an integer. The version is a range +// so can be noted as 0..2 or something of the sorts, we also support constrains +// which means it should not use that package version this is also represented +// with a range. +// +// You can also use just a single number for a range like `package 0` which +// means the range from 0..1 (excluding the end) +// +// Lets call the tuples of (Name, Version) a `Pack` and the tuples of (Name, +// Ranges) a `Spec` +// +// We also need to create a custom provider that tells us how to sort the +// candidates. This is unique to each packaging ecosystem. Let's call our +// ecosystem 'BundleBox' so that how we call the provider as well. + +mod conditional_spec; +mod pack; +pub mod parser; +mod spec; + +use std::{ + any::Any, + cell::{Cell, RefCell}, + collections::HashSet, + fmt::Display, + rc::Rc, + sync::{ + Arc, + atomic::{AtomicUsize, Ordering}, + }, + time::Duration, +}; + +use ahash::HashMap; +pub use conditional_spec::{ConditionalSpec, SpecCondition}; +use indexmap::IndexMap; +use itertools::Itertools; +pub use pack::Pack; +use resolvo::{ + Candidates, Condition, ConditionId, ConditionalRequirement, Dependencies, DependencyProvider, + Interner, KnownDependencies, NameId, SolvableId, SolverCache, StringId, VersionSetId, + VersionSetUnionId, snapshot::DependencySnapshot, utils::Pool, +}; +pub use spec::Spec; +use version_ranges::Ranges; + +/// This provides sorting functionality for our `BundleBox` packaging system +#[derive(Default)] +pub struct BundleBoxProvider { + pub pool: Pool>, + id_to_condition: Vec, + conditions: HashMap, + packages: IndexMap>, + favored: HashMap, + locked: HashMap, + excluded: HashMap>, + cancel_solving: Cell, + // TODO: simplify? + concurrent_requests: Arc, + pub concurrent_requests_max: Rc>, + pub sleep_before_return: bool, + + // A mapping of packages that we have requested candidates for. This way we can keep track of + // duplicate requests. + requested_candidates: RefCell>, + requested_dependencies: RefCell>, + interned_solvables: RefCell>, +} + +#[derive(Debug, Clone)] +struct BundleBoxPackageDependencies { + dependencies: Vec, + constrains: Vec, +} + +impl BundleBoxProvider { + pub fn new() -> Self { + Default::default() + } + + pub fn package_name(&self, name: &str) -> NameId { + self.pool + .lookup_package_name(&name.to_string()) + .expect("package missing") + } + + pub fn intern_condition(&mut self, condition: &SpecCondition) -> ConditionId { + if let Some(id) = self.conditions.get(&condition) { + return *id; + } + + if let SpecCondition::Binary(_op, sides) = condition { + self.intern_condition(&sides[0]); + self.intern_condition(&sides[1]); + } + + let id = ConditionId::new(self.id_to_condition.len() as u32); + self.id_to_condition.push(condition.clone()); + self.conditions.insert(condition.clone(), id); + id + } + + pub fn requirements(&mut self, requirements: &[&str]) -> Vec { + requirements + .iter() + .map(|dep| ConditionalSpec::from_str(*dep).unwrap()) + .map(|spec| { + let mut iter = spec + .specs + .into_iter() + .map(|spec| self.intern_version_set(&spec)) + .peekable(); + let first = iter.next().unwrap(); + let requirement = if iter.peek().is_some() { + self.pool.intern_version_set_union(first, iter).into() + } else { + first.into() + }; + + let condition = spec.condition.map(|c| self.intern_condition(&c)); + + ConditionalRequirement { + condition, + requirement, + } + }) + .collect() + } + + pub fn version_sets(&mut self, requirements: &[&str]) -> Vec { + requirements + .iter() + .map(|dep| Spec::from_str(*dep).unwrap()) + .map(|spec| { + let name = self.pool.intern_package_name(&spec.name); + self.pool.intern_version_set(name, spec.versions) + }) + .collect() + } + + pub fn intern_version_set(&self, spec: &Spec) -> VersionSetId { + let dep_name = self.pool.intern_package_name(&spec.name); + self.pool + .intern_version_set(dep_name, spec.versions.clone()) + } + + pub fn from_packages(packages: &[(&str, u32, Vec<&str>)]) -> Self { + let mut result = Self::new(); + for (name, version, deps) in packages { + result.add_package(name, Pack::new(*version), deps, &[]); + } + result + } + + pub fn set_favored(&mut self, package_name: &str, version: u32) { + self.favored + .insert(package_name.to_owned(), Pack::new(version)); + } + + pub fn exclude(&mut self, package_name: &str, version: u32, reason: impl Into) { + self.excluded + .entry(package_name.to_owned()) + .or_default() + .insert(Pack::new(version), reason.into()); + } + + pub fn set_locked(&mut self, package_name: &str, version: u32) { + self.locked + .insert(package_name.to_owned(), Pack::new(version)); + } + + pub fn add_package( + &mut self, + package_name: &str, + package_version: Pack, + dependencies: &[&str], + constrains: &[&str], + ) { + self.pool.intern_package_name(package_name); + + let dependencies = self.requirements(dependencies); + + let constrains = constrains + .iter() + .map(|dep| Spec::from_str(dep)) + .collect::, _>>() + .unwrap(); + + self.packages + .entry(package_name.to_owned()) + .or_default() + .insert( + package_version, + BundleBoxPackageDependencies { + dependencies, + constrains, + }, + ); + } + + // Sends a value from the dependency provider to the solver, introducing a + // minimal delay to force concurrency to be used (unless there is no async + // runtime available) + async fn maybe_delay(&self, value: T) -> T { + if self.sleep_before_return { + tokio::time::sleep(Duration::from_millis(10)).await; + self.concurrent_requests.fetch_sub(1, Ordering::SeqCst); + value + } else { + value + } + } + + pub fn into_snapshot(self) -> DependencySnapshot { + let name_ids = self + .packages + .keys() + .filter_map(|name| self.pool.lookup_package_name(name)) + .collect::>(); + DependencySnapshot::from_provider(self, name_ids, [], []).unwrap() + } + + pub fn intern_solvable(&self, name_id: NameId, pack: Pack) -> SolvableId { + *self + .interned_solvables + .borrow_mut() + .entry((name_id, pack)) + .or_insert_with_key(|&(name_id, pack)| self.pool.intern_solvable(name_id, pack)) + } + + pub fn solvable_id(&self, name: impl Into, version: impl Into) -> SolvableId { + self.intern_solvable(self.pool.intern_package_name(name.into()), version.into()) + } +} + +impl Interner for BundleBoxProvider { + fn display_solvable(&self, solvable: SolvableId) -> impl Display + '_ { + let solvable = self.pool.resolve_solvable(solvable); + format!("{}={}", self.display_name(solvable.name), solvable.record) + } + + fn display_merged_solvables(&self, solvables: &[SolvableId]) -> impl Display + '_ { + if solvables.is_empty() { + return "".to_string(); + } + + let name = self.display_name(self.pool.resolve_solvable(solvables[0]).name); + let versions = solvables + .iter() + .map(|&s| self.pool.resolve_solvable(s).record.version) + .sorted(); + format!("{name} {}", versions.format(" | ")) + } + + fn display_name(&self, name: NameId) -> impl Display + '_ { + self.pool.resolve_package_name(name).clone() + } + + fn display_version_set(&self, version_set: VersionSetId) -> impl Display + '_ { + self.pool.resolve_version_set(version_set).clone() + } + + fn display_string(&self, string_id: StringId) -> impl Display + '_ { + self.pool.resolve_string(string_id).to_owned() + } + + fn version_set_name(&self, version_set: VersionSetId) -> NameId { + self.pool.resolve_version_set_package_name(version_set) + } + + fn solvable_name(&self, solvable: SolvableId) -> NameId { + self.pool.resolve_solvable(solvable).name + } + fn version_sets_in_union( + &self, + version_set_union: VersionSetUnionId, + ) -> impl Iterator { + self.pool.resolve_version_set_union(version_set_union) + } + + fn resolve_condition(&self, condition: ConditionId) -> Condition { + let condition = condition.as_u32(); + let condition = &self.id_to_condition[condition as usize]; + match condition { + SpecCondition::Binary(op, items) => Condition::Binary( + *op, + *self.conditions.get(&items[0]).unwrap(), + *self.conditions.get(&items[1]).unwrap(), + ), + SpecCondition::Requirement(requirement) => { + Condition::Requirement(self.intern_version_set(requirement)) + } + } + } +} + +impl DependencyProvider for BundleBoxProvider { + async fn filter_candidates( + &self, + candidates: &[SolvableId], + version_set: VersionSetId, + inverse: bool, + ) -> Vec { + let range = self.pool.resolve_version_set(version_set); + candidates + .iter() + .copied() + .filter(|s| range.contains(&self.pool.resolve_solvable(*s).record) == !inverse) + .collect() + } + + async fn sort_candidates(&self, _solver: &SolverCache, solvables: &mut [SolvableId]) { + solvables.sort_by(|a, b| { + let a = self.pool.resolve_solvable(*a).record; + let b = self.pool.resolve_solvable(*b).record; + // We want to sort with highest version on top + b.version.cmp(&a.version) + }); + } + + async fn get_candidates(&self, name: NameId) -> Option { + let concurrent_requests = self.concurrent_requests.fetch_add(1, Ordering::SeqCst); + self.concurrent_requests_max.set( + self.concurrent_requests_max + .get() + .max(concurrent_requests + 1), + ); + + assert!( + self.requested_candidates.borrow_mut().insert(name), + "duplicate get_candidates request" + ); + + let package_name = self.pool.resolve_package_name(name); + let Some(package) = self.packages.get(package_name) else { + return self.maybe_delay(None).await; + }; + + let mut candidates = Candidates { + candidates: Vec::with_capacity(package.len()), + ..Candidates::default() + }; + let favor = self.favored.get(package_name); + let locked = self.locked.get(package_name); + let excluded = self.excluded.get(package_name); + for pack in package.keys() { + let solvable = self.intern_solvable(name, *pack); + candidates.candidates.push(solvable); + if Some(pack) == favor { + candidates.favored = Some(solvable); + } + if Some(pack) == locked { + candidates.locked = Some(solvable); + } + if let Some(excluded) = excluded.and_then(|d| d.get(pack)) { + candidates + .excluded + .push((solvable, self.pool.intern_string(excluded))); + } + } + + self.maybe_delay(Some(candidates)).await + } + + async fn get_dependencies(&self, solvable: SolvableId) -> Dependencies { + tracing::info!( + "get dependencies for {}", + self.pool + .resolve_solvable(solvable) + .name + .display(&self.pool) + ); + + let concurrent_requests = self.concurrent_requests.fetch_add(1, Ordering::SeqCst); + self.concurrent_requests_max.set( + self.concurrent_requests_max + .get() + .max(concurrent_requests + 1), + ); + + assert!( + self.requested_dependencies.borrow_mut().insert(solvable), + "duplicate get_dependencies request" + ); + + let candidate = self.pool.resolve_solvable(solvable); + let package_name = self.pool.resolve_package_name(candidate.name); + let pack = candidate.record; + + if pack.cancel_during_get_dependencies { + self.cancel_solving.set(true); + let reason = self.pool.intern_string("cancelled"); + return self.maybe_delay(Dependencies::Unknown(reason)).await; + } + + if pack.unknown_deps { + let reason = self.pool.intern_string("could not retrieve deps"); + return self.maybe_delay(Dependencies::Unknown(reason)).await; + } + + let Some(deps) = self.packages.get(package_name).and_then(|v| v.get(&pack)) else { + return self + .maybe_delay(Dependencies::Known(Default::default())) + .await; + }; + + let mut result = KnownDependencies { + requirements: Vec::with_capacity(deps.dependencies.len()), + constrains: Vec::with_capacity(deps.constrains.len()), + }; + result.requirements = deps.dependencies.clone(); + + for req in &deps.constrains { + let dep_name = self.pool.intern_package_name(&req.name); + let dep_spec = self.pool.intern_version_set(dep_name, req.versions.clone()); + result.constrains.push(dep_spec); + } + + self.maybe_delay(Dependencies::Known(result)).await + } + + fn should_cancel_with_value(&self) -> Option> { + if self.cancel_solving.get() { + Some(Box::new("cancelled!".to_string())) + } else { + None + } + } +} diff --git a/tests/solver/bundle_box/pack.rs b/tests/solver/bundle_box/pack.rs new file mode 100644 index 00000000..1cf3c873 --- /dev/null +++ b/tests/solver/bundle_box/pack.rs @@ -0,0 +1,57 @@ +use std::fmt::{Display, Formatter}; +use std::str::FromStr; + +/// This is `Pack` which is a unique version and name in our bespoke packaging +/// system +#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Copy, Clone, Hash)] +pub struct Pack { + pub version: u32, + pub unknown_deps: bool, + pub cancel_during_get_dependencies: bool, +} + +impl Pack { + pub fn new(version: u32) -> Pack { + Pack { + version, + unknown_deps: false, + cancel_during_get_dependencies: false, + } + } + + pub fn with_unknown_deps(mut self) -> Pack { + self.unknown_deps = true; + self + } + + pub fn cancel_during_get_dependencies(mut self) -> Pack { + self.cancel_during_get_dependencies = true; + self + } +} + +impl From for Pack { + fn from(value: u32) -> Self { + Pack::new(value) + } +} + +impl From for Pack { + fn from(value: i32) -> Self { + Pack::new(value as u32) + } +} + +impl Display for Pack { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.version) + } +} + +impl FromStr for Pack { + type Err = std::num::ParseIntError; + + fn from_str(s: &str) -> Result { + u32::from_str(s).map(Pack::new) + } +} diff --git a/tests/solver/bundle_box/parser.rs b/tests/solver/bundle_box/parser.rs new file mode 100644 index 00000000..6e3d6985 --- /dev/null +++ b/tests/solver/bundle_box/parser.rs @@ -0,0 +1,119 @@ +use super::{ConditionalSpec, Pack, Spec, SpecCondition}; +use chumsky::{ + IterParser, Parser, error, + error::LabelError, + extra, + extra::ParserExtra, + input::{SliceInput, StrInput}, + pratt::{infix, left}, + prelude::{any, just}, + text, + text::{Char, TextExpected}, + util::MaybeRef, +}; +use resolvo::LogicalOperator; +use version_ranges::Ranges; + +/// Parses a package name identifier. +pub fn name<'src, I, E>() -> impl Parser<'src, I, >::Slice, E> + Copy +where + I: StrInput<'src>, + I::Token: Char + 'src, + E: ParserExtra<'src, I>, + E::Error: LabelError<'src, I, TextExpected<'src, I>>, +{ + any() + .try_map(|c: I::Token, span| { + if c.to_ascii() + .map(|i| i.is_ascii_alphabetic() || i == b'_') + .unwrap_or(false) + { + Ok(c) + } else { + Err(LabelError::expected_found( + [TextExpected::IdentifierPart], + Some(MaybeRef::Val(c)), + span, + )) + } + }) + .then( + any() + .try_map(|c: I::Token, span| { + if c.to_ascii().map_or(false, |i| { + i.is_ascii_alphanumeric() || i == b'_' || i == b'-' + }) { + Ok(()) + } else { + Err(LabelError::expected_found( + [TextExpected::IdentifierPart], + Some(MaybeRef::Val(c)), + span, + )) + } + }) + .repeated(), + ) + .to_slice() +} + +/// Parses a range of package versions. E.g. `5` or `1..5`. +fn ranges<'src>() +-> impl Parser<'src, &'src str, Ranges, extra::Err>> { + text::int(10) + .map(|s: &str| s.parse().unwrap()) + .then( + just("..") + .padded() + .ignore_then(text::int(10).map(|s: &str| s.parse().unwrap()).padded()) + .or_not(), + ) + .map(|(left, right)| { + let right = Pack::new(right.unwrap_or_else(|| left + 1)); + Ranges::between(Pack::new(left), right) + }) +} + +/// Parses a single [`Spec`]. E.g. `foo 1..2` or `bar 3` or `baz`. +pub(crate) fn spec<'src>() +-> impl Parser<'src, &'src str, Spec, extra::Err>> { + name() + .padded() + .then(ranges().or_not()) + .map(|(name, range)| Spec::new(name.to_string(), range.unwrap_or(Ranges::full()))) +} + +fn condition<'src>() +-> impl Parser<'src, &'src str, SpecCondition, extra::Err>> { + let and = just("and").padded().map(|_| LogicalOperator::And); + let or = just("or").padded().map(|_| LogicalOperator::Or); + + let single = spec().map(SpecCondition::Requirement); + + single.pratt(( + infix(left(1), and, |lhs, op, rhs, _| { + SpecCondition::Binary(op, Box::new([lhs, rhs])) + }), + infix(left(1), or, |lhs, op, rhs, _| { + SpecCondition::Binary(op, Box::new([lhs, rhs])) + }), + )) +} + +pub(crate) fn union_spec<'src>() +-> impl Parser<'src, &'src str, Vec, extra::Err>> { + spec() + .separated_by(just("|").padded()) + .at_least(1) + .collect() +} + +pub(crate) fn conditional_spec<'src>() +-> impl Parser<'src, &'src str, ConditionalSpec, extra::Err>> { + union_spec() + .then(just("; if").padded().ignore_then(condition()).or_not()) + .map(|(spec, condition)| ConditionalSpec { + condition, + specs: spec, + }) +} diff --git a/tests/solver/bundle_box/spec.rs b/tests/solver/bundle_box/spec.rs new file mode 100644 index 00000000..cf22ed27 --- /dev/null +++ b/tests/solver/bundle_box/spec.rs @@ -0,0 +1,23 @@ +use super::Pack; +use chumsky::{Parser, error}; +use version_ranges::Ranges; + +/// We can use this to see if a `Pack` is contained in a range of package +/// versions or a `Spec` +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Spec { + pub name: String, + pub versions: Ranges, +} + +impl Spec { + pub fn new(name: String, versions: Ranges) -> Self { + Self { name, versions } + } +} + +impl Spec { + pub fn from_str(s: &str) -> Result>> { + super::parser::spec().parse(s).into_result() + } +} diff --git a/tests/solver.rs b/tests/solver/main.rs similarity index 57% rename from tests/solver.rs rename to tests/solver/main.rs index 6406f087..20222057 100644 --- a/tests/solver.rs +++ b/tests/solver/main.rs @@ -1,655 +1,15 @@ -use std::{ - any::Any, - cell::{Cell, RefCell}, - collections::HashSet, - fmt::{Debug, Display, Formatter}, - io::{Write, stderr}, - num::ParseIntError, - rc::Rc, - str::FromStr, - sync::{ - Arc, - atomic::{AtomicUsize, Ordering}, - }, - time::Duration, -}; +mod bundle_box; + +use std::io::{Write, stderr}; -use ahash::HashMap; -use chumsky::{Parser, error}; -use indexmap::IndexMap; +use bundle_box::{BundleBoxProvider, Pack}; use insta::assert_snapshot; use itertools::Itertools; use resolvo::{ - Candidates, Condition, ConditionId, ConditionalRequirement, Dependencies, DependencyProvider, - Interner, KnownDependencies, LogicalOperator, NameId, Problem, SolvableId, Solver, SolverCache, - StringId, UnsolvableOrCancelled, VersionSetId, VersionSetUnionId, - snapshot::{DependencySnapshot, SnapshotProvider}, - utils::Pool, + ConditionalRequirement, DependencyProvider, Interner, Problem, SolvableId, Solver, + UnsolvableOrCancelled, VersionSetId, snapshot::DependencySnapshot, }; use tracing_test::traced_test; -use version_ranges::Ranges; - -// Let's define our own packaging version system and dependency specification. -// This is a very simple version system, where a package is identified by a name -// and a version in which the version is just an integer. The version is a range -// so can be noted as 0..2 or something of the sorts, we also support constrains -// which means it should not use that package version this is also represented -// with a range. -// -// You can also use just a single number for a range like `package 0` which -// means the range from 0..1 (excluding the end) -// -// Lets call the tuples of (Name, Version) a `Pack` and the tuples of (Name, -// Ranges) a `Spec` -// -// We also need to create a custom provider that tells us how to sort the -// candidates. This is unique to each packaging ecosystem. Let's call our -// ecosystem 'BundleBox' so that how we call the provider as well. - -/// This is `Pack` which is a unique version and name in our bespoke packaging -/// system -#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Copy, Clone, Hash)] -struct Pack { - version: u32, - unknown_deps: bool, - cancel_during_get_dependencies: bool, -} - -impl Pack { - fn new(version: u32) -> Pack { - Pack { - version, - unknown_deps: false, - cancel_during_get_dependencies: false, - } - } - - fn with_unknown_deps(mut self) -> Pack { - self.unknown_deps = true; - self - } - - fn cancel_during_get_dependencies(mut self) -> Pack { - self.cancel_during_get_dependencies = true; - self - } - - fn offset(&self, version_offset: i32) -> Pack { - let mut pack = *self; - pack.version = pack.version.wrapping_add_signed(version_offset); - pack - } -} - -impl From for Pack { - fn from(value: u32) -> Self { - Pack::new(value) - } -} - -impl From for Pack { - fn from(value: i32) -> Self { - Pack::new(value as u32) - } -} - -impl Display for Pack { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.version) - } -} - -impl FromStr for Pack { - type Err = ParseIntError; - - fn from_str(s: &str) -> Result { - u32::from_str(s).map(Pack::new) - } -} - -/// We can use this to see if a `Pack` is contained in a range of package -/// versions or a `Spec` -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -struct Spec { - name: String, - versions: Ranges, -} - -impl Spec { - pub fn new(name: String, versions: Ranges) -> Self { - Self { name, versions } - } -} - -impl Spec { - fn from_str(s: &str) -> Result>> { - parser::spec().parse(s).into_result() - } -} - -#[derive(Debug, Clone)] -struct ConditionalSpec { - condition: Option, - specs: Vec, -} - -impl ConditionalSpec { - fn from_str(s: &str) -> Result>> { - parser::conditional_spec().parse(s).into_result() - } -} - -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -enum SpecCondition { - Binary(LogicalOperator, Box<[SpecCondition; 2]>), - Requirement(Spec), -} - -mod parser { - use chumsky::{ - error, - error::LabelError, - extra::ParserExtra, - input::{SliceInput, StrInput}, - pratt::*, - prelude::*, - text, - text::{Char, TextExpected}, - util::MaybeRef, - }; - use resolvo::LogicalOperator; - use version_ranges::Ranges; - - use super::{ConditionalSpec, Pack, Spec, SpecCondition}; - - /// Parses a package name identifier. - pub fn name<'src, I, E>() -> impl Parser<'src, I, >::Slice, E> + Copy - where - I: StrInput<'src>, - I::Token: Char + 'src, - E: ParserExtra<'src, I>, - E::Error: LabelError<'src, I, TextExpected<'src, I>>, - { - any() - .try_map(|c: I::Token, span| { - if c.to_ascii() - .map(|i| i.is_ascii_alphabetic() || i == b'_') - .unwrap_or(false) - { - Ok(c) - } else { - Err(LabelError::expected_found( - [TextExpected::IdentifierPart], - Some(MaybeRef::Val(c)), - span, - )) - } - }) - .then( - any() - .try_map(|c: I::Token, span| { - if c.to_ascii().map_or(false, |i| { - i.is_ascii_alphanumeric() || i == b'_' || i == b'-' - }) { - Ok(()) - } else { - Err(LabelError::expected_found( - [TextExpected::IdentifierPart], - Some(MaybeRef::Val(c)), - span, - )) - } - }) - .repeated(), - ) - .to_slice() - } - - /// Parses a range of package versions. E.g. `5` or `1..5`. - fn ranges<'src>() - -> impl Parser<'src, &'src str, Ranges, extra::Err>> { - text::int(10) - .map(|s: &str| s.parse().unwrap()) - .then( - just("..") - .padded() - .ignore_then(text::int(10).map(|s: &str| s.parse().unwrap()).padded()) - .or_not(), - ) - .map(|(left, right)| { - let right = Pack::new(right.unwrap_or_else(|| left + 1)); - Ranges::between(Pack::new(left), right) - }) - } - - /// Parses a single [`Spec`]. E.g. `foo 1..2` or `bar 3` or `baz`. - pub(crate) fn spec<'src>() - -> impl Parser<'src, &'src str, Spec, extra::Err>> { - name() - .padded() - .then(ranges().or_not()) - .map(|(name, range)| Spec::new(name.to_string(), range.unwrap_or(Ranges::full()))) - } - - fn condition<'src>() - -> impl Parser<'src, &'src str, SpecCondition, extra::Err>> { - let and = just("and").padded().map(|_| LogicalOperator::And); - let or = just("or").padded().map(|_| LogicalOperator::Or); - - let single = spec().map(SpecCondition::Requirement); - - single.pratt(( - infix(left(1), and, |lhs, op, rhs, _| { - SpecCondition::Binary(op, Box::new([lhs, rhs])) - }), - infix(left(1), or, |lhs, op, rhs, _| { - SpecCondition::Binary(op, Box::new([lhs, rhs])) - }), - )) - } - - pub(crate) fn union_spec<'src>() - -> impl Parser<'src, &'src str, Vec, extra::Err>> { - spec() - .separated_by(just("|").padded()) - .at_least(1) - .collect() - } - - pub(crate) fn conditional_spec<'src>() - -> impl Parser<'src, &'src str, ConditionalSpec, extra::Err>> { - union_spec() - .then(just("; if").padded().ignore_then(condition()).or_not()) - .map(|(spec, condition)| ConditionalSpec { - condition, - specs: spec, - }) - } -} - -/// This provides sorting functionality for our `BundleBox` packaging system -#[derive(Default)] -struct BundleBoxProvider { - pool: Pool>, - id_to_condition: Vec, - conditions: HashMap, - packages: IndexMap>, - favored: HashMap, - locked: HashMap, - excluded: HashMap>, - cancel_solving: Cell, - // TODO: simplify? - concurrent_requests: Arc, - concurrent_requests_max: Rc>, - sleep_before_return: bool, - - // A mapping of packages that we have requested candidates for. This way we can keep track of - // duplicate requests. - requested_candidates: RefCell>, - requested_dependencies: RefCell>, - interned_solvables: RefCell>, -} - -#[derive(Debug, Clone)] -struct BundleBoxPackageDependencies { - dependencies: Vec, - constrains: Vec, -} - -impl BundleBoxProvider { - pub fn new() -> Self { - Default::default() - } - - pub fn package_name(&self, name: &str) -> NameId { - self.pool - .lookup_package_name(&name.to_string()) - .expect("package missing") - } - - pub fn intern_condition(&mut self, condition: &SpecCondition) -> ConditionId { - if let Some(id) = self.conditions.get(&condition) { - return *id; - } - - if let SpecCondition::Binary(_op, sides) = condition { - self.intern_condition(&sides[0]); - self.intern_condition(&sides[1]); - } - - let id = ConditionId::new(self.id_to_condition.len() as u32); - self.id_to_condition.push(condition.clone()); - self.conditions.insert(condition.clone(), id); - id - } - - pub fn requirements(&mut self, requirements: &[&str]) -> Vec { - requirements - .iter() - .map(|dep| ConditionalSpec::from_str(*dep).unwrap()) - .map(|spec| { - let mut iter = spec - .specs - .into_iter() - .map(|spec| self.intern_version_set(&spec)) - .peekable(); - let first = iter.next().unwrap(); - let requirement = if iter.peek().is_some() { - self.pool.intern_version_set_union(first, iter).into() - } else { - first.into() - }; - - let condition = spec.condition.map(|c| self.intern_condition(&c)); - - ConditionalRequirement { - condition, - requirement, - } - }) - .collect() - } - - pub fn version_sets(&mut self, requirements: &[&str]) -> Vec { - requirements - .iter() - .map(|dep| Spec::from_str(*dep).unwrap()) - .map(|spec| { - let name = self.pool.intern_package_name(&spec.name); - self.pool.intern_version_set(name, spec.versions) - }) - .collect() - } - - pub fn intern_version_set(&self, spec: &Spec) -> VersionSetId { - let dep_name = self.pool.intern_package_name(&spec.name); - self.pool - .intern_version_set(dep_name, spec.versions.clone()) - } - - pub fn from_packages(packages: &[(&str, u32, Vec<&str>)]) -> Self { - let mut result = Self::new(); - for (name, version, deps) in packages { - result.add_package(name, Pack::new(*version), deps, &[]); - } - result - } - - pub fn set_favored(&mut self, package_name: &str, version: u32) { - self.favored - .insert(package_name.to_owned(), Pack::new(version)); - } - - pub fn exclude(&mut self, package_name: &str, version: u32, reason: impl Into) { - self.excluded - .entry(package_name.to_owned()) - .or_default() - .insert(Pack::new(version), reason.into()); - } - - pub fn set_locked(&mut self, package_name: &str, version: u32) { - self.locked - .insert(package_name.to_owned(), Pack::new(version)); - } - - pub fn add_package( - &mut self, - package_name: &str, - package_version: Pack, - dependencies: &[&str], - constrains: &[&str], - ) { - self.pool.intern_package_name(package_name); - - let dependencies = self.requirements(dependencies); - - let constrains = constrains - .iter() - .map(|dep| Spec::from_str(dep)) - .collect::, _>>() - .unwrap(); - - self.packages - .entry(package_name.to_owned()) - .or_default() - .insert( - package_version, - BundleBoxPackageDependencies { - dependencies, - constrains, - }, - ); - } - - // Sends a value from the dependency provider to the solver, introducing a - // minimal delay to force concurrency to be used (unless there is no async - // runtime available) - async fn maybe_delay(&self, value: T) -> T { - if self.sleep_before_return { - tokio::time::sleep(Duration::from_millis(10)).await; - self.concurrent_requests.fetch_sub(1, Ordering::SeqCst); - value - } else { - value - } - } - - pub fn into_snapshot(self) -> DependencySnapshot { - let name_ids = self - .packages - .keys() - .filter_map(|name| self.pool.lookup_package_name(name)) - .collect::>(); - DependencySnapshot::from_provider(self, name_ids, [], []).unwrap() - } - - pub fn intern_solvable(&self, name_id: NameId, pack: Pack) -> SolvableId { - *self - .interned_solvables - .borrow_mut() - .entry((name_id, pack)) - .or_insert_with_key(|&(name_id, pack)| self.pool.intern_solvable(name_id, pack)) - } - - pub fn solvable_id(&self, name: impl Into, version: impl Into) -> SolvableId { - self.intern_solvable(self.pool.intern_package_name(name.into()), version.into()) - } -} - -impl Interner for BundleBoxProvider { - fn display_solvable(&self, solvable: SolvableId) -> impl Display + '_ { - let solvable = self.pool.resolve_solvable(solvable); - format!("{}={}", self.display_name(solvable.name), solvable.record) - } - - fn display_merged_solvables(&self, solvables: &[SolvableId]) -> impl Display + '_ { - if solvables.is_empty() { - return "".to_string(); - } - - let name = self.display_name(self.pool.resolve_solvable(solvables[0]).name); - let versions = solvables - .iter() - .map(|&s| self.pool.resolve_solvable(s).record.version) - .sorted(); - format!("{name} {}", versions.format(" | ")) - } - - fn display_name(&self, name: NameId) -> impl Display + '_ { - self.pool.resolve_package_name(name).clone() - } - - fn display_version_set(&self, version_set: VersionSetId) -> impl Display + '_ { - self.pool.resolve_version_set(version_set).clone() - } - - fn display_string(&self, string_id: StringId) -> impl Display + '_ { - self.pool.resolve_string(string_id).to_owned() - } - - fn version_set_name(&self, version_set: VersionSetId) -> NameId { - self.pool.resolve_version_set_package_name(version_set) - } - - fn solvable_name(&self, solvable: SolvableId) -> NameId { - self.pool.resolve_solvable(solvable).name - } - fn version_sets_in_union( - &self, - version_set_union: VersionSetUnionId, - ) -> impl Iterator { - self.pool.resolve_version_set_union(version_set_union) - } - - fn resolve_condition(&self, condition: ConditionId) -> Condition { - let condition = condition.as_u32(); - let condition = &self.id_to_condition[condition as usize]; - match condition { - SpecCondition::Binary(op, items) => Condition::Binary( - *op, - *self.conditions.get(&items[0]).unwrap(), - *self.conditions.get(&items[1]).unwrap(), - ), - SpecCondition::Requirement(requirement) => { - Condition::Requirement(self.intern_version_set(requirement)) - } - } - } -} - -impl DependencyProvider for BundleBoxProvider { - async fn filter_candidates( - &self, - candidates: &[SolvableId], - version_set: VersionSetId, - inverse: bool, - ) -> Vec { - let range = self.pool.resolve_version_set(version_set); - candidates - .iter() - .copied() - .filter(|s| range.contains(&self.pool.resolve_solvable(*s).record) == !inverse) - .collect() - } - - async fn sort_candidates(&self, _solver: &SolverCache, solvables: &mut [SolvableId]) { - solvables.sort_by(|a, b| { - let a = self.pool.resolve_solvable(*a).record; - let b = self.pool.resolve_solvable(*b).record; - // We want to sort with highest version on top - b.version.cmp(&a.version) - }); - } - - async fn get_candidates(&self, name: NameId) -> Option { - let concurrent_requests = self.concurrent_requests.fetch_add(1, Ordering::SeqCst); - self.concurrent_requests_max.set( - self.concurrent_requests_max - .get() - .max(concurrent_requests + 1), - ); - - assert!( - self.requested_candidates.borrow_mut().insert(name), - "duplicate get_candidates request" - ); - - let package_name = self.pool.resolve_package_name(name); - let Some(package) = self.packages.get(package_name) else { - return self.maybe_delay(None).await; - }; - - let mut candidates = Candidates { - candidates: Vec::with_capacity(package.len()), - ..Candidates::default() - }; - let favor = self.favored.get(package_name); - let locked = self.locked.get(package_name); - let excluded = self.excluded.get(package_name); - for pack in package.keys() { - let solvable = self.intern_solvable(name, *pack); - candidates.candidates.push(solvable); - if Some(pack) == favor { - candidates.favored = Some(solvable); - } - if Some(pack) == locked { - candidates.locked = Some(solvable); - } - if let Some(excluded) = excluded.and_then(|d| d.get(pack)) { - candidates - .excluded - .push((solvable, self.pool.intern_string(excluded))); - } - } - - self.maybe_delay(Some(candidates)).await - } - - async fn get_dependencies(&self, solvable: SolvableId) -> Dependencies { - tracing::info!( - "get dependencies for {}", - self.pool - .resolve_solvable(solvable) - .name - .display(&self.pool) - ); - - let concurrent_requests = self.concurrent_requests.fetch_add(1, Ordering::SeqCst); - self.concurrent_requests_max.set( - self.concurrent_requests_max - .get() - .max(concurrent_requests + 1), - ); - - assert!( - self.requested_dependencies.borrow_mut().insert(solvable), - "duplicate get_dependencies request" - ); - - let candidate = self.pool.resolve_solvable(solvable); - let package_name = self.pool.resolve_package_name(candidate.name); - let pack = candidate.record; - - if pack.cancel_during_get_dependencies { - self.cancel_solving.set(true); - let reason = self.pool.intern_string("cancelled"); - return self.maybe_delay(Dependencies::Unknown(reason)).await; - } - - if pack.unknown_deps { - let reason = self.pool.intern_string("could not retrieve deps"); - return self.maybe_delay(Dependencies::Unknown(reason)).await; - } - - let Some(deps) = self.packages.get(package_name).and_then(|v| v.get(&pack)) else { - return self - .maybe_delay(Dependencies::Known(Default::default())) - .await; - }; - - let mut result = KnownDependencies { - requirements: Vec::with_capacity(deps.dependencies.len()), - constrains: Vec::with_capacity(deps.constrains.len()), - }; - result.requirements = deps.dependencies.clone(); - - for req in &deps.constrains { - let dep_name = self.pool.intern_package_name(&req.name); - let dep_spec = self.pool.intern_version_set(dep_name, req.versions.clone()); - result.constrains.push(dep_spec); - } - - self.maybe_delay(Dependencies::Known(result)).await - } - - fn should_cancel_with_value(&self) -> Option> { - if self.cancel_solving.get() { - Some(Box::new("cancelled!".to_string())) - } else { - None - } - } -} /// Create a string from a [`Transaction`] fn transaction_to_string(interner: &impl Interner, solvables: &Vec) -> String { @@ -1425,30 +785,33 @@ fn test_solve_with_additional_with_constrains() { "###); } -// #[test] -// fn test_snapshot() { -// let provider = BundleBoxProvider::from_packages(&[ -// ("menu", 15, vec!["dropdown 2..3"]), -// ("menu", 10, vec!["dropdown 1..2"]), -// ("dropdown", 2, vec!["icons 2"]), -// ("dropdown", 1, vec!["intl 3"]), -// ("icons", 2, vec![]), -// ("icons", 1, vec![]), -// ("intl", 5, vec![]), -// ("intl", 3, vec![]), -// ]); -// -// let menu_name_id = provider.package_name("menu"); -// -// let snapshot = provider.into_snapshot(); -// -// #[cfg(feature = "serde")] -// serialize_snapshot(&snapshot, "snapshot_pubgrub_menu.json"); -// -// let mut snapshot_provider = snapshot.provider(); -// -// assert_snapshot!(solve_for_snapshot(snapshot_provider, &[menu_req], -// &[])); } +#[test] +fn test_snapshot() { + let provider = BundleBoxProvider::from_packages(&[ + ("menu", 15, vec!["dropdown 2..3"]), + ("menu", 10, vec!["dropdown 1..2"]), + ("dropdown", 2, vec!["icons 2"]), + ("dropdown", 1, vec!["intl 3; if menu"]), + ("icons", 2, vec![]), + ("icons", 1, vec![]), + ("intl", 5, vec![]), + ("intl", 3, vec![]), + ]); + + let menu_name_id = provider.package_name("menu"); + + let snapshot = provider.into_snapshot(); + + #[cfg(feature = "serde")] + serialize_snapshot(&snapshot, "snapshot_pubgrub_menu.json"); + + let mut snapshot_provider = snapshot.provider(); + let menu_req = snapshot_provider + .add_package_requirement(menu_name_id, "*") + .into(); + + assert_snapshot!(solve_for_snapshot(snapshot_provider, &[menu_req], &[])); +} #[test] fn test_snapshot_union_requirements() { @@ -1528,7 +891,11 @@ fn test_conditional_requirements() { ]); let requirements = provider.requirements(&["foo", "bar"]); - assert_snapshot!(solve_for_snapshot(provider, &requirements, &[])); + assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @r###" + bar=1 + baz=1 + foo=1 + "###); } #[test] @@ -1540,7 +907,14 @@ fn test_conditional_unsolvable() { ]); let requirements = provider.requirements(&["foo", "bar"]); - assert_snapshot!(solve_for_snapshot(provider, &requirements, &[])); + assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @r###" + foo * cannot be installed because there are no viable options: + └─ foo 1 would require + └─ baz >=2, <3, for which no candidates were found. + The following packages are incompatible + └─ bar * can be installed with any of the following options: + └─ bar 1 + "###); } #[test] @@ -1555,7 +929,11 @@ fn test_conditional_unsolvable_without_condition() { ]); let requirements = provider.requirements(&["foo", "bar", "baz 1"]); - assert_snapshot!(solve_for_snapshot(provider, &requirements, &[])); + assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @r###" + bar=1 + baz=1 + foo=1 + "###); } #[test] @@ -1568,7 +946,10 @@ fn test_conditional_requirements_version_set() { ]); let requirements = provider.requirements(&["foo", "bar"]); - assert_snapshot!(solve_for_snapshot(provider, &requirements, &[])); + assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @r###" + bar=2 + foo=1 + "###); } #[test] @@ -1578,7 +959,7 @@ fn test_condition_missing_requirement() { BundleBoxProvider::from_packages(&[("menu", 1, vec!["bla; if intl"]), ("intl", 1, vec![])]); let requirements = provider.requirements(&["menu"]); - assert_snapshot!(solve_for_snapshot(provider, &requirements, &[])); + assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @"menu=1"); } #[cfg(feature = "serde")] diff --git a/tests/snapshots/solver__condition_is_disabled.snap b/tests/solver/snapshots/solver__condition_is_disabled.snap similarity index 100% rename from tests/snapshots/solver__condition_is_disabled.snap rename to tests/solver/snapshots/solver__condition_is_disabled.snap diff --git a/tests/snapshots/solver__condition_missing_requirement.snap b/tests/solver/snapshots/solver__condition_missing_requirement.snap similarity index 100% rename from tests/snapshots/solver__condition_missing_requirement.snap rename to tests/solver/snapshots/solver__condition_missing_requirement.snap diff --git a/tests/snapshots/solver__conditional_requirements.snap b/tests/solver/snapshots/solver__conditional_requirements.snap similarity index 100% rename from tests/snapshots/solver__conditional_requirements.snap rename to tests/solver/snapshots/solver__conditional_requirements.snap diff --git a/tests/snapshots/solver__conditional_requirements_version_set.snap b/tests/solver/snapshots/solver__conditional_requirements_version_set.snap similarity index 100% rename from tests/snapshots/solver__conditional_requirements_version_set.snap rename to tests/solver/snapshots/solver__conditional_requirements_version_set.snap diff --git a/tests/snapshots/solver__conditional_unsolvable.snap b/tests/solver/snapshots/solver__conditional_unsolvable.snap similarity index 100% rename from tests/snapshots/solver__conditional_unsolvable.snap rename to tests/solver/snapshots/solver__conditional_unsolvable.snap diff --git a/tests/snapshots/solver__conditional_unsolvable_without_condition.snap b/tests/solver/snapshots/solver__conditional_unsolvable_without_condition.snap similarity index 100% rename from tests/snapshots/solver__conditional_unsolvable_without_condition.snap rename to tests/solver/snapshots/solver__conditional_unsolvable_without_condition.snap diff --git a/tests/snapshots/solver__excluded.snap b/tests/solver/snapshots/solver__excluded.snap similarity index 100% rename from tests/snapshots/solver__excluded.snap rename to tests/solver/snapshots/solver__excluded.snap diff --git a/tests/snapshots/solver__incremental_crash.snap b/tests/solver/snapshots/solver__incremental_crash.snap similarity index 100% rename from tests/snapshots/solver__incremental_crash.snap rename to tests/solver/snapshots/solver__incremental_crash.snap diff --git a/tests/snapshots/solver__merge_excluded.snap b/tests/solver/snapshots/solver__merge_excluded.snap similarity index 100% rename from tests/snapshots/solver__merge_excluded.snap rename to tests/solver/snapshots/solver__merge_excluded.snap diff --git a/tests/snapshots/solver__merge_installable.snap b/tests/solver/snapshots/solver__merge_installable.snap similarity index 100% rename from tests/snapshots/solver__merge_installable.snap rename to tests/solver/snapshots/solver__merge_installable.snap diff --git a/tests/snapshots/solver__merge_installable_non_continuous_range.snap b/tests/solver/snapshots/solver__merge_installable_non_continuous_range.snap similarity index 100% rename from tests/snapshots/solver__merge_installable_non_continuous_range.snap rename to tests/solver/snapshots/solver__merge_installable_non_continuous_range.snap diff --git a/tests/snapshots/solver__missing_dep.snap b/tests/solver/snapshots/solver__missing_dep.snap similarity index 100% rename from tests/snapshots/solver__missing_dep.snap rename to tests/solver/snapshots/solver__missing_dep.snap diff --git a/tests/snapshots/solver__no_backtracking.snap b/tests/solver/snapshots/solver__no_backtracking.snap similarity index 100% rename from tests/snapshots/solver__no_backtracking.snap rename to tests/solver/snapshots/solver__no_backtracking.snap diff --git a/tests/snapshots/solver__resolve_and_cancel.snap b/tests/solver/snapshots/solver__resolve_and_cancel.snap similarity index 100% rename from tests/snapshots/solver__resolve_and_cancel.snap rename to tests/solver/snapshots/solver__resolve_and_cancel.snap diff --git a/tests/snapshots/solver__resolve_with_concurrent_metadata_fetching.snap b/tests/solver/snapshots/solver__resolve_with_concurrent_metadata_fetching.snap similarity index 100% rename from tests/snapshots/solver__resolve_with_concurrent_metadata_fetching.snap rename to tests/solver/snapshots/solver__resolve_with_concurrent_metadata_fetching.snap diff --git a/tests/snapshots/solver__resolve_with_conflict.snap b/tests/solver/snapshots/solver__resolve_with_conflict.snap similarity index 100% rename from tests/snapshots/solver__resolve_with_conflict.snap rename to tests/solver/snapshots/solver__resolve_with_conflict.snap diff --git a/tests/snapshots/solver__root_constraints.snap b/tests/solver/snapshots/solver__root_constraints.snap similarity index 100% rename from tests/snapshots/solver__root_constraints.snap rename to tests/solver/snapshots/solver__root_constraints.snap diff --git a/tests/snapshots/solver__root_excluded.snap b/tests/solver/snapshots/solver__root_excluded.snap similarity index 100% rename from tests/snapshots/solver__root_excluded.snap rename to tests/solver/snapshots/solver__root_excluded.snap diff --git a/tests/snapshots/solver__snapshot.snap b/tests/solver/snapshots/solver__snapshot.snap similarity index 100% rename from tests/snapshots/solver__snapshot.snap rename to tests/solver/snapshots/solver__snapshot.snap diff --git a/tests/snapshots/solver__snapshot_union_requirements.snap b/tests/solver/snapshots/solver__snapshot_union_requirements.snap similarity index 100% rename from tests/snapshots/solver__snapshot_union_requirements.snap rename to tests/solver/snapshots/solver__snapshot_union_requirements.snap diff --git a/tests/snapshots/solver__unsat_after_backtracking.snap b/tests/solver/snapshots/solver__unsat_after_backtracking.snap similarity index 100% rename from tests/snapshots/solver__unsat_after_backtracking.snap rename to tests/solver/snapshots/solver__unsat_after_backtracking.snap diff --git a/tests/snapshots/solver__unsat_applies_graph_compression.snap b/tests/solver/snapshots/solver__unsat_applies_graph_compression.snap similarity index 100% rename from tests/snapshots/solver__unsat_applies_graph_compression.snap rename to tests/solver/snapshots/solver__unsat_applies_graph_compression.snap diff --git a/tests/snapshots/solver__unsat_bluesky_conflict.snap b/tests/solver/snapshots/solver__unsat_bluesky_conflict.snap similarity index 100% rename from tests/snapshots/solver__unsat_bluesky_conflict.snap rename to tests/solver/snapshots/solver__unsat_bluesky_conflict.snap diff --git a/tests/snapshots/solver__unsat_constrains.snap b/tests/solver/snapshots/solver__unsat_constrains.snap similarity index 100% rename from tests/snapshots/solver__unsat_constrains.snap rename to tests/solver/snapshots/solver__unsat_constrains.snap diff --git a/tests/snapshots/solver__unsat_constrains_2.snap b/tests/solver/snapshots/solver__unsat_constrains_2.snap similarity index 100% rename from tests/snapshots/solver__unsat_constrains_2.snap rename to tests/solver/snapshots/solver__unsat_constrains_2.snap diff --git a/tests/snapshots/solver__unsat_incompatible_root_requirements.snap b/tests/solver/snapshots/solver__unsat_incompatible_root_requirements.snap similarity index 100% rename from tests/snapshots/solver__unsat_incompatible_root_requirements.snap rename to tests/solver/snapshots/solver__unsat_incompatible_root_requirements.snap diff --git a/tests/snapshots/solver__unsat_locked_and_excluded.snap b/tests/solver/snapshots/solver__unsat_locked_and_excluded.snap similarity index 100% rename from tests/snapshots/solver__unsat_locked_and_excluded.snap rename to tests/solver/snapshots/solver__unsat_locked_and_excluded.snap diff --git a/tests/snapshots/solver__unsat_missing_top_level_dep_1.snap b/tests/solver/snapshots/solver__unsat_missing_top_level_dep_1.snap similarity index 100% rename from tests/snapshots/solver__unsat_missing_top_level_dep_1.snap rename to tests/solver/snapshots/solver__unsat_missing_top_level_dep_1.snap diff --git a/tests/snapshots/solver__unsat_missing_top_level_dep_2.snap b/tests/solver/snapshots/solver__unsat_missing_top_level_dep_2.snap similarity index 100% rename from tests/snapshots/solver__unsat_missing_top_level_dep_2.snap rename to tests/solver/snapshots/solver__unsat_missing_top_level_dep_2.snap diff --git a/tests/snapshots/solver__unsat_no_candidates_for_child_1.snap b/tests/solver/snapshots/solver__unsat_no_candidates_for_child_1.snap similarity index 100% rename from tests/snapshots/solver__unsat_no_candidates_for_child_1.snap rename to tests/solver/snapshots/solver__unsat_no_candidates_for_child_1.snap diff --git a/tests/snapshots/solver__unsat_no_candidates_for_child_2.snap b/tests/solver/snapshots/solver__unsat_no_candidates_for_child_2.snap similarity index 100% rename from tests/snapshots/solver__unsat_no_candidates_for_child_2.snap rename to tests/solver/snapshots/solver__unsat_no_candidates_for_child_2.snap diff --git a/tests/snapshots/solver__unsat_pubgrub_article.snap b/tests/solver/snapshots/solver__unsat_pubgrub_article.snap similarity index 100% rename from tests/snapshots/solver__unsat_pubgrub_article.snap rename to tests/solver/snapshots/solver__unsat_pubgrub_article.snap From 5846050b64880c546a97e0a449d6bee264e00bbd Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Thu, 8 May 2025 17:02:03 +0200 Subject: [PATCH 11/28] conditional operator tests --- tests/solver/main.rs | 70 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/tests/solver/main.rs b/tests/solver/main.rs index 20222057..1770e9b9 100644 --- a/tests/solver/main.rs +++ b/tests/solver/main.rs @@ -952,6 +952,76 @@ fn test_conditional_requirements_version_set() { "###); } +#[test] +fn test_conditional_and() { + let mut provider = BundleBoxProvider::from_packages(&[ + ("foo", 1, vec!["icon; if bar and baz"]), + ("bar", 1, vec![]), + ("bar", 2, vec![]), + ("baz", 1, vec![]), + ("icon", 1, vec![]) + ]); + + let requirements = provider.requirements(&["foo", "bar", "baz"]); + assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @r###" + bar=2 + baz=1 + foo=1 + icon=1 + "###); +} + +#[test] +fn test_conditional_and_mismatch() { + let mut provider = BundleBoxProvider::from_packages(&[ + ("foo", 1, vec!["icon; if bar and baz"]), + ("bar", 1, vec![]), + ("baz", 1, vec![]), + ("icon", 1, vec![]) + ]); + + let requirements = provider.requirements(&["foo", "bar"]); + assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @r###" + bar=1 + foo=1 + "###); +} + +#[test] +fn test_conditional_or() { + let mut provider = BundleBoxProvider::from_packages(&[ + ("foo", 1, vec!["icon; if bar or baz"]), + ("bar", 1, vec![]), + ("baz", 1, vec![]), + ("icon", 1, vec![]) + ]); + + let requirements = provider.requirements(&["foo", "bar"]); + assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @r###" + bar=1 + foo=1 + icon=1 + "###); +} + +#[test] +fn test_conditional_complex() { + let mut provider = BundleBoxProvider::from_packages(&[ + ("foo", 1, vec!["icon; if bar and baz or menu"]), + ("bar", 1, vec![]), + ("baz", 1, vec![]), + ("icon", 1, vec![]) + ]); + + let requirements = provider.requirements(&["foo", "bar", "baz"]); + assert_snapshot!(solve_for_snapshot(provider, &requirements, &[]), @r###" + bar=1 + baz=1 + foo=1 + icon=1 + "###); +} + #[test] #[traced_test] fn test_condition_missing_requirement() { From c05d45a6317c6156d697e521d2c4dd6cb352fd03 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Thu, 8 May 2025 20:55:39 +0200 Subject: [PATCH 12/28] fix: formatting --- tests/solver/main.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/solver/main.rs b/tests/solver/main.rs index e21b6a62..78d46924 100644 --- a/tests/solver/main.rs +++ b/tests/solver/main.rs @@ -959,7 +959,7 @@ fn test_conditional_and() { ("bar", 1, vec![]), ("bar", 2, vec![]), ("baz", 1, vec![]), - ("icon", 1, vec![]) + ("icon", 1, vec![]), ]); let requirements = provider.requirements(&["foo", "bar", "baz"]); @@ -977,7 +977,7 @@ fn test_conditional_and_mismatch() { ("foo", 1, vec!["icon; if bar and baz"]), ("bar", 1, vec![]), ("baz", 1, vec![]), - ("icon", 1, vec![]) + ("icon", 1, vec![]), ]); let requirements = provider.requirements(&["foo", "bar"]); @@ -993,7 +993,7 @@ fn test_conditional_or() { ("foo", 1, vec!["icon; if bar or baz"]), ("bar", 1, vec![]), ("baz", 1, vec![]), - ("icon", 1, vec![]) + ("icon", 1, vec![]), ]); let requirements = provider.requirements(&["foo", "bar"]); @@ -1010,7 +1010,7 @@ fn test_conditional_complex() { ("foo", 1, vec!["icon; if bar and baz or menu"]), ("bar", 1, vec![]), ("baz", 1, vec![]), - ("icon", 1, vec![]) + ("icon", 1, vec![]), ]); let requirements = provider.requirements(&["foo", "bar", "baz"]); From 8c22a1fab2acaf2bb2d6dce5e79ee2ba1195f88a Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Thu, 8 May 2025 21:59:53 +0200 Subject: [PATCH 13/28] fix: cpp --- CMakeLists.txt | 2 +- cpp/CMakeLists.txt | 2 +- cpp/build.rs | 5 + cpp/include/resolvo.h | 1 + cpp/include/resolvo_dependency_provider.h | 12 ++ cpp/src/lib.rs | 139 +++++++++++++++++++--- cpp/tests/solve.cpp | 18 ++- 7 files changed, 161 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 21e1ffeb..ebec3d79 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FeatureSummary) -option(RESOLVO_BUILD_TESTING "Build tests" OFF) +option(RESOLVO_BUILD_TESTING "Build tests" ON) add_feature_info(RESOLVO_BUILD_TESTING RESOLVO_BUILD_TESTING "configure whether to build the test suite") include(CTest) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 19fd0545..797b9eaf 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -43,7 +43,7 @@ endif() corrosion_import_crate( MANIFEST_PATH - "${CMAKE_CURRENT_SOURCE_DIR}/Cargo.toml" + "${CMAKE_CURRENT_SOURCE_DIR}/Cargo.toml"resolvo_internal CRATES resolvo_cpp CRATE_TYPES diff --git a/cpp/build.rs b/cpp/build.rs index 452c5fdb..a3eea520 100644 --- a/cpp/build.rs +++ b/cpp/build.rs @@ -72,6 +72,11 @@ fn main() -> anyhow::Result<()> { constexpr Slice(const T *ptr, uintptr_t len) : ptr(ptr ? const_cast(ptr) : reinterpret_cast(sizeof(T))), len(len) {}" .to_owned(), ); + config.export.body.insert( + "ConditionalRequirement".to_owned(), + r" + constexpr ConditionalRequirement(Requirement requirement) : requirement(requirement), condition(nullptr) {}; + ".to_owned()); cbindgen::Builder::new() .with_config(config.clone()) diff --git a/cpp/include/resolvo.h b/cpp/include/resolvo.h index 97d00f5c..39810e72 100644 --- a/cpp/include/resolvo.h +++ b/cpp/include/resolvo.h @@ -44,6 +44,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, diff --git a/cpp/include/resolvo_dependency_provider.h b/cpp/include/resolvo_dependency_provider.h index 94840794..119ddd72 100644 --- a/cpp/include/resolvo_dependency_provider.h +++ b/cpp/include/resolvo_dependency_provider.h @@ -14,6 +14,9 @@ using cbindgen_private::SolvableId; using cbindgen_private::StringId; using cbindgen_private::VersionSetId; using cbindgen_private::VersionSetUnionId; +using cbindgen_private::ConditionId; +using cbindgen_private::Condition; +using cbindgen_private::ConditionalRequirement; /** * An interface that implements ecosystem specific logic. @@ -81,6 +84,11 @@ struct DependencyProvider { */ virtual Slice 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. @@ -138,6 +146,10 @@ 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 void bridge_resolve_condition(void *data, ConditionId solvable_id, + Condition *result) { + *result = reinterpret_cast(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)); diff --git a/cpp/src/lib.rs b/cpp/src/lib.rs index f00247e8..e1120096 100644 --- a/cpp/src/lib.rs +++ b/cpp/src/lib.rs @@ -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::{Condition, ConditionId, HintDependenciesAvailable, KnownDependencies, SolverCache}; +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)] @@ -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), @@ -156,13 +159,108 @@ impl From 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 for ConditionId { + fn from(id: resolvo::ConditionId) -> Self { + Self { id: id.as_u32() } + } +} + +impl From 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), + /// The condition is the combination of two other conditions using a logical + /// and operator. E.g. both conditions must be true for the condition to + /// be true. + /// + /// cbindgen:derive-eq + /// cbindgen:derive-neq + And(ConditionId, ConditionId), + /// The condition is the combination of two other conditions using a logical + /// or operator. E.g. if one of the conditions is true the entire condition + /// is true. + /// + /// cbindgen:derive-eq + /// cbindgen:derive-neq + Or(ConditionId, ConditionId), +} + +impl From for Condition { + fn from(value: resolvo::Condition) -> Self { + match value { + resolvo::Condition::Requirement(id) => Condition::Requirement(id.into()), + resolvo::Condition::Binary(resolvo::LogicalOperator::And, lhs, rhs) => { + Condition::And(lhs.into(), rhs.into()) + } + resolvo::Condition::Binary(resolvo::LogicalOperator::Or, lhs, rhs) => { + Condition::Or(lhs.into(), rhs.into()) + } + } + } +} + +impl From for resolvo::Condition { + fn from(value: Condition) -> Self { + match value { + Condition::Requirement(id) => resolvo::Condition::Requirement(id.into()), + Condition::And(lhs, rhs) => { + resolvo::Condition::Binary(resolvo::LogicalOperator::And, lhs.into(), rhs.into()) + } + Condition::Or(lhs, rhs) => { + resolvo::Condition::Binary(resolvo::LogicalOperator::Or, 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 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, + 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 @@ -296,6 +394,10 @@ pub struct DependencyProvider { version_set_union_id: VersionSetUnionId, ) -> 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), + /// Obtains a list of solvables that should be considered when a package /// with the given name is requested. pub get_candidates: @@ -394,8 +496,17 @@ impl resolvo::Interner for &DependencyProvider { .map(Into::into) } - fn resolve_condition(&self, condition: ConditionId) -> Condition { - todo!() + 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() } } @@ -490,7 +601,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>, } @@ -511,7 +622,7 @@ pub extern "C" fn resolvo_solve( .requirements .as_slice() .iter() - .copied() + .cloned() .map(Into::into) .collect(), ) diff --git a/cpp/tests/solve.cpp b/cpp/tests/solve.cpp index 1bb02b7b..ca18e011 100644 --- a/cpp/tests/solve.cpp +++ b/cpp/tests/solve.cpp @@ -30,6 +30,7 @@ struct VersionSet { struct PackageDatabase : public resolvo::DependencyProvider { resolvo::Pool names; resolvo::Pool strings; + std::vector conditions; std::vector candidates; std::vector version_sets; std::vector> version_set_unions; @@ -83,6 +84,15 @@ struct PackageDatabase : public resolvo::DependencyProvider { return id; } + /** + * Allocates a new candidate and return the id of the candidate. + */ + resolvo::ConditionId alloc_condition(resolvo::Condition condition) { + auto id = resolvo::ConditionId{static_cast(conditions.size())}; + conditions.push_back(condition); + return id; + } + resolvo::String display_name(resolvo::NameId name) override { return resolvo::String(names[name]); } @@ -142,6 +152,10 @@ struct PackageDatabase : public resolvo::DependencyProvider { return {version_set_ids.data(), version_set_ids.size()}; } + resolvo::Condition resolve_condition(resolvo::ConditionId condition_id) override { + return conditions[condition_id.id]; + } + resolvo::Candidates get_candidates(resolvo::NameId package) override { resolvo::Candidates result; @@ -219,7 +233,7 @@ SCENARIO("Solve") { const auto d_1 = db.alloc_candidate("d", 1, {}); // Construct a problem to be solved by the solver - resolvo::Vector requirements = {db.alloc_requirement("a", 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), @@ -263,7 +277,7 @@ SCENARIO("Solve Union") { "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 = { + 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), From e78624122bbe8f334598f536fdccf7f14c4472f4 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Thu, 8 May 2025 23:21:58 +0200 Subject: [PATCH 14/28] add conditional cpp --- cpp/build.rs | 10 ++++++- cpp/include/resolvo_dependency_provider.h | 19 ++++++------- cpp/src/lib.rs | 21 ++++++++++----- cpp/tests/solve.cpp | 33 +++++++++++++++++++++-- 4 files changed, 65 insertions(+), 18 deletions(-) diff --git a/cpp/build.rs b/cpp/build.rs index a3eea520..a8380f70 100644 --- a/cpp/build.rs +++ b/cpp/build.rs @@ -75,7 +75,15 @@ fn main() -> anyhow::Result<()> { config.export.body.insert( "ConditionalRequirement".to_owned(), r" - constexpr ConditionalRequirement(Requirement requirement) : requirement(requirement), condition(nullptr) {}; + /** + * Constructs a new conditional requirement with the specified condition + * and requirement. + */ + constexpr ConditionalRequirement(const ConditionId *condition, Requirement &&requirement) : condition(condition), requirement(std::forward(requirement)) {}; + /** + * Constructs a new conditional requirement without a condition. + */ + constexpr ConditionalRequirement(Requirement &&requirement) : requirement(std::forward(requirement)), condition(nullptr) {}; ".to_owned()); cbindgen::Builder::new() diff --git a/cpp/include/resolvo_dependency_provider.h b/cpp/include/resolvo_dependency_provider.h index 119ddd72..3bac3c9f 100644 --- a/cpp/include/resolvo_dependency_provider.h +++ b/cpp/include/resolvo_dependency_provider.h @@ -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; @@ -14,9 +17,6 @@ using cbindgen_private::SolvableId; using cbindgen_private::StringId; using cbindgen_private::VersionSetId; using cbindgen_private::VersionSetUnionId; -using cbindgen_private::ConditionId; -using cbindgen_private::Condition; -using cbindgen_private::ConditionalRequirement; /** * An interface that implements ecosystem specific logic. @@ -146,18 +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(data)->solvable_name(solvable_id); } -extern "C" inline void bridge_resolve_condition(void *data, ConditionId solvable_id, - Condition *result) { +extern "C" inline void bridge_resolve_condition(void *data, ConditionId solvable_id, + Condition *result) { *result = reinterpret_cast(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)); -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_version_sets_in_union(void *data, + VersionSetUnionId version_set_union_id, + Slice *result) { + *result = + reinterpret_cast(data)->version_sets_in_union(version_set_union_id); } extern "C" inline void bridge_get_candidates(void *data, NameId package, Candidates *result) { diff --git a/cpp/src/lib.rs b/cpp/src/lib.rs index e1120096..555d3301 100644 --- a/cpp/src/lib.rs +++ b/cpp/src/lib.rs @@ -392,7 +392,8 @@ 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>, + ), /// Returns the condition that the given condition id describes pub resolve_condition: @@ -489,11 +490,19 @@ impl resolvo::Interner for &DependencyProvider { &self, version_set_union: resolvo::VersionSetUnionId, ) -> impl Iterator { - 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 { diff --git a/cpp/tests/solve.cpp b/cpp/tests/solve.cpp index ca18e011..43d79b23 100644 --- a/cpp/tests/solve.cpp +++ b/cpp/tests/solve.cpp @@ -233,7 +233,8 @@ SCENARIO("Solve") { const auto d_1 = db.alloc_candidate("d", 1, {}); // Construct a problem to be solved by the solver - resolvo::Vector requirements = {db.alloc_requirement("a", 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), @@ -253,6 +254,35 @@ SCENARIO("Solve") { REQUIRE(result[2] == c_1); } +SCENARIO("Solve conditional") { + /// Construct a database with packages a, b, and c. + PackageDatabase db; + + auto b_cond_version_set = db.alloc_version_set("b", 1, 3); + auto b_cond = db.alloc_condition( + resolvo::Condition{resolvo::Condition::Tag::Requirement, {b_cond_version_set}}); + auto a_cond_req = resolvo::ConditionalRequirement{&b_cond, db.alloc_requirement("a", 1, 3)}; + + auto a_1 = db.alloc_candidate("a", 1, {{}, {}}); + auto b_1 = db.alloc_candidate("b", 1, {{}, {}}); + auto c_1 = db.alloc_candidate("c", 1, {{a_cond_req}, {}}); + + // Construct a problem to be solved by the solver + resolvo::Vector requirements = { + db.alloc_requirement("b", 1, 3), db.alloc_requirement("c", 1, 3)}; + + // Solve the problem + resolvo::Vector result; + resolvo::Problem problem = {requirements, {}, {}}; + resolvo::solve(db, problem, result); + + // Check the result + REQUIRE(result.size() == 3); + REQUIRE(result[0] == c_1); + REQUIRE(result[1] == b_1); + REQUIRE(result[2] == a_1); +} + SCENARIO("Solve Union") { /// Construct a database with packages a, b, and c. PackageDatabase db; @@ -288,7 +318,6 @@ SCENARIO("Solve Union") { resolvo::Vector result; resolvo::Problem problem = {requirements, constraints, {}}; resolvo::solve(db, problem, result); - ; // Check the result REQUIRE(result.size() == 4); From cb97de9a4a8fc0b4b62f1beb035de839cb64fac7 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Thu, 8 May 2025 23:22:50 +0200 Subject: [PATCH 15/28] fmt --- cpp/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/src/lib.rs b/cpp/src/lib.rs index 555d3301..d4f8ab95 100644 --- a/cpp/src/lib.rs +++ b/cpp/src/lib.rs @@ -12,7 +12,7 @@ use crate::{slice::Slice, string::String, vector::Vector}; /// 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)] @@ -47,7 +47,7 @@ pub enum Requirement { /// 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. + /// belonging to different packages. /// cbindgen:derive-eq /// cbindgen:derive-neq Union(VersionSetUnionId), From d4be2014c819bdf25cd40ef2d144c18311e7bc97 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Thu, 8 May 2025 23:25:33 +0200 Subject: [PATCH 16/28] fmt --- tools/solve-snapshot/src/main.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/solve-snapshot/src/main.rs b/tools/solve-snapshot/src/main.rs index 40139e67..b58e5973 100644 --- a/tools/solve-snapshot/src/main.rs +++ b/tools/solve-snapshot/src/main.rs @@ -15,7 +15,7 @@ use rand::{ prelude::IteratorRandom, rngs::StdRng, }; -use resolvo::{Problem, Requirement, Solver, UnsolvableOrCancelled, snapshot::DependencySnapshot}; +use resolvo::{Problem, Solver, UnsolvableOrCancelled, snapshot::DependencySnapshot, ConditionalRequirement}; #[derive(Parser)] #[clap(version = "0.1.0", author = "Bas Zalmstra ")] @@ -79,7 +79,7 @@ fn main() { .with_timeout(SystemTime::now().add(Duration::from_secs(opts.timeout))); // Construct a problem with a random number of requirements. - let mut requirements: Vec = Vec::new(); + let mut requirements: Vec = Vec::new(); // Determine the number of requirements to solve for. let num_requirements = rng.gen_range(1..=10usize); @@ -114,7 +114,7 @@ fn main() { requirements.iter().format_with("\n", |requirement, f| { f(&format_args!( "- {}", - style(requirement.display(&provider)).dim() + style(requirement.requirement.display(&provider)).dim() )) }) ); @@ -122,7 +122,7 @@ fn main() { let problem_name = requirements .iter() .format_with("\n", |requirement, f| { - f(&format_args!("{}", requirement.display(&provider))) + f(&format_args!("{}", requirement.requirement.display(&provider))) }) .to_string(); From 898f4566945718f39edf91375809baa573c939c1 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Thu, 8 May 2025 23:25:55 +0200 Subject: [PATCH 17/28] fmt --- tools/solve-snapshot/src/main.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/solve-snapshot/src/main.rs b/tools/solve-snapshot/src/main.rs index b58e5973..5ecb8a23 100644 --- a/tools/solve-snapshot/src/main.rs +++ b/tools/solve-snapshot/src/main.rs @@ -15,7 +15,9 @@ use rand::{ prelude::IteratorRandom, rngs::StdRng, }; -use resolvo::{Problem, Solver, UnsolvableOrCancelled, snapshot::DependencySnapshot, ConditionalRequirement}; +use resolvo::{ + ConditionalRequirement, Problem, Solver, UnsolvableOrCancelled, snapshot::DependencySnapshot, +}; #[derive(Parser)] #[clap(version = "0.1.0", author = "Bas Zalmstra ")] @@ -122,7 +124,10 @@ fn main() { let problem_name = requirements .iter() .format_with("\n", |requirement, f| { - f(&format_args!("{}", requirement.requirement.display(&provider))) + f(&format_args!( + "{}", + requirement.requirement.display(&provider) + )) }) .to_string(); From 49dc66ef4318b6fd1731891ccac0e8b8c1e5d1d1 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Fri, 9 May 2025 22:05:33 +0200 Subject: [PATCH 18/28] fix: cpp tests --- CMakeLists.txt | 2 +- cpp/build.rs | 19 ++++++++++++ cpp/include/resolvo.h | 17 ----------- cpp/src/lib.rs | 68 +++++++++++++++++++++---------------------- cpp/tests/solve.cpp | 11 ++++--- tests/solver/main.rs | 3 +- 6 files changed, 60 insertions(+), 60 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ebec3d79..21e1ffeb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FeatureSummary) -option(RESOLVO_BUILD_TESTING "Build tests" ON) +option(RESOLVO_BUILD_TESTING "Build tests" OFF) add_feature_info(RESOLVO_BUILD_TESTING RESOLVO_BUILD_TESTING "configure whether to build the test suite") include(CTest) diff --git a/cpp/build.rs b/cpp/build.rs index a8380f70..41193573 100644 --- a/cpp/build.rs +++ b/cpp/build.rs @@ -85,6 +85,25 @@ fn main() -> anyhow::Result<()> { */ constexpr ConditionalRequirement(Requirement &&requirement) : requirement(std::forward(requirement)), condition(nullptr) {}; ".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()) diff --git a/cpp/include/resolvo.h b/cpp/include/resolvo.h index 39810e72..e009a3c6 100644 --- a/cpp/include/resolvo.h +++ b/cpp/include/resolvo.h @@ -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. * diff --git a/cpp/src/lib.rs b/cpp/src/lib.rs index d4f8ab95..bdd79919 100644 --- a/cpp/src/lib.rs +++ b/cpp/src/lib.rs @@ -191,31 +191,46 @@ pub enum Condition { /// cbindgen:derive-eq /// cbindgen:derive-neq Requirement(VersionSetId), - /// The condition is the combination of two other conditions using a logical - /// and operator. E.g. both conditions must be true for the condition to - /// be true. - /// - /// cbindgen:derive-eq - /// cbindgen:derive-neq - And(ConditionId, ConditionId), - /// The condition is the combination of two other conditions using a logical - /// or operator. E.g. if one of the conditions is true the entire condition - /// is true. + + /// Combines two conditions using a logical operator. /// /// cbindgen:derive-eq /// cbindgen:derive-neq - Or(ConditionId, ConditionId), + 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 for LogicalOperator { + fn from(value: resolvo::LogicalOperator) -> Self { + match value { + resolvo::LogicalOperator::And => LogicalOperator::And, + resolvo::LogicalOperator::Or => LogicalOperator::Or, + } + } +} + +impl From for resolvo::LogicalOperator { + fn from(value: LogicalOperator) -> Self { + match value { + LogicalOperator::And => resolvo::LogicalOperator::And, + LogicalOperator::Or => resolvo::LogicalOperator::Or, + } + } } impl From for Condition { fn from(value: resolvo::Condition) -> Self { match value { resolvo::Condition::Requirement(id) => Condition::Requirement(id.into()), - resolvo::Condition::Binary(resolvo::LogicalOperator::And, lhs, rhs) => { - Condition::And(lhs.into(), rhs.into()) - } - resolvo::Condition::Binary(resolvo::LogicalOperator::Or, lhs, rhs) => { - Condition::Or(lhs.into(), rhs.into()) + resolvo::Condition::Binary(op, lhs, rhs) => { + Condition::Binary(op.into(), lhs.into(), rhs.into()) } } } @@ -225,11 +240,8 @@ impl From for resolvo::Condition { fn from(value: Condition) -> Self { match value { Condition::Requirement(id) => resolvo::Condition::Requirement(id.into()), - Condition::And(lhs, rhs) => { - resolvo::Condition::Binary(resolvo::LogicalOperator::And, lhs.into(), rhs.into()) - } - Condition::Or(lhs, rhs) => { - resolvo::Condition::Binary(resolvo::LogicalOperator::Or, lhs.into(), rhs.into()) + Condition::Binary(op, lhs, rhs) => { + resolvo::Condition::Binary(op.into(), lhs.into(), rhs.into()) } } } @@ -669,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::*; diff --git a/cpp/tests/solve.cpp b/cpp/tests/solve.cpp index 43d79b23..6dab5dbd 100644 --- a/cpp/tests/solve.cpp +++ b/cpp/tests/solve.cpp @@ -52,7 +52,7 @@ struct PackageDatabase : public resolvo::DependencyProvider { 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); + return {id}; } /** @@ -68,9 +68,9 @@ struct PackageDatabase : public resolvo::DependencyProvider { version_set_union[i] = alloc_version_set(package, version_start, version_end); } - auto id = resolvo::VersionSetUnionId{static_cast(version_set_unions.size())}; + resolvo::VersionSetUnionId id = {static_cast(version_set_unions.size())}; version_set_unions.push_back(std::move(version_set_union)); - return resolvo::requirement_union(id); + return {id}; } /** @@ -259,8 +259,7 @@ SCENARIO("Solve conditional") { PackageDatabase db; auto b_cond_version_set = db.alloc_version_set("b", 1, 3); - auto b_cond = db.alloc_condition( - resolvo::Condition{resolvo::Condition::Tag::Requirement, {b_cond_version_set}}); + auto b_cond = db.alloc_condition({b_cond_version_set}); auto a_cond_req = resolvo::ConditionalRequirement{&b_cond, db.alloc_requirement("a", 1, 3)}; auto a_1 = db.alloc_candidate("a", 1, {{}, {}}); @@ -317,7 +316,7 @@ SCENARIO("Solve Union") { // Solve the problem resolvo::Vector result; resolvo::Problem problem = {requirements, constraints, {}}; - resolvo::solve(db, problem, result); + auto error = resolvo::solve(db, problem, result); // Check the result REQUIRE(result.size() == 4); diff --git a/tests/solver/main.rs b/tests/solver/main.rs index 78d46924..0ed374e3 100644 --- a/tests/solver/main.rs +++ b/tests/solver/main.rs @@ -7,7 +7,7 @@ use insta::assert_snapshot; use itertools::Itertools; use resolvo::{ ConditionalRequirement, DependencyProvider, Interner, Problem, SolvableId, Solver, - UnsolvableOrCancelled, VersionSetId, snapshot::DependencySnapshot, + UnsolvableOrCancelled, VersionSetId, }; use tracing_test::traced_test; @@ -175,6 +175,7 @@ fn test_resolve_with_concurrent_metadata_fetching() { /// In case of a conflict the version should not be selected with the conflict #[test] +#[traced_test] fn test_resolve_with_conflict() { let provider = BundleBoxProvider::from_packages(&[ ("asdf", 4, vec!["conflicting 1"]), From ee6be084a5e09accf48480ee4d316df8fde4f64b Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Fri, 9 May 2025 22:05:46 +0200 Subject: [PATCH 19/28] fix: cpp tests --- cpp/build.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cpp/build.rs b/cpp/build.rs index 41193573..16eb764f 100644 --- a/cpp/build.rs +++ b/cpp/build.rs @@ -93,7 +93,9 @@ fn main() -> anyhow::Result<()> { constexpr bool is_union() const { return tag == Tag::Union; } constexpr bool is_single() const { return tag == Tag::Single; } - ".to_owned()); + " + .to_owned(), + ); config.export.body.insert( "Condition".to_owned(), From 6c777038b7b4cf83ea75ca785525d69670074868 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Fri, 9 May 2025 22:09:31 +0200 Subject: [PATCH 20/28] fix initialization order --- cpp/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/build.rs b/cpp/build.rs index 16eb764f..c545bc7f 100644 --- a/cpp/build.rs +++ b/cpp/build.rs @@ -83,7 +83,7 @@ fn main() -> anyhow::Result<()> { /** * Constructs a new conditional requirement without a condition. */ - constexpr ConditionalRequirement(Requirement &&requirement) : requirement(std::forward(requirement)), condition(nullptr) {}; + constexpr ConditionalRequirement(Requirement &&requirement) : condition(nullptr), requirement(std::forward(requirement)) {}; ".to_owned()); config.export.body.insert( "Requirement".to_owned(), From 844473037b17041e629055958f8c4170dbf120b1 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Fri, 9 May 2025 22:11:50 +0200 Subject: [PATCH 21/28] fix: solver issue --- tests/solver/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/solver/main.rs b/tests/solver/main.rs index 0ed374e3..b38695f4 100644 --- a/tests/solver/main.rs +++ b/tests/solver/main.rs @@ -1034,7 +1034,7 @@ fn test_condition_missing_requirement() { } #[cfg(feature = "serde")] -fn serialize_snapshot(snapshot: &DependencySnapshot, destination: impl AsRef) { +fn serialize_snapshot(snapshot: &resolvo::snapshot::DependencySnapshot, destination: impl AsRef) { let file = std::io::BufWriter::new(std::fs::File::create(destination.as_ref()).unwrap()); serde_json::to_writer_pretty(file, snapshot).unwrap() } From 3044e7608a58452722e32cad451f6b5acb98457c Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Fri, 9 May 2025 22:12:30 +0200 Subject: [PATCH 22/28] fmt --- tests/solver/main.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/solver/main.rs b/tests/solver/main.rs index b38695f4..ec8b32f6 100644 --- a/tests/solver/main.rs +++ b/tests/solver/main.rs @@ -1034,7 +1034,10 @@ fn test_condition_missing_requirement() { } #[cfg(feature = "serde")] -fn serialize_snapshot(snapshot: &resolvo::snapshot::DependencySnapshot, destination: impl AsRef) { +fn serialize_snapshot( + snapshot: &resolvo::snapshot::DependencySnapshot, + destination: impl AsRef, +) { let file = std::io::BufWriter::new(std::fs::File::create(destination.as_ref()).unwrap()); serde_json::to_writer_pretty(file, snapshot).unwrap() } From e12906c15dde3c9d3e6f30de47828fb448f7692e Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Fri, 9 May 2025 22:21:00 +0200 Subject: [PATCH 23/28] fix small issues --- cpp/CMakeLists.txt | 2 +- src/solver/clause.rs | 2 -- src/solver/encoding.rs | 6 +++--- src/solver/mod.rs | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 797b9eaf..19fd0545 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -43,7 +43,7 @@ endif() corrosion_import_crate( MANIFEST_PATH - "${CMAKE_CURRENT_SOURCE_DIR}/Cargo.toml"resolvo_internal + "${CMAKE_CURRENT_SOURCE_DIR}/Cargo.toml" CRATES resolvo_cpp CRATE_TYPES diff --git a/src/solver/clause.rs b/src/solver/clause.rs index f93291f4..e798818e 100644 --- a/src/solver/clause.rs +++ b/src/solver/clause.rs @@ -59,8 +59,6 @@ pub(crate) enum Clause { /// Optionally the requirement can be associated with a condition in the /// form of a disjunction. /// - /// ~A v ~C1 ^ C2 ^ C3 ^ v R - /// /// In SAT terms: (¬A ∨ ¬D1 v ¬D2 .. v ¬D99 v B1 ∨ B2 ∨ ... ∨ B99), where D1 /// to D99 represent the candidates of the disjunction and B1 to B99 /// represent the possible candidates for the provided [`Requirement`]. diff --git a/src/solver/encoding.rs b/src/solver/encoding.rs index 436e56d6..f3f6880f 100644 --- a/src/solver/encoding.rs +++ b/src/solver/encoding.rs @@ -311,7 +311,7 @@ impl<'a, 'cache, D: DependencyProvider> Encoder<'a, 'cache, D> { let name_id = self.cache.provider().version_set_name(version_set); let at_least_one_of_var = match self .state - .at_last_once_tracker + .at_least_one_tracker .get(&name_id) .copied() .or_else(|| self.new_at_least_one_packages.get(&name_id).copied()) @@ -686,7 +686,7 @@ impl<'a, 'cache, D: DependencyProvider> Encoder<'a, 'cache, D> { ); if variable_is_new { - if let Some(&at_least_one_variable) = self.state.at_last_once_tracker.get(&name_id) + if let Some(&at_least_one_variable) = self.state.at_least_one_tracker.get(&name_id) { let (watched_literals, kind) = WatchedLiterals::any_of(at_least_one_variable, candidate_var); @@ -754,7 +754,7 @@ impl<'a, 'cache, D: DependencyProvider> Encoder<'a, 'cache, D> { // Record that we have a variable for this package. self.state - .at_last_once_tracker + .at_least_one_tracker .insert(name_id, at_least_one_variable); } } diff --git a/src/solver/mod.rs b/src/solver/mod.rs index ae77516f..8e0e25ec 100644 --- a/src/solver/mod.rs +++ b/src/solver/mod.rs @@ -190,7 +190,7 @@ pub(crate) struct SolverState { /// Keeps track of auxiliary variables that are used to encode at-least-one /// solvable for a package. - at_last_once_tracker: HashMap, + at_least_one_tracker: HashMap, pub(crate) decision_tracker: DecisionTracker, From 6cc440f9f92a0a88dc323f7c1f653ba85deddbe1 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Fri, 9 May 2025 22:26:29 +0200 Subject: [PATCH 24/28] Update src/internal/id.rs --- src/internal/id.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/internal/id.rs b/src/internal/id.rs index b551dd37..2e643787 100644 --- a/src/internal/id.rs +++ b/src/internal/id.rs @@ -64,7 +64,7 @@ impl ArenaId for VersionSetId { pub struct ConditionId(NonZero); impl ConditionId { - /// Creates a new `ConditionId` from a `u32`, panicking if the value is zero. + /// Creates a new `ConditionId` from a `u32` pub fn new(id: u32) -> Self { Self::from_usize(id as usize) } From dacccb54acb87f7b6ec2bc98d6c4d5853d7b4243 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra <4995967+baszalmstra@users.noreply.github.com> Date: Tue, 22 Jul 2025 00:44:53 +0200 Subject: [PATCH 25/28] feat: use ahash where applicable --- src/solver/binary_encoding.rs | 2 +- src/solver/encoding.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/solver/binary_encoding.rs b/src/solver/binary_encoding.rs index 092794bf..49a5261c 100644 --- a/src/solver/binary_encoding.rs +++ b/src/solver/binary_encoding.rs @@ -6,7 +6,7 @@ use indexmap::IndexSet; /// that at most one of a set of variables can be true. pub(crate) struct AtMostOnceTracker { /// The set of variables of which at most one can be assigned true. - pub(crate) variables: IndexSet, + pub(crate) variables: IndexSet, pub(crate) helpers: Vec, } diff --git a/src/solver/encoding.rs b/src/solver/encoding.rs index f3f6880f..9daab6bc 100644 --- a/src/solver/encoding.rs +++ b/src/solver/encoding.rs @@ -44,10 +44,10 @@ pub(crate) struct Encoder<'a, 'cache, D: DependencyProvider> { conflicting_clauses: Vec, /// Stores for which packages and solvables we want to add forbid clauses. - pending_forbid_clauses: IndexMap>, + pending_forbid_clauses: IndexMap, ahash::RandomState>, /// A set of packages that should have an at-least-once tracker. - new_at_least_one_packages: IndexMap, + new_at_least_one_packages: IndexMap, } /// The result of a future that was queued for processing. @@ -106,7 +106,7 @@ impl<'a, 'cache, D: DependencyProvider> Encoder<'a, 'cache, D> { conflicting_clauses: Vec::new(), pending_forbid_clauses: IndexMap::default(), level, - new_at_least_one_packages: IndexMap::new(), + new_at_least_one_packages: IndexMap::default(), } } From 073c85a02805edfc2a67330e9f6080e7567a40b7 Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Tue, 22 Jul 2025 22:50:23 +0200 Subject: [PATCH 26/28] add condition to pool --- src/utils/pool.rs | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/src/utils/pool.rs b/src/utils/pool.rs index f660f200..966a9744 100644 --- a/src/utils/pool.rs +++ b/src/utils/pool.rs @@ -3,11 +3,14 @@ use std::{ hash::Hash, }; -use crate::internal::{ - arena::Arena, - frozen_copy_map::FrozenCopyMap, - id::{NameId, SolvableId, StringId, VersionSetId, VersionSetUnionId}, - small_vec::SmallVec, +use crate::{ + Condition, ConditionId, + internal::{ + arena::Arena, + frozen_copy_map::FrozenCopyMap, + id::{NameId, SolvableId, StringId, VersionSetId, VersionSetUnionId}, + small_vec::SmallVec, + }, }; /// A solvable represents a single candidate of a package. @@ -49,6 +52,12 @@ pub struct Pool { /// Map from version set to the id of their interned counterpart version_set_to_id: FrozenCopyMap<(NameId, VS), VersionSetId, ahash::RandomState>, + /// Conditions that can be used to filter solvables. + conditions: Arena, + + /// Map from condition to its id + pub(crate) condition_to_id: FrozenCopyMap, + version_set_unions: Arena>, } @@ -65,6 +74,8 @@ impl Default for Pool { version_set_to_id: Default::default(), version_sets: Arena::new(), version_set_unions: Arena::new(), + conditions: Arena::new(), + condition_to_id: Default::default(), } } } @@ -213,6 +224,23 @@ impl Pool { ) -> impl Iterator + '_ { self.version_set_unions[id].iter().copied() } + + /// Resolve the condition associated with the provided id. + pub fn resolve_condition(&self, id: ConditionId) -> &Condition { + &self.conditions[id] + } + + /// Interns a condition into the pool and returns its `ConditionId`. + /// Conditions are deduplicated, so if the same condition is inserted + /// twice the same `ConditionId` will be returned. + pub fn intern_condition(&self, condition: Condition) -> ConditionId { + if let Some(id) = self.condition_to_id.get_copy(&condition) { + return id; + } + let id = self.conditions.alloc(condition.clone()); + self.condition_to_id.insert_copy(condition, id); + id + } } /// A helper struct to visualize a name. From 5d0763aa914a58faaf9a3704ad31deac8f9e92d6 Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Wed, 23 Jul 2025 09:44:07 +0200 Subject: [PATCH 27/28] undo changes to notebook --- tools/solve-snapshot/timing_comparison.ipynb | 52 ++++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/tools/solve-snapshot/timing_comparison.ipynb b/tools/solve-snapshot/timing_comparison.ipynb index df93d4e7..4936b474 100644 --- a/tools/solve-snapshot/timing_comparison.ipynb +++ b/tools/solve-snapshot/timing_comparison.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 2, + "execution_count": 5, "id": "e2ecd20f-bd4f-4a4a-82d0-28d2d9db18ac", "metadata": {}, "outputs": [ @@ -10,40 +10,40 @@ "name": "stdout", "output_type": "stream", "text": [ - "timings_main_1000.csv: 1000 records\n", - "timings_cond_clean_1000.csv: 1000 records\n", + "base_timings.csv: 1000 records\n", + "timings.csv: 1000 records\n", "\n", - "Summary for timings_main_1000.csv:\n", - "- Average Solve Time: 1.27 seconds (mean of all durations)\n", - "- Median Solve Time: 0.64 seconds (middle value when sorted)\n", - "- Standard Deviation: 3.06 seconds (spread of durations around the mean)\n", + "Summary for base_timings.csv:\n", + "- Average Solve Time: 1.40 seconds (mean of all durations)\n", + "- Median Solve Time: 0.47 seconds (middle value when sorted)\n", + "- Standard Deviation: 3.26 seconds (spread of durations around the mean)\n", "- Minimum Solve Time: 0.00 seconds (shortest solve duration)\n", "- Maximum Solve Time: 50.00 seconds (longest solve duration, capped at 50s)\n", - "- 25th Percentile: 0.22 seconds (25% of solves were faster than this)\n", - "- 75th Percentile: 1.56 seconds (75% of solves were faster than this)\n", - "- Number of Outliers: 1 (solves capped at 50s)\n", + "- 25th Percentile: 0.14 seconds (25% of solves were faster than this)\n", + "- 75th Percentile: 2.03 seconds (75% of solves were faster than this)\n", + "- Number of Outliers: 3 (solves capped at 50s)\n", "\n", - "Summary for timings_cond_clean_1000.csv:\n", - "- Average Solve Time: 1.20 seconds (mean of all durations)\n", - "- Median Solve Time: 0.68 seconds (middle value when sorted)\n", - "- Standard Deviation: 2.86 seconds (spread of durations around the mean)\n", + "Summary for timings.csv:\n", + "- Average Solve Time: 0.83 seconds (mean of all durations)\n", + "- Median Solve Time: 0.35 seconds (middle value when sorted)\n", + "- Standard Deviation: 2.95 seconds (spread of durations around the mean)\n", "- Minimum Solve Time: 0.00 seconds (shortest solve duration)\n", "- Maximum Solve Time: 50.00 seconds (longest solve duration, capped at 50s)\n", - "- 25th Percentile: 0.20 seconds (25% of solves were faster than this)\n", - "- 75th Percentile: 1.59 seconds (75% of solves were faster than this)\n", - "- Number of Outliers: 2 (solves capped at 50s)\n", + "- 25th Percentile: 0.11 seconds (25% of solves were faster than this)\n", + "- 75th Percentile: 0.89 seconds (75% of solves were faster than this)\n", + "- Number of Outliers: 3 (solves capped at 50s)\n", "\n", "Comparison between the datasets:\n", - "- Average Solve Time: 'timings_cond_clean_1000.csv' was 1.05 times faster than 'timings_main_1000.csv'\n", - "- Median Solve Time: 'timings_cond_clean_1000.csv' was 0.95 times faster than 'timings_main_1000.csv'\n", - "- 25th Percentile: 'timings_cond_clean_1000.csv' was 1.10 times faster than 'timings_main_1000.csv'\n", - "- 75th Percentile: 'timings_cond_clean_1000.csv' was 0.98 times faster than 'timings_main_1000.csv'\n", - "- Outliers: 'timings_cond_clean_1000.csv' had -1 fewer solves capped at 50s\n" + "- Average Solve Time: 'timings.csv' was 1.68 times faster than 'base_timings.csv'\n", + "- Median Solve Time: 'timings.csv' was 1.33 times faster than 'base_timings.csv'\n", + "- 25th Percentile: 'timings.csv' was 1.22 times faster than 'base_timings.csv'\n", + "- 75th Percentile: 'timings.csv' was 2.28 times faster than 'base_timings.csv'\n", + "- Outliers: 'timings.csv' had 0 fewer solves capped at 50s\n" ] }, { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA7QAAALYCAYAAABMhr47AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAXEgAAFxIBZ5/SUgAAxYpJREFUeJzs3Qm8jOX///EPjn3f910hkiyRkq1UFBVCWZJKe4nQSrSQUtKmQrs2KqkUpb5KiVJJFCJb2fd9uf+P99Xvnv+cOTNnnXOcOef1fDzGnJl7X+Z2f+7ruj5XDs/zPAMAAAAAIMbkPNErAAAAAABAahDQAgAAAABiEgEtAAAAACAmEdACAAAAAGISAS0AAAAAICYR0AIAAAAAYhIBLQAAAAAgJhHQAgAAAABiEgEtAAAAACAmEdACAAAAAGISAS0AAAAAICYR0AIAAAAAYhIBLQAAAAAgJhHQAgAAAABiEgEtgGyjdevWliNHDhsxYsSJXhVkQlOmTLEzzzzTihQp4s4TvZ588knLjL766qvAOmYla9asCWyX/s7K/O3UsQQApB4BLYBMTcFncm/cg2+GX3755XRfNy1D68cNaex7/PHH7eqrr7bvv//eDhw4YGXKlLGyZctawYIFT/SqIcboIYiuCz///POJXhUAyBbiTvQKAEBGqVKlitWuXdtKlSoVtYD266+/DpT+InaNHTvWvd9666322GOPWe7cuU/0KiGGA9q///7bqlWrZg0bNow4nq5FUqBAgQxcOwDIeghoAWQbr7766oleBWRCW7ZssU2bNrm/r732WoJZZIjly5ef6FUAgCyBKscAgGxt//79gb8LFSp0QtcFAACkDAEtgGwjsaRQR48etRdeeMGNoyrJKqUrWbKkqxbYvXt3mzRpUryqxpqPX934gQceCLTdjZTQ5tixYzZ58mRr27atm3/evHmtYsWK1q1btyTb4HqeF0hYVLhwYStatKg1a9bMra+GXXXVVW6Zeg+lao9+m+K9e/fa/fffb6eeeqqbT/B6HjlyxGbMmGHXXXedNWnSxMqXL2958uRxbUnPP/98mzp1qltWchIU/frrr9azZ0+rUKGC5c+f3+rWreuq8Wof+7799lu75JJL3HLy5ctn9evXt2eeeSbiMpJr+vTpdtFFF7n2r1p/vevz+++/H3G9tY981atXD2xL8PfJ8fbbb9uFF17olqnzp1ixYnbSSSdZp06d3LYdPHgw7HSLFy+2Pn36WNWqVd2+KF68uLVo0cJVXT106FCK1qFz585u3S+77LJEx1u1alVgO+fNmxe21Pree++1008/3Z1vWq8aNWpY//79benSpZYWGzZssAEDBljlypXd76BSpUrWr18/W7lyZbLa0ydWvT+xZFmh00+bNs3at2/vzvGcOXPGuy789ttv7rN+rzVr1nTnsZKFaX9ov2zdujXi/FXdWLRNodeFlCSF0vmic0Dngs4JHQOdIzpXEmufG/ybP3z4sKtOf9ppp7n24DqW2qZZs2ZFnF5tyPV71fVGy9W5XLp0aTvllFOsb9++br8BQKbiAUAmNnz4cEU47pWU1atXB8adMmVKguGtWrVywzTPYEePHvXOO++8wLR6FS1a1MubN2+873xvvfWWV7ZsWS937tzu+4IFC7rPwa+1a9cGxt+5c6fXunXrwHxy5crlFStWzMuRI0fgu8GDB4fdJq1b9+7dA+NpmuLFi3s5c+Z0n3v27On17dvX/a33UFWrVnXDHnvsMe/kk092f+fJk8ctX39rn8ncuXPjbWuRIkW8woULx/uuW7du3rFjxxIsI3jaTz75xMuXL19gHwZvY48ePdz4L774otsHGqZxgpcxdOhQLzUOHToUbz9p/wTvJ39fHT58ODDNt99+645VqVKlAuPob/8YNmnSJNnL79evX7ztKFSokFegQIF43/n7Oti4cePi7SPtD/+80qtBgwbexo0bE93nwd59993AMd62bVvE9R0xYoQbr3r16t7x48fjDZs9e3bg/NBL66Nz3P+seb/yyiteavz444/uuPjzyp8/v9tX/jn39ttvR9xf/rVAv+NIIu2X0OnvuOOOeL8nnY/B1wX/d6OXzucSJUrEO04VK1b0li9fHm/+Y8eOdeeNf85pe0KvC8H8eWmdQ61fv96rX79+vGMQ/FvRMp566qmw+8Bf9wkTJnjNmjULTO/vZ3+7J02alGDa3bt3e6eddlq88XQuxMXFBb7T/AEgMyGgBeBl94D2tddeC9y4vvTSS96ePXvc97rR37Rpkzd9+nSva9euyZ5fqC5dugQCAd2E7tu3z33/zz//eFdffXVgnZ977rkE0z7yyCOB4boJ37p1q/t+165d3sMPPxy4IU8qoNXNbLly5bz3338/ENStW7cusC4LFizw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umC2SJEi9vbbb7vlaR6ff/65lS9f3v7++2+7+OKL7dixY6nYOwCAaCKgBQAAUaVgcunSpfbaa6+54FJVdZNLpaMPPvig+1vVi1UVX6WzolLomTNnWrly5VyAef/99yeY/s8//3SluDJy5EhX7TlPnjzuc5MmTezjjz921Y///fdfFziHe3D06aefur8nTpzoAmeVEMt5551n06ZNc38vWbLkhLRnBwDER0ALACeQbojVrtCvnvnAAw8E2hr6L1WzTCoplD+u2uCq9EklYqqirBt3VYtUCd6WLVsC46uE6YYbbnBtElUVU1VVBw0aFLFtYGJJh6666io3TO/y3nvvufHUblFtJxs2bGjjx4+348ePJ1rFVCVyam9ZuHBhV626WbNmLjDRsNBlBDt69KgbT8tU1VuVypUsWdIFP927d3ftKNNK+zW4Deivv/5qPXv2tAoVKrh9XLduXXvsscfcuvi+/fZbu+SSS1yJnvZx/fr17ZlnnnHbE44CrAkTJriqspqf9oHmXatWLbvmmmtcgBhJeh+flFL13NR6//33A+fhXXfdlWB48eLFXZVmUXCpwDaYqiOr5FRB8C233JJg+ho1arjzQhRwh1J14tDxgukc9fdzWkrE161bZ0OGDHH73z/W+s3q+Gu+Bw8ejDf+gQMH3Dmm5Wsf6DwvXbq0Kwnv27dvINAWlS7756vO1cT06dPHjacq1gAQkzwAwAnz1ltveWXLlvVy586tKMcrWLCg+xz8Wrt2rRu3atWqbpwpU6YkmI++1+uVV17xKlWqFJhXnjx5AsPq1q3r7dixw/vhhx+8kiVLuu+KFCnixcXFBcY566yzvKNHjyaY//Dhw93wVq1aJRjWt29fN0zvN910k/s7Z86cXrFixQLz1atPnz5h94GW171798B4OXLk8IoXL+7moc89e/aMt4zQac8777x4yylatKiXN2/eeN+l1dy5cwPz+uSTT7x8+fIFlqX19Yf16NHDjf/iiy96uXLlcsM0TvC6DB06NOwy/G3US8ekRIkS8Y6Ntum9994LO216Hp9o0Dmb3GOhfajxTjnllIjjLFiwIDC/WbNmxRvWvHlz932HDh0iTv/2228Hpl++fHm8YeXKlXPf33jjjRGnHzNmTGA/7t+/30upV199NXAO6aXfqX6Twcd78eLFgfF3797tnXbaafF+Izp+wePr+hCsXr167vvBgwdHXI+9e/e664TGe/nll1O8HQCQGVBCCwAnkEqAVDKndoEyePBg9zn4Vbly5WTP77bbbnOllN9//73t3bvXvaZOnepK4pYtW2b33XefdevWzU477TSX3EbtElUappJBlaqpVNFvu5hSM2bMsBdffNG1mVS1Ub22bt3qShdFpU5ffvllgulU7VPtFEUlyypJ3r59u5v+4YcftrfeesvNOxxt2+zZs10J6EsvveS2ZefOna40S5lslSW3a9euFk1XXHGFK0VTKbeWpX3olyRqXUePHm033nije+n4aRxtj19Cqu1VtdhQKonVMFVl1fqrpF3JjXSc1KZTf6skbuPGjRl6fDKatldUoh1J8LDQkmv/c2qm1z7XMUvu9CrV1u8qJVTlWcdRJbBnnXWWzZs3zx1vHQuVNuvztddeG6gmLSpB/+WXX1ypukpiNb6On86JDRs2uGPXvn37BCWv8uabb0YsfVdpuJZZsGBB69KlS4q2AwAyjRMdUQMAPFeypkuyStoiSU4JrUp0t27dmmD4fffdFxhHJTcHDx5MME7v3r3d8Hbt2qWqBDDSuknjxo3d8GuuuSZBCZFKiTWsf//+Yaf1lx2uhPaGG25w31933XVeegouoVWJ8PHjxxOM07Jly8A4odvplyZXr17dDR81alSK16Fjx44Rp02v43MiSmhVMq3xBg4cmOh4fgnzoEGD4pVk+ssZP358xGl37twZGG/ChAmB73/99dfA9x9++GHE6X/++efAeB999JGXXEeOHAmcA2effbZ36NChZE134YUXumkefvjhZC9r/fr1gVoOn332Wdhx2rdv74b36tUr2fMFgMyGEloAyEJUsqP2o6HUl6dPpaDqhiTSOEm1uYtEJckqeQqnU6dOYeetrLF+ptl77rkn7LRq26sS5nCKFSvm3v1StYwwdOjQsP2pBu/jcG0/VQLut1NMzT7u2LGje//mm28so47PieC3n410zH3+8OB238F/JzZ98LBoTp+UuXPnBjIzq/ug4FLYxPjn+T///JPsZVWsWNHatm0bsa2w5qUEWNK7d+9kzxcAMhsCWgDIQs4444yw35ctWzbwd9OmTRMdR1UZU0PzDRfoiZIniareBvvpp5/cu5JSKUFVOEoSFam/UPVDqmWqOu2FF17oqiCntkputPaxqoUqoVBq9rGqlaqqcoMGDVyX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92633Qs/sIL12ljpS+6ywqd3cOMXb3uNHVyz2PavXJDs5X322WfupaA21IoVK+yPP/6wt956y66//np77733bO/evfFKae+++24755xzrH379lHaAwAAAACQOIrSMqkWLVok+C53iYqWp1QVO7Jtnft8eMNys+PHrGDdc+KNp88qtd237H/JWtaRI0fstttuc6+aNWsmGH7gwAH3Xrx4cfdeoEABy58/v+3fv999/umnn+yNN96wJUuWpGJLAQAAACB1KKGNIZ7n2bF9Oy1n/iL/fT52xL3niMsbb7wcuf/7fPjfVcma75NPPmk7duywe++9N+zwk08+2YoWLWojRoxwpcaqdqzqxo0aNXLDb731Vtfet1atWmnaPgAAAABICUpoY8i+37+yY3u3WbGWV7rPcSUquveDG363fFUbBMY7tG6pe9e4Sfn3339t1KhR9thjj1mRIv8FyqEKFixozz33nPXv398lfsqVK5dr41u1alV78803beXKlfbJJ59EaSsBAAAAIHkooY0Rqma8/fPnLG+FOlawfjv3Xd5ytSxP+dq2e8E02/vrbDu6a5MdWLXItn32jFnOOPOOHEpyvkOHDrUaNWrYNddck+h4PXv2tA0bNrh2s3ofNGiQq3Ks6R966CErVKiQPfDAA25eDRo0sPfffz9q2w4AAAAA4VBCGwOO7d1hm997wHLmLWilLrnLcuTMFRhW+tK7bOuHj9q2T8f/90WOnFak6SV2cN1vYbv4Cfb999/ba6+9Zl988YXlzJn0sw21oW3evHng8yOPPOIyHvfr188mT55szz//vGtLu2bNGuvevbvr6odqyAAAAADSCwFtJnf80D7b9O5wO35wn5W9cozFFS4Zb3hc4VJWrtejLng9tm+H5S5e0XIVKu76o81dokKi8x4yZIjrW7Z69eouCJWtW7e693/++cfWrl1rVapUCTutxn/88cft888/d8Hw1KlTbcCAAda2bVs3/JVXXnFZkSO1ywUAAACAtCKgzcQOHjxom98baUd3bLCy3R90GY4jUQZkveTw1rV2bO/2QNXkSBSwKsmTAtpQnTp1comgdu7cGXbawYMHu3HOPvts93njxo1WocL/D6D1t6omAwAAAEB6IaDNpI4dO+aq7R7auNzKXHav5a1YN1nTed5x2/nVFJfpuPDpF8brmmfVqlUuSC1fvrz77oUX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vJdeesmNoza0vXv3juYqAAAAAACyiahWOT5+/Li1a9fOdd+jYFX9z7Zp08b1Rev3Qzt37lzXF60W27p1a/viiy/cuAAAAAAAnLCAVtR+9uqrr7bp06f/t4CQYNVfnPqpVSmu2tACAAAAAHDCA1rfDz/8YG+//bYtWrTINm/e7L5T9uMmTZpYjx49rGnTpumxWAAAAABANpFuAS0AAAAAADGTFAoAAAAAgIxCQAsAAAAAiEkEtAAAAACAmERACwAAAACISQS0AAAAAICYREALAAAAAIhJBLQAAAAAgJhEQAsAAAAAiEkEtAAAAACAmBSXXjM+fvy4/fjjj/b333/b/v37rU+fPum1KAAAAABANpTD8zwv2jOdMGGCPfjgg7Z169bAd8eOHQv8vWPHDmvZsqUdPXrUvv76aytbtmy0VwEAAAAAkMVFvcrxTTfdZLfffrtt2bLFChcubDly5EgwTvHixa1Ro0a2YsUKe/fdd6O9CgAAAACAbCCqAe2sWbPsueees0KFCtn7779vO3futNKlS4cd94orrjAVDs+ZMyeaqwAAAAAAyCaiGtA+//zzrkR25MiR1rlz50THPfPMM937kiVLorkKAAAAAIBsIqptaMuXL2+bN292JbOqbhz8XXAbWl+xYsXsyJEjtm/fvmitAgAAAAAgm4hqCe327dutaNGigWA2yYXnzOmyIQMAAAAAcEID2iJFitju3btdqWtygt9du3ZZqVKlorkKAJAm1apVc00nXn755Qxf9ogRI9yyW7duneHLRvr56quv3HENlyQxo6xZsyawDvobAICsIqoB7amnnuoSPS1YsCDJcadOnerGbdKkSTRXAQAiUpCqoFEBBoDsafny5TZ58mTXK4PyeRQoUCBVDxymT59u559/vpUpU8by5ctn1atXtwEDBtjKlSuTnFb3P5MmTXJdGJYoUcLy589vJ598st1xxx22adOmJKc/fPiwjRs3zpo2bepqxikZp+7Bhg8fbnv27EnRdgBArIuL5sy6du3qbhR1w/j555+7KsXh/PLLL3bvvfe6/zx69uwZzVUAgEQDWvV9LZFKQWvWrOluTnWTmNFUY6V27dpWpUqVDF82kF1cf/31getAaigY7d+/v02ZMsV91r2OAkqVfL/wwgv2+uuvuy4JO3ToEHb6Q4cOucSZn332mfscFxfnrjnqyvCJJ56wV1991Q1r3Lhx2Ol37Nhh7dq1s8WLF7vPefPmtVy5ctlvv/3mXq+88orbvqpVq6Z6GwEg25bQXnvttXbKKafY3Llz7bzzzrOZM2cGkkHpQj179my79dZbrUWLFq66cfPmza1bt27RXAUASJMvvvjCleBceumlGb7sm2++2S1bN7QA0ocCSN2r9OrVy5VyqlQ0JcaOHRsIZlUiqvsZvfTb1f3N/v377fLLL7fVq1eHnX7gwIEuYM2dO7c9/fTTLjGmSlUXLlxoderUsW3bttlFF13kmnCFc+WVV7pgVs283n77bbc8zUMFCUrE+ffff9vFF18cNhknAGRJXpStWbPGq1OnjpcjRw4vZ86cYV8a1qBBA++ff/6J9uIBIKJWrVopq7s3fPjwE70qyEbmzp3rzrt0+C832VavXh1YB/2dnR09ejTe5ylTpiT7+Gzfvt0rXLiwG3fAgAFhh5crV84N79WrV4Lhf/zxh5crVy43/JFHHkkwfNWqVV7+/Pnd8HvvvTfB8Dlz5gTWderUqQmGz58/PzD8pZdeSnJ7ACAriGoJraiKy48//mgPPPCAqzanqjnBrwoVKrgqyfPnz7dy5cpFe/EAELaqsZo4+NUMdX3y28yFJspJLCmUP66aVqgURSU7qqKs9m+69qmEdcuWLYHxVVJyww03uLZ1qlKoa+KgQYMitnFLLCnUVVdd5YbpXd577z03ntrfqQ1gw4YNbfz48Ylmjtc1WCVLajeobPSqVt2sWTNXTVLDQpcR7OjRo248LVNVo1W6VLJkSVdFunv37q49YLRoG9555x275JJLrGLFiq5KZenSpV0VzKFDh7pqleGsWrXK7e+TTjrJHROVYDVq1Mj1jR6ptCs0YZPaP1599dVWuXJlt9xKlSq52kcbNmxIdJ1VOqeSM/2/pmNdo0YNu+WWW5LVHjK1lK+iX79+VqtWLXcOaHtV8qj196uzpnS/v/HGG66qbNmyZS1Pnjxuv7dv3z6Q9yKcf//91yZMmOCq0datW9edV9r/Wq9rrrnGli5dGnGZ0TivU0rVc1Pr/fffD/x+77rrrgTDixcv7qo0y7Rp0xJ0S6jqyCo5VRVlnR+hdN7o9ySvvfZaguGqThw6XjD9tv3rR1pqeqxbt86GDBni9r9/PHWt0zHWfA8ePBhv/AMHDthjjz3mlq99oOuDzh2dj3379nX7wqfSZf839+uvvya6Hn369HHjqYo1AESU3hHzhg0bvIULF3rff/+9K70FgIz21ltveWXLlvVy587tSi4KFizoPge/1q5d68atWrWqG0elNqH8ko9XXnnFq1SpUmBeefLkCQyrW7eut2PHDu+HH37wSpYs6b4rUqSIFxcXFxjnrLPOSlBKJCo51nCVJIfq27evG6b3m266yf2tGi/FihULzFevPn36hN0HWl737t0D46mmTPHixd089Llnz57xlhE67XnnnRdvOUWLFvXy5s0b77to2LJli3fOOefEm6+2sVChQoHPnTt3TjDd22+/HW99VIoW/Lly5cre77//nmjp6ZdffhlYjqYPPmYVKlTw1q9fH3adP/3003jL0jzy5cvn/i5fvrw3efLkqO4jHY9bb7013j7SeajjqePqH5+UlNBu27YtwX7XPII/d+rUyTt06FCCaf3zRi/tsxIlSsTbd9o37733XthtSet5HQ0pKaHt0aOHG++UU06JOM6CBQsC85s1a1a8Yc2bN3ffd+jQIeL0Opf96ZcvXx5vmF/6e+ONN0acfsyYMYH9uH//fi+lXn311cD5q5eub7qWBR/TxYsXB8bfvXu3d9ppp8W7tuj4BY+v62qwevXque8HDx4ccT327t3rzmuN9/LLL6d4OwBkHyeu/hMAZMIqx8kJaHWz1rBhQ/egTg4fPuyq/xUoUMANv/nmm9182rZt6/32229unAMHDngTJkwIVDd88cUXUxXQKmjRDea4ceO8Xbt2uWFbt271rrnmmsD6ffHFFwmmV/VGf/gdd9zhphHN4+GHHw4EuOEC2tdee819r5tcVWPcs2eP+/748ePepk2bvOnTp3tdu3b10urIkSMu2PeDIN2Yb968Od4D0okTJ3p33XVXvOl+/PHHwMMKTf/rr7+6748dO+bNmDHDBZUaVrNmzcC6hwtotf0K2pYtW+aGKXhTcOFXMe3du3eCdV63bp17YKHhakqjYMZftgJdPfgIDs6iYciQIYH5XX311a4aq2/nzp3eBx984B5eJDegVYDs/zZ0Xn/00Ufevn37AkGFHuCUKVPGDb/99tsTrM+oUaO8sWPHekuWLHHH0N9+nftXXnllIODW8Yv2eZ3RAW39+vXdeJdffnnEcbTv/Pk9/vjj8Yb555KOYSRLly4NTD9t2rTA99of/vfPPvtsxOk//vjjwHj6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v7Kj29db8Tb9LWee/G68o7s2296lX7q/D/2z0r3vnP+We48rUsbMOgbmWbduXWvVqpV99dVXge9+/fVXmzFjhvt75cqVtmvXLnvwwQfd59NOO80uvvjiBOumZE9KEtWtW7fAd0oMNXLkSLvhhhusatWqri2tuvkBAAAAgPRCQJtJde/e3SZNmmR7fv7Ejh/Y4wLYPGVrWfHW/azASc0C4x3d9a/tmvd6vGn9z3kr109yOT/99JMLUIP5n/v27ZsgoF2yZIm99NJLtmDBgnjfK8CdMmWKjRgxwlVFvvHGG+26665LxZYDAAAAQPLk8LJ5R7L16tVz70uXLrXMqNqwj9M0/ZrR/7+EFgAAAACyUjxGG1oAAAAAQEwioAUAAAAAxCTa0GZxaamyTHVlAAAAAJkZJbQAAAAAgJhEQAsAAAAAiEkEtAAAAACAmERACwAAAACISQS0AAAAAICYREALAAAAAIhJBLQAAAAAgJhEQAsAAAAAiEkEtAAAAACAmERACwAAAACISQS0AAAAAICYREALAAAAAIhJBLQAAAAAgJhEQAsAAAAAiEkEtAAAAACAmERACwAAAACISQS0AAAAAICYREALAAAAAIhJBLQAAAAAgJhEQAsAAAAAiEkEtAAAAACAmERACwAAAACISQS0AAAAAICYREALAAAAAIhJBLQAAAAAgJhEQAsAAAAAiEkEtAAAAACAmERACwAAAACISQS0AAAAAICYREALAAAAAIhJBLQAAAAAgJhEQAsAAAAAiEkEtAAAAACAmERACwAAAACISQS0AAAAAICYREALAAAAAIhJBLQAAAAAgJhEQAsAAAAAiEkEtAAAAACAmERACwAAAACISQS0AAAAAICYREALAAAAAIhJBLQAAAAAgJhEQAsAAAAAiEkEtAAAAACAmERACwAAAACISQS0AAAAAICYREALAAAAAIhJBLQAAAAAgJhEQAsAAAAAiEkEtAAAAACAmERACwAAAACISQS0AAAAAICYREALAAAAAIhJBLQAAAAAgJhEQAsAAAAAiEkEtAAAAACAmERACwAAAACISQS0AAAAAICYREALAAAAAIhJBLQAAAAAgJhEQAsAAAAAiEkEtAAAAACAmERACwAAAACISQS0AAAAAICYREALAAAAAIhJBLQAAAAAgJhEQAsAAAAAiEkEtAAAAACAmERACwAAAACISQS0AAAAAICYRECbzRzZvsG2fDjG1j/T19Y+3sU2vHi97fx2qh0/cjDJaTds2GCXX365FStWzIoUKWKdO3e2v/76K944hw4dsltuucVKly5tlSpVsgcffDDBfNavX2+FChWyb7/9NqrbBgAAACB7iTvRK4CMc3T3Fvv31TssR96CVrjxRZYzX2E7tGG57frmDTv870or0+W+iNPu3bvX2rRpY7t27bK7777bcufObU888YS1atXKfv75ZytZsqQbb+zYsfbqq6/aPffcY3v27LGRI0dazZo1rWfPnoF53XnnndapUyc766yzMmS7AQAAAGRNBLTZyL6lc+34oX1W/spHLU/pqu67wg0vMLPjtu+3L+3Ywb2WK1+hsNM+++yztmLFCvvhhx+sadOm7rsLL7zQ6tevb48//rg9/PDD7ruZM2faoEGDbMiQIe7zunXrbMaMGYGA9ptvvrGPPvrIli9fnkFbDQAAACCrospxNnL80H73nqtgsXjf5ypYwixHTsuRM/Lzjffee88Fsn4wK3Xq1LF27drZO++8E/juwIEDVrx48cDnEiVK2P79/y33+PHjdtttt7lgV9WRAQAAACAtCGizkXxVTnXv2z59yg5v+stVQd637H+2Z/EnVrjxxZYzT76w0ykQ/fXXX61JkyYJhp1xxhm2atUqV71YFPC+8MILtmTJEvvuu+9s6tSpbhyZNGmSbd261VU5BgAAAIC0ospxNpK/RmMr2rKX7f7uXftn5YLA90XO7G7Fz+kdcbrt27e7ZE/ly5dPMMz/buPGjVa7dm0bMWKEXXDBBdagQQP3fcuWLV2prNreql3thAkTLH/+/OmyfQAAAACyFwLabCauaFnLW7meFTi5heXKX8T2r1pou797x1VDLtL44rDTqBqx5M2bN8GwfPnyxRtHVYkXL15sS5cutTx58rhqyTlz5rQ77rjDBbzdu3d37WjVzlZB8KWXXmqPPfaYGxcAAAAAUoKANhvZ9/vXtn3W01bh2okWV6SU+65A7RZmnmc7v37ZCp7SygW5ofwSVZXShjp48L/ufoJLXZUBuWHDhoHPSgClpFLz5893pb0dO3a0YcOGuazJ/fr1s4ceesgeeOCBdNlmAAAAAFkXbWizEbWVzVO2RiCY9RU46Qzzjhxy7WrDUWInlc7+888/CYb531WoUCHicgcOHGi9evWyRo0a2ccff+zmd9ddd1nz5s1dgqg33ngjzdsGAAAAIPuhhDYbObZ/p+XMm7BbHu/Ysf/+OP5/7yFUZfjUU0+1RYsWJRi2YMECq1GjhhUuXDjstOrGRyWz6vJHVM04uC2uAuENGzakdpMAAAAAZGOU0GYjuYtXsMObV9mR7fEDyH3Lvnbd9uQuXc19Prp7sx3Zti7eOF27drWFCxfGC2r/+OMP+/LLL61bt25hl3f48GHXdvbee++1MmXKuO/Kli1rK1eutKNHj7rPy5Yts3LlykV9WwEAAABkfTk8z/MsG6tXr557VxKjzKjasI+jNq+D636zTVPvtpz5i1jhRh3/LynUD3bwrx+tUIP2VvLCW914/745zA6t+82CTw11y3P66ae798GDB7t2suPGjbNjx47Zzz//bKVLl06wvLFjx9qLL75ov/32WyDp0+bNm6169equHW2LFi1s1KhRds0119iYMWOitp0AAAAAskc8RpXjbCRf5fpWrtdY2/ntm7Z38Sd27MAeiytW1oqd08eKNOuS6LSqUvzVV1+59rAPPvig65u2devW9sQTT4QNZjdt2uSCVbWPDc5grJLaadOmufnMnj3bOnXqZMOHD0+X7QUAAACQtVFCm41KaFNqzeiOJ2zZAAAAALK+emmMx2hDCwAAAACISQS0AAAAAICYRBtapFt1Z6osAwAAAEhPlNACAAAAAGISAS0AAAAAICYR0AIAAAAAYhIBLQAAAAAgJhHQAgAAAABiEgEtAAAAACAmEdACAAAAAGISAS0AAAAAICYR0AIAAAAAYhIBLZLt+OEDtnPeG7bpnftt3fge9veYi2zvkjnJmvaff/6xYcOGWZs2baxw4cKWI0cO++qrr8KOO3HiRKtevbqVKFHCevfubbt3746/HseP2+mnn24PP/xwVLYLAAAAQGwioEWyHT+w23bNn2pHtq2z3GWqp2jaP/74w8aMGWO//PKLHTp0yH13ww032OzZs+ON980337jvO3fubCNGjLA5c+bYnXfe6d4VDJcqVcoKFSpkv//+u5UrVy7etJrvLbfcYqVLl7ZKlSrZgw8+mGA91q9f76b/9ttvU7UPAAAAAGQeBLRItlwFS1ilm16zSjdMseKtr07RtI0bN7bLLrvM9uzZYxdccMF/88uVyzp06OCCWN/MmTOtdevW9uSTT9qtt95qjzzyiL3zzjvWvn17O3z4sA0ZMsRy5sxptWrVsv79+9sTTzwRmHbs2LH26quvugC4X79+NnLkSJs6dWq89dCwTp062VlnnZXm/QEAAADgxCKgRbLliMttuQoVT9W0y5Yts+nTp7sAtU+fPu67cePGWdWqVV2Q6jtw4IAVL/7/l6FqxwqCy5cvb59++qkLbo8cOWJ//fWX5c2b1yZMmBAvGB40aJCb36hRo+yKK66wGTNm2Pvvv2/nnXee5c+f39566y0X5Gq+L7zwQrx19DzPHnjgAatYsaKVKVPGbr/9dre87t27uyrQuXPntri4OFddWp+DS5fDTasAXMs+//zzrUKFCm59FcSfccYZ9ttvvyW5bE2vUuehQ4e66bX+efLksbvuuivJ/R26XJVYd+3aNdnLDbZ37143/M0330zGkQYAAEByhd7rNWvWLEENxnB0X6171Bo1aliBAgWsdu3a7j54586d2e5ej4AWGeK9995zwdx1110X+E7BmUpZv/vuO1u3bp37rmnTpjZr1iz7/PPPbcWKFfb4449bwYIFXZDbo0cP+/HHH61nz542fvx4F2CuWbMmUMIbLhjev3+/LVmyxFV5PnjwoNWsWdNVZ9b3AwYMsJdffjkw/htvvOHa5WqddEGYPHmyK+lVMK510AVBVZ5l06ZN8UqXw02rddeyixQpYieffLILhDWPX3/91ZVYq/p1YstWm+MGDRq4kufNmze79dc+e+yxx+KVaoe7UP38889uX9x222327LPPuvl+8MEHCZar7VdJttop79ixwz0guP766+Mdu4ceesiqVavmHhD4tD8HDhxoLVq0sHz58rlt07EIldhF1L+A62GFplepe3Iu4Nl12bJhwwa7/PLLrVixYu680rmshzvJrXrvL7ts2bJu2aecckqGbXd6/qeZ2puBaOzTWG3OwD5LHfZbyrHPshaOZ/RdddVVrpDnyiuvdPe34WowhqN7at2j9urVy5566ilXA/Lpp5+2M888090T+yLdoyZ1rxdTvGzulFNOca/MqurQmZnyVa7PE55On5Idbo84TrBzzz3Xq1u3rvv73XffddPOnTvXmzNnjvt7xowZbtjRo0e9yy67zH2nV+XKlb2rr7468Llhw4beypUrvZEjR3o5c+b0ypUr55155plu2v79+3v169f3fv31V2/+/Plu2IMPPugtWLDATVusWDFv//79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Y8eGxWyJEiWcChUqhE07ePBg2Do6vsaNG4fta+h7at1nn33W9/0U+1rmueeec9q0aeOt755PPRSvupb86HibN2/uLatz754nrffCCy947zFp0qSwdXfs2OGcddZZEd9JRYsW9ab5XVsAUJCQ0AIoFPRjXj8O9QOvSZMmzsaNG7O8jeHDh3s/MkeNGuX9KD5w4IAzYsQI7wekErDMJm7Lli3z1lu/fr3v+27bts3bdyUeoV599VUviXvsscecPXv22Om//fabs3LlSqdz5852fo0aNZy0tLQsHe+qVau8xEYJtbt/Stjffvtt+6Pb/eGdkwmtjk0P/Zh3k9J9+/bZRMD9UX/99dc7U6dO9aaJjlfrKCHTdu69994sJXyhYiW027dv987Deeed5yxatMibt3DhQqd+/fp2npKq9Ilh6PsrEWnfvr2zYsUKO+/XX391Zs+ebW9IuEl5TiW0em8lie5nrGTx3Xfftcmt5t94441Zfu+6devadbt27eqsXbvWm37s2DHnm2++ccaMGRORoClu3c/9/PPPd6ZNm+YcOXLEzjt8+LDz+uuve0n/0KFDI95TyV9o0vvll19687QdJdm6KXXo0KGw93QTUSWxb775ppcsf/fdd06PHj286/7TTz+NeE832dQ5rF69uvPRRx95N7U2bNjgtG3b1ovjn3/+OWJ93TjRfCX/L730kj0/snXrVjtPybFucPgltLfeequdXrt2bWfOnDn2WNxj0vovvvii7/cRABQkJLQACo2BAweGlZioVOSuu+6yJSf6wf37779HXVeJiJvc3X///b7LDBs2zCud2b17d6YTNzfhibbdxx9/3M5XSUzoPv7yyy9eIjVz5kzfdZUUtWzZ0i4zceJEJyu6d+9u1zv33HMjSjdF7+mez5xMaPVQshovJYhuqambLGRnQjto0CAvoXFvKIRSwq0SVi2jEvlo768Sb7/zrGN3l9G2ciKhveSSS5xTp05FrK9SSc0vWbKkjaXM+vHHH71tp78WYpkyZYp3LvySP9GNGl2/qimg93HpxpKbgPft2zfm9RzqnXfe8fZ11qxZEfN13G7Cq1LcaAmtElK/m1J79+61pcRaRslytBtafiW4Skwvuugib5n0CW3Dhg3t9LfeeitTxwoABRFtaAEUGi+88IIZNWqUbdumG3pffvmlnXbrrbeaJk2a2HZnw4YN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", + "image/png": "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gt5880317t07y74UKVJEXbp0kfS/y4zT/m3fvr11/zMAoGAQaAEAOXL06FF98sknGZanpqZq3LhxkqSqVauqZs2akqSAgABJ10ZLMwt8a9asyTLsJCQkZNsXb29vSdcmTErj5uamgQMHSpI++eSTDDMnXy9tAqn8SAvQFy9ezPe2nE1wcLBatmwpSZo0aZISExMzlPnuu++0adOmTOvn5TW+ER8fH2tEdPz48dYfVW4kv31Ju6z466+/1oEDB6yRWi43BoCCR6AFAORIQECAhgwZopkzZyo+Pl7StVHYXr16acOGDZKkN954wyp/7733SpJ2796toUOHWgEyLi5OH330kbp162ZNJHW9CRMmqH379po/f77dpc4JCQlasmSJJk6cKEnq0KGDXb2XXnpJFSpUUHJysu69915NnjzZbvKiS5cu6auvvlL//v2zfJxMbtSoUcPaxx9//DHf23M2r7zyimw2m37//Xd17tzZemRQcnKyli9frgcffFBBQUGZ1s3ra3z48GFrluzMLgF+4403FBISonPnzqlJkyZasmSJdW9rQkKCdu7cqeeff95uEqennnpKDz30kD7//HO7Sc1iY2P14Ycfat68eZn2JU2bNm1UvHhxJScnq3fv3rp69aqCgoLUqVOnGx1CAEB+3dSn3gIAnE5kZKSRZF544QXTtGlTI8m4u7uboKAgI8n6evHFFzPU7dmzp12ZwMBA4+rqaiSZO++807z33ntGkomIiLCrN2bMGLt63t7eJjg42NhsNmtZ1apVzcmTJzO0efDgQVO7du0M7fr7+9stq1ixYr6PTVJSkqlcubK1zaCgIBMREWEiIiLM0qVLrXL9+/c3kkz//v2zPL5jxozJsp2IiAgjycyZMyfLMml92LBhg93yDRs2WOuuN2fOnEyPf3qHDh2y6h86dCjD+ilTpmQ41p6enkaSqVGjhrW+cuXKdvXy+hqn709Wx+yXX34xJUuWtMq5urqaoKAgu21PmTLFKp/2+qR9+fn5mcDAQLtlTZs2NbGxsVkep+HDh9uVf/zxx7MsCwBwHEZoAQA54uHhofXr12vcuHGqXLmyEhISFBAQoFatWmn16tV67bXXMtT59NNPNXXqVNWqVUuenp5KSUlRzZo19eabb+qHH36Qn59fpm0NHjxYM2bMUK9evVSjRg35+Pjo8uXLCgoKUrNmzTR1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cMxyj2OWNxy32wuvp+OlfUYdMmSIZs6cqQ0bNmjw4MH66quvMv2Mmn5+mfvvv18vv/yyli9fro0bN2rhwoW69957tXHjRr3xxhsZJu+S/vcZdcaMGdb69Fcgrlu3Tk8++WSWVyD+Y+X1eT/fffedcXd3Nzabzbi4uBgXFxe773P65erqmtcuOATPob09v64XExNjnnnmGVO8eHHj4eFhatWqZRYuXGhXJu0ZtIcOHbKWDRs2zPj6+hpJxsXFxRQtWtRUrFjRSDILFiywyg0cONC4urqaQ4cOmWbNmpmhQ4eap556ykgy4eHhpkiRImbAgAGmbNmyxtXV1UgyL7zwQob669atM15eXmbz5s1WfQ8PD+Pv729q1aplGjdubCQZT09PEx0dbVf38OHDZs+ePVb9pUuXmqVLl5o33njDSDLlypUzL730kmnVqpUJCAgw0dHRpk2bNiYoKMjMmDHDzJ071+pb2r4lJCQYX19fY7PZzIIFC8ygQYOsumnbnzBhgpFkhg4dai3LTPq+GWOstoODg02TJk1Mhw4djCTj7e2d6b6ll5+209p9//33TVhYmImIiDCSTIsWLax9K4h2b+U+56ft+Ph4U716dVO6dGnz6aefmm+++cY88MADxs3NzURFReWobWOuvZciIiJMly5dzDfffGP3Xsqv9O/jb7/91jz22GNZ/o46274VFI5Z7nHM8objdnvh9XS8vH5GnTBhgmnQoIEJCgoyrq6upmjRoqZdu3Zm1aqMn4ONMSY1NdX6jHq9X375xdx1113G19fX1KxZ06xbt86h+3gj+c1jNmPy/ieNLVu26J133tGuXbt05coVHT58WK6uripVqlSutnPo0KG8diHfqlevLul/04X/02T2CBsUrMPjO9r9HBsbq3//+99asmSJzp8/rypVquiFF15Qz549rTIDBgzQJ598okOHDlmjw2XLlrX7S1p6ISEhdvc2pNVv0KCBGjZsqGnTpmVbPywsTKdOnbKre/DgQfXv31+1atXStGnTsp18Yd26dWrVqpXdvp0+fVrBwcGaNm2a3b7dd999WrVqlby9vVWxYkVNmTJFrVq1ynb7159WjDGKjIy0+pb+uH766ac6f/68bDabSpYsqTlz5qhVq1ZZHlcp+4klbtR2+n0+d+6c3N3dlZKSojvuuMPat4Jo91buc37bPn36tEaOHKlV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X375JVfbrVmzpmbOnKkmTZpIkr755psc17377rslSadPn9a2bdty1e71XFxcdNddd2nZsmUqU6ZMln255557VKpUKaWmpuqzzz6z/pUYnQWAwsihgXbSpEkyxmjixIk3vPzorbfeUkpKit566y1r2ejRoyX9b5IJAACu5+/vL0m6ePHire3ITda2bVtr38eNG5dpmSlTpujKlSuZrktISMh2+97e3pIkV1fXHPepZcuWKl++vCTp2WefVWJiYo7qZdcXV1dX64/hmfXFxcVFDz/8sKRrI7NpI7Wurq7MwwEAhZBDA+22bdsUGBiYoxmLS5UqpcDAQG3dutVaVrlyZfn4+OjMmTOO7BYA4DaSdknpl19+aV1yWhj4+vpq1KhRkqSZM2dq5MiR1kzIMTExmjBhgsaOHaugoKBM63fp0kUDBw7UmjVr7P4YcP78eb3++uvWI286dOhgV2/u3Lmy2Wyy2WyKioqyW+fq6qpp06bJZrPp+++/V6tWrfT9998rNTVVknT58mVFRUWpT58++uOPP6x6jRo10jPPPKOoqCjFxcVZy0+cOKGnn35af/75Z6Z9SZM2Ertr1y698MILkq4F/vQzVAMACgeHznIcExOj1NRUJSUlyd3dPduyiYmJiouLy/DXV3d3d6WkpDiyWwCA20j//v01adIk/fnnnypTpoyKFSsmLy8vSdL333+vUqVK3eIeFpyRI0dqx44dWrZsmSZOnKhJkyYpICBAly9fVkpKivr27SubzaZ58+ZZxyTN1atXNWfOHM2ZM0fS/0a6055tK0ndunWz7qfNqfbt22vu3LkaPHiwvv/+ezVr1kyenp7y9va2C84jRoywvr948aLee+89vffee7LZbAoICFBSUpJduH322WfVtm3bTNusVq2a6tWrp+3bt1uXOnO5MQAUTg4doS1btqySkpKse1mys3DhQiUlJdlN9R8bG6tLly5ZE18AAHC9SpUqacOGDercubOKFSumc+fO6ciRIzpy5EieH6PjLNzc3LRkyRJ9/PHHatiwoby9vZWcnKz69evr448/1rx586wQGRgYaFf3vffe04QJE9ShQwdVqlRJxhhdvXpV4eHh6ty5sz7//HMtXbo0T4+86devn/bu3athw4apWrVqcnNzU2JioipUqKAuXbpo/vz5qlq1qlV+0aJFeuWVV9SqVSuVK1dOiYmJ1meCHj16aP369Zo8efIN20zj7++v+++/P9f9BgA4P5tx4KwaY8aM0WuvvSYfHx/NnDlTvXr1yrTcokWLNGjQIF29elUvvviiXnnlFUnSpk2bFBkZqfbt22v16tWO6hYAAIWCMUZlypTRsWPHNG/ePLvntAIAcDtyaKC9cuWKGjRooD179shms6ls2bJq3ry5wsPDZbPZdOLECW3cuFGHDx+WMUZVq1bVzz//LB8fH0nSY489plmzZmnSpEl69tlnHdUtAAAKhXnz5ql///5yc3PTkSNHFB4efqu7BABAgXJooJWk6Oho9evXT2vXrr3WwHWzHac116ZNG82bN89uAod9+/YpPj5eFSpUkJ+fnyO7BQDAbaFXr1568MEH1aJFC4WEhEi69ticOXPmaMyYMUpMTNTAgQM1a9asW9xTAAAKnsMDbZoffvhBS5cu1fbt2xUdHS1jjEJDQ1WvXj1169ZNTZs2LYhmAQC4rQUGBurSpUuSJB8fH7m7u1s/S1KzZs20atUqa9InAABuZwUWaAEAgOPNmzdPa9as0Y4dO3TmzBnFxsYqMDBQderUUc+ePdW3b98bPmkAAIDbBYEWAAAAAOCUHPrYHgAAAAAAbha3gtrwsWPH9OOPP+rYsWOKi4tTdgPBL7/8ckF1AwAAAABwm3L4Jcdnz57VE088of/85z/Zhljp2ozHNptNKSkpjuwCAAAAAKAQcOglx3FxcWrRooVWrFghd3d3NWjQQMYYubu7q0mTJqpQoYKMMTLGKCgoSJGRkWrevLkju/CPs2PHDnXp0kXh4eHy8fFRlSpV9Oqrr+rKlSt25bZv367WrVvLz89PgYGB6tq1qw4ePJijNv7973+rbt26Cg4OlpeXl8qXL6/BgwfryJEjduUuXLigXr16KSgoSOXLl9eMGTMybGvr1q3y9vbWnj178r7TAAAAAHATODTQTp8+XX/88YcqV66sgwcPasuWLZKk4OBgfffdd9q/f78OHTqkhx56SBcvXtS9996rDRs2OLIL/yh//PGH7r77bh0+fFhTp07VqlWr1LNnT7366qvq1auXVW7v3r1q0aKFEhMTtWTJEs2ePVv79+9Xs2bNFB0dfcN2Ll68qF69eumTTz7RV199pREjRmjVqlVq1KiRzp07Z5V77rnntGPHDi1YsEBPP/20hgwZok2bNlnrk5OTNXjwYI0cOVJVq1Z17MEAAAAAAAdz6CXHjRs31k8//aTly5fr/vvvlyS5uLioePHiOnHihF3Z3r17a/Hixfr666/VqlUrR3Uh14oXL664uDiVKVPG4ds+c+aMzp49q4oVK8rDw8NafuLECV28eFGVK1eWq6urdZ9xxYoV5erqKklKTEzUn3/+qaJFiyosLCzXbcfExOjvv/9WiRIlFBQUJEnat2+fihcvroCAAEnSkSNH5OXlZW3/7NmzunjxosqXLy8XF+YLAwAAAFCwjh49Kl9fX506dSpP9R2aWvbu3StJuvfee+2WJyUlZSj7xhtvyBij9957z5FdyLW4uLhM++cINptNkjKEw7TQarPZZIxRTEyM/P39reWS5OHhIV9fX8XExOSpbTc3N7s+SNfuWU7fFxcXF+s+58TEREVHR6tEiRKEWQAAAAA3RVJSkuLi4vJc36EjtN7e3vLx8bG7zNXHx0fGGF29ejVD+aCgIHl7e2cYvb2ZqlevLknavXu3w7d9+PBh1a1bV61bt9aECRNUrFgxbdy4UX369FG/fv307rvvat++fapSpYqmT5+uJ5980q7+888/r0mTJunKlSvy8vK6YXvJyclKSkrS3r179fTTTys6Olq//PKL/Pz8JEnt27dXSkqKFixYoAMHDqht27aaM2eOHnroIbVr106lSpXSrFmzHH4cAAAAACAz+c1jDh2KCwsL0+XLl5WammotK1asmBITE3Xs2DG7sikpKYqLi7MLv7ebsmXLavPmzfr9999VoUIF+fv767777lP//v31zjvvSJK1/8HBwRnqBwcHyxijCxcu3LCtU6dOyd3dXT4+PqpXr56Sk5O1YcMGK8xK0tSpU3X48GGFhYWpadOm6tmzp7p3764FCxbo119/1cSJEx205wAAAABQ8BwaaCMiIpSammo34lqnTh1J0ooVK+zKfvHFF0pOTlZoaKgju/CPcvjwYd13330qWrSoli1bpo0bN+qtt97S3LlzNWjQILuy6S8Nvl5269KEhITo559/1vfff6+ZM2fq/PnzatmypU6ePGmVqVy5svbu3asDBw4oOjpas2bN0oULFzR8+HBNmTJFwcHBev/991WhQgWFhITo4YcfzlGYBgAAAIBbwc2RG2vVqpW+//57ffvtt+rXr58kqUePHvrvf/+rF154QfHx8apTp45+++03vf7667LZbGrf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", "text/plain": [ "
" ] @@ -60,8 +60,8 @@ "plt.rcParams['figure.dpi'] = 150\n", "plt.rcParams['savefig.dpi'] = 150\n", "\n", - "base_path = \"timings_main_1000.csv\"\n", - "path = \"timings_cond_clean_1000.csv\"\n", + "base_path = \"base_timings.csv\"\n", + "path = \"timings.csv\"\n", "\n", "# These are all the timings we want to see\n", "paths = [base_path, path]\n", @@ -294,7 +294,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.3" + "version": "3.13.1" } }, "nbformat": 4, From a4d6ccf37bebc4a2a33240f730dcd38396dc4ea4 Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Wed, 23 Jul 2025 11:06:00 +0200 Subject: [PATCH 28/28] remove `pub(crate)` --- src/utils/pool.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/pool.rs b/src/utils/pool.rs index 966a9744..f4dc9542 100644 --- a/src/utils/pool.rs +++ b/src/utils/pool.rs @@ -52,13 +52,13 @@ pub struct Pool { /// Map from version set to the id of their interned counterpart version_set_to_id: FrozenCopyMap<(NameId, VS), VersionSetId, ahash::RandomState>, + version_set_unions: Arena>, + /// Conditions that can be used to filter solvables. conditions: Arena, /// Map from condition to its id - pub(crate) condition_to_id: FrozenCopyMap, - - version_set_unions: Arena>, + condition_to_id: FrozenCopyMap, } impl Default for Pool {