#![deny(missing_docs)]
#[cfg(feature = "libsolv_c")]
pub mod libsolv_c;
#[cfg(feature = "resolvo")]
pub mod resolvo;
use std::collections::HashSet;
use std::fmt;
use chrono::{DateTime, Utc};
use rattler_conda_types::{
GenericVirtualPackage, MatchSpec, PackageName, RepoDataRecord, SolverResult,
};
pub trait SolverImpl {
type RepoData<'a>: SolverRepoData<'a>;
fn solve<
'a,
R: IntoRepoData<'a, Self::RepoData<'a>>,
TAvailablePackagesIterator: IntoIterator<Item = R>,
>(
&mut self,
task: SolverTask<TAvailablePackagesIterator>,
) -> Result<SolverResult, SolveError>;
}
#[derive(thiserror::Error, Debug)]
pub enum SolveError {
Unsolvable(Vec<String>),
UnsupportedOperations(Vec<String>),
#[error(transparent)]
ParseMatchSpecError(#[from] rattler_conda_types::ParseMatchSpecError),
DuplicateRecords(String),
Cancelled,
}
impl fmt::Display for SolveError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SolveError::Unsolvable(operations) => {
write!(
f,
"Cannot solve the request because of: {}",
operations.join(", ")
)
}
SolveError::UnsupportedOperations(operations) => {
write!(f, "Unsupported operations: {}", operations.join(", "))
}
SolveError::ParseMatchSpecError(e) => {
write!(f, "Error parsing match spec: {e}")
}
SolveError::Cancelled => {
write!(f, "Solve operation has been cancelled")
}
SolveError::DuplicateRecords(filename) => {
write!(f, "encountered duplicate records for {filename}")
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MinimumAgeConfig {
pub min_age: std::time::Duration,
pub now: DateTime<Utc>,
pub exempt_packages: HashSet<PackageName>,
pub include_unknown_timestamp: bool,
}
impl Default for MinimumAgeConfig {
fn default() -> Self {
Self {
min_age: std::time::Duration::default(),
now: Utc::now(),
exempt_packages: HashSet::new(),
include_unknown_timestamp: false,
}
}
}
impl MinimumAgeConfig {
pub fn new(min_age: std::time::Duration) -> Self {
Self {
min_age,
now: Utc::now(),
exempt_packages: HashSet::new(),
include_unknown_timestamp: false,
}
}
pub fn with_now(mut self, now: DateTime<Utc>) -> Self {
self.now = now;
self
}
pub fn with_exempt_package(mut self, package: PackageName) -> Self {
self.exempt_packages.insert(package);
self
}
pub fn with_exempt_packages(mut self, packages: impl IntoIterator<Item = PackageName>) -> Self {
self.exempt_packages = packages.into_iter().collect();
self
}
pub fn with_include_unknown_timestamp(mut self, include: bool) -> Self {
self.include_unknown_timestamp = include;
self
}
pub fn is_exempt(&self, package: &PackageName) -> bool {
self.exempt_packages.contains(package)
}
pub fn cutoff(&self) -> DateTime<Utc> {
let duration = chrono::Duration::from_std(self.min_age)
.expect("min_release_age duration is too large");
self.now - duration
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
pub enum ChannelPriority {
#[default]
Strict,
Disabled,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SolverTask<TAvailablePackagesIterator> {
pub available_packages: TAvailablePackagesIterator,
pub locked_packages: Vec<RepoDataRecord>,
pub pinned_packages: Vec<RepoDataRecord>,
pub virtual_packages: Vec<GenericVirtualPackage>,
pub specs: Vec<MatchSpec>,
pub constraints: Vec<MatchSpec>,
pub timeout: Option<std::time::Duration>,
pub channel_priority: ChannelPriority,
pub exclude_newer: Option<DateTime<Utc>>,
pub min_age: Option<MinimumAgeConfig>,
pub strategy: SolveStrategy,
pub dependency_overrides: Vec<(MatchSpec, MatchSpec)>,
}
impl<'r, I: IntoIterator<Item = &'r RepoDataRecord>> FromIterator<I>
for SolverTask<Vec<RepoDataIter<I>>>
{
fn from_iter<T: IntoIterator<Item = I>>(iter: T) -> Self {
Self {
available_packages: iter.into_iter().map(|iter| RepoDataIter(iter)).collect(),
locked_packages: Vec::new(),
pinned_packages: Vec::new(),
virtual_packages: Vec::new(),
specs: Vec::new(),
constraints: Vec::new(),
timeout: None,
channel_priority: ChannelPriority::default(),
exclude_newer: None,
min_age: None,
strategy: SolveStrategy::default(),
dependency_overrides: Vec::new(),
}
}
}
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
pub enum SolveStrategy {
#[default]
Highest,
LowestVersion,
LowestVersionDirect,
}
pub trait SolverRepoData<'a>: FromIterator<&'a RepoDataRecord> {}
pub trait IntoRepoData<'a, S: SolverRepoData<'a>> {
fn into(self) -> S;
}
impl<'a, S: SolverRepoData<'a>> IntoRepoData<'a, S> for &'a Vec<RepoDataRecord> {
fn into(self) -> S {
self.iter().collect()
}
}
impl<'a, S: SolverRepoData<'a>> IntoRepoData<'a, S> for &'a [RepoDataRecord] {
fn into(self) -> S {
self.iter().collect()
}
}
impl<'a, S: SolverRepoData<'a>> IntoRepoData<'a, S> for S {
fn into(self) -> S {
self
}
}
pub struct RepoDataIter<T>(pub T);
impl<'a, T: IntoIterator<Item = &'a RepoDataRecord>, S: SolverRepoData<'a>> IntoRepoData<'a, S>
for RepoDataIter<T>
{
fn into(self) -> S {
self.0.into_iter().collect()
}
}