use std::{cmp::Ordering, collections::HashMap};
use rattler_conda_types::{
GenericVirtualPackage, RepoDataRecord, RepodataRevision,
package::{ArchiveIdentifier, DistArchiveType},
};
use rattler_conda_types::{MatchSpec, MatchSpecCondition, ParseMatchSpecOptions};
use std::collections::HashSet;
use crate::ExcludeNewer;
use super::{
c_string,
libc_byte_slice::LibcByteSlice,
wrapper::{
keys::{
REPOKEY_TYPE_MD5, REPOKEY_TYPE_SHA256, SOLVABLE_BUILDFLAVOR, SOLVABLE_BUILDTIME,
SOLVABLE_BUILDVERSION, SOLVABLE_CHECKSUM, SOLVABLE_CONSTRAINS, SOLVABLE_DOWNLOADSIZE,
SOLVABLE_EXTRA_NAME, SOLVABLE_EXTRA_PACKAGE, SOLVABLE_LICENSE, SOLVABLE_PKGID,
SOLVABLE_REPODATA_RECORD_INDEX, SOLVABLE_TRACK_FEATURES,
},
pool::Pool,
repo::Repo,
repodata::Repodata,
solvable::SolvableId,
},
};
use crate::SolveError;
fn parse_libsolv_match_spec(match_spec_str: &str) -> Result<Option<MatchSpec>, SolveError> {
let Ok(match_spec) = MatchSpec::from_str(
match_spec_str,
ParseMatchSpecOptions::lenient().with_repodata_revision(RepodataRevision::V3),
) else {
return Ok(None);
};
super::ensure_matchspec_flags_supported(&match_spec)?;
Ok(Some(match_spec))
}
#[cfg(not(target_family = "unix"))]
pub fn add_solv_file(_pool: &Pool, _repo: &Repo<'_>, _solv_bytes: &LibcByteSlice) {
unimplemented!("this platform does not support in-memory .solv files");
}
#[cfg(target_family = "unix")]
pub fn add_solv_file(pool: &Pool, repo: &Repo<'_>, solv_bytes: &LibcByteSlice) {
let mode = c_string("r");
let file = unsafe { libc::fmemopen(solv_bytes.as_ptr(), solv_bytes.len(), mode.as_ptr()) };
repo.add_solv(pool, file);
unsafe { libc::fclose(file) };
}
pub fn parse_condition(condition: &MatchSpecCondition, pool: &Pool) -> super::wrapper::ffi::Id {
match condition {
MatchSpecCondition::MatchSpec(match_spec) => {
let match_spec_str = match_spec.to_string();
let c_str = c_string(&match_spec_str);
pool.conda_matchspec(&c_str)
}
MatchSpecCondition::And(left, right) => {
let left_id = parse_condition(left, pool);
let right_id = parse_condition(right, pool);
pool.rel_and(left_id, right_id)
}
MatchSpecCondition::Or(left, right) => {
let left_id = parse_condition(left, pool);
let right_id = parse_condition(right, pool);
pool.rel_or(left_id, right_id)
}
}
}
pub fn add_repodata_records<'a>(
pool: &Pool,
repo: &Repo<'_>,
repo_data: impl IntoIterator<Item = &'a RepoDataRecord>,
exclude_newer: Option<&ExcludeNewer>,
) -> Result<Vec<SolvableId>, SolveError> {
repo.ensure_belongs_to_pool(pool);
let solvable_buildflavor_id = pool.find_interned_str(SOLVABLE_BUILDFLAVOR).unwrap();
let solvable_buildtime_id = pool.find_interned_str(SOLVABLE_BUILDTIME).unwrap();
let solvable_buildversion_id = pool.find_interned_str(SOLVABLE_BUILDVERSION).unwrap();
let solvable_constraints = pool.find_interned_str(SOLVABLE_CONSTRAINS).unwrap();
let solvable_download_size_id = pool.find_interned_str(SOLVABLE_DOWNLOADSIZE).unwrap();
let solvable_license_id = pool.find_interned_str(SOLVABLE_LICENSE).unwrap();
let solvable_pkg_id = pool.find_interned_str(SOLVABLE_PKGID).unwrap();
let solvable_checksum = pool.find_interned_str(SOLVABLE_CHECKSUM).unwrap();
let solvable_track_features = pool.find_interned_str(SOLVABLE_TRACK_FEATURES).unwrap();
let repo_type_md5 = pool.find_interned_str(REPOKEY_TYPE_MD5).unwrap();
let repo_type_sha256 = pool.find_interned_str(REPOKEY_TYPE_SHA256).unwrap();
let solvable_index_id = pool.intern_str(SOLVABLE_REPODATA_RECORD_INDEX);
let mut package_to_type: HashMap<&ArchiveIdentifier, (DistArchiveType, SolvableId)> =
HashMap::new();
let data = repo.add_repodata();
let mut solvable_ids = Vec::new();
let mut extras: HashSet<(String, String)> = HashSet::new();
for (repo_data_index, repo_data) in repo_data.into_iter().enumerate() {
if let Some(config) = exclude_newer
&& config.is_excluded(
&repo_data.package_record.name,
repo_data.channel.as_deref(),
repo_data.package_record.timestamp.as_ref(),
)
{
continue;
}
let solvable_id =
match add_or_reuse_solvable(pool, repo, &data, &mut package_to_type, repo_data)? {
Some(id) => id,
None => continue,
};
data.set_num(solvable_id, solvable_index_id, repo_data_index as u64);
let solvable = unsafe { solvable_id.resolve_raw(pool).as_mut() };
let record = &repo_data.package_record;
solvable.name = pool.intern_str(record.name.as_normalized()).into();
solvable.evr = pool.intern_str(record.version.to_string()).into();
let rel_eq = pool.rel_eq(solvable.name, solvable.evr);
repo.add_provides(solvable, rel_eq);
data.set_location(
solvable_id,
&c_string(&record.subdir),
&c_string(repo_data.identifier.to_string()),
);
for match_spec_str in record.depends.iter() {
if let Some(match_spec) = parse_libsolv_match_spec(match_spec_str)?
&& let Some(condition) = match_spec.condition.as_ref()
{
let mut dep_spec = match_spec.clone();
dep_spec.condition = None;
let dep_id = pool.conda_matchspec(&c_string(dep_spec.to_string()));
let condition_id = parse_condition(condition, pool);
let conditional_dep_id = pool.rel_cond(dep_id, condition_id);
repo.add_requires(solvable, conditional_dep_id);
continue;
}
let match_spec_id = pool.conda_matchspec(&c_string(match_spec_str));
repo.add_requires(solvable, match_spec_id);
}
for match_spec in record.constrains.iter() {
parse_libsolv_match_spec(match_spec)?;
let match_spec_id = pool.conda_matchspec(&c_string(match_spec));
data.add_idarray(solvable_id, solvable_constraints, match_spec_id);
}
for (extra_name, deps) in record.extra_depends.iter() {
extras.insert((record.name.as_normalized().to_string(), extra_name.clone()));
for dep_str in deps.iter() {
parse_libsolv_match_spec(dep_str)?;
let dep_id = pool.conda_matchspec(&c_string(dep_str));
let extra_name_str = format!("{}[{}]", record.name.as_normalized(), extra_name);
let extra_condition_id = pool.intern_str(extra_name_str.as_str()).into();
let conditional_dep_id = pool.rel_cond(dep_id, extra_condition_id);
repo.add_requires(solvable, conditional_dep_id);
}
}
for track_features in record.track_features.iter() {
let track_feature = track_features.trim();
if !track_feature.is_empty() {
data.add_idarray(
solvable_id,
solvable_track_features,
pool.intern_str(track_features.trim()).into(),
);
}
}
if let Some(timestamp) = record.timestamp {
data.set_num(
solvable_id,
solvable_buildtime_id,
timestamp.timestamp() as u64,
);
}
if let Some(size) = record.size {
data.set_num(solvable_id, solvable_download_size_id, size);
}
data.add_poolstr_array(
solvable_id,
solvable_buildflavor_id,
&c_string(&record.build),
);
data.set_str(
solvable_id,
solvable_buildversion_id,
&c_string(record.build_number.to_string()),
);
if let Some(license) = record.license.as_ref() {
data.add_poolstr_array(solvable_id, solvable_license_id, &c_string(license));
}
if let Some(md5) = record.md5.as_ref() {
data.set_checksum(
solvable_id,
solvable_pkg_id,
repo_type_md5,
&c_string(hex::encode(md5)),
);
}
if let Some(sha256) = record.sha256.as_ref() {
data.set_checksum(
solvable_id,
solvable_checksum,
repo_type_sha256,
&c_string(hex::encode(sha256)),
);
}
solvable_ids.push(solvable_id);
}
let extra_package_id = pool.intern_str(SOLVABLE_EXTRA_PACKAGE);
let extra_name_id = pool.intern_str(SOLVABLE_EXTRA_NAME);
for (package_name, extra_name) in extras {
let solvable_id = repo.add_solvable();
let solvable = unsafe { solvable_id.resolve_raw(pool).as_mut() };
let synthetic_name = format!("{package_name}[{extra_name}]");
solvable.name = pool.intern_str(synthetic_name.as_str()).into();
solvable.evr = pool.intern_str("0").into();
let rel_eq = pool.rel_eq(solvable.name, solvable.evr);
repo.add_provides(solvable, rel_eq);
let package_name_interned = pool.intern_str(package_name.as_str());
let extra_name_interned = pool.intern_str(extra_name.as_str());
data.set_id(solvable_id, extra_package_id, package_name_interned.into());
data.set_id(solvable_id, extra_name_id, extra_name_interned.into());
solvable_ids.push(solvable_id);
}
repo.internalize();
Ok(solvable_ids)
}
fn add_or_reuse_solvable<'a>(
pool: &Pool,
repo: &Repo<'_>,
data: &Repodata<'_>,
package_to_type: &mut HashMap<&'a ArchiveIdentifier, (DistArchiveType, SolvableId)>,
repo_data: &'a RepoDataRecord,
) -> Result<Option<SolvableId>, SolveError> {
let identifier = &repo_data.identifier.identifier;
let archive_type = repo_data.identifier.archive_type;
if let Some(&(other_package_type, old_solvable_id)) = package_to_type.get(identifier) {
match archive_type.cmp_preference(other_package_type) {
Ordering::Less => {
Ok(None)
}
Ordering::Greater => {
package_to_type.insert(identifier, (archive_type, old_solvable_id));
reset_solvable(pool, repo, data, old_solvable_id);
Ok(Some(old_solvable_id))
}
Ordering::Equal => Err(SolveError::DuplicateRecords(identifier.to_string())),
}
} else {
let solvable_id = repo.add_solvable();
package_to_type.insert(identifier, (archive_type, solvable_id));
Ok(Some(solvable_id))
}
}
pub fn add_virtual_packages(pool: &Pool, repo: &Repo<'_>, packages: &[GenericVirtualPackage]) {
let data = repo.add_repodata();
let solvable_buildflavor_id = pool.find_interned_str(SOLVABLE_BUILDFLAVOR).unwrap();
for package in packages {
let solvable_id = repo.add_solvable();
let solvable = unsafe { solvable_id.resolve_raw(pool).as_mut() };
solvable.name = pool.intern_str(package.name.as_normalized()).into();
solvable.evr = pool.intern_str(package.version.to_string()).into();
let rel_eq = pool.rel_eq(solvable.name, solvable.evr);
repo.add_provides(solvable, rel_eq);
data.add_poolstr_array(
solvable_id,
solvable_buildflavor_id,
&c_string(&package.build_string),
);
}
}
fn reset_solvable(pool: &Pool, repo: &Repo<'_>, data: &Repodata<'_>, solvable_id: SolvableId) {
let blank_solvable = repo.add_solvable();
pool.swap_solvables(blank_solvable, solvable_id);
data.swap_attrs(blank_solvable, solvable_id);
unsafe { repo.free_solvable(blank_solvable) };
}
#[cfg(not(target_family = "unix"))]
pub fn cache_repodata(_url: String, _data: &[RepoDataRecord]) -> Result<LibcByteSlice, SolveError> {
unimplemented!("this function is only available on unix-like operating systems")
}
#[cfg(target_family = "unix")]
pub fn cache_repodata(
url: String,
data: &[RepoDataRecord],
channel_priority: Option<i32>,
) -> Result<LibcByteSlice, SolveError> {
let pool = Pool::default();
let repo = Repo::new(&pool, url, channel_priority.unwrap_or(0));
add_repodata_records(&pool, &repo, data, None)?;
let mut stream_ptr = std::ptr::null_mut();
let mut stream_size = 0;
let file = unsafe { libc::open_memstream(&mut stream_ptr, &mut stream_size) };
assert!(!file.is_null(), "unable to open memstream");
repo.write(&pool, file);
unsafe { libc::fclose(file) };
let stream_ptr = std::ptr::NonNull::new(stream_ptr).expect("stream_ptr was null");
Ok(unsafe { LibcByteSlice::from_raw_parts(stream_ptr.cast(), stream_size) })
}