use std::{
collections::{HashMap, HashSet},
ffi::CString,
mem::ManuallyDrop,
};
pub use input::cache_repodata;
use input::{add_repodata_records, add_solv_file, add_virtual_packages, parse_condition};
pub use libc_byte_slice::LibcByteSlice;
use output::{get_required_packages, SolverOutput};
use rattler_conda_types::{
match_spec::package_name_matcher::PackageNameMatcher, MatchSpec, NamelessMatchSpec,
RepoDataRecord, SolverResult,
};
use wrapper::{
flags::SolverFlag,
pool::{MatchSpecId, Pool, Verbosity},
repo::Repo,
solve_goal::SolveGoal,
};
use crate::{ChannelPriority, IntoRepoData, SolveError, SolveStrategy, SolverRepoData, SolverTask};
mod input;
mod libc_byte_slice;
mod output;
mod wrapper;
#[derive(Clone)]
pub struct RepoData<'a> {
pub records: Vec<&'a RepoDataRecord>,
pub solv_file: Option<&'a LibcByteSlice>,
}
impl<'a> FromIterator<&'a RepoDataRecord> for RepoData<'a> {
fn from_iter<T: IntoIterator<Item = &'a RepoDataRecord>>(iter: T) -> Self {
Self {
records: Vec::from_iter(iter),
solv_file: None,
}
}
}
impl<'a> RepoData<'a> {
#[deprecated(since = "0.6.0", note = "use From::from instead")]
pub fn from_records(records: impl Into<Vec<&'a RepoDataRecord>>) -> Self {
Self {
records: records.into(),
solv_file: None,
}
}
}
impl<'a> SolverRepoData<'a> for RepoData<'a> {}
fn c_string<T: AsRef<str>>(str: T) -> CString {
let bytes = str.as_ref().as_bytes();
let mut vec = Vec::with_capacity(bytes.len() + 1);
vec.extend_from_slice(bytes);
for byte in &mut vec {
if *byte == 0 {
*byte = b' ';
}
}
vec.push(0);
unsafe { CString::from_vec_with_nul_unchecked(vec) }
}
#[derive(Default)]
pub struct Solver;
impl super::SolverImpl for Solver {
type RepoData<'a> = RepoData<'a>;
fn solve<
'a,
R: IntoRepoData<'a, Self::RepoData<'a>>,
TAvailablePackagesIterator: IntoIterator<Item = R>,
>(
&mut self,
task: SolverTask<TAvailablePackagesIterator>,
) -> Result<SolverResult, SolveError> {
if task.timeout.is_some() {
return Err(SolveError::UnsupportedOperations(vec![
"timeout".to_string()
]));
}
if task.strategy != SolveStrategy::Highest {
return Err(SolveError::UnsupportedOperations(vec![
"strategy".to_string()
]));
}
let pool = Pool::default();
pool.set_debug_callback(|msg, _flags| {
tracing::event!(tracing::Level::DEBUG, "{}", msg.trim_end());
});
pool.set_debug_level(Verbosity::Low);
let repodatas: Vec<Self::RepoData<'_>> = task
.available_packages
.into_iter()
.map(IntoRepoData::into)
.collect();
let mut highest_priority: i32 = 0;
let channel_priority = if task.channel_priority == ChannelPriority::Strict {
let mut seen_channels = HashSet::new();
let mut channel_order = Vec::new();
for channel in repodatas
.iter()
.filter(|&r| !r.records.is_empty())
.map(|r| r.records[0].channel.clone())
{
if !seen_channels.contains(&channel) {
channel_order.push(channel.clone());
seen_channels.insert(channel);
}
}
let mut channel_priority = HashMap::new();
for (index, channel) in channel_order.iter().enumerate() {
let reverse_index = channel_order.len() - index;
if index == 0 {
highest_priority = reverse_index as i32;
}
channel_priority.insert(channel.clone(), reverse_index as i32);
}
channel_priority
} else {
HashMap::new()
};
let repo = Repo::new(&pool, "virtual_packages", highest_priority);
add_virtual_packages(&pool, &repo, &task.virtual_packages);
pool.set_installed(&repo);
let mut repo_mapping = HashMap::new();
let mut all_repodata_records = Vec::new();
for repodata in repodatas.iter() {
if repodata.records.is_empty() {
continue;
}
let channel_name = &repodata.records[0].channel;
let priority: i32 = if task.channel_priority == ChannelPriority::Strict {
*channel_priority.get(channel_name).unwrap()
} else {
0
};
let repo = ManuallyDrop::new(Repo::new(
&pool,
channel_name.as_ref().map_or("<direct>", String::as_str),
priority,
));
if let Some(solv_file) = repodata.solv_file {
add_solv_file(&pool, &repo, solv_file);
} else {
add_repodata_records(
&pool,
&repo,
repodata.records.iter().copied(),
task.exclude_newer.as_ref(),
)?;
}
repo_mapping.insert(repo.id(), repo_mapping.len());
all_repodata_records.push(repodata.records.clone());
}
let repo = Repo::new(&pool, "locked", highest_priority);
let installed_solvables = add_repodata_records(&pool, &repo, &task.locked_packages, None)?;
repo_mapping.insert(repo.id(), repo_mapping.len());
all_repodata_records.push(task.locked_packages.iter().collect());
let repo = Repo::new(&pool, "pinned", highest_priority);
let pinned_solvables = add_repodata_records(&pool, &repo, &task.pinned_packages, None)?;
repo_mapping.insert(repo.id(), repo_mapping.len());
all_repodata_records.push(task.pinned_packages.iter().collect());
pool.create_whatprovides();
let mut goal = SolveGoal::default();
for favor_solvable in installed_solvables {
goal.favor(favor_solvable);
}
for locked_solvable in pinned_solvables {
goal.lock(locked_solvable);
}
for spec in task.specs {
let (base_spec, extras_opt, condition_opt) = {
let mut base = spec.clone();
let extras = base.extras.take();
let condition = base.condition.take();
(base, extras, condition)
};
let base_id = pool.intern_matchspec(&base_spec);
let id = if let Some(condition) = condition_opt.as_ref() {
let condition_id = parse_condition(condition, &pool);
MatchSpecId(pool.rel_cond(base_id.into(), condition_id))
} else {
base_id
};
goal.install(id, false);
if let Some(extras) = extras_opt {
if let Some(exact_name) = spec.name.as_exact() {
for extra in extras.iter() {
let extra_name = format!("{}[{}]", exact_name.as_normalized(), extra);
let name_id = pool.intern_str(extra_name.as_str());
let extra_id = MatchSpecId(name_id.into());
goal.install(extra_id, false);
}
}
}
}
for spec in task.constraints {
let id = pool.intern_matchspec(&spec);
goal.install(id, true);
}
for virtual_package in task.virtual_packages {
let id = pool.intern_matchspec(&MatchSpec::from_nameless(
NamelessMatchSpec::default(),
PackageNameMatcher::Exact(virtual_package.name),
));
goal.install(id, false);
}
let mut solver = pool.create_solver();
solver.set_flag(SolverFlag::allow_uninstall(), true);
solver.set_flag(SolverFlag::allow_downgrade(), true);
solver.set_flag(
SolverFlag::strict_channel_priority(),
task.channel_priority == ChannelPriority::Strict,
);
let transaction = solver.solve(&mut goal).map_err(SolveError::Unsolvable)?;
let SolverOutput {
packages: required_records,
extras,
} = get_required_packages(
&pool,
&repo_mapping,
&transaction,
all_repodata_records.as_slice(),
)
.map_err(|unsupported_operation_ids| {
SolveError::UnsupportedOperations(
unsupported_operation_ids
.into_iter()
.map(|id| format!("libsolv operation {id}"))
.collect(),
)
})?;
Ok(SolverResult {
records: required_records,
extras,
})
}
}
#[cfg(test)]
mod test {
use rstest::rstest;
use super::*;
#[rstest]
#[case("", "")]
#[case("a\0b\0c\0d\0", "a b c d ")]
#[case("a b c d", "a b c d")]
#[case("😒", "😒")]
fn test_c_string(#[case] input: &str, #[case] expected_output: &str) {
let output = c_string(input);
assert_eq!(output.as_bytes(), expected_output.as_bytes());
}
}