Skip to content

Commit

Permalink
feat: added experimental fields to conda lock (#221)
Browse files Browse the repository at this point in the history
* feat: added experimental fields to conda lock

So that it is possible to re-create a package record from a locked
package

* fix: intra-doc links

* fix: cargo fmt

* fix: forgot some intra-doc links

* feat: added to and from conversion methods

* feat: added from and conversion methods

* fix: cargo fmt
  • Loading branch information
tdejager authored Jun 22, 2023
1 parent 11309d1 commit a08f0ec
Show file tree
Hide file tree
Showing 10 changed files with 1,391 additions and 4,365 deletions.
2 changes: 1 addition & 1 deletion crates/rattler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub fn empty_channel() -> rattler_conda_types::Channel {
let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let channel_path = manifest_dir.join("../../test-data/channels/empty");
rattler_conda_types::Channel::from_str(
&format!("file://{}[noarch]", channel_path.display()),
format!("file://{}[noarch]", channel_path.display()),
&rattler_conda_types::ChannelConfig::default(),
)
.unwrap()
Expand Down
307 changes: 295 additions & 12 deletions crates/rattler_conda_types/src/conda_lock/builder.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
//! Builder for the creation of lock files. Currently,
//!
use super::ConversionError;
use crate::conda_lock::content_hash::CalculateContentHashError;
use crate::conda_lock::{
content_hash, Channel, CondaLock, GitMeta, LockMeta, LockedDependency, Manager, PackageHashes,
TimeMeta, VersionConstraint,
TimeMeta,
};
use crate::{MatchSpec, Platform};
use crate::{MatchSpec, NamelessMatchSpec, NoArchType, Platform, RepoDataRecord};
use fxhash::{FxHashMap, FxHashSet};
use std::str::FromStr;
use url::Url;

/// Struct used to build a conda-lock file
Expand Down Expand Up @@ -139,6 +141,25 @@ impl LockedPackages {
category: super::default_category(),
source: None,
build: Some(locked_package.build_string),
arch: locked_package.arch,
subdir: locked_package.subdir,
build_number: locked_package.build_number,
constrains: if locked_package.constrains.is_empty() {
None
} else {
Some(locked_package.constrains)
},
features: locked_package.features,
track_features: if locked_package.track_features.is_empty() {
None
} else {
Some(locked_package.track_features)
},
license: locked_package.license,
license_family: locked_package.license_family,
noarch: locked_package.noarch,
size: locked_package.size,
timestamp: locked_package.timestamp,
}
})
.collect()
Expand All @@ -158,9 +179,98 @@ pub struct LockedPackage {
/// Collection of package hash fields
pub package_hashes: PackageHashes,
/// List of dependencies for this package
pub dependency_list: FxHashMap<String, VersionConstraint>,
pub dependency_list: FxHashMap<String, NamelessMatchSpec>,
/// Check if package is optional
pub optional: Option<bool>,

/// Experimental: architecture field
pub arch: Option<String>,

/// Experimental: the subdir where the package can be found
pub subdir: Option<String>,

/// Experimental: conda build number of the package
pub build_number: Option<u64>,

/// Experimental: see: [Constrains](crate::repo_data::PackageRecord::constrains)
pub constrains: Vec<String>,

/// Experimental: see: [Features](crate::repo_data::PackageRecord::features)
pub features: Option<String>,

/// Experimental: see: [Track features](crate::repo_data::PackageRecord::track_features)
pub track_features: Vec<String>,

/// Experimental: the specific license of the package
pub license: Option<String>,

/// Experimental: the license family of the package
pub license_family: Option<String>,

/// Experimental: If this package is independent of architecture this field specifies in what way. See
/// [`NoArchType`] for more information.
pub noarch: NoArchType,

/// Experimental: The size of the package archive in bytes
pub size: Option<u64>,

/// Experimental: The date this entry was created.
pub timestamp: Option<chrono::DateTime<chrono::Utc>>,
}

impl TryFrom<&RepoDataRecord> for LockedPackage {
type Error = ConversionError;

fn try_from(value: &RepoDataRecord) -> Result<Self, Self::Error> {
Self::try_from(value.clone())
}
}

