-
Notifications
You must be signed in to change notification settings - Fork 63
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: merge purls from matching records in lock-file (#965)
- Loading branch information
1 parent
b90daf5
commit a8082a6
Showing
8 changed files
with
379 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,15 @@ | ||
//! Builder for the creation of lock files. | ||
use std::{ | ||
borrow::Cow, | ||
collections::{BTreeSet, HashMap}, | ||
sync::Arc, | ||
}; | ||
|
||
use fxhash::FxHashMap; | ||
use indexmap::{IndexMap, IndexSet}; | ||
use pep508_rs::ExtraName; | ||
use rattler_conda_types::Platform; | ||
use rattler_conda_types::{Platform, Version}; | ||
|
||
use crate::{ | ||
file_format_version::FileFormatVersion, Channel, CondaBinaryData, CondaPackageData, | ||
|
@@ -118,11 +119,34 @@ pub struct LockFileBuilder { | |
environments: IndexMap<String, EnvironmentData>, | ||
|
||
/// A list of all package metadata stored in the lock file. | ||
conda_packages: IndexSet<CondaPackageData>, | ||
conda_packages: IndexMap<UniqueCondaIdentifier, CondaPackageData>, | ||
pypi_packages: IndexSet<PypiPackageData>, | ||
pypi_runtime_configurations: IndexSet<HashablePypiPackageEnvironmentData>, | ||
} | ||
|
||
/// A unique identifier for a conda package. This is used to deduplicate | ||
/// packages. This only includes the unique identifying aspects of a package. | ||
#[derive(Debug, Hash, Eq, PartialEq)] | ||
struct UniqueCondaIdentifier { | ||
location: UrlOrPath, | ||
normalized_name: String, | ||
version: Version, | ||
build: String, | ||
subdir: String, | ||
} | ||
|
||
impl<'a> From<&'a CondaPackageData> for UniqueCondaIdentifier { | ||
fn from(value: &'a CondaPackageData) -> Self { | ||
Self { | ||
location: value.location().clone(), | ||
normalized_name: value.record().name.as_normalized().to_string(), | ||
version: value.record().version.version().clone(), | ||
build: value.record().build.clone(), | ||
subdir: value.record().subdir.clone(), | ||
} | ||
} | ||
} | ||
|
||
impl LockFileBuilder { | ||
/// Generate a new lock file using the builder pattern | ||
pub fn new() -> Self { | ||
|
@@ -184,15 +208,25 @@ impl LockFileBuilder { | |
indexes: None, | ||
}); | ||
|
||
let unique_identifier = UniqueCondaIdentifier::from(&locked_package); | ||
|
||
// Add the package to the list of packages. | ||
let package_idx = self.conda_packages.insert_full(locked_package).0; | ||
let entry = self.conda_packages.entry(unique_identifier); | ||
let package_idx = entry.index(); | ||
entry | ||
.and_modify(|pkg| { | ||
if let Cow::Owned(merged_package) = pkg.merge(&locked_package) { | ||
*pkg = merged_package; | ||
} | ||
}) | ||
.or_insert(locked_package); | ||
|
||
// Add the package to the environment that it is intended for. | ||
environment | ||
.packages | ||
.entry(platform) | ||
.or_default() | ||
.push(EnvironmentPackageData::Conda(package_idx)); | ||
.insert(EnvironmentPackageData::Conda(package_idx)); | ||
|
||
self | ||
} | ||
|
@@ -231,7 +265,7 @@ impl LockFileBuilder { | |
.packages | ||
.entry(platform) | ||
.or_default() | ||
.push(EnvironmentPackageData::Pypi(package_idx, runtime_idx)); | ||
.insert(EnvironmentPackageData::Pypi(package_idx, runtime_idx)); | ||
|
||
self | ||
} | ||
|
@@ -327,7 +361,7 @@ impl LockFileBuilder { | |
LockFile { | ||
inner: Arc::new(LockFileInner { | ||
version: FileFormatVersion::LATEST, | ||
conda_packages: self.conda_packages.into_iter().collect(), | ||
conda_packages: self.conda_packages.into_values().collect(), | ||
pypi_packages: self.pypi_packages.into_iter().collect(), | ||
pypi_environment_package_data: self | ||
.pypi_runtime_configurations | ||
|
@@ -362,3 +396,83 @@ impl From<PypiPackageEnvironmentData> for HashablePypiPackageEnvironmentData { | |
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use std::str::FromStr; | ||
|
||
use rattler_conda_types::{PackageName, PackageRecord, Platform, Version}; | ||
use url::Url; | ||
|
||
use crate::{CondaBinaryData, LockFile}; | ||
|
||
#[test] | ||
fn test_merge_records_and_purls() { | ||
let record = PackageRecord { | ||
subdir: "linux-64".into(), | ||
..PackageRecord::new( | ||
PackageName::new_unchecked("foobar"), | ||
Version::from_str("1.0.0").unwrap(), | ||
"build".into(), | ||
) | ||
}; | ||
|
||
let record_with_purls = PackageRecord { | ||
purls: Some( | ||
["pkg:pypi/[email protected]".parse().unwrap()] | ||
.into_iter() | ||
.collect(), | ||
), | ||
..record.clone() | ||
}; | ||
|
||
let lock_file = LockFile::builder() | ||
.with_conda_package( | ||
"default", | ||
Platform::Linux64, | ||
CondaBinaryData { | ||
package_record: record.clone(), | ||
location: Url::parse( | ||
"https://prefix.dev/example/linux-64/foobar-1.0.0-build.tar.bz2", | ||
) | ||
.unwrap() | ||
.into(), | ||
file_name: "foobar-1.0.0-build.tar.bz2".to_string(), | ||
channel: None, | ||
} | ||
.into(), | ||
) | ||
.with_conda_package( | ||
"default", | ||
Platform::Linux64, | ||
CondaBinaryData { | ||
package_record: record.clone(), | ||
location: Url::parse( | ||
"https://prefix.dev/example/linux-64/foobar-1.0.0-build.tar.bz2", | ||
) | ||
.unwrap() | ||
.into(), | ||
file_name: "foobar-1.0.0-build.tar.bz2".to_string(), | ||
channel: None, | ||
} | ||
.into(), | ||
) | ||
.with_conda_package( | ||
"foobar", | ||
Platform::Linux64, | ||
CondaBinaryData { | ||
package_record: record_with_purls, | ||
location: Url::parse( | ||
"https://prefix.dev/example/linux-64/foobar-1.0.0-build.tar.bz2", | ||
) | ||
.unwrap() | ||
.into(), | ||
file_name: "foobar-1.0.0-build.tar.bz2".to_string(), | ||
channel: None, | ||
} | ||
.into(), | ||
) | ||
.finish(); | ||
insta::assert_snapshot!(lock_file.render_to_string().unwrap()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.