#![deny(missing_docs)]
#[cfg(feature = "libsolv_c")]
pub mod libsolv_c;
#[cfg(feature = "resolvo")]
pub mod resolvo;
use std::collections::HashMap;
use std::fmt;
use chrono::{DateTime, Utc};
use rattler_conda_types::{
utils::TimestampMs, 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 ExcludeNewer {
cutoff: DateTime<Utc>,
channel_cutoffs: HashMap<String, DateTime<Utc>>,
package_cutoffs: HashMap<PackageName, DateTime<Utc>>,
include_unknown_timestamp: bool,
}
impl ExcludeNewer {
fn cutoff_from_duration(duration: std::time::Duration, now: DateTime<Utc>) -> DateTime<Utc> {
let duration =
chrono::Duration::from_std(duration).expect("exclude_newer duration is too large");
now - duration
}
pub fn from_datetime(cutoff: DateTime<Utc>) -> Self {
Self {
cutoff,
channel_cutoffs: HashMap::new(),
package_cutoffs: HashMap::new(),
include_unknown_timestamp: false,
}
}
pub fn from_duration(duration: std::time::Duration) -> Self {
Self::from_duration_with_now(duration, Utc::now())
}
pub fn from_duration_with_now(duration: std::time::Duration, now: DateTime<Utc>) -> Self {
Self {
cutoff: Self::cutoff_from_duration(duration, now),
channel_cutoffs: HashMap::new(),
package_cutoffs: HashMap::new(),
include_unknown_timestamp: false,
}
}
pub fn with_package_cutoff(mut self, package: PackageName, cutoff: DateTime<Utc>) -> Self {
self.package_cutoffs.insert(package, cutoff);
self
}
pub fn with_package_duration(
mut self,
package: PackageName,
duration: std::time::Duration,
) -> Self {
self.package_cutoffs
.insert(package, Self::cutoff_from_duration(duration, Utc::now()));
self
}
pub fn with_package_duration_with_now(
mut self,
package: PackageName,
duration: std::time::Duration,
now: DateTime<Utc>,
) -> Self {
self.package_cutoffs
.insert(package, Self::cutoff_from_duration(duration, now));
self
}
pub fn with_channel_duration(
mut self,
channel: impl Into<String>,
duration: std::time::Duration,
) -> Self {
self.channel_cutoffs.insert(
channel.into(),
Self::cutoff_from_duration(duration, Utc::now()),
);
self
}
pub fn with_channel_duration_with_now(
mut self,
channel: impl Into<String>,
duration: std::time::Duration,
now: DateTime<Utc>,
) -> Self {
self.channel_cutoffs
.insert(channel.into(), Self::cutoff_from_duration(duration, now));
self
}
pub fn with_channel_cutoff(
mut self,
channel: impl Into<String>,
cutoff: DateTime<Utc>,
) -> Self {
self.channel_cutoffs.insert(channel.into(), cutoff);
self
}
pub fn with_include_unknown_timestamp(mut self, include: bool) -> Self {
self.include_unknown_timestamp = include;
self
}
pub fn include_unknown_timestamp(&self) -> bool {
self.include_unknown_timestamp
}
pub fn cutoff_for_package(
&self,
package: &PackageName,
channel: Option<&str>,
) -> DateTime<Utc> {
self.package_cutoffs
.get(package)
.copied()
.or_else(|| channel.and_then(|channel| self.channel_cutoffs.get(channel).copied()))
.unwrap_or(self.cutoff)
}
pub fn is_excluded(
&self,
package: &PackageName,
channel: Option<&str>,
timestamp: Option<&TimestampMs>,
) -> bool {
match timestamp {
Some(timestamp) => *timestamp > self.cutoff_for_package(package, channel),
None => !self.include_unknown_timestamp(),
}
}
}
impl From<DateTime<Utc>> for ExcludeNewer {
fn from(value: DateTime<Utc>) -> Self {
Self::from_datetime(value)
}
}
impl From<std::time::Duration> for ExcludeNewer {
fn from(value: std::time::Duration) -> Self {
Self::from_duration(value)
}
}
#[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<ExcludeNewer>,
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,
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()
}
}