impl TryFrom<RepoDataRecord> for LockedPackage {
type Error = ConversionError;

fn try_from(record: RepoDataRecord) -> Result<Self, Self::Error> {
// Generate hashes
let hashes =
PackageHashes::from_hashes(record.package_record.md5, record.package_record.sha256);
let hashes = hashes.ok_or_else(|| ConversionError::Missing("md5 or sha265".to_string()))?;

// Convert dependencies
let mut dependencies = FxHashMap::default();
for match_spec_str in record.package_record.depends.iter() {
let matchspec = MatchSpec::from_str(match_spec_str)?;
let name = matchspec
.name
.as_ref()
.ok_or_else(|| {
ConversionError::Missing(format!("dependency name for {}", match_spec_str))
})?
.to_string();
let version_constraint = NamelessMatchSpec::from(matchspec);
dependencies.insert(name, version_constraint);
}

Ok(Self {
name: record.package_record.name,
version: record.package_record.version.to_string(),
build_string: record.package_record.build,
url: record.url,
package_hashes: hashes,
dependency_list: dependencies,
optional: None,
arch: record.package_record.arch,
subdir: Some(record.package_record.subdir),
build_number: Some(record.package_record.build_number),
constrains: record.package_record.constrains,
features: record.package_record.features,
track_features: record.package_record.track_features,
license: record.package_record.license,
license_family: record.package_record.license_family,
noarch: record.package_record.noarch,
size: record.package_record.size,
timestamp: record.package_record.timestamp,
})
}
}

