Skip to content

Commit

Permalink
feat: removed Ord and more (#673)
Browse files Browse the repository at this point in the history
* Turns `purls` into a BtreeSet to ensure consistent ordering (fixes
prefix-dev/pixi#1367)
* Removed `Ord` and `PartialOrd` from `PackageRecord`. It doesnt make
sense.
* Bumped `resolvo` to remove `Ord` constraint from `Version`. 
* Conversion from `CondaPackage` and `PypiPackage` to Package.
* The ability to add packages to the lock file builder from another
environment.
  • Loading branch information
baszalmstra authored May 23, 2024
1 parent eb0a7af commit ed561f3
Show file tree
Hide file tree
Showing 12 changed files with 294 additions and 176 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ regex = "1.10.4"
reqwest = { version = "0.12.3", default-features = false }
reqwest-middleware = "0.3.0"
reqwest-retry = "0.5.0"
resolvo = { version = "0.4.0" }
resolvo = { version = "0.4.1" }
retry-policies = { version = "0.3.0", default-features = false }
rmp-serde = { version = "1.2.0" }
rstest = { version = "0.19.0" }
Expand Down
4 changes: 2 additions & 2 deletions crates/rattler_conda_types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ itertools = { workspace = true }
lazy-regex = { workspace = true }
nom = { workspace = true }
purl = { workspace = true, features = ["serde"] }
rattler_digest = { path="../rattler_digest", version = "0.19.4", default-features = false, features = ["serde"] }
rattler_macros = { path="../rattler_macros", version = "0.19.3", default-features = false }
rattler_digest = { path = "../rattler_digest", version = "0.19.4", default-features = false, features = ["serde"] }
rattler_macros = { path = "../rattler_macros", version = "0.19.3", default-features = false }
regex = { workspace = true }
serde = { workspace = true, features = ["derive", "rc"] }
serde_json = { workspace = true }
Expand Down
97 changes: 54 additions & 43 deletions crates/rattler_conda_types/src/repo_data/mod.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
//! Defines [`RepoData`]. `RepoData` stores information of all packages present in a subdirectory
//! of a channel. It provides indexing functionality.
//! Defines [`RepoData`]. `RepoData` stores information of all packages present
//! in a subdirectory of a channel. It provides indexing functionality.

pub mod patches;
pub mod sharded;
mod topological_sort;

use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet};
use std::fmt::{Display, Formatter};
use std::path::Path;
use std::{
borrow::Cow,
collections::{BTreeMap, BTreeSet},
fmt::{Display, Formatter},
path::Path,
};

use fxhash::{FxHashMap, FxHashSet};

use rattler_digest::{serde::SerializableHash, Md5Hash, Sha256Hash};
use rattler_macros::sorted;
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, skip_serializing_none, OneOrMany};
use thiserror::Error;
use url::Url;

use rattler_macros::sorted;

use crate::{
build_spec::BuildNumber, package::IndexJson, utils::serde::DeserializeFromStrUnchecked,
Channel, NoArchType, PackageName, PackageUrl, Platform, RepoDataRecord, VersionWithSource,
};

/// [`RepoData`] is an index of package binaries available on in a subdirectory of a Conda channel.
/// [`RepoData`] is an index of package binaries available on in a subdirectory
/// of a Conda channel.
// Note: we cannot use the sorted macro here, because the `packages` and `conda_packages` fields are
// serialized in a special way. Therefore we do it manually.
#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)]
Expand All @@ -37,16 +38,18 @@ pub struct RepoData {
#[serde(serialize_with = "sort_map_alphabetically")]
pub packages: FxHashMap<String, PackageRecord>,

/// The conda packages contained in the repodata.json file (under a different key for
/// backwards compatibility with previous conda versions)
/// The conda packages contained in the repodata.json file (under a
/// different key for backwards compatibility with previous conda
/// versions)
#[serde(
default,
rename = "packages.conda",
serialize_with = "sort_map_alphabetically"
)]
pub conda_packages: FxHashMap<String, PackageRecord>,

/// removed packages (files are still accessible, but they are not installable like regular packages)
/// removed packages (files are still accessible, but they are not
/// installable like regular packages)
#[serde(
default,
serialize_with = "sort_set_alphabetically",
Expand All @@ -70,12 +73,12 @@ pub struct ChannelInfo {
pub base_url: Option<String>,
}

