diff --git a/Cargo.toml b/Cargo.toml index d23ece78d..f35b1b386 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ async-compression = { version = "0.4.17", features = [ "zstd", ] } async-fd-lock = "0.2.0" -fs4 = "0.10.0" +fs4 = "0.11.0" async-trait = "0.1.83" axum = { version = "0.7.7", default-features = false, features = [ "tokio", diff --git a/crates/rattler/src/install/clobber_registry.rs b/crates/rattler/src/install/clobber_registry.rs index 83a0c5cbf..e4173627b 100644 --- a/crates/rattler/src/install/clobber_registry.rs +++ b/crates/rattler/src/install/clobber_registry.rs @@ -1154,9 +1154,12 @@ mod tests { // Create a transaction let operations = test_python_noarch_operations(); - let python_info = - PythonInfo::from_version(&Version::from_str("3.11.0").unwrap(), Platform::current()) - .unwrap(); + let python_info = PythonInfo::from_version( + &Version::from_str("3.11.0").unwrap(), + None, + Platform::current(), + ) + .unwrap(); let transaction = transaction::Transaction:: { operations, python_info: Some(python_info.clone()), diff --git a/crates/rattler/src/install/entry_point.rs b/crates/rattler/src/install/entry_point.rs index 761a2aa5f..30687ac9d 100644 --- a/crates/rattler/src/install/entry_point.rs +++ b/crates/rattler/src/install/entry_point.rs @@ -205,8 +205,12 @@ mod test { "/prefix", false, &EntryPoint::from_str("jupyter-lab = jupyterlab.labapp:main").unwrap(), - &PythonInfo::from_version(&Version::from_str("3.11.0").unwrap(), Platform::Linux64) - .unwrap(), + &PythonInfo::from_version( + &Version::from_str("3.11.0").unwrap(), + None, + Platform::Linux64, + ) + .unwrap(), ); insta::assert_snapshot!(script); @@ -214,8 +218,12 @@ mod test { "/prefix", true, &EntryPoint::from_str("jupyter-lab = jupyterlab.labapp:main").unwrap(), - &PythonInfo::from_version(&Version::from_str("3.11.0").unwrap(), Platform::Linux64) - .unwrap(), + &PythonInfo::from_version( + &Version::from_str("3.11.0").unwrap(), + None, + Platform::Linux64, + ) + .unwrap(), ); insta::assert_snapshot!("windows", script); } diff --git a/crates/rattler/src/install/mod.rs b/crates/rattler/src/install/mod.rs index d4ac33122..ed5691ff9 100644 --- a/crates/rattler/src/install/mod.rs +++ b/crates/rattler/src/install/mod.rs @@ -784,7 +784,8 @@ mod test { // Specify python version let python_version = - PythonInfo::from_version(&Version::from_str("3.11.0").unwrap(), platform).unwrap(); + PythonInfo::from_version(&Version::from_str("3.11.0").unwrap(), None, platform) + .unwrap(); // Download and install each layer into an environment. let install_driver = InstallDriver::default(); diff --git a/crates/rattler/src/install/python.rs b/crates/rattler/src/install/python.rs index 116c57251..bf0e76840 100644 --- a/crates/rattler/src/install/python.rs +++ b/crates/rattler/src/install/python.rs @@ -1,9 +1,13 @@ -use rattler_conda_types::{Platform, Version}; -use std::borrow::Cow; -use std::path::{Path, PathBuf}; +use std::{ + borrow::Cow, + path::{Path, PathBuf}, +}; -/// Information required for linking no-arch python packages. The struct contains information about -/// a specific Python version that is installed in an environment. +use rattler_conda_types::{PackageRecord, Platform, Version}; + +/// Information required for linking no-arch python packages. The struct +/// contains information about a specific Python version that is installed in an +/// environment. #[derive(Debug, Clone)] pub struct PythonInfo { /// The platform that the python package is installed for @@ -29,9 +33,26 @@ pub enum PythonInfoError { } impl PythonInfo { - /// Build an instance based on the version of the python package and the platform it is - /// installed for. - pub fn from_version(version: &Version, platform: Platform) -> Result { + /// Build an instance based on metadata of the package that represents the + /// python interpreter. + pub fn from_python_record( + record: &PackageRecord, + platform: Platform, + ) -> Result { + Self::from_version( + record.version.version(), + record.python_site_packages_path.as_deref(), + platform, + ) + } + + /// Build an instance based on the version of the python package and the + /// platform it is installed for. + pub fn from_version( + version: &Version, + site_packages_path: Option<&str>, + platform: Platform, + ) -> Result { // Determine the major, and minor versions of the version let (major, minor) = version .as_major_minor() @@ -45,11 +66,16 @@ impl PythonInfo { }; // Find the location of the site packages - let site_packages_path = if platform.is_windows() { - PathBuf::from("Lib/site-packages") - } else { - PathBuf::from(format!("lib/python{major}.{minor}/site-packages")) - }; + let site_packages_path = site_packages_path.map_or_else( + || { + if platform.is_windows() { + PathBuf::from("Lib/site-packages") + } else { + PathBuf::from(format!("lib/python{major}.{minor}/site-packages")) + } + }, + PathBuf::from, + ); // Binary directory let bin_dir = if platform.is_windows() { @@ -89,8 +115,8 @@ impl PythonInfo { } } - /// Returns the target location of a file in a noarch python package given its location in its - /// package archive. + /// Returns the target location of a file in a noarch python package given + /// its location in its package archive. pub fn get_python_noarch_target_path<'a>(&self, relative_path: &'a Path) -> Cow<'a, Path> { if let Ok(rest) = relative_path.strip_prefix("site-packages/") { self.site_packages_path.join(rest).into() @@ -101,8 +127,8 @@ impl PythonInfo { } } - /// Returns true if this version of python differs so much that a relink is required for all - /// noarch python packages. + /// Returns true if this version of python differs so much that a relink is + /// required for all noarch python packages. pub fn is_relink_required(&self, previous: &PythonInfo) -> bool { self.short_version.0 != previous.short_version.0 || self.short_version.1 != previous.short_version.1 diff --git a/crates/rattler/src/install/transaction.rs b/crates/rattler/src/install/transaction.rs index df8f85a66..9f14adb25 100644 --- a/crates/rattler/src/install/transaction.rs +++ b/crates/rattler/src/install/transaction.rs @@ -1,9 +1,9 @@ use std::collections::HashSet; -use crate::install::python::PythonInfoError; -use crate::install::PythonInfo; use rattler_conda_types::{PackageRecord, Platform}; +use crate::install::{python::PythonInfoError, PythonInfo}; + /// Error that occurred during creation of a Transaction #[derive(Debug, thiserror::Error)] pub enum TransactionError { @@ -31,8 +31,9 @@ pub enum TransactionOperation { new: New, }, - /// Reinstall a package. This can happen if the Python version changed in the environment, we - /// need to relink all noarch python packages in that case. + /// Reinstall a package. This can happen if the Python version changed in + /// the environment, we need to relink all noarch python packages in + /// that case. Reinstall(Old), /// Completely remove a package @@ -40,8 +41,9 @@ pub enum TransactionOperation { } impl, New> TransactionOperation { - /// Returns the record of the package to install for this operation. If this operation does not - /// refer to an installable package, `None` is returned. + /// Returns the record of the package to install for this operation. If this + /// operation does not refer to an installable package, `None` is + /// returned. pub fn record_to_install(&self) -> Option<&New> { match self { TransactionOperation::Install(record) => Some(record), @@ -53,8 +55,9 @@ impl, New> TransactionOperation { } impl TransactionOperation { - /// Returns the record of the package to remove for this operation. If this operation does not - /// refer to an removable package, `None` is returned. + /// Returns the record of the package to remove for this operation. If this + /// operation does not refer to an removable package, `None` is + /// returned. pub fn record_to_remove(&self) -> Option<&Old> { match self { TransactionOperation::Install(_) => None, @@ -65,17 +68,19 @@ impl TransactionOperation { } } -/// Describes the operations to perform to bring an environment from one state into another. +/// Describes the operations to perform to bring an environment from one state +/// into another. #[derive(Debug)] pub struct Transaction { /// A list of operations to update an environment pub operations: Vec>, - /// The python version of the target state, or None if python doesnt exist in the environment. + /// The python version of the target state, or None if python doesnt exist + /// in the environment. pub python_info: Option, - /// The python version of the current state, or None if python didnt exist in the previous - /// environment. + /// The python version of the current state, or None if python didnt exist + /// in the previous environment. pub current_python_info: Option, /// The target platform of the transaction @@ -83,7 +88,8 @@ pub struct Transaction { } impl Transaction { - /// Return an iterator over the prefix records of all packages that are going to be removed. + /// Return an iterator over the prefix records of all packages that are + /// going to be removed. pub fn removed_packages(&self) -> impl Iterator + '_ { self.operations .iter() @@ -97,7 +103,8 @@ impl Transaction { } impl, New> Transaction { - /// Return an iterator over the prefix records of all packages that are going to be installed. + /// Return an iterator over the prefix records of all packages that are + /// going to be installed. pub fn installed_packages(&self) -> impl Iterator + '_ { self.operations .iter() @@ -111,8 +118,8 @@ impl, New> Transaction { } impl, New: AsRef> Transaction { - /// Constructs a [`Transaction`] by taking the current situation and diffing that against the - /// desired situation. + /// Constructs a [`Transaction`] by taking the current situation and diffing + /// that against the desired situation. pub fn from_current_and_desired< CurIter: IntoIterator, NewIter: IntoIterator, @@ -148,7 +155,8 @@ impl, New: AsRef> Transaction .map(|r| r.as_ref().name.clone()) .collect::>(); - // Remove all current packages that are not in desired (but keep order of current) + // Remove all current packages that are not in desired (but keep order of + // current) for record in current_iter { if !desired_names.contains(&record.as_ref().name) { operations.push(TransactionOperation::Remove(record)); @@ -158,7 +166,8 @@ impl, New: AsRef> Transaction // reverse all removals, last in first out operations.reverse(); - // Figure out the operations to perform, but keep the order of the original "desired" iterator + // Figure out the operations to perform, but keep the order of the original + // "desired" iterator for record in desired_iter { let name = &record.as_ref().name; let old_record = current_map.remove(name); @@ -190,8 +199,8 @@ impl, New: AsRef> Transaction } } -/// Determine the version of Python used by a set of packages. Returns `None` if none of the -/// packages refers to a Python installation. +/// Determine the version of Python used by a set of packages. Returns `None` if +/// none of the packages refers to a Python installation. fn find_python_info( records: impl IntoIterator>, platform: Platform, @@ -199,7 +208,7 @@ fn find_python_info( records .into_iter() .find(|r| is_python_record(r.as_ref())) - .map(|record| PythonInfo::from_version(&record.as_ref().version, platform)) + .map(|record| PythonInfo::from_python_record(record.as_ref(), platform)) .map_or(Ok(None), |info| info.map(Some)) } diff --git a/crates/rattler/src/install/unlink.rs b/crates/rattler/src/install/unlink.rs index d99e32e8c..3c23c1a80 100644 --- a/crates/rattler/src/install/unlink.rs +++ b/crates/rattler/src/install/unlink.rs @@ -163,7 +163,7 @@ mod tests { rattler_package_streaming::fs::extract(&package_path, package_dir.path()).unwrap(); let py_info = - PythonInfo::from_version(&Version::from_str("3.10").unwrap(), Platform::Linux64) + PythonInfo::from_version(&Version::from_str("3.10").unwrap(), None, Platform::Linux64) .unwrap(); let install_options = InstallOptions { python_info: Some(py_info), diff --git a/crates/rattler_conda_types/src/match_spec/parse.rs b/crates/rattler_conda_types/src/match_spec/parse.rs index b4c39c706..14027b982 100644 --- a/crates/rattler_conda_types/src/match_spec/parse.rs +++ b/crates/rattler_conda_types/src/match_spec/parse.rs @@ -1360,7 +1360,7 @@ mod tests { }); // insta check all the strings - let vec_strings = specs.iter().map(|s| s.to_string()).collect::>(); + let vec_strings = specs.iter().map(ToString::to_string).collect::>(); insta::assert_debug_snapshot!(vec_strings); // parse back the strings and check if they are the same diff --git a/crates/rattler_conda_types/src/package/index.rs b/crates/rattler_conda_types/src/package/index.rs index 585cac162..bb74b003b 100644 --- a/crates/rattler_conda_types/src/package/index.rs +++ b/crates/rattler_conda_types/src/package/index.rs @@ -1,16 +1,17 @@ use std::path::Path; -use super::PackageFile; -use crate::{NoArchType, PackageName, VersionWithSource}; +use rattler_macros::sorted; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, skip_serializing_none, OneOrMany}; -use rattler_macros::sorted; +use super::PackageFile; +use crate::{NoArchType, PackageName, VersionWithSource}; /// A representation of the `index.json` file found in package archives. /// -/// The `index.json` file contains information about the package build and dependencies of the package. -/// This data makes up the repodata.json file in the repository. +/// The `index.json` file contains information about the package build and +/// dependencies of the package. This data makes up the repodata.json file in +/// the repository. #[serde_as] #[sorted] #[skip_serializing_none] @@ -22,7 +23,8 @@ pub struct IndexJson { /// The build string of the package. pub build: String, - /// The build number of the package. This is also included in the build string. + /// The build number of the package. This is also included in the build + /// string. pub build_number: u64, /// The package constraints of the package @@ -33,8 +35,9 @@ pub struct IndexJson { #[serde(default, skip_serializing_if = "Vec::is_empty")] pub depends: Vec, - /// 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, @@ -47,14 +50,19 @@ pub struct IndexJson { /// The lowercase name of the package 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 OS the package is build for. pub platform: Option, + /// Optionally a path within the environment of the site-packages directory. + /// This field is only present for python interpreter packages. + /// This field was introduced with . + pub python_site_packages_path: Option, + /// The subdirectory that contains this package pub subdir: Option, @@ -62,8 +70,9 @@ pub struct IndexJson { #[serde_as(as = "Option")] pub timestamp: Option>, - /// 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<_>")] diff --git a/crates/rattler_conda_types/src/repo_data/mod.rs b/crates/rattler_conda_types/src/repo_data/mod.rs index 0664c9399..56c3ec299 100644 --- a/crates/rattler_conda_types/src/repo_data/mod.rs +++ b/crates/rattler_conda_types/src/repo_data/mod.rs @@ -19,15 +19,15 @@ use serde_with::{serde_as, skip_serializing_none, OneOrMany}; use thiserror::Error; use url::Url; -use crate::utils::url::add_trailing_slash; use crate::{ build_spec::BuildNumber, package::{IndexJson, RunExportsJson}, - utils::serde::DeserializeFromStrUnchecked, - Channel, NoArchType, PackageName, PackageUrl, Platform, RepoDataRecord, VersionWithSource, -}; -use crate::{ - utils::serde::sort_map_alphabetically, MatchSpec, Matches, ParseMatchSpecError, ParseStrictness, + utils::{ + serde::{sort_map_alphabetically, DeserializeFromStrUnchecked}, + url::add_trailing_slash, + }, + Channel, MatchSpec, Matches, NoArchType, PackageName, PackageUrl, ParseMatchSpecError, + ParseStrictness, Platform, RepoDataRecord, VersionWithSource, }; /// [`RepoData`] is an index of package binaries available on in a subdirectory @@ -153,6 +153,11 @@ pub struct PackageRecord { #[serde(default, skip_serializing_if = "Option::is_none")] pub purls: Option>, + /// Optionally a path within the environment of the site-packages directory. + /// This field is only present for python interpreter packages. + /// This field was introduced with . + pub python_site_packages_path: Option, + /// Run exports that are specified in the package. #[serde(default, skip_serializing_if = "Option::is_none")] pub run_exports: Option, @@ -302,6 +307,7 @@ impl PackageRecord { name, noarch: NoArchType::default(), platform: None, + python_site_packages_path: None, sha256: None, size: None, subdir: Platform::current().to_string(), @@ -324,10 +330,11 @@ impl PackageRecord { topological_sort::sort_topologically(records) } - /// Validate that the given package records are valid w.r.t. 'depends' and 'constrains'. - /// This function will return Ok(()) if all records form a valid environment, i.e., all dependencies - /// of each package are satisfied by the other packages in the list. - /// If there is a dependency that is not satisfied, this function will return an error. + /// Validate that the given package records are valid w.r.t. 'depends' and + /// 'constrains'. This function will return Ok(()) if all records form a + /// valid environment, i.e., all dependencies of each package are + /// satisfied by the other packages in the list. If there is a + /// dependency that is not satisfied, this function will return an error. pub fn validate>( records: Vec, ) -> Result<(), ValidatePackageRecordsError> { @@ -488,6 +495,7 @@ impl PackageRecord { name: index.name, noarch: index.noarch, platform: index.platform, + python_site_packages_path: index.python_site_packages_path, sha256, size, subdir, diff --git a/crates/rattler_conda_types/src/version/mod.rs b/crates/rattler_conda_types/src/version/mod.rs index 37b65c104..165ca4b13 100644 --- a/crates/rattler_conda_types/src/version/mod.rs +++ b/crates/rattler_conda_types/src/version/mod.rs @@ -1,21 +1,19 @@ -use std::borrow::Cow; -use std::cell::RefCell; -use std::collections::Bound; -use std::hash::{Hash, Hasher}; -use std::ops::RangeBounds; use std::{ + borrow::Cow, + cell::RefCell, cmp::Ordering, + collections::Bound, fmt, fmt::{Debug, Display, Formatter}, + hash::{Hash, Hasher}, iter, + ops::RangeBounds, }; use itertools::{Either, EitherOrBoth, Itertools}; -use serde::de::Error; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use smallvec::SmallVec; - pub use parse::{ParseVersionError, ParseVersionErrorKind}; +use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; +use smallvec::SmallVec; mod flags; pub(crate) mod parse; @@ -24,53 +22,59 @@ mod with_source; pub(crate) mod bump; pub use bump::{VersionBumpError, VersionBumpType}; - use flags::Flags; use segment::Segment; - use thiserror::Error; pub use with_source::VersionWithSource; -/// This class implements an order relation between version strings. Version strings can contain the -/// usual alphanumeric characters (A-Za-z0-9), separated into segments by dots and underscores. -/// Empty segments (i.e. two consecutive dots, a leading/trailing underscore) are not permitted. An -/// optional epoch number - an integer followed by '!' - can precede the actual version string (this -/// is useful to indicate a change in the versioning scheme itself). Version comparison is -/// case-insensitive. +/// This class implements an order relation between version strings. Version +/// strings can contain the usual alphanumeric characters (A-Za-z0-9), separated +/// into segments by dots and underscores. Empty segments (i.e. two consecutive +/// dots, a leading/trailing underscore) are not permitted. An optional epoch +/// number - an integer followed by '!' - can precede the actual version string +/// (this is useful to indicate a change in the versioning scheme itself). +/// Version comparison is case-insensitive. /// /// Rattler supports six types of version strings: /// /// * Release versions contain only integers, e.g. '1.0', '2.3.5'. -/// * Pre-release versions use additional letters such as 'a' or 'rc', for example '1.0a1', -/// '1.2.beta3', '2.3.5rc3'. -/// * Development versions are indicated by the string 'dev', for example '1.0dev42', '2.3.5.dev12'. -/// * Post-release versions are indicated by the string 'post', for example '1.0post1', '2.3.5.post2'. -/// * Tagged versions have a suffix that specifies a particular property of interest, e.g. '1.1.parallel'. -/// Tags can be added to any of the preceding four types. As far as sorting is concerned, -/// tags are treated like strings in pre-release versions. -/// * An optional local version string separated by '+' can be appended to the main (upstream) version string. -/// It is only considered in comparisons when the main versions are equal, but otherwise handled in -/// exactly the same manner. +/// * Pre-release versions use additional letters such as 'a' or 'rc', for +/// example '1.0a1', '1.2.beta3', '2.3.5rc3'. +/// * Development versions are indicated by the string 'dev', for example +/// '1.0dev42', '2.3.5.dev12'. +/// * Post-release versions are indicated by the string 'post', for example +/// '1.0post1', '2.3.5.post2'. +/// * Tagged versions have a suffix that specifies a particular property of +/// interest, e.g. '1.1.parallel'. Tags can be added to any of the preceding +/// four types. As far as sorting is concerned, tags are treated like strings +/// in pre-release versions. +/// * An optional local version string separated by '+' can be appended to the +/// main (upstream) version string. It is only considered in comparisons when +/// the main versions are equal, but otherwise handled in exactly the same +/// manner. /// /// To obtain a predictable version ordering, it is crucial to keep the /// version number scheme of a given package consistent over time. /// /// Specifically, /// -/// * version strings should always have the same number of components (except for an optional tag suffix -/// or local version string), -/// * letters/strings indicating non-release versions should always occur at the same position. +/// * version strings should always have the same number of components (except +/// for an optional tag suffix or local version string), +/// * letters/strings indicating non-release versions should always occur at the +/// same position. /// /// Before comparison, version strings are parsed as follows: /// -/// * They are first split into epoch, version number, and local version number at '!' and '+' respectively. -/// If there is no '!', the epoch is set to 0. If there is no '+', the local version is empty. +/// * They are first split into epoch, version number, and local version number +/// at '!' and '+' respectively. If there is no '!', the epoch is set to 0. If +/// there is no '+', the local version is empty. /// * The version part is then split into components at '.' and '_'. /// * Each component is split again into runs of numerals and non-numerals /// * Subcomponents containing only numerals are converted to integers. -/// * Strings are converted to lower case, with special treatment for 'dev' and 'post'. -/// * When a component starts with a letter, the fillvalue 0 is inserted to keep numbers and strings in phase, -/// resulting in '1.1.a1' == 1.1.0a1'. +/// * Strings are converted to lower case, with special treatment for 'dev' and +/// 'post'. +/// * When a component starts with a letter, the fillvalue 0 is inserted to keep +/// numbers and strings in phase, resulting in '1.1.a1' == 1.1.0a1'. /// * The same is repeated for the local version part. /// /// # Examples: @@ -137,16 +141,17 @@ pub use with_source::VersionWithSource; pub struct Version { /// Individual components of the version. /// - /// We store a maximum of 3 components on the stack. If a version consists of more components - /// they are stored on the heap instead. We choose 3 here because most versions only consist of - /// 3 components. + /// We store a maximum of 3 components on the stack. If a version consists + /// of more components they are stored on the heap instead. We choose 3 + /// here because most versions only consist of 3 components. /// /// So for the version `1.2g.beta15.rc` this stores: /// /// [1, 2, 'g', 0, 'beta', 15, 0, 'rc'] components: ComponentVec, - /// Information on each individual segment. Segments group different components together. + /// Information on each individual segment. Segments group different + /// components together. /// /// So for the version `1.2g.beta15.rc` this stores: /// @@ -154,15 +159,16 @@ pub struct Version { /// /// e.g. `1` consists of 1 component /// `2g` consists of 2 components (`2` and `g`) - /// `beta15` consists of 3 components (`0`, `beta` and `15`). Segments must always start - /// with a number. - /// `rc` consists of 2 components (`0`, `rc`). Segments must always start with a number. + /// `beta15` consists of 3 components (`0`, `beta` and `15`). Segments + /// must always start with a number. + /// `rc` consists of 2 components (`0`, `rc`). Segments must always + /// start with a number. segments: SegmentVec, /// Flags to indicate edge cases /// The first bit indicates whether or not this version has an epoch. - /// The rest of the bits indicate from which segment the local version starts or 0 if there is - /// no local version. + /// The rest of the bits indicate from which segment the local version + /// starts or 0 if there is no local version. flags: Flags, } @@ -179,7 +185,8 @@ pub enum VersionExtendError { } impl Version { - /// Constructs a version with just a major component and no other components, e.g. "1". + /// Constructs a version with just a major component and no other + /// components, e.g. "1". pub fn major(major: u64) -> Version { Version { components: smallvec::smallvec![Component::Numeral(major)], @@ -198,8 +205,8 @@ impl Version { self.flags.local_segment_index() > 0 } - /// Returns the index of the first segment that belongs to the local version or `None` if there - /// is no local version + /// Returns the index of the first segment that belongs to the local version + /// or `None` if there is no local version fn local_segment_index(&self) -> Option { let index = self.flags.local_segment_index(); if index > 0 { @@ -209,13 +216,14 @@ impl Version { } } - /// Returns the epoch part of the version. If the version did not specify an epoch `0` is - /// returned. + /// Returns the epoch part of the version. If the version did not specify an + /// epoch `0` is returned. pub fn epoch(&self) -> u64 { self.epoch_opt().unwrap_or(0) } - /// Returns the epoch part of the version or `None` if the version did not specify an epoch. + /// Returns the epoch part of the version or `None` if the version did not + /// specify an epoch. pub fn epoch_opt(&self) -> Option { if self.has_epoch() { Some( @@ -251,7 +259,8 @@ impl Version { /// Returns the segments that belong the local part of the version. /// - /// The local part of a a version is the part behind the (optional) `+`. E.g.: + /// The local part of a a version is the part behind the (optional) `+`. + /// E.g.: /// /// ```text /// 1.2+3.2.1-alpha0 @@ -281,8 +290,9 @@ impl Version { } } - /// Tries to extract the major and minor versions from the version. Returns None if this instance - /// doesnt appear to contain a major and minor version. + /// Tries to extract the major and minor versions from the version. Returns + /// None if this instance doesnt appear to contain a major and minor + /// version. pub fn as_major_minor(&self) -> Option<(u64, u64)> { let mut segments = self.segments(); let major_segment = segments.next()?; @@ -306,14 +316,16 @@ impl Version { /// Returns true if this is considered a dev version. /// - /// If a version has a single component named "dev" it is considered to be a dev version. + /// If a version has a single component named "dev" it is considered to be a + /// dev version. pub fn is_dev(&self) -> bool { self.segments() .flat_map(|segment| segment.components()) .any(Component::is_dev) } - /// Check if this version version and local strings start with the same as other. + /// Check if this version version and local strings start with the same as + /// other. pub fn starts_with(&self, other: &Self) -> bool { self.epoch() == other.epoch() && segments_starts_with(self.segments(), other.segments()) @@ -332,8 +344,8 @@ impl Version { /// Returns a new version with only the given segments. /// - /// Calling this function on a version that looks like `1.3a.4-alpha3` with the range `[1..3]` - /// will return the version: `3a.4`. + /// Calling this function on a version that looks like `1.3a.4-alpha3` with + /// the range `[1..3]` will return the version: `3a.4`. pub fn with_segments(&self, segments: impl RangeBounds) -> Option { // Determine the actual bounds to use let segment_count = self.segment_count(); @@ -411,8 +423,9 @@ impl Version { }) } - /// Pops the specified number of segments from the version. Returns `None` if the resulting - /// version would become invalid because it no longer contains any segments. + /// Pops the specified number of segments from the version. Returns `None` + /// if the resulting version would become invalid because it no longer + /// contains any segments. pub fn pop_segments(&self, n: usize) -> Option { let segment_count = self.segment_count(); if segment_count < n { @@ -422,8 +435,8 @@ impl Version { } } - /// Returns the number of segments in the version. Segments are the part of the version - /// separated by dots or dashes. + /// Returns the number of segments in the version. Segments are the part of + /// the version separated by dots or dashes. pub fn segment_count(&self) -> usize { if let Some(local_index) = self.local_segment_index() { local_index @@ -432,8 +445,8 @@ impl Version { } } - /// Returns either this [`Version`] or a new [`Version`] where the local version part has been - /// removed. + /// Returns either this [`Version`] or a new [`Version`] where the local + /// version part has been removed. pub fn strip_local(&self) -> Cow<'_, Version> { if self.has_local() { let mut components = SmallVec::<[Component; 3]>::default(); @@ -464,8 +477,9 @@ impl Version { } } - /// Extend the version to the specified length by adding default components (0s). - /// If the version is already longer than the specified length it is returned as is. + /// Extend the version to the specified length by adding default components + /// (0s). If the version is already longer than the specified length it + /// is returned as is. pub fn extend_to_length(&self, length: usize) -> Result, VersionExtendError> { if self.segment_count() >= length { return Ok(Cow::Borrowed(self)); @@ -510,7 +524,8 @@ impl Version { } } -/// Returns true if the specified segments are considered to start with the other segments. +/// Returns true if the specified segments are considered to start with the +/// other segments. fn segments_starts_with< 'a, 'b, @@ -585,9 +600,10 @@ impl Hash for Version { ) { let default = Component::default(); for segment in segments { - // The versions `1.0` and `1` are considered equal because a version has an infinite - // number of default components in each segment. The get an equivalent hash we skip - // trailing default components when computing the hash + // The versions `1.0` and `1` are considered equal because a version has an + // infinite number of default components in each segment. The + // get an equivalent hash we skip trailing default components + // when computing the hash segment .components() .rev() @@ -614,9 +630,10 @@ impl Debug for Version { } } -/// A helper struct to format an iterator of [`SegmentIter`]. Implements both [`std::fmt::Debug`] -/// where segments are displayed as an array of arrays (e.g. `[[1], [2,3,4]]`) and -/// [`std::fmt::Display`] where segments are display in their canonical form (e.g. `1.2-rc2`). +/// A helper struct to format an iterator of [`SegmentIter`]. Implements both +/// [`std::fmt::Debug`] where segments are displayed as an array of arrays (e.g. +/// `[[1], [2,3,4]]`) and [`std::fmt::Display`] where segments are display in +/// their canonical form (e.g. `1.2-rc2`). struct SegmentFormatter<'v, I: Iterator> + 'v> { inner: RefCell, I)>>, } @@ -691,8 +708,8 @@ pub enum Component { /// Dev should always be ordered less than anything else. Dev, - /// A generic string identifier. Identifiers are compared lexicographically. They are always - /// ordered less than numbers. + /// A generic string identifier. Identifiers are compared lexicographically. + /// They are always ordered less than numbers. Iden(Box), /// An underscore or dash. @@ -943,20 +960,22 @@ impl<'v> SegmentIter<'v> { self.components().all(Component::is_zero) } - /// Returns true if the first component is an implicit default added while parsing the version. - /// E.g. `2.a` is represented as `2.0a`. The `0` is added implicitly. + /// Returns true if the first component is an implicit default added while + /// parsing the version. E.g. `2.a` is represented as `2.0a`. The `0` is + /// added implicitly. pub fn has_implicit_default(&self) -> bool { self.segment.has_implicit_default() } - /// Returns the separator that is found in from of this segment or `None` if this segment was - /// not preceded by a separator. + /// Returns the separator that is found in from of this segment or `None` if + /// this segment was not preceded by a separator. pub fn separator(&self) -> Option { self.segment.separator() } - /// Returns the number of components stored in the version. Note that the number of components - /// returned by [`Self::components`] might differ because it might include an implicit default. + /// Returns the number of components stored in the version. Note that the + /// number of components returned by [`Self::components`] might differ + /// because it might include an implicit default. pub fn component_count(&self) -> usize { self.segment.len() as usize } @@ -1026,18 +1045,18 @@ impl Hash for StrictVersion { #[cfg(test)] mod test { - use std::cmp::Ordering; - use std::str::FromStr; - - use std::collections::hash_map::DefaultHasher; - use std::hash::{Hash, Hasher}; + use std::{ + cmp::Ordering, + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, + str::FromStr, + }; use rand::seq::SliceRandom; use rstest::rstest; - use crate::version::StrictVersion; - use super::{Component, Version}; + use crate::version::StrictVersion; // Tests are inspired by: https://github.com/conda/conda/blob/33a142c16530fcdada6c377486f1c1a385738a96/tests/models/test_version.py @@ -1160,8 +1179,8 @@ mod test { #[test] fn test_pep440() { - // this list must be in sorted order (slightly modified from the PEP 440 test suite - // https://github.com/pypa/packaging/blob/master/tests/test_version.py) + // this list must be in sorted order (slightly modified from the PEP 440 test + // suite https://github.com/pypa/packaging/blob/master/tests/test_version.py) let versions = [ // Implicit epoch of 0 "1.0a1", @@ -1422,10 +1441,7 @@ mod test { assert_eq!( ord, Ordering::Less, - "Expected {:?} < {:?}, but found {:?}", - a, - b, - ord + "Expected {a:?} < {b:?}, but found {ord:?}", ); } // Check the reverse ordering as well @@ -1436,10 +1452,7 @@ mod test { assert_eq!( ord, Ordering::Greater, - "Expected {:?} > {:?}, but found {:?}", - a, - b, - ord + "Expected {a:?} > {b:?}, but found {ord:?}", ); } } diff --git a/crates/rattler_index/src/lib.rs b/crates/rattler_index/src/lib.rs index 681ff69ff..4c78ad990 100644 --- a/crates/rattler_index/src/lib.rs +++ b/crates/rattler_index/src/lib.rs @@ -1,11 +1,7 @@ -//! Indexing of packages in a output folder to create up to date repodata.json files +//! Indexing of packages in a output folder to create up to date repodata.json +//! files #![deny(missing_docs)] -use rattler_conda_types::{ - package::ArchiveType, package::IndexJson, package::PackageFile, ChannelInfo, PackageRecord, - Platform, RepoData, -}; -use rattler_package_streaming::{read, seek}; use std::{ collections::{HashMap, HashSet}, ffi::OsStr, @@ -14,6 +10,11 @@ use std::{ }; use fs_err::File; +use rattler_conda_types::{ + package::{ArchiveType, IndexJson, PackageFile}, + ChannelInfo, PackageRecord, Platform, RepoData, +}; +use rattler_package_streaming::{read, seek}; use walkdir::WalkDir; /// Extract the package record from an `index.json` file. @@ -46,6 +47,7 @@ pub fn package_record_from_index_json( license: index.license, license_family: index.license_family, timestamp: index.timestamp, + python_site_packages_path: index.python_site_packages_path, legacy_bz2_md5: None, legacy_bz2_size: None, purls: None, @@ -56,8 +58,8 @@ pub fn package_record_from_index_json( } /// Extract the package record from a `.tar.bz2` package file. -/// This function will look for the `info/index.json` file in the conda package and extract the -/// package record from it. +/// This function will look for the `info/index.json` file in the conda package +/// and extract the package record from it. pub fn package_record_from_tar_bz2(file: &Path) -> Result { let reader = std::fs::File::open(file)?; let mut archive = read::stream_tar_bz2(reader); @@ -75,8 +77,8 @@ pub fn package_record_from_tar_bz2(file: &Path) -> Result Result { let reader = std::fs::File::open(file)?; let mut archive = seek::stream_conda_info(reader).expect("Could not open conda file"); @@ -94,9 +96,9 @@ pub fn package_record_from_conda(file: &Path) -> Result, diff --git a/crates/rattler_lock/src/parse/v3.rs b/crates/rattler_lock/src/parse/v3.rs index 1f0c47b92..a3310c04d 100644 --- a/crates/rattler_lock/src/parse/v3.rs +++ b/crates/rattler_lock/src/parse/v3.rs @@ -1,12 +1,7 @@ //! A module that enables parsing of lock files version 3 or lower. -use super::ParseCondaLockError; -use crate::file_format_version::FileFormatVersion; -use crate::{ - Channel, CondaPackageData, EnvironmentData, EnvironmentPackageData, LockFile, LockFileInner, - PackageHashes, PypiPackageData, PypiPackageEnvironmentData, UrlOrPath, - DEFAULT_ENVIRONMENT_NAME, -}; +use std::{collections::BTreeSet, ops::Not, sync::Arc}; + use fxhash::FxHashMap; use indexmap::IndexSet; use pep440_rs::VersionSpecifiers; @@ -16,10 +11,15 @@ use rattler_conda_types::{ }; use serde::Deserialize; use serde_with::{serde_as, skip_serializing_none, OneOrMany}; -use std::ops::Not; -use std::{collections::BTreeSet, sync::Arc}; use url::Url; +use super::ParseCondaLockError; +use crate::{ + file_format_version::FileFormatVersion, Channel, CondaPackageData, EnvironmentData, + EnvironmentPackageData, LockFile, LockFileInner, PackageHashes, PypiPackageData, + PypiPackageEnvironmentData, UrlOrPath, DEFAULT_ENVIRONMENT_NAME, +}; + #[derive(Deserialize)] struct LockFileV3 { metadata: LockMetaV3, @@ -109,6 +109,7 @@ pub struct CondaLockedPackageV3 { pub track_features: Vec, pub license: Option, pub license_family: Option, + pub python_site_packages_path: Option, #[serde(skip_serializing_if = "NoArchType::is_none")] pub noarch: NoArchType, pub size: Option, @@ -126,8 +127,8 @@ pub fn parse_v3_or_lower( let lock_file: LockFileV3 = serde_yaml::from_value(document).map_err(ParseCondaLockError::ParseError)?; - // Iterate over all packages, deduplicate them and store the list of packages per platform. - // There might be duplicates for noarch packages. + // Iterate over all packages, deduplicate them and store the list of packages + // per platform. There might be duplicates for noarch packages. let mut conda_packages = IndexSet::with_capacity(lock_file.package.len()); let mut pypi_packages = IndexSet::with_capacity(lock_file.package.len()); let mut pypi_runtime_configs = IndexSet::with_capacity(lock_file.package.len()); @@ -172,6 +173,7 @@ pub fn parse_v3_or_lower( track_features: value.track_features, version: value.version, purls: value.purls.is_empty().not().then_some(value.purls), + python_site_packages_path: value.python_site_packages_path, run_exports: None, }, url: value.url, diff --git a/crates/rattler_lock/src/utils/serde/raw_conda_package_data.rs b/crates/rattler_lock/src/utils/serde/raw_conda_package_data.rs index 1dfa3176c..5d444fc9c 100644 --- a/crates/rattler_lock/src/utils/serde/raw_conda_package_data.rs +++ b/crates/rattler_lock/src/utils/serde/raw_conda_package_data.rs @@ -1,35 +1,38 @@ -use crate::CondaPackageData; +use std::{borrow::Cow, cmp::Ordering, collections::BTreeSet}; + use rattler_conda_types::{ BuildNumber, NoArchType, PackageName, PackageRecord, PackageUrl, VersionWithSource, }; use rattler_digest::{serde::SerializableHash, Md5Hash, Sha256Hash}; use serde::{Deserialize, Serialize}; use serde_with::serde_as; -use std::borrow::Cow; -use std::cmp::Ordering; -use std::collections::BTreeSet; use url::Url; +use crate::CondaPackageData; + fn is_default(value: &T) -> bool { value == &T::default() } -/// A helper struct that wraps all fields of a [`CondaPackageData`] and allows for easy conversion -/// between the two. +/// A helper struct that wraps all fields of a [`CondaPackageData`] and allows +/// for easy conversion between the two. /// -/// This type provides full control over the order of the fields when serializing. This is important -/// because one of the design goals is that it should be easy to read the lock file. A -/// [`PackageRecord`] is serialized in alphabetic order which might not be the most readable. This -/// type instead puts the "most important" fields at the top followed by more detailed ones. +/// This type provides full control over the order of the fields when +/// serializing. This is important because one of the design goals is that it +/// should be easy to read the lock file. A [`PackageRecord`] is serialized in +/// alphabetic order which might not be the most readable. This type instead +/// puts the "most important" fields at the top followed by more detailed ones. /// -/// So note that for reproducibility the order of these fields should not change or should be -/// reflected in a version change. +/// So note that for reproducibility the order of these fields should not change +/// or should be reflected in a version change. // -/// This type also adds more default values (e.g. for `build_number` and `build_string`). +/// This type also adds more default values (e.g. for `build_number` and +/// `build_string`). /// -/// The complexity with `Cow<_>` types is introduced to allow both efficient deserialization and -/// serialization without requiring all data to be cloned when serializing. We want to be able -/// to use the same type of both serialization and deserialization to ensure that when any of the +/// The complexity with `Cow<_>` types is introduced to allow both efficient +/// deserialization and serialization without requiring all data to be cloned +/// when serializing. We want to be able to use the same type of both +/// serialization and deserialization to ensure that when any of the /// types involved change we are forced to update this struct as well. #[serde_as] #[derive(Serialize, Deserialize, Eq, PartialEq)] @@ -83,6 +86,9 @@ pub(crate) struct RawCondaPackageData<'a> { #[serde(default, skip_serializing_if = "Option::is_none")] pub file_name: Cow<'a, Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub python_site_packages_path: Cow<'a, Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] pub license: Cow<'a, Option>, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -126,6 +132,7 @@ impl<'a> From> for CondaPackageData { track_features: value.track_features.into_owned(), version: value.version.into_owned(), run_exports: None, + python_site_packages_path: value.python_site_packages_path.into_owned(), }, url: value.url.into_owned(), file_name: value.file_name.into_owned(), @@ -161,6 +168,9 @@ impl<'a> From<&'a CondaPackageData> for RawCondaPackageData<'a> { track_features: Cow::Borrowed(&value.package_record.track_features), license: Cow::Borrowed(&value.package_record.license), license_family: Cow::Borrowed(&value.package_record.license_family), + python_site_packages_path: Cow::Borrowed( + &value.package_record.python_site_packages_path, + ), } } } diff --git a/crates/rattler_solve/benches/sorting_bench.rs b/crates/rattler_solve/benches/sorting_bench.rs index 73f0431a5..5b99bd9aa 100644 --- a/crates/rattler_solve/benches/sorting_bench.rs +++ b/crates/rattler_solve/benches/sorting_bench.rs @@ -18,7 +18,7 @@ fn bench_sort(c: &mut Criterion, sparse_repo_data: &SparseRepoData, spec: &str) .expect("failed to load records"); // Construct a cache - c.bench_function(&format!("sort {}", spec), |b| { + c.bench_function(&format!("sort {spec}"), |b| { // Get the candidates for the package b.iter_batched( || (package_name.clone(), match_spec.clone()), diff --git a/crates/rattler_solve/tests/backends.rs b/crates/rattler_solve/tests/backends.rs index 6f7deacca..2d0430df0 100644 --- a/crates/rattler_solve/tests/backends.rs +++ b/crates/rattler_solve/tests/backends.rs @@ -108,6 +108,7 @@ fn installed_package( legacy_bz2_size: None, legacy_bz2_md5: None, purls: None, + python_site_packages_path: None, run_exports: None, }, } diff --git a/py-rattler/Cargo.lock b/py-rattler/Cargo.lock index 3a171da34..1d3e7a7c1 100644 --- a/py-rattler/Cargo.lock +++ b/py-rattler/Cargo.lock @@ -943,9 +943,9 @@ dependencies = [ [[package]] name = "fs4" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec6fcfb3c0c1d71612528825042261419d5dade9678c39a781e05b63677d9b32" +checksum = "adc91b3da7f1a7968b00f9f65a4971252f6a927d3cb9eec05d91cbeaff678f9a" dependencies = [ "fs-err 2.11.0", "rustix", diff --git a/py-rattler/rattler/repo_data/package_record.py b/py-rattler/rattler/repo_data/package_record.py index fd07919ee..b0e200011 100644 --- a/py-rattler/rattler/repo_data/package_record.py +++ b/py-rattler/rattler/repo_data/package_record.py @@ -202,6 +202,7 @@ def __init__( legacy_bz2_size: Optional[int] = None, license: Optional[str] = None, license_family: Optional[str] = None, + python_site_packages_path: Optional[str] = None, ) -> None: # Convert Platform to str if isinstance(subdir, Platform): @@ -226,6 +227,7 @@ def __init__( arch, platform, noarch, + python_site_packages_path, ) if constrains is not None: @@ -827,6 +829,48 @@ def version(self) -> VersionWithSource: def version(self, value: VersionWithSource) -> None: self._record.version = (value._version, value._source) + @property + def python_site_packages_path(self) -> Optional[str]: + """ + Optionally a path within the environment of the site-packages directory. This field is only + present for python interpreter packages. + This field was introduced with . + + Examples + -------- + ```python + >>> from rattler import PrefixRecord + >>> record = PrefixRecord.from_path( + ... "../test-data/conda-meta/python-3.11.9-h932a869_0_cpython.json" + ... ) + >>> record.python_site_packages_path + 'lib/python3.11/site-packages' + >>> + ``` + """ + return self._record.python_site_packages_path + + @python_site_packages_path.setter + def python_site_packages_path(self, value: Optional[str]) -> None: + """ + Sets the optional path within the environment of the site-packages directory. + Examples + -------- + ```python + >>> from rattler import PrefixRecord + >>> record = PrefixRecord.from_path( + ... "../test-data/conda-meta/libsqlite-3.40.0-hcfcfb64_0.json" + ... ) + >>> record.python_site_packages_path + None + >>> record.python_site_packages_path = "lib/something" + >>> record.python_site_packages_path + 'lib/something' + >>> + ``` + """ + self._record.set_python_site_packages_path(value) + def __str__(self) -> str: """ Returns the string representation of the PackageRecord. diff --git a/py-rattler/src/record.rs b/py-rattler/src/record.rs index 7aa2a7f11..c31bd9658 100644 --- a/py-rattler/src/record.rs +++ b/py-rattler/src/record.rs @@ -10,7 +10,6 @@ use rattler_conda_types::{ prefix_record::{Link, LinkType}, NoArchType, PackageRecord, PrefixRecord, RepoDataRecord, VersionWithSource, }; - use rattler_digest::{parse_digest_from_hex, Md5, Sha256}; use url::Url; @@ -26,7 +25,8 @@ use crate::{ /// Python bindings for `PrefixRecord`, `RepoDataRecord`, `PackageRecord`. /// This is to expose these structs in Object Oriented manner, via a single /// class. This class handles the conversion on its own. -/// It uses a `RecordInner` enum and (try_)as_{x}_record methods for this interface. +/// It uses a `RecordInner` enum and (try_)as_{x}_record methods for this +/// interface. /// /// PyO3 cannot expose tagged enums directly, to achieve this we use the /// `PyRecord` wrapper pyclass on top of `RecordInner`. @@ -140,7 +140,7 @@ impl From for Link { impl PyRecord { #[staticmethod] #[allow(clippy::too_many_arguments)] - #[pyo3(signature = (name, version, build, build_number, subdir, arch=None, platform=None, noarch=None))] + #[pyo3(signature = (name, version, build, build_number, subdir, arch=None, platform=None, noarch=None, python_site_packages_path=None))] pub fn create( name: PyPackageName, version: (PyVersion, String), @@ -150,6 +150,7 @@ impl PyRecord { arch: Option, platform: Option, noarch: Option, + python_site_packages_path: Option, ) -> Self { let noarch = noarch.map(Into::into); Self { @@ -171,6 +172,7 @@ impl PyRecord { md5: None, noarch: noarch.unwrap_or(NoArchType::none()), purls: None, + python_site_packages_path, run_exports: None, sha256: None, size: None, @@ -508,6 +510,17 @@ impl PyRecord { VersionWithSource::new(version.0.inner.clone(), version.1); } + /// Optionally a path within the environment of the site-packages directory. + #[getter] + pub fn python_site_packages_path(&self) -> Option { + self.as_package_record().python_site_packages_path.clone() + } + + #[setter] + pub fn set_python_site_packages_path(&mut self, python_site_packages_path: Option) { + self.as_package_record_mut().python_site_packages_path = python_site_packages_path; + } + /// The filename of the package. #[getter] pub fn file_name(&self) -> PyResult { @@ -585,7 +598,8 @@ impl PyRecord { Ok(()) } - /// Information about how files have been linked when installing the package. + /// Information about how files have been linked when installing the + /// package. #[getter] pub fn paths_data(&self) -> PyResult { Ok(self.try_as_prefix_record()?.paths_data.clone().into()) @@ -597,8 +611,8 @@ impl PyRecord { Ok(()) } - /// The spec that was used when this package was installed. Note that this field is not updated if the - /// currently another spec was used. + /// The spec that was used when this package was installed. Note that this + /// field is not updated if the currently another spec was used. #[getter] pub fn requested_spec(&self) -> PyResult> { Ok(self.try_as_prefix_record()?.requested_spec.clone()) @@ -725,7 +739,8 @@ impl PyRecord { .map_err(PyRattlerError::from)?) } - /// Writes the contents of this instance to the file at the specified location. + /// Writes the contents of this instance to the file at the specified + /// location. pub fn write_to_path(&self, path: PathBuf, pretty: bool) -> PyResult<()> { Ok(self .try_as_prefix_record()? @@ -764,10 +779,11 @@ impl PyRecord { .map_err(PyRattlerError::from)?) } - /// Validate that the given package records are valid w.r.t. 'depends' and 'constrains'. - /// This function will return nothing if all records form a valid environment, i.e., all dependencies - /// of each package are satisfied by the other packages in the list. - /// If there is a dependency that is not satisfied, this function will raise an exception. + /// Validate that the given package records are valid w.r.t. 'depends' and + /// 'constrains'. This function will return nothing if all records form + /// a valid environment, i.e., all dependencies of each package are + /// satisfied by the other packages in the list. If there is a + /// dependency that is not satisfied, this function will raise an exception. #[staticmethod] fn validate(records: Vec>) -> PyResult<()> { let records = records @@ -779,8 +795,9 @@ impl PyRecord { /// 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. #[staticmethod] diff --git a/test-data/conda-meta/python-3.11.9-h932a869_0_cpython.json b/test-data/conda-meta/python-3.11.9-h932a869_0_cpython.json index 6fa9db3cd..4d3be1e73 100644 --- a/test-data/conda-meta/python-3.11.9-h932a869_0_cpython.json +++ b/test-data/conda-meta/python-3.11.9-h932a869_0_cpython.json @@ -30,6 +30,7 @@ "url": "https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.9-h932a869_0_cpython.conda", "channel": "https://conda.anaconda.org/conda-forge/", "extracted_package_dir": "/Users/graf/Library/Caches/rattler/cache/pkgs/python-3.11.9-h932a869_0_cpython", + "python_site_packages_path": "lib/python3.11/site-packages", "files": [ "bin/2to3", "bin/2to3-3.11", @@ -16995,4 +16996,4 @@ "source": "/Users/graf/Library/Caches/rattler/cache/pkgs/python-3.11.9-h932a869_0_cpython", "type": 1 } -} \ No newline at end of file +}