impl LockedPackage {
Expand All @@ -174,7 +284,7 @@ impl LockedPackage {
pub fn add_dependency<S: AsRef<str>>(
mut self,
key: S,
version_constraint: VersionConstraint,
version_constraint: NamelessMatchSpec,
) -> Self {
self.dependency_list
.insert(key.as_ref().to_string(), version_constraint);
Expand All @@ -184,24 +294,109 @@ impl LockedPackage {
/// Add multiple dependencies
pub fn add_dependencies(
mut self,
value: impl IntoIterator<Item = (String, VersionConstraint)>,
value: impl IntoIterator<Item = (String, NamelessMatchSpec)>,
) -> Self {
self.dependency_list.extend(value);
self
}

/// Set the subdir for for the package
pub fn set_arch<S: AsRef<str>>(mut self, arch: String) -> Self {
self.subdir = Some(arch);
self
}

/// Set the subdir for for the package
pub fn set_subdir<S: AsRef<str>>(mut self, subdir: String) -> Self {
self.subdir = Some(subdir);
self
}

/// Set the subdir for for the package
pub fn set_build_number<S: AsRef<str>>(mut self, build_number: u64) -> Self {
self.build_number = Some(build_number);
self
}

/// Add the constrains for this package
pub fn add_constrain<S: AsRef<str>>(mut self, constrain: S) -> Self {
self.constrains.push(constrain.as_ref().to_string());
self
}

/// Add the constrains for this package
pub fn add_constrains<S: AsRef<str>>(
mut self,
constrain: impl IntoIterator<Item = String>,
) -> Self {
self.constrains.extend(constrain);
self
}

/// Set the features for for the package
pub fn set_features<S: AsRef<str>>(mut self, features: S) -> Self {
self.features = Some(features.as_ref().to_string());
self
}

/// Add a track feature for the package
pub fn add_track_feature<S: AsRef<str>>(mut self, track_feature: S) -> Self {
self.track_features.push(track_feature.as_ref().to_string());
self
}

/// Add multiple track features for for the package
pub fn add_track_features(mut self, value: impl IntoIterator<Item = String>) -> Self {
self.track_features.extend(value);
self
}

/// Set the licence for for the package
pub fn add_license<S: AsRef<str>>(mut self, license: S) -> Self {
self.license = Some(license.as_ref().to_string());
self
}

/// Set the license family for for the package
pub fn add_license_family<S: AsRef<str>>(mut self, license_family: S) -> Self {
self.license_family = Some(license_family.as_ref().to_string());
self
}

/// Set the noarch type for for the package
pub fn add_noarch(mut self, noarch_type: NoArchType) -> Self {
self.noarch = noarch_type;
self
}

/// Set the size of the package
pub fn set_size(mut self, size: u64) -> Self {
self.size = Some(size);
self
}

/// Set the timestamp of the package
pub fn set_timestamp(mut self, timestamp: chrono::DateTime<chrono::Utc>) -> Self {
self.timestamp = Some(timestamp);
self
}
}

#[cfg(test)]
mod tests {
use chrono::Utc;
use fxhash::FxHashMap;
use std::str::FromStr;

use crate::conda_lock::builder::{LockFileBuilder, LockedPackage, LockedPackages};
use crate::conda_lock::{CondaLock, PackageHashes};
use crate::{ChannelConfig, MatchSpec, Platform};
use crate::conda_lock::PackageHashes;
use crate::{
ChannelConfig, MatchSpec, NamelessMatchSpec, NoArchType, Platform, RepoDataRecord,
};
use rattler_digest::parse_digest_from_hex;

#[test]
fn create_lock_file() {
fn conda_lock_builder_and_conversions() {
let _channel_config = ChannelConfig::default();
let lock = LockFileBuilder::new(
["conda_forge"],
Expand All @@ -216,13 +411,101 @@ mod tests {
url: "https://conda.anaconda.org/conda-forge/osx-64/python-3.11.0-h4150a38_1_cpython.conda".parse().unwrap(),
package_hashes: PackageHashes::Md5Sha256(parse_digest_from_hex::<rattler_digest::Md5>("c6f4b87020c72e2700e3e94c1fc93b70").unwrap(),
parse_digest_from_hex::<rattler_digest::Sha256>("7c58de8c7d98b341bd9be117feec64782e704fec5c30f6e14713ebccaab9b5d8").unwrap()),
dependency_list: Default::default(),
dependency_list: FxHashMap::from_iter([("python".to_string(), NamelessMatchSpec::from_str("3.11.0.*").unwrap())]),
optional: None,
arch: Some("x86_64".to_string()),
subdir: Some("noarch".to_string()),
build_number: Some(12),
constrains: vec!["bla".to_string()],
features: Some("foobar".to_string()),
track_features: vec!["dont-track".to_string()],
license: Some("BSD-3-Clause".to_string()),
license_family: Some("BSD".to_string()),
noarch: NoArchType::python(),
size: Some(12000),
timestamp: Some(Utc::now()),
}))
.build().unwrap();

// See if we can serialize/deserialize it
let s = serde_yaml::to_string(&lock).unwrap();
serde_yaml::from_str::<CondaLock>(&s).unwrap();
// Convert to RepoDataRecord
let locked_dep = lock.package.first().unwrap();
let record = RepoDataRecord::try_from(locked_dep).unwrap();

assert_eq!(record.package_record.name, locked_dep.name);
assert_eq!(
record.channel,
"https://conda.anaconda.org/conda-forge".to_string()
);
assert_eq!(
record.file_name,
"python-3.11.0-h4150a38_1_cpython.conda".to_string()
);
assert_eq!(
record.package_record.version.to_string(),
locked_dep.version
);
assert_eq!(
record.package_record.build,
locked_dep.build.clone().unwrap_or_default()
);
assert_eq!(
record.package_record.platform.clone().unwrap(),
locked_dep.platform.to_string()
);
assert_eq!(record.package_record.arch, locked_dep.arch);
assert_eq!(
record.package_record.subdir,
locked_dep.subdir.clone().unwrap_or_default()
);
assert_eq!(
record.package_record.build_number,
locked_dep.build_number.unwrap_or_default()
);
assert_eq!(
record.package_record.constrains,
locked_dep.constrains.clone().unwrap_or_default()
);
assert_eq!(record.package_record.features, locked_dep.features);
assert_eq!(
record.package_record.track_features,
locked_dep.track_features.clone().unwrap_or_default()
);
assert_eq!(
record.package_record.license_family,
locked_dep.license_family
);
assert_eq!(record.package_record.noarch, locked_dep.noarch);
assert_eq!(record.package_record.size, locked_dep.size);
assert_eq!(record.package_record.timestamp, locked_dep.timestamp);

// Convert to LockedDependency
let locked_package = LockedPackage::try_from(record.clone()).unwrap();
assert_eq!(record.package_record.name, locked_package.name);
assert_eq!(
record.package_record.version.to_string(),
locked_package.version
);
assert_eq!(
record.package_record.build,
locked_package.build_string.clone()
);
assert_eq!(record.package_record.arch, locked_package.arch);
assert_eq!(
record.package_record.subdir,
locked_package.subdir.clone().unwrap_or_default()
);
assert_eq!(
record.package_record.build_number,
locked_package.build_number.unwrap_or_default()
);
assert_eq!(record.package_record.constrains, locked_package.constrains);
assert_eq!(record.package_record.features, locked_package.features);
assert_eq!(
record.package_record.license_family,
locked_package.license_family
);
assert_eq!(record.package_record.noarch, locked_package.noarch);
assert_eq!(record.package_record.size, locked_package.size);
assert_eq!(record.package_record.timestamp, locked_package.timestamp);
}
}
Loading

0 comments on commit a08f0ec

Please sign in to comment.