From 9d2927fb1cca9c4c0bb7da3b23ca6729804b270d Mon Sep 17 00:00:00 2001 From: nichmor Date: Wed, 8 May 2024 13:08:06 +0300 Subject: [PATCH 01/12] feat: warn on clobbering of pypi to conda --- src/install_pypi.rs | 47 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/src/install_pypi.rs b/src/install_pypi.rs index c261707d0..ef0f136d3 100644 --- a/src/install_pypi.rs +++ b/src/install_pypi.rs @@ -36,7 +36,7 @@ use rattler_conda_types::{Platform, RepoDataRecord}; use rattler_lock::{PypiPackageData, PypiPackageEnvironmentData, UrlOrPath}; use std::collections::HashMap; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::str::FromStr; use std::time::Duration; @@ -524,6 +524,8 @@ fn whats_the_plan<'a>( remote.push(convert_to_dist(pkg, lock_file_dir)); } } + eprintln!("reinstalls : {:?}", reinstalls); + eprintln!("extraneous : {:?}", extraneous); Ok(PixiInstallPlan { local, @@ -743,11 +745,12 @@ pub async fn update_python_distributions( // No python interpreter in the environment, so there is nothing to do here. return Ok(()); }; - // If we have changed interpreter, we need to uninstall all site-packages from the old interpreter if let PythonStatus::Changed { old, new: _ } = status { let site_packages_path = prefix.root().join(&old.site_packages_path); if site_packages_path.exists() { + eprintln!("Status is {:?}", status); + eprintln!("removing sitepackages"); uninstall_outdated_site_packages(&site_packages_path).await?; } } @@ -861,6 +864,12 @@ pub async fn update_python_distributions( lock_file_dir, )?; + let mut pypi_conda_clobber = PypiCondaClobber::default(); + pypi_conda_clobber.register_paths([reinstalls.as_slice(), extraneous.as_slice()].concat()); + + // let mut removed_registry = Vec::from_iter(reinstalls.iter().map(|dist|dist.path())); + // removed_registry.extend(extraneous.iter().map(|dist| dist.path())); + // Nothing to do. if remote.is_empty() && local.is_empty() && reinstalls.is_empty() && extraneous.is_empty() { let s = if python_packages.len() == 1 { "" } else { "s" }; @@ -998,6 +1007,11 @@ pub async fn update_python_distributions( .with_capacity(wheels.len() + 30) .with_starting_tasks(wheels.iter().map(|d| format!("{}", d.name()))) .with_top_level_message("Installing distributions"); + + eprintln!("wheel path is {:?}", wheels.first().unwrap().path()); + + eprintln!("venv path {:?}", venv.interpreter().purelib()); + if !wheels.is_empty() { let start = std::time::Instant::now(); uv_installer::Installer::new(&venv) @@ -1018,5 +1032,34 @@ pub async fn update_python_distributions( ); } + // let's reiterate over site-packages again and find if we have something overlapping + pypi_conda_clobber.warn_on_clobbering(site_packages.iter()); + Ok(()) } + +#[derive(Default)] +struct PypiCondaClobber { + dists: HashMap, +} + +impl PypiCondaClobber { + pub fn register_paths(&mut self, removed_dists: Vec) { + for dist in removed_dists { + self.dists.insert(dist.path().into(), dist); + } + } + + pub fn warn_on_clobbering<'a>(&self, installed_dists: impl Iterator) { + let mut to_warn = Vec::new(); + + for dist in installed_dists { + let path = PathBuf::from(dist.path()); + if let Some(found_dist) = self.dists.get(&path) { + to_warn.push(found_dist.name().to_string()); + } + } + + tracing::warn!("The installation of the following PyPI package(s) will overwrite existing Conda package(s): {:?} ", to_warn); + } +} From dddac7216cc6bcc9f2224fbcb584fc4f577b4b75 Mon Sep 17 00:00:00 2001 From: nichmor Date: Wed, 8 May 2024 13:11:38 +0300 Subject: [PATCH 02/12] misc: fmt --- src/install_pypi.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/install_pypi.rs b/src/install_pypi.rs index ef0f136d3..4fe42dcb4 100644 --- a/src/install_pypi.rs +++ b/src/install_pypi.rs @@ -524,8 +524,6 @@ fn whats_the_plan<'a>( remote.push(convert_to_dist(pkg, lock_file_dir)); } } - eprintln!("reinstalls : {:?}", reinstalls); - eprintln!("extraneous : {:?}", extraneous); Ok(PixiInstallPlan { local, @@ -749,8 +747,6 @@ pub async fn update_python_distributions( if let PythonStatus::Changed { old, new: _ } = status { let site_packages_path = prefix.root().join(&old.site_packages_path); if site_packages_path.exists() { - eprintln!("Status is {:?}", status); - eprintln!("removing sitepackages"); uninstall_outdated_site_packages(&site_packages_path).await?; } } @@ -867,9 +863,6 @@ pub async fn update_python_distributions( let mut pypi_conda_clobber = PypiCondaClobber::default(); pypi_conda_clobber.register_paths([reinstalls.as_slice(), extraneous.as_slice()].concat()); - // let mut removed_registry = Vec::from_iter(reinstalls.iter().map(|dist|dist.path())); - // removed_registry.extend(extraneous.iter().map(|dist| dist.path())); - // Nothing to do. if remote.is_empty() && local.is_empty() && reinstalls.is_empty() && extraneous.is_empty() { let s = if python_packages.len() == 1 { "" } else { "s" }; @@ -1008,10 +1001,6 @@ pub async fn update_python_distributions( .with_starting_tasks(wheels.iter().map(|d| format!("{}", d.name()))) .with_top_level_message("Installing distributions"); - eprintln!("wheel path is {:?}", wheels.first().unwrap().path()); - - eprintln!("venv path {:?}", venv.interpreter().purelib()); - if !wheels.is_empty() { let start = std::time::Instant::now(); uv_installer::Installer::new(&venv) From f9dd9ef48d194a28537102c0c5cf2d2852c9a40c Mon Sep 17 00:00:00 2001 From: nichmor Date: Wed, 8 May 2024 15:12:15 +0300 Subject: [PATCH 03/12] misc: add test for clobbering warning --- src/install_pypi.rs | 87 ++++++++++++++++--- ...stall_pypi__tests__warn_on_clobbering.snap | 5 ++ 2 files changed, 80 insertions(+), 12 deletions(-) create mode 100644 src/snapshots/pixi__install_pypi__tests__warn_on_clobbering.snap diff --git a/src/install_pypi.rs b/src/install_pypi.rs index 4fe42dcb4..bc5dbd669 100644 --- a/src/install_pypi.rs +++ b/src/install_pypi.rs @@ -860,8 +860,8 @@ pub async fn update_python_distributions( lock_file_dir, )?; - let mut pypi_conda_clobber = PypiCondaClobber::default(); - pypi_conda_clobber.register_paths([reinstalls.as_slice(), extraneous.as_slice()].concat()); + let pypi_conda_clobber = + PypiCondaClobber::with_paths([reinstalls.as_slice(), extraneous.as_slice()].concat()); // Nothing to do. if remote.is_empty() && local.is_empty() && reinstalls.is_empty() && extraneous.is_empty() { @@ -1033,22 +1033,85 @@ struct PypiCondaClobber { } impl PypiCondaClobber { - pub fn register_paths(&mut self, removed_dists: Vec) { - for dist in removed_dists { - self.dists.insert(dist.path().into(), dist); + pub fn with_paths(removed_dists: Vec) -> Self { + let dists = removed_dists + .iter() + .map(|dist| (PathBuf::from(dist.path()), dist.clone())) + .collect::>(); + + Self { dists } + } + + pub fn warn_on_clobbering<'a>( + &self, + installed_dists: impl Iterator, + ) -> Option> { + let to_warn: Vec = installed_dists + .filter_map(|dist| { + self.dists + .get(dist.path()) + .map(|seen_dist| seen_dist.name().to_string()) + }) + .collect(); + + if !to_warn.is_empty() { + tracing::warn!("The installation of the following PyPI package(s) will overwrite existing Conda package(s): {:?} ", to_warn); + Some(to_warn) + } else { + None } } +} + +#[cfg(test)] +mod tests { + use std::{path::PathBuf, str::FromStr}; - pub fn warn_on_clobbering<'a>(&self, installed_dists: impl Iterator) { - let mut to_warn = Vec::new(); + use distribution_types::{InstalledDist, InstalledRegistryDist}; + use insta::assert_snapshot; + use pep440_rs::Version; + use pep508_rs::PackageName; - for dist in installed_dists { - let path = PathBuf::from(dist.path()); - if let Some(found_dist) = self.dists.get(&path) { - to_warn.push(found_dist.name().to_string()); + use super::PypiCondaClobber; + + struct TestSitePackages { + installed_dists: Vec>, + } + + impl TestSitePackages { + pub fn new(dists: Vec) -> Self { + Self { + installed_dists: dists.into_iter().map(Some).collect(), } } - tracing::warn!("The installation of the following PyPI package(s) will overwrite existing Conda package(s): {:?} ", to_warn); + /// Returns an iterator over the installed distributions. + pub fn iter(&self) -> impl Iterator { + self.installed_dists.iter().flatten() + } + } + + #[test] + fn test_warn_on_clobbering() { + // during resolution, conda installed mdurl + // when we do uv::install, it will be removed + let mdurl = InstalledDist::Registry(InstalledRegistryDist { + name: PackageName::new("mdurl".to_owned()).unwrap(), + version: Version::from_str("1.19").unwrap(), + path: PathBuf::from_str("site-packages/mdurl.dist_info").unwrap(), + }); + + // let's register it + let clobber = PypiCondaClobber::with_paths([mdurl.clone()].to_vec()); + + // let's assume that we map wrongly mdurl + // mdurl: some_wrong_mdurl + // this means that uv::install will install it again + // so we warn user about it + + let some_site_packages = TestSitePackages::new([mdurl].to_vec()); + + let warned_packages = clobber.warn_on_clobbering(some_site_packages.iter()); + assert_snapshot!(format!("{:?}", warned_packages.unwrap())); } } diff --git a/src/snapshots/pixi__install_pypi__tests__warn_on_clobbering.snap b/src/snapshots/pixi__install_pypi__tests__warn_on_clobbering.snap new file mode 100644 index 000000000..a9ec8b847 --- /dev/null +++ b/src/snapshots/pixi__install_pypi__tests__warn_on_clobbering.snap @@ -0,0 +1,5 @@ +--- +source: src/install_pypi.rs +expression: "format!(\"{:?}\", warned_packages.unwrap())" +--- +["mdurl"] From 7e4fc80bda99296a94b491104376d8fb3d8bf2e8 Mon Sep 17 00:00:00 2001 From: nichmor Date: Tue, 14 May 2024 16:18:27 +0300 Subject: [PATCH 04/12] feat: warn on pypi clobbering --- Cargo.lock | 1 + Cargo.toml | 1 + src/consts.rs | 1 + src/install_pypi.rs | 198 ++++++++++++++++++++++++---------------- src/install_wheel.rs | 194 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/lock_file/update.rs | 2 + 7 files changed, 319 insertions(+), 79 deletions(-) create mode 100644 src/install_wheel.rs diff --git a/Cargo.lock b/Cargo.lock index b493a88c5..cfa2f5eae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3252,6 +3252,7 @@ dependencies = [ "clap_complete", "console", "crossbeam-channel", + "csv", "deno_task_shell", "dialoguer", "dirs", diff --git a/Cargo.toml b/Cargo.toml index cd7e1eddf..1a562d8d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ clap-verbosity-flag = "2.2.0" clap_complete = "4.5.2" console = { version = "0.15.8", features = ["windows-console-colors"] } crossbeam-channel = "0.5.12" +csv = "1.3.0" deno_task_shell = "0.16.0" dialoguer = "0.11.0" dirs = "5.0.1" diff --git a/src/consts.rs b/src/consts.rs index fc7154f9f..a9eb4c423 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -13,6 +13,7 @@ pub const SOLVE_GROUP_ENVIRONMENTS_DIR: &str = "solve-group-envs"; pub const PYPI_DEPENDENCIES: &str = "pypi-dependencies"; pub const TASK_CACHE_DIR: &str = "task-cache-v0"; pub const PIXI_UV_INSTALLER: &str = "uv-pixi"; +pub const CONDA_INSTALLER: &str = "conda"; pub const ONE_TIME_MESSAGES_DIR: &str = "one-time-messages"; diff --git a/src/install_pypi.rs b/src/install_pypi.rs index bc5dbd669..fccf52f23 100644 --- a/src/install_pypi.rs +++ b/src/install_pypi.rs @@ -1,4 +1,5 @@ use crate::environment::PythonStatus; +use crate::install_wheel::get_wheel_info; use crate::prefix::Prefix; use crate::project::manifest::pypi_options::PypiOptions; use crate::uv_reporter::{UvReporter, UvReporterOptions}; @@ -32,10 +33,10 @@ use distribution_types::{ }; use install_wheel_rs::linker::LinkMode; -use rattler_conda_types::{Platform, RepoDataRecord}; +use rattler_conda_types::{Platform, PrefixRecord, RepoDataRecord}; use rattler_lock::{PypiPackageData, PypiPackageEnvironmentData, UrlOrPath}; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::time::Duration; @@ -84,7 +85,7 @@ struct PixiInstallPlan { /// Keep track of any packages that have been re-installed because of installer mismatch /// we can warn the user later that this has happened - pub installer_mismatch: Vec, + pub installer_mismatch: Vec, } /// Converts our locked data to a file @@ -445,6 +446,10 @@ fn whats_the_plan<'a>( } } + // Used to verify if there are any additional .dist-info installed + // that should be removed + let required_map_copy = required_map.clone(); + // Walk over all installed packages and check if they are required for dist in site_packages.iter() { // Check if we require the package to be installed @@ -455,20 +460,24 @@ fn whats_the_plan<'a>( // Empty string if no installer or any other error .map_or(String::new(), |f| f.unwrap_or_default()); + if required_map_copy.contains_key(&dist.name()) && installer != PIXI_UV_INSTALLER { + // We are managing the package but something else has installed a version + // let's re-install to make sure that we have the **correct** version + reinstalls.push(dist.clone()); + installer_mismatch.push(dist.name().to_string()); + } + if let Some(pkg) = pkg { - if installer != PIXI_UV_INSTALLER { - // We are managing the package but something else has installed a version - // let's re-install to make sure that we have the **correct** version - reinstalls.push(dist.clone()); - installer_mismatch.push(dist.name().clone()); - } else { + if installer == PIXI_UV_INSTALLER { // Check if we need to reinstall match need_reinstall(dist, pkg, python_version)? { ValidateInstall::Keep => { // We are done here + // eprintln!("pkg is keep"); continue; } ValidateInstall::Reinstall => { + // eprintln!("pkg is reinstall"); reinstalls.push(dist.clone()); } } @@ -849,7 +858,7 @@ pub async fn update_python_distributions( remote, reinstalls, extraneous, - installer_mismatch, + mut installer_mismatch, } = whats_the_plan( &python_packages, &editables_with_temp.resolved_editables, @@ -860,8 +869,18 @@ pub async fn update_python_distributions( lock_file_dir, )?; - let pypi_conda_clobber = - PypiCondaClobber::with_paths([reinstalls.as_slice(), extraneous.as_slice()].concat()); + // Determine the currently installed conda packages. + let installed_packages = prefix + .find_installed_packages(None) + .await + .with_context(|| { + format!( + "failed to determine the currently installed packages for {}", + prefix.root().display() + ) + })?; + + let pypi_conda_clobber = PypiCondaClobberRegistry::with_conda_packages(&installed_packages); // Nothing to do. if remote.is_empty() && local.is_empty() && reinstalls.is_empty() && extraneous.is_empty() { @@ -877,6 +896,7 @@ pub async fn update_python_distributions( elapsed(start.elapsed()) ) ); + return Ok(()); } @@ -949,17 +969,6 @@ pub async fn update_python_distributions( wheels }; - // Notify the user if there are any packages that were re-installed because they were installed - // by a different installer. - if !installer_mismatch.is_empty() { - let packages = installer_mismatch - .iter() - .map(|name| name.to_string()) - .join(", "); - // BREAK(0.20.1): change this into a warning in a future release - tracing::info!("These pypi-packages were re-installed because they were previously installed by a different installer but are currently managed by pixi: \n\t{packages}") - } - // Remove any unnecessary packages. if !extraneous.is_empty() || !reinstalls.is_empty() { let start = std::time::Instant::now(); @@ -995,6 +1004,30 @@ pub async fn update_python_distributions( // Install the resolved distributions. let wheels = wheels.into_iter().chain(local).collect::>(); + + if let Ok(Some(clobber_packages)) = + pypi_conda_clobber.clobber_on_instalation(wheels.clone(), &venv) + { + let packages_names = clobber_packages.iter().join(", "); + + tracing::warn!("These conda-packages will be overridden by pypi: \n\t{packages_names}"); + + if !installer_mismatch.is_empty() { + installer_mismatch.retain(|name| !packages_names.contains(name)); + } + } + + if !installer_mismatch.is_empty() { + // Notify the user if there are any packages that were re-installed because they were installed + // by a different installer. + let packages = installer_mismatch + .iter() + .map(|name| name.to_string()) + .join(", "); + // BREAK(0.20.1): change this into a warning in a future release + tracing::warn!("These pypi-packages were re-installed because they were previously installed by a different installer but are currently managed by pixi: \n\t{packages}") + } + let options = UvReporterOptions::new() .with_length(wheels.len() as u64) .with_capacity(wheels.len() + 30) @@ -1021,45 +1054,54 @@ pub async fn update_python_distributions( ); } - // let's reiterate over site-packages again and find if we have something overlapping - pypi_conda_clobber.warn_on_clobbering(site_packages.iter()); - Ok(()) } -#[derive(Default)] -struct PypiCondaClobber { - dists: HashMap, +#[derive(Default, Debug)] +struct PypiCondaClobberRegistry { + paths_registry: HashMap, } -impl PypiCondaClobber { - pub fn with_paths(removed_dists: Vec) -> Self { - let dists = removed_dists - .iter() - .map(|dist| (PathBuf::from(dist.path()), dist.clone())) - .collect::>(); - - Self { dists } +impl PypiCondaClobberRegistry { + pub fn with_conda_packages(conda_packages: &[PrefixRecord]) -> Self { + let mut registry = HashMap::default(); + for record in conda_packages { + for path in &record.paths_data.paths { + registry.insert( + path.relative_path.clone(), + record.repodata_record.package_record.name.clone(), + ); + } + } + Self { + paths_registry: registry, + } } - pub fn warn_on_clobbering<'a>( - &self, - installed_dists: impl Iterator, - ) -> Option> { - let to_warn: Vec = installed_dists - .filter_map(|dist| { - self.dists - .get(dist.path()) - .map(|seen_dist| seen_dist.name().to_string()) - }) - .collect(); - - if !to_warn.is_empty() { - tracing::warn!("The installation of the following PyPI package(s) will overwrite existing Conda package(s): {:?} ", to_warn); - Some(to_warn) - } else { - None + pub fn clobber_on_instalation( + self, + wheels: Vec, + venv: &PythonEnvironment, + ) -> miette::Result>> { + let mut clobber_packages: HashSet = HashSet::default(); + + for wheel in wheels { + let Ok(Some(whl_info)) = get_wheel_info(wheel.path(), venv) else { + continue; + }; + + for entry in whl_info.0 { + let path_to_clobber = whl_info.1.join(entry.path); + + if let Some(name) = self.paths_registry.get(&path_to_clobber) { + clobber_packages.insert(name.as_normalized().to_string()); + } + } + } + if clobber_packages.is_empty() { + return Ok(None); } + Ok(Some(clobber_packages)) } } @@ -1072,8 +1114,6 @@ mod tests { use pep440_rs::Version; use pep508_rs::PackageName; - use super::PypiCondaClobber; - struct TestSitePackages { installed_dists: Vec>, } @@ -1091,27 +1131,27 @@ mod tests { } } - #[test] - fn test_warn_on_clobbering() { - // during resolution, conda installed mdurl - // when we do uv::install, it will be removed - let mdurl = InstalledDist::Registry(InstalledRegistryDist { - name: PackageName::new("mdurl".to_owned()).unwrap(), - version: Version::from_str("1.19").unwrap(), - path: PathBuf::from_str("site-packages/mdurl.dist_info").unwrap(), - }); - - // let's register it - let clobber = PypiCondaClobber::with_paths([mdurl.clone()].to_vec()); - - // let's assume that we map wrongly mdurl - // mdurl: some_wrong_mdurl - // this means that uv::install will install it again - // so we warn user about it - - let some_site_packages = TestSitePackages::new([mdurl].to_vec()); - - let warned_packages = clobber.warn_on_clobbering(some_site_packages.iter()); - assert_snapshot!(format!("{:?}", warned_packages.unwrap())); - } + // #[test] + // fn test_warn_on_clobbering() { + // // during resolution, conda installed mdurl + // // when we do uv::install, it will be removed + // let mdurl = InstalledDist::Registry(InstalledRegistryDist { + // name: PackageName::new("mdurl".to_owned()).unwrap(), + // version: Version::from_str("1.19").unwrap(), + // path: PathBuf::from_str("site-packages/mdurl.dist_info").unwrap(), + // }); + + // // let's register it + // let clobber = PypiCondaClobber::with_dists([mdurl.clone()].to_vec()); + + // // let's assume that we map wrongly mdurl + // // mdurl: some_wrong_mdurl + // // this means that uv::install will install it again + // // so we warn user about it + + // let some_site_packages = TestSitePackages::new([mdurl].to_vec()); + + // let warned_packages = clobber.warn_on_clobbering(some_site_packages.iter()); + // assert_snapshot!(format!("{:?}", warned_packages.unwrap())); + // } } diff --git a/src/install_wheel.rs b/src/install_wheel.rs new file mode 100644 index 000000000..02a7bd82a --- /dev/null +++ b/src/install_wheel.rs @@ -0,0 +1,194 @@ +/// Some vendored structs and functions from +/// https://github.com/astral-sh/uv/tree/main/crates/install-wheel-rs +use csv::ReaderBuilder; + +type WheelInfo = (Vec, PathBuf); + +pub fn get_wheel_info(whl: &Path, venv: &PythonEnvironment) -> miette::Result> { + let dist_info_prefix = find_dist_info(whl)?; + // Read the RECORD file. + let mut record_file = + File::open(whl.join(format!("{dist_info_prefix}.dist-info/RECORD"))).into_diagnostic()?; + let records = read_record_file(&mut record_file)?; + + let whl_kind = get_wheel_kind(whl, dist_info_prefix).unwrap_or(LibKind::Unknown); + + let site_packages_dir = match whl_kind { + LibKind::Unknown => return Ok(None), + LibKind::Plat => venv.interpreter().virtualenv().platlib.clone(), + LibKind::Pure => venv.interpreter().virtualenv().purelib.clone(), + }; + + Ok(Some((records, site_packages_dir))) +} + +/// Find the `dist-info` directory in an unzipped wheel. +/// +/// See: +/// +/// See: +fn find_dist_info(path: impl AsRef) -> miette::Result { + // Iterate over `path` to find the `.dist-info` directory. It should be at the top-level. + let Some(dist_info) = fs::read_dir(path.as_ref()) + .into_diagnostic()? + .find_map(|entry| { + let entry = entry.ok()?; + let file_type = entry.file_type().ok()?; + if file_type.is_dir() { + let path = entry.path(); + if path.extension().is_some_and(|ext| ext == "dist-info") { + Some(path) + } else { + None + } + } else { + None + } + }) + else { + miette::bail!("Missing .dist-info directory",); + }; + + let Some(dist_info_prefix) = dist_info.file_stem() else { + miette::bail!("Missing .dist-info directory",); + }; + + Ok(dist_info_prefix.to_string_lossy().to_string()) +} + +/// Reads the record file +/// +pub fn read_record_file(record: &mut impl Read) -> miette::Result> { + ReaderBuilder::new() + .has_headers(false) + .escape(Some(b'"')) + .from_reader(record) + .deserialize() + .map(|entry| { + let entry: RecordEntry = entry.into_diagnostic()?; + Ok(RecordEntry { + // selenium uses absolute paths for some reason + path: entry.path.trim_start_matches('/').to_string(), + ..entry + }) + }) + .collect() +} + +pub fn get_wheel_kind(wheel_path: &Path, dist_info_prefix: String) -> miette::Result { + // We're going step by step though + // https://packaging.python.org/en/latest/specifications/binary-distribution-format/#installing-a-wheel-distribution-1-0-py32-none-any-whl + // > 1.a Parse distribution-1.0.dist-info/WHEEL. + // > 1.b Check that installer is compatible with Wheel-Version. Warn if minor version is greater, abort if major version is greater. + let wheel_file_path = wheel_path.join(format!("{dist_info_prefix}.dist-info/WHEEL")); + let wheel_text = fs::read_to_string(wheel_file_path).into_diagnostic()?; + let lib_kind = parse_wheel_file(&wheel_text)?; + Ok(lib_kind) +} + +use std::{ + collections::HashMap, + fs::{self, File}, + io::{BufRead, BufReader, Read}, + path::{Path, PathBuf}, +}; + +use miette::IntoDiagnostic; +use serde::{Deserialize, Serialize}; +use uv_interpreter::PythonEnvironment; + +/// Line in a RECORD file +/// +/// +/// ```csv +/// tqdm/cli.py,sha256=x_c8nmc4Huc-lKEsAXj78ZiyqSJ9hJ71j7vltY67icw,10509 +/// tqdm-4.62.3.dist-info/RECORD,, +/// ``` +#[derive(Deserialize, Serialize, PartialOrd, PartialEq, Ord, Eq)] +pub(crate) struct RecordEntry { + pub(crate) path: String, + pub(crate) hash: Option, + #[allow(dead_code)] + pub(crate) size: Option, +} + +/// Whether the wheel should be installed into the `purelib` or `platlib` directory. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum LibKind { + /// Install into the `purelib` directory. + Pure, + /// Install into the `platlib` directory. + Plat, + /// Unknown wheel kind when major version + /// for `Wheel-Version: 1.0` + /// is greater than 1 + Unknown, +} + +/// Parse a file with `Key: value` entries such as WHEEL and METADATA +fn parse_key_value_file( + file: impl Read, + debug_filename: &str, +) -> miette::Result>> { + let mut data: HashMap> = HashMap::default(); + + let file = BufReader::new(file); + for (line_no, line) in file.lines().enumerate() { + let line = line.into_diagnostic()?.trim().to_string(); + if line.is_empty() { + continue; + } + let (key, value) = line.split_once(':').ok_or_else(|| { + miette::miette!( + "Line {} of the {debug_filename} file is invalid", + line_no + 1 + ) + })?; + data.entry(key.trim().to_string()) + .or_default() + .push(value.trim().to_string()); + } + Ok(data) +} + +/// Parse WHEEL file. +/// +/// > {distribution}-{version}.dist-info/WHEEL is metadata about the archive itself in the same +/// > basic key: value format: +pub(crate) fn parse_wheel_file(wheel_text: &str) -> miette::Result { + // {distribution}-{version}.dist-info/WHEEL is metadata about the archive itself in the same basic key: value format: + let data = parse_key_value_file(&mut wheel_text.as_bytes(), "WHEEL")?; + + // Determine whether Root-Is-Purelib == ‘true’. + // If it is, the wheel is pure, and should be installed into purelib. + let root_is_purelib = data + .get("Root-Is-Purelib") + .and_then(|root_is_purelib| root_is_purelib.first()) + .is_some_and(|root_is_purelib| root_is_purelib == "true"); + let lib_kind = if root_is_purelib { + LibKind::Pure + } else { + LibKind::Plat + }; + + // mkl_fft-1.3.6-58-cp310-cp310-manylinux2014_x86_64.whl has multiple Wheel-Version entries, we have to ignore that + // like pip + let wheel_version = data + .get("Wheel-Version") + .and_then(|wheel_versions| wheel_versions.first()); + let wheel_version = wheel_version + .and_then(|wheel_version| wheel_version.split_once('.')) + .ok_or_else(|| miette::miette!("Invalid Wheel-Version in WHEEL file: {wheel_version:?}"))?; + + // pip has some test wheels that use that ancient version, + // and technically we only need to check that the version is not higher + if wheel_version == ("0", "1") { + return Ok(lib_kind); + } + // Check that installer is compatible with Wheel-Version. Warn if minor version is greater, abort if major version is greater. + // Wheel-Version: 1.0 + if wheel_version.0 != "1" { + return Ok(LibKind::Unknown); + } + Ok(lib_kind) +} diff --git a/src/lib.rs b/src/lib.rs index 7d9a74e22..92ffc4428 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ pub mod consts; mod environment; mod install; mod install_pypi; +mod install_wheel; mod lock_file; mod prefix; mod progress; diff --git a/src/lock_file/update.rs b/src/lock_file/update.rs index daa080c6d..b1632760d 100644 --- a/src/lock_file/update.rs +++ b/src/lock_file/update.rs @@ -215,6 +215,8 @@ impl<'p> LockFileDerivedData<'p> { ) .await?; + // eprintln!("Installed packages {:?}", installed_packages); + // Store that we updated the environment, so we won't have to do it again. self.updated_conda_prefixes .insert(environment.clone(), (prefix.clone(), python_status.clone())); From 2560df86ecc4512517958bc53f3bb53dba060aa6 Mon Sep 17 00:00:00 2001 From: nichmor Date: Tue, 14 May 2024 16:25:37 +0300 Subject: [PATCH 05/12] feat: warn on pypi clobbering --- src/install_pypi.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/install_pypi.rs b/src/install_pypi.rs index 572fb02d1..6a641f5f3 100644 --- a/src/install_pypi.rs +++ b/src/install_pypi.rs @@ -472,11 +472,9 @@ fn whats_the_plan<'a>( match need_reinstall(dist, pkg, python_version)? { ValidateInstall::Keep => { // We are done here - // eprintln!("pkg is keep"); continue; } ValidateInstall::Reinstall => { - // eprintln!("pkg is reinstall"); reinstalls.push(dist.clone()); } } @@ -836,7 +834,6 @@ pub async fn update_python_distributions( elapsed(start.elapsed()) ) ); - return Ok(()); } @@ -945,6 +942,8 @@ pub async fn update_python_distributions( // Install the resolved distributions. let wheels = wheels.into_iter().chain(local).collect::>(); + // Verify if pypi wheels will override existent conda packages + // and warn if they are if let Ok(Some(clobber_packages)) = pypi_conda_clobber.clobber_on_instalation(wheels.clone(), &venv) { @@ -952,6 +951,8 @@ pub async fn update_python_distributions( tracing::warn!("These conda-packages will be overridden by pypi: \n\t{packages_names}"); + // because we are removing conda packages + // we filter the ones we already warn if !installer_mismatch.is_empty() { installer_mismatch.retain(|name| !packages_names.contains(name)); } From 6cba70dbf724fc8d4bde91ae108f680aa6913d9e Mon Sep 17 00:00:00 2001 From: nichmor Date: Tue, 14 May 2024 16:34:28 +0300 Subject: [PATCH 06/12] feat: warn on pypi clobbering --- src/install_pypi.rs | 51 --------------------------------------------- 1 file changed, 51 deletions(-) diff --git a/src/install_pypi.rs b/src/install_pypi.rs index 6a641f5f3..15a1cda9f 100644 --- a/src/install_pypi.rs +++ b/src/install_pypi.rs @@ -1045,54 +1045,3 @@ impl PypiCondaClobberRegistry { Ok(Some(clobber_packages)) } } - -#[cfg(test)] -mod tests { - use std::{path::PathBuf, str::FromStr}; - - use distribution_types::{InstalledDist, InstalledRegistryDist}; - use insta::assert_snapshot; - use pep440_rs::Version; - use pep508_rs::PackageName; - - struct TestSitePackages { - installed_dists: Vec>, - } - - impl TestSitePackages { - pub fn new(dists: Vec) -> Self { - Self { - installed_dists: dists.into_iter().map(Some).collect(), - } - } - - /// Returns an iterator over the installed distributions. - pub fn iter(&self) -> impl Iterator { - self.installed_dists.iter().flatten() - } - } - - // #[test] - // fn test_warn_on_clobbering() { - // // during resolution, conda installed mdurl - // // when we do uv::install, it will be removed - // let mdurl = InstalledDist::Registry(InstalledRegistryDist { - // name: PackageName::new("mdurl".to_owned()).unwrap(), - // version: Version::from_str("1.19").unwrap(), - // path: PathBuf::from_str("site-packages/mdurl.dist_info").unwrap(), - // }); - - // // let's register it - // let clobber = PypiCondaClobber::with_dists([mdurl.clone()].to_vec()); - - // // let's assume that we map wrongly mdurl - // // mdurl: some_wrong_mdurl - // // this means that uv::install will install it again - // // so we warn user about it - - // let some_site_packages = TestSitePackages::new([mdurl].to_vec()); - - // let warned_packages = clobber.warn_on_clobbering(some_site_packages.iter()); - // assert_snapshot!(format!("{:?}", warned_packages.unwrap())); - // } -} From 5e04efbaf6f74f6f0dea15f30dc239d28761d6c0 Mon Sep 17 00:00:00 2001 From: nichmor Date: Tue, 14 May 2024 16:40:37 +0300 Subject: [PATCH 07/12] feat: warn on pypi clobbering --- src/install_pypi.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/install_pypi.rs b/src/install_pypi.rs index 15a1cda9f..b22942fc0 100644 --- a/src/install_pypi.rs +++ b/src/install_pypi.rs @@ -966,7 +966,7 @@ pub async fn update_python_distributions( .map(|name| name.to_string()) .join(", "); // BREAK(0.20.1): change this into a warning in a future release - tracing::warn!("These pypi-packages were re-installed because they were previously installed by a different installer but are currently managed by pixi: \n\t{packages}") + tracing::info!("These pypi-packages were re-installed because they were previously installed by a different installer but are currently managed by pixi: \n\t{packages}") } let options = UvReporterOptions::new() From 0f9ea3005789fb37fa97a53a1347376c0c1b69cd Mon Sep 17 00:00:00 2001 From: nichmor Date: Tue, 14 May 2024 19:08:37 +0300 Subject: [PATCH 08/12] update src/install_pypi.rs Co-authored-by: Tim de Jager --- src/install_pypi.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/install_pypi.rs b/src/install_pypi.rs index b22942fc0..267929a04 100644 --- a/src/install_pypi.rs +++ b/src/install_pypi.rs @@ -942,7 +942,7 @@ pub async fn update_python_distributions( // Install the resolved distributions. let wheels = wheels.into_iter().chain(local).collect::>(); - // Verify if pypi wheels will override existent conda packages + // Verify if pypi wheels will override existing conda packages // and warn if they are if let Ok(Some(clobber_packages)) = pypi_conda_clobber.clobber_on_instalation(wheels.clone(), &venv) From 8d5d7ec703d605eb72445ac184130a697a0b4535 Mon Sep 17 00:00:00 2001 From: nichmor Date: Wed, 15 May 2024 12:44:35 +0300 Subject: [PATCH 09/12] feat: switch to ahash --- Cargo.lock | 36 +++++++++++++- Cargo.toml | 1 + src/conda_pypi_clobber.rs | 57 +++++++++++++++++++++ src/install_pypi.rs | 56 ++------------------- src/install_wheel.rs | 102 ++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/lock_file/update.rs | 2 - 7 files changed, 200 insertions(+), 55 deletions(-) create mode 100644 src/conda_pypi_clobber.rs diff --git a/Cargo.lock b/Cargo.lock index d83d4efe1..e2a4094a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,6 +40,19 @@ dependencies = [ "version_check", ] +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -1733,7 +1746,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.8", ] [[package]] @@ -3243,6 +3256,7 @@ dependencies = [ name = "pixi" version = "0.22.0" dependencies = [ + "ahash 0.8.11", "assert_matches", "async-once-cell", "cfg-if", @@ -6585,6 +6599,26 @@ dependencies = [ "zvariant", ] +[[package]] +name = "zerocopy" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "zeroize" version = "1.7.0" diff --git a/Cargo.toml b/Cargo.toml index 1965414cb..ecfb4e736 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ rustls-tls = [ slow_integration_tests = [] [dependencies] +ahash = "0.8.11" assert_matches = "1.5.0" async-once-cell = "0.5.3" cfg-if = "1.0" diff --git a/src/conda_pypi_clobber.rs b/src/conda_pypi_clobber.rs new file mode 100644 index 000000000..536651cda --- /dev/null +++ b/src/conda_pypi_clobber.rs @@ -0,0 +1,57 @@ +use std::path::PathBuf; + +use distribution_types::CachedDist; +use rattler_conda_types::PrefixRecord; +use uv_interpreter::PythonEnvironment; + +use crate::install_wheel::get_wheel_info; + +use ahash::{AHashMap, AHashSet}; + +#[derive(Default, Debug)] +pub(crate) struct PypiCondaClobberRegistry { + paths_registry: AHashMap, +} + +impl PypiCondaClobberRegistry { + pub fn with_conda_packages(conda_packages: &[PrefixRecord]) -> Self { + let mut registry = AHashMap::with_capacity(conda_packages.len() * 50); + for record in conda_packages { + for path in &record.paths_data.paths { + registry.insert( + path.relative_path.clone(), + record.repodata_record.package_record.name.clone(), + ); + } + } + Self { + paths_registry: registry, + } + } + + pub fn clobber_on_instalation( + self, + wheels: Vec, + venv: &PythonEnvironment, + ) -> miette::Result>> { + let mut clobber_packages: AHashSet = AHashSet::default(); + + for wheel in wheels { + let Ok(Some(whl_info)) = get_wheel_info(wheel.path(), venv) else { + continue; + }; + + for entry in whl_info.0 { + let path_to_clobber = whl_info.1.join(entry.path); + + if let Some(name) = self.paths_registry.get(&path_to_clobber) { + clobber_packages.insert(name.as_normalized().to_string()); + } + } + } + if clobber_packages.is_empty() { + return Ok(None); + } + Ok(Some(clobber_packages)) + } +} diff --git a/src/install_pypi.rs b/src/install_pypi.rs index 267929a04..9416db210 100644 --- a/src/install_pypi.rs +++ b/src/install_pypi.rs @@ -1,4 +1,4 @@ -use crate::install_wheel::get_wheel_info; +use crate::conda_pypi_clobber::PypiCondaClobberRegistry; use crate::prefix::Prefix; use crate::project::manifest::pypi_options::PypiOptions; use crate::uv_reporter::{UvReporter, UvReporterOptions}; @@ -32,11 +32,11 @@ use distribution_types::{ }; use install_wheel_rs::linker::LinkMode; -use rattler_conda_types::{Platform, PrefixRecord, RepoDataRecord}; +use rattler_conda_types::{Platform, RepoDataRecord}; use rattler_lock::{PypiPackageData, PypiPackageEnvironmentData, UrlOrPath}; -use std::collections::{HashMap, HashSet}; -use std::path::{Path, PathBuf}; +use std::collections::HashMap; +use std::path::Path; use std::str::FromStr; use std::time::Duration; @@ -997,51 +997,3 @@ pub async fn update_python_distributions( Ok(()) } - -#[derive(Default, Debug)] -struct PypiCondaClobberRegistry { - paths_registry: HashMap, -} - -impl PypiCondaClobberRegistry { - pub fn with_conda_packages(conda_packages: &[PrefixRecord]) -> Self { - let mut registry = HashMap::default(); - for record in conda_packages { - for path in &record.paths_data.paths { - registry.insert( - path.relative_path.clone(), - record.repodata_record.package_record.name.clone(), - ); - } - } - Self { - paths_registry: registry, - } - } - - pub fn clobber_on_instalation( - self, - wheels: Vec, - venv: &PythonEnvironment, - ) -> miette::Result>> { - let mut clobber_packages: HashSet = HashSet::default(); - - for wheel in wheels { - let Ok(Some(whl_info)) = get_wheel_info(wheel.path(), venv) else { - continue; - }; - - for entry in whl_info.0 { - let path_to_clobber = whl_info.1.join(entry.path); - - if let Some(name) = self.paths_registry.get(&path_to_clobber) { - clobber_packages.insert(name.as_normalized().to_string()); - } - } - } - if clobber_packages.is_empty() { - return Ok(None); - } - Ok(Some(clobber_packages)) - } -} diff --git a/src/install_wheel.rs b/src/install_wheel.rs index 02a7bd82a..1e1ae1539 100644 --- a/src/install_wheel.rs +++ b/src/install_wheel.rs @@ -4,6 +4,11 @@ use csv::ReaderBuilder; type WheelInfo = (Vec, PathBuf); +/// Returns records from `.dist-info/RECORD` and determines where +/// the wheel should be installed +/// (`purelib`, `platlib` or `unknown`). +/// +/// This function is used to detect if Python wheels will clobber already installed Conda packages pub fn get_wheel_info(whl: &Path, venv: &PythonEnvironment) -> miette::Result> { let dist_info_prefix = find_dist_info(whl)?; // Read the RECORD file. @@ -192,3 +197,100 @@ pub(crate) fn parse_wheel_file(wheel_text: &str) -> miette::Result { } Ok(lib_kind) } + +#[cfg(test)] +mod test { + use crate::install_wheel::LibKind; + use std::io::Cursor; + + use super::{parse_key_value_file, parse_wheel_file, read_record_file}; + + #[test] + fn test_parse_key_value_file() { + let text = r#" +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.1) +Root-Is-Purelib: false +Tag: cp38-cp38-manylinux_2_17_x86_64 +Tag: cp38-cp38-manylinux2014_x86_64"#; + + parse_key_value_file(&mut text.as_bytes(), "WHEEL").unwrap(); + } + + #[test] + fn test_parse_wheel_version() { + fn wheel_with_version(version: &str) -> String { + format!( + r#" +Wheel-Version: {version} +Generator: bdist_wheel (0.37.0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + "# + ) + } + assert_eq!( + parse_wheel_file(&wheel_with_version("1.0")).unwrap(), + LibKind::Pure + ); + assert_eq!( + parse_wheel_file(&wheel_with_version("2.0")).unwrap(), + LibKind::Unknown + ); + } + + #[test] + fn record_with_absolute_paths() { + let record: &str = r#" +/selenium/__init__.py,sha256=l8nEsTP4D2dZVula_p4ZuCe8AGnxOq7MxMeAWNvR0Qc,811 +/selenium/common/exceptions.py,sha256=oZx2PS-g1gYLqJA_oqzE4Rq4ngplqlwwRBZDofiqni0,9309 +selenium-4.1.0.dist-info/METADATA,sha256=jqvBEwtJJ2zh6CljTfTXmpF1aiFs-gvOVikxGbVyX40,6468 +selenium-4.1.0.dist-info/RECORD,,"#; + + let entries = read_record_file(&mut record.as_bytes()).unwrap(); + let expected = [ + "selenium/__init__.py", + "selenium/common/exceptions.py", + "selenium-4.1.0.dist-info/METADATA", + "selenium-4.1.0.dist-info/RECORD", + ] + .map(ToString::to_string) + .to_vec(); + let actual = entries + .into_iter() + .map(|entry| entry.path) + .collect::>(); + assert_eq!(expected, actual); + } + + #[test] + fn test_empty_value() -> miette::Result<()> { + let wheel = r#" +Wheel-Version: 1.0 +Generator: custom +Root-Is-Purelib: false +Tag: +Tag: -manylinux_2_17_x86_64 +Tag: -manylinux2014_x86_64"#; + + let reader = Cursor::new(wheel.to_string().into_bytes()); + let wheel_file = parse_key_value_file(reader, "WHEEL")?; + assert_eq!( + wheel_file.get("Wheel-Version"), + Some(&["1.0".to_string()].to_vec()) + ); + assert_eq!( + wheel_file.get("Tag"), + Some( + &[ + String::new(), + "-manylinux_2_17_x86_64".to_string(), + "-manylinux2014_x86_64".to_string() + ] + .to_vec() + ) + ); + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 92ffc4428..0fa07a240 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ mod activation; pub mod cli; +pub(crate) mod conda_pypi_clobber; pub mod config; pub mod consts; mod environment; diff --git a/src/lock_file/update.rs b/src/lock_file/update.rs index 24bf1caed..0c458ceff 100644 --- a/src/lock_file/update.rs +++ b/src/lock_file/update.rs @@ -214,8 +214,6 @@ impl<'p> LockFileDerivedData<'p> { ) .await?; - // eprintln!("Installed packages {:?}", installed_packages); - // Store that we updated the environment, so we won't have to do it again. self.updated_conda_prefixes .insert(environment.clone(), (prefix.clone(), python_status.clone())); From cd9ad485a1e0ee5d59098e236ce9c275cd9c3697 Mon Sep 17 00:00:00 2001 From: nichmor Date: Wed, 15 May 2024 12:47:17 +0300 Subject: [PATCH 10/12] misc: remove snapshot --- .../pixi__install_pypi__tests__warn_on_clobbering.snap | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 src/snapshots/pixi__install_pypi__tests__warn_on_clobbering.snap diff --git a/src/snapshots/pixi__install_pypi__tests__warn_on_clobbering.snap b/src/snapshots/pixi__install_pypi__tests__warn_on_clobbering.snap deleted file mode 100644 index a9ec8b847..000000000 --- a/src/snapshots/pixi__install_pypi__tests__warn_on_clobbering.snap +++ /dev/null @@ -1,5 +0,0 @@ ---- -source: src/install_pypi.rs -expression: "format!(\"{:?}\", warned_packages.unwrap())" ---- -["mdurl"] From b7db01326a6b81b3a121dcea621ce972db0c3d5e Mon Sep 17 00:00:00 2001 From: nichmor Date: Fri, 17 May 2024 16:22:02 +0300 Subject: [PATCH 11/12] misc: added comments for pypicondaclobber --- src/conda_pypi_clobber.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/conda_pypi_clobber.rs b/src/conda_pypi_clobber.rs index 536651cda..ca12e950d 100644 --- a/src/conda_pypi_clobber.rs +++ b/src/conda_pypi_clobber.rs @@ -10,10 +10,13 @@ use ahash::{AHashMap, AHashSet}; #[derive(Default, Debug)] pub(crate) struct PypiCondaClobberRegistry { + /// A registry of the paths of the installed conda paths and the package names paths_registry: AHashMap, } impl PypiCondaClobberRegistry { + /// Register the paths of the installed conda packages + /// to later check if they are going to be clobbered by the installation of the wheels pub fn with_conda_packages(conda_packages: &[PrefixRecord]) -> Self { let mut registry = AHashMap::with_capacity(conda_packages.len() * 50); for record in conda_packages { @@ -29,6 +32,8 @@ impl PypiCondaClobberRegistry { } } + /// Check if the installation of the wheels is going to clobber any installed conda package + /// and return the names of the packages that are going to be clobbered pub fn clobber_on_instalation( self, wheels: Vec, From eb6b014a05237966a8bd4a6db84379a4a86d7de6 Mon Sep 17 00:00:00 2001 From: nichmor Date: Fri, 17 May 2024 16:23:44 +0300 Subject: [PATCH 12/12] misc: add comment why its important --- src/conda_pypi_clobber.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/conda_pypi_clobber.rs b/src/conda_pypi_clobber.rs index ca12e950d..57aa1ca96 100644 --- a/src/conda_pypi_clobber.rs +++ b/src/conda_pypi_clobber.rs @@ -34,6 +34,9 @@ impl PypiCondaClobberRegistry { /// Check if the installation of the wheels is going to clobber any installed conda package /// and return the names of the packages that are going to be clobbered + /// this allow to warn the user about the overwriting of already installed packages + /// in case of wrong mapping data + /// or malicious packages pub fn clobber_on_instalation( self, wheels: Vec,