/// A single record in the Conda repodata. A single record refers to a single binary distribution
/// of a package on a Conda channel.
/// A single record in the Conda repodata. A single record refers to a single
/// binary distribution of a package on a Conda channel.
#[serde_as]
#[skip_serializing_none]
#[sorted]
#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Ord, PartialOrd, Clone, Hash)]
#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone, Hash)]
pub struct PackageRecord {
/// Optionally the architecture the package supports
pub arch: Option<String>,
Expand All @@ -86,19 +89,21 @@ pub struct PackageRecord {
/// The build number of the package
pub build_number: BuildNumber,

/// Additional constraints on packages. `constrains` are different from `depends` in that packages
/// specified in `depends` must be installed next to this package, whereas packages specified in
/// `constrains` are not required to be installed, but if they are installed they must follow these
/// constraints.
/// Additional constraints on packages. `constrains` are different from
/// `depends` in that packages specified in `depends` must be installed
/// next to this package, whereas packages specified in `constrains` are
/// not required to be installed, but if they are installed they must follow
/// these constraints.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub constrains: Vec<String>,

/// Specification of packages this package depends on
#[serde(default)]
pub depends: Vec<String>,

/// Features are a deprecated way to specify different feature sets for the conda solver. This is not
/// supported anymore and should not be used. Instead, `mutex` packages should be used to specify
/// Features are a deprecated way to specify different feature sets for the
/// conda solver. This is not supported anymore and should not be used.
/// Instead, `mutex` packages should be used to specify
/// mutually exclusive features.
pub features: Option<String>,

Expand All @@ -122,25 +127,25 @@ pub struct PackageRecord {
#[serde_as(deserialize_as = "DeserializeFromStrUnchecked")]
pub name: PackageName,

/// If this package is independent of architecture this field specifies in what way. See
/// [`NoArchType`] for more information.
/// If this package is independent of architecture this field specifies in
/// what way. See [`NoArchType`] for more information.
#[serde(skip_serializing_if = "NoArchType::is_none")]
pub noarch: NoArchType,

/// Optionally the platform the package supports
pub platform: Option<String>, // Note that this does not match the [`Platform`] enum..

/// Package identifiers of packages that are equivalent to this package but from other
/// ecosystems.
/// Package identifiers of packages that are equivalent to this package but
/// from other ecosystems.
/// starting from 0.23.2, this field became [`Option<Vec<PackageUrl>>`].
/// This was done to support older lockfiles,
/// where we didn't differentiate between empty purl and missing one.
/// Now, None:: means that the purl is missing, and it will be tried to filled in.
/// So later it can be one of the following:
/// [`Some(vec![])`] means that the purl is empty and package is not pypi one.
/// [`Some([`PackageUrl`])`] means that it is a pypi package.
/// Now, None:: means that the purl is missing, and it will be tried to
/// filled in. So later it can be one of the following:
/// [`Some(vec![])`] means that the purl is empty and package is not pypi
/// one. [`Some([`PackageUrl`])`] means that it is a pypi package.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub purls: Option<Vec<PackageUrl>>,
pub purls: Option<BTreeSet<PackageUrl>>,

/// Optionally a SHA256 hash of the package archive
#[serde_as(as = "Option<SerializableHash::<rattler_digest::Sha256>>")]
Expand All @@ -157,8 +162,9 @@ pub struct PackageRecord {
#[serde_as(as = "Option<crate::utils::serde::Timestamp>")]
pub timestamp: Option<chrono::DateTime<chrono::Utc>>,

/// Track features are nowadays only used to downweight packages (ie. give them less priority). To
/// that effect, the number of track features is counted (number of commas) and the package is downweighted
/// Track features are nowadays only used to downweight packages (ie. give
/// them less priority). To that effect, the number of track features is
/// counted (number of commas) and the package is downweighted
/// by the number of track_features.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[serde_as(as = "OneOrMany<_>")]
Expand Down Expand Up @@ -201,8 +207,8 @@ impl RepoData {
self.info.as_ref().and_then(|i| i.base_url.as_deref())
}

/// Builds a [`Vec<RepoDataRecord>`] from the packages in a [`RepoData`] given the source of the
/// data.
/// Builds a [`Vec<RepoDataRecord>`] from the packages in a [`RepoData`]
/// given the source of the data.
pub fn into_repo_data_records(self, channel: &Channel) -> Vec<RepoDataRecord> {
let mut records = Vec::with_capacity(self.packages.len() + self.conda_packages.len());
let channel_name = channel.canonical_name();
Expand Down Expand Up @@ -273,7 +279,8 @@ fn add_trailing_slash(url: &Url) -> Cow<'_, Url> {
}

impl PackageRecord {
/// A simple helper method that constructs a `PackageRecord` with the bare minimum values.
/// A simple helper method that constructs a `PackageRecord` with the bare
/// minimum values.
pub fn new(name: PackageName, version: impl Into<VersionWithSource>, build: String) -> Self {
Self {
arch: None,
Expand Down Expand Up @@ -302,8 +309,9 @@ impl PackageRecord {

/// Sorts the records topologically.
///
/// This function is deterministic, meaning that it will return the same result regardless of
/// the order of `records` and of the `depends` vector inside the records.
/// This function is deterministic, meaning that it will return the same
/// result regardless of the order of `records` and of the `depends`
/// vector inside the records.
///
/// Note that this function only works for packages with unique names.
pub fn sort_topologically<T: AsRef<PackageRecord> + Clone>(records: Vec<T>) -> Vec<T> {
Expand Down Expand Up @@ -338,8 +346,8 @@ pub enum ConvertSubdirError {
/// # Why can we not use `Platform::FromStr`?
///
/// We cannot use the [`Platform`] `FromStr` directly because `x86` and `x86_64`
/// are different architecture strings. Also some combinations have been removed,
/// because they have not been found.
/// are different architecture strings. Also some combinations have been
/// removed, because they have not been found.
fn determine_subdir(
platform: Option<String>,
arch: Option<String>,
Expand Down Expand Up @@ -377,7 +385,8 @@ fn determine_subdir(
}

impl PackageRecord {
/// Builds a [`PackageRecord`] from a [`IndexJson`] and optionally a size, sha256 and md5 hash.
/// Builds a [`PackageRecord`] from a [`IndexJson`] and optionally a size,
/// sha256 and md5 hash.
pub fn from_index_json(
index: IndexJson,
size: Option<u64>,
Expand Down Expand Up @@ -435,10 +444,12 @@ fn sort_set_alphabetically<S: serde::Serializer>(

#[cfg(test)]
mod test {
use crate::repo_data::{compute_package_url, determine_subdir};
use fxhash::FxHashMap;

use crate::{Channel, ChannelConfig, RepoData};
use crate::{
repo_data::{compute_package_url, determine_subdir},
Channel, ChannelConfig, RepoData,
};

// isl-0.12.2-1.tar.bz2
// gmp-5.1.2-6.tar.bz2
Expand Down
3 changes: 2 additions & 1 deletion crates/rattler_conda_types/src/repo_data/patches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use fxhash::{FxHashMap, FxHashSet};
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, skip_serializing_none, OneOrMany};
use std::collections::BTreeSet;
use std::io;
use std::path::Path;

Expand Down Expand Up @@ -97,7 +98,7 @@ pub struct PackageRecordPatch {

/// Package identifiers of packages that are equivalent to this package but from other
/// ecosystems.
pub purls: Option<Vec<PackageUrl>>,
pub purls: Option<BTreeSet<PackageUrl>>,
}

/// Repodata patch instructions for a single subdirectory. See [`RepoDataPatch`] for more
Expand Down
2 changes: 1 addition & 1 deletion crates/rattler_conda_types/src/repo_data_record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use url::Url;

/// Information about a package from repodata. It includes a [`crate::PackageRecord`] but it also stores
/// the source of the data (like the url and the channel).
#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Ord, PartialOrd, Clone, Hash)]
#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone, Hash)]
pub struct RepoDataRecord {
/// The data stored in the repodata.json.
#[serde(flatten)]
Expand Down
73 changes: 54 additions & 19 deletions crates/rattler_lock/src/builder.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
//! Builder for the creation of lock files.

use crate::file_format_version::FileFormatVersion;
use crate::{
Channel, CondaPackageData, EnvironmentData, EnvironmentPackageData, LockFile, LockFileInner,
PypiIndexes, PypiPackageData, PypiPackageEnvironmentData,
use std::{
collections::{BTreeSet, HashMap},
sync::Arc,
};

use fxhash::FxHashMap;
use indexmap::{IndexMap, IndexSet};
use pep508_rs::ExtraName;
use rattler_conda_types::Platform;
use std::{
collections::{BTreeSet, HashMap},
sync::Arc,

use crate::{
file_format_version::FileFormatVersion, Channel, CondaPackageData, EnvironmentData,
EnvironmentPackageData, LockFile, LockFileInner, Package, PypiIndexes, PypiPackageData,
PypiPackageEnvironmentData,
};

/// A struct to incrementally build a lock-file.
Expand Down Expand Up @@ -68,9 +70,9 @@ impl LockFileBuilder {

/// Adds a conda locked package to a specific environment and platform.
///
/// This function is similar to [`Self::with_conda_package`] but differs in that it takes a
/// mutable reference to self instead of consuming it. This allows for a more fluent with
/// chaining calls.
/// This function is similar to [`Self::with_conda_package`] but differs in
/// that it takes a mutable reference to self instead of consuming it.
/// This allows for a more fluent with chaining calls.
pub fn add_conda_package(
&mut self,
environment: impl Into<String>,
Expand Down Expand Up @@ -102,9 +104,9 @@ impl LockFileBuilder {

/// Adds a pypi locked package to a specific environment and platform.
///
/// This function is similar to [`Self::with_pypi_package`] but differs in that it takes a
/// mutable reference to self instead of consuming it. This allows for a more fluent with
/// chaining calls.
/// This function is similar to [`Self::with_pypi_package`] but differs in
/// that it takes a mutable reference to self instead of consuming it.
/// This allows for a more fluent with chaining calls.
pub fn add_pypi_package(
&mut self,
environment: impl Into<String>,
Expand Down Expand Up @@ -141,9 +143,9 @@ impl LockFileBuilder {

/// Adds a conda locked package to a specific environment and platform.
///
/// This function is similar to [`Self::add_conda_package`] but differs in that it consumes
/// `self` instead of taking a mutable reference. This allows for a better interface when
/// modifying an existing instance.
/// This function is similar to [`Self::add_conda_package`] but differs in
/// that it consumes `self` instead of taking a mutable reference. This
/// allows for a better interface when modifying an existing instance.
pub fn with_conda_package(
mut self,
environment: impl Into<String>,
Expand All @@ -154,11 +156,44 @@ impl LockFileBuilder {
self
}

/// Adds a package from another environment to a specific environment and
/// platform.
pub fn with_package(
mut self,
environment: impl Into<String>,
platform: Platform,
locked_package: Package,
) -> Self {
self.add_package(environment, platform, locked_package);
self
}

/// Adds a package from another environment to a specific environment and
/// platform.
pub fn add_package(
&mut self,
environment: impl Into<String>,
platform: Platform,
locked_package: Package,
) -> &mut Self {
match locked_package {
Package::Conda(p) => {
self.add_conda_package(environment, platform, p.package_data().clone())
}
Package::Pypi(p) => self.add_pypi_package(
environment,
platform,
p.package_data().clone(),
p.environment_data().clone(),
),
}
}

/// Adds a pypi locked package to a specific environment and platform.
///
/// This function is similar to [`Self::add_pypi_package`] but differs in that it consumes
/// `self` instead of taking a mutable reference. This allows for a better interface when
/// modifying an existing instance.
/// This function is similar to [`Self::add_pypi_package`] but differs in
/// that it consumes `self` instead of taking a mutable reference. This
/// allows for a better interface when modifying an existing instance.
pub fn with_pypi_package(
mut self,
environment: impl Into<String>,
Expand Down
1 change: 0 additions & 1 deletion crates/rattler_lock/src/conda.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use rattler_conda_types::{PackageRecord, RepoDataRecord};
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, skip_serializing_none};
use std::cmp::Ordering;
use std::hash::Hash;
use url::Url;

/// A locked conda dependency is just a [`PackageRecord`] with some additional information on where
Expand Down
Loading

0 comments on commit ed561f3

Please sign in to comment.