From 5f955e4cc1106fd72a6df0e9656eacc339d16f07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20H=C3=A4cker?= Date: Fri, 8 Aug 2025 15:58:10 +0200 Subject: [PATCH 01/13] refactor: Move prefix into pixi_utils crate --- crates/pixi_utils/Cargo.toml | 3 +++ crates/pixi_utils/src/lib.rs | 2 ++ {src => crates/pixi_utils/src}/prefix.rs | 5 +++-- src/cli/exec.rs | 7 ++----- src/cli/shell.rs | 3 +-- src/environment/conda_prefix.rs | 2 +- src/environment/mod.rs | 2 +- src/environment/pypi_prefix.rs | 2 +- src/global/common.rs | 3 +-- src/global/install.rs | 12 ++++-------- src/global/mod.rs | 3 +-- src/global/project/environment.rs | 6 ++---- src/global/project/mod.rs | 2 +- src/install_pypi/mod.rs | 3 ++- src/lib.rs | 1 - src/lock_file/update.rs | 2 +- src/workspace/grouped_environment.rs | 2 +- 17 files changed, 27 insertions(+), 33 deletions(-) rename {src => crates/pixi_utils/src}/prefix.rs (97%) diff --git a/crates/pixi_utils/Cargo.toml b/crates/pixi_utils/Cargo.toml index b7414bfcc4..44d7d265d7 100644 --- a/crates/pixi_utils/Cargo.toml +++ b/crates/pixi_utils/Cargo.toml @@ -27,6 +27,7 @@ rustls-tls = [ async-fd-lock = { workspace = true } fs-err = { workspace = true } indicatif = { workspace = true } +is_executable = { workspace = true } itertools = { workspace = true } miette = { workspace = true } pep508_rs = { workspace = true } @@ -39,6 +40,7 @@ rattler_networking = { workspace = true, features = [ "netrc-rs", "system-integration", ] } +rattler_shell = { workspace = true } reqwest = { workspace = true } reqwest-middleware = { workspace = true } reqwest-retry = { workspace = true } @@ -51,6 +53,7 @@ tokio = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true } url = { workspace = true } +uv-configuration = { workspace = true } [dev-dependencies] insta = { workspace = true } diff --git a/crates/pixi_utils/src/lib.rs b/crates/pixi_utils/src/lib.rs index 7f1d486385..be1810339e 100644 --- a/crates/pixi_utils/src/lib.rs +++ b/crates/pixi_utils/src/lib.rs @@ -1,6 +1,7 @@ pub mod cache; pub mod conda_environment_file; pub mod indicatif; +pub mod prefix; mod prefix_guard; pub mod reqwest; @@ -10,4 +11,5 @@ pub use executable_utils::{ }; pub use cache::EnvironmentHash; +pub use prefix::{Executable, Prefix}; pub use prefix_guard::{AsyncPrefixGuard, AsyncWriteGuard}; diff --git a/src/prefix.rs b/crates/pixi_utils/src/prefix.rs similarity index 97% rename from src/prefix.rs rename to crates/pixi_utils/src/prefix.rs index e61afe41fc..af49e4ba2b 100644 --- a/src/prefix.rs +++ b/crates/pixi_utils/src/prefix.rs @@ -1,6 +1,5 @@ use itertools::Itertools; use miette::{Context, Diagnostic, IntoDiagnostic}; -use pixi_utils::{is_binary_folder, strip_executable_extension}; use rattler_conda_types::{PackageName, Platform, PrefixRecord}; use rattler_shell::{ activation::{ActivationVariables, Activator}, @@ -15,6 +14,8 @@ use std::{ use thiserror::Error; use uv_configuration::RAYON_INITIALIZE; +use crate::{is_binary_folder, strip_executable_extension}; + #[derive(Error, Debug, Diagnostic)] pub enum PrefixError { #[error("failed to collect prefix records from '{1}'")] @@ -105,7 +106,7 @@ impl Prefix { } /// Checks if the given relative path points to an executable file. - pub(crate) fn is_executable(&self, relative_path: &Path) -> bool { + pub fn is_executable(&self, relative_path: &Path) -> bool { let parent_folder = match relative_path.parent() { Some(dir) => dir, None => return false, diff --git a/src/cli/exec.rs b/src/cli/exec.rs index 8b5e560e46..75f67b9b87 100644 --- a/src/cli/exec.rs +++ b/src/cli/exec.rs @@ -5,7 +5,7 @@ use itertools::Itertools; use miette::{Context, IntoDiagnostic}; use pixi_config::{self, Config, ConfigCli}; use pixi_progress::{await_in_progress, global_multi_progress, wrap_in_progress}; -use pixi_utils::{AsyncPrefixGuard, EnvironmentHash, reqwest::build_reqwest_clients}; +use pixi_utils::{AsyncPrefixGuard, EnvironmentHash, Prefix, reqwest::build_reqwest_clients}; use rattler::{ install::{IndicatifReporter, Installer}, package_cache::PackageCache, @@ -17,10 +17,7 @@ use reqwest_middleware::ClientWithMiddleware; use uv_configuration::RAYON_INITIALIZE; use super::cli_config::ChannelsConfig; -use crate::{ - environment::list::{PackageToOutput, print_package_table}, - prefix::Prefix, -}; +use crate::environment::list::{PackageToOutput, print_package_table}; /// Run a command and install it in a temporary environment. /// diff --git a/src/cli/shell.rs b/src/cli/shell.rs index 0549885421..15e53d6ef1 100644 --- a/src/cli/shell.rs +++ b/src/cli/shell.rs @@ -21,10 +21,9 @@ use crate::{ use pixi_config::{ConfigCli, ConfigCliActivation, ConfigCliPrompt}; #[cfg(target_family = "unix")] use pixi_pty::unix::PtySession; +use pixi_utils::Prefix; #[cfg(target_family = "unix")] -use crate::prefix::Prefix; - use super::cli_config::LockFileUpdateConfig; /// Start a shell in a pixi environment, run `exit` to leave the shell. diff --git a/src/environment/conda_prefix.rs b/src/environment/conda_prefix.rs index 4cd2df654a..0e354bcf48 100644 --- a/src/environment/conda_prefix.rs +++ b/src/environment/conda_prefix.rs @@ -5,6 +5,7 @@ use miette::IntoDiagnostic; use pixi_command_dispatcher::{BuildEnvironment, CommandDispatcher, InstallPixiEnvironmentSpec}; use pixi_manifest::FeaturesExt; use pixi_record::PixiRecord; +use pixi_utils::Prefix; use rattler::install::link_script::LinkScriptType; use rattler_conda_types::{ ChannelConfig, ChannelUrl, GenericVirtualPackage, PackageName, Platform, @@ -16,7 +17,6 @@ use super::{ }; use crate::{ environment::PythonStatus, - prefix::Prefix, variants::VariantConfig, workspace::{ HasWorkspaceRef, diff --git a/src/environment/mod.rs b/src/environment/mod.rs index a6899b892c..06cf7655c6 100644 --- a/src/environment/mod.rs +++ b/src/environment/mod.rs @@ -13,6 +13,7 @@ use pixi_manifest::FeaturesExt; use pixi_progress::await_in_progress; use pixi_pypi_spec::PixiPypiSpec; use pixi_spec::{GitSpec, PixiSpec}; +use pixi_utils::Prefix; pub use pypi_prefix::{ContinuePyPIPrefixUpdate, on_python_interpreter_change}; pub use python_status::PythonStatus; use rattler_conda_types::Platform; @@ -30,7 +31,6 @@ use xxhash_rust::xxh3::Xxh3; use crate::{ Workspace, lock_file::{LockFileDerivedData, ReinstallPackages, UpdateLockFileOptions, UpdateMode}, - prefix::Prefix, rlimit::try_increase_rlimit_to_sensible, workspace::{Environment, HasWorkspaceRef, grouped_environment::GroupedEnvironment}, }; diff --git a/src/environment/pypi_prefix.rs b/src/environment/pypi_prefix.rs index 5ff78d20d2..61bf9d70fd 100644 --- a/src/environment/pypi_prefix.rs +++ b/src/environment/pypi_prefix.rs @@ -3,8 +3,8 @@ use std::path::Path; use miette::IntoDiagnostic; use rattler::install::PythonInfo; -use crate::prefix::Prefix; use pixi_consts::consts; +use pixi_utils::Prefix; use rattler_lock::{PypiPackageData, PypiPackageEnvironmentData}; use uv_distribution_types::{InstalledDist, Name}; diff --git a/src/global/common.rs b/src/global/common.rs index 78cabf5c56..6551d53ebc 100644 --- a/src/global/common.rs +++ b/src/global/common.rs @@ -1,6 +1,5 @@ use super::trampoline::{GlobalExecutable, Trampoline}; use super::{EnvironmentName, ExposedName, Mapping}; -use crate::prefix::Executable; use ahash::HashSet; use console::StyledObject; @@ -13,7 +12,7 @@ use itertools::Itertools; use miette::{Context, IntoDiagnostic}; use pixi_config::pixi_home; use pixi_manifest::PrioritizedChannel; -use pixi_utils::executable_from_path; +use pixi_utils::{Executable, executable_from_path}; use rattler::install::{Transaction, TransactionOperation}; use rattler_conda_types::{ Channel, ChannelConfig, NamedChannelOrUrl, PackageName, PackageRecord, PrefixRecord, diff --git a/src/global/install.rs b/src/global/install.rs index 31bb9ac62b..7047e2c181 100644 --- a/src/global/install.rs +++ b/src/global/install.rs @@ -1,16 +1,12 @@ use super::{EnvDir, EnvironmentName, ExposedName, StateChanges}; -use crate::{ - global::{ - BinDir, StateChange, - trampoline::{Configuration, Trampoline}, - }, - prefix::Executable, - prefix::Prefix, +use crate::global::{ + BinDir, StateChange, + trampoline::{Configuration, Trampoline}, }; use indexmap::IndexSet; use itertools::Itertools; use miette::IntoDiagnostic; -use pixi_utils::{executable_from_path, is_binary_folder}; +use pixi_utils::{Executable, Prefix, executable_from_path, is_binary_folder}; use rattler_conda_types::{ MatchSpec, Matches, PackageName, ParseStrictness, Platform, RepoDataRecord, }; diff --git a/src/global/mod.rs b/src/global/mod.rs index 3eb17e2361..5a6bcfaed4 100644 --- a/src/global/mod.rs +++ b/src/global/mod.rs @@ -7,10 +7,9 @@ pub(crate) mod project; pub(crate) mod trampoline; pub(crate) use common::{BinDir, EnvChanges, EnvDir, EnvRoot, EnvState, StateChange, StateChanges}; -use pixi_utils::executable_from_path; pub(crate) use project::{EnvironmentName, ExposedName, Mapping, Project}; -use crate::prefix::{Executable, Prefix}; +use pixi_utils::{Executable, Prefix, executable_from_path}; use rattler_conda_types::PrefixRecord; use std::path::{Path, PathBuf}; diff --git a/src/global/project/environment.rs b/src/global/project/environment.rs index b76cb40f0b..1f178491f1 100644 --- a/src/global/project/environment.rs +++ b/src/global/project/environment.rs @@ -101,11 +101,9 @@ pub(crate) async fn environment_specs_in_sync( mod tests { use super::*; - use crate::{ - global::{EnvDir, EnvRoot}, - prefix::Prefix, - }; + use crate::global::{EnvDir, EnvRoot}; use fs_err::tokio as tokio_fs; + use pixi_utils::Prefix; use rattler_conda_types::ParseStrictness; use std::path::PathBuf; diff --git a/src/global/project/mod.rs b/src/global/project/mod.rs index 4f75aa0ba5..2540dadff0 100644 --- a/src/global/project/mod.rs +++ b/src/global/project/mod.rs @@ -31,6 +31,7 @@ use pixi_manifest::PrioritizedChannel; use pixi_progress::global_multi_progress; use pixi_reporters::TopLevelProgress; use pixi_spec_containers::DependencyMap; +use pixi_utils::{Executable, Prefix}; use pixi_utils::{executable_from_path, reqwest::build_reqwest_clients}; use rattler_conda_types::{ ChannelConfig, GenericVirtualPackage, MatchSpec, PackageName, Platform, PrefixRecord, @@ -61,7 +62,6 @@ use crate::{ install::{create_executable_trampolines, script_exec_mapping}, project::environment::environment_specs_in_sync, }, - prefix::{Executable, Prefix}, repodata::Repodata, rlimit::try_increase_rlimit_to_sensible, }; diff --git a/src/install_pypi/mod.rs b/src/install_pypi/mod.rs index 7c9b889976..f40a5f5824 100644 --- a/src/install_pypi/mod.rs +++ b/src/install_pypi/mod.rs @@ -13,6 +13,7 @@ use pixi_manifest::{ }; use pixi_progress::await_in_progress; use pixi_record::PixiRecord; +use pixi_utils::Prefix; use pixi_uv_conversions::{ BuildIsolation, locked_indexes_to_index_locations, pypi_options_to_build_options, to_exclude_newer, to_index_strategy, @@ -44,7 +45,7 @@ use uv_resolver::{ExcludeNewer, FlatIndex}; use crate::{ install_pypi::plan::{CachedWheels, RequiredDists}, lock_file::UvResolutionContext, - prefix::Prefix, + uv_reporter::{UvReporter, UvReporterOptions}, }; use pixi_reporters::{UvReporter, UvReporterOptions}; diff --git a/src/lib.rs b/src/lib.rs index a8b7b44d92..80a2f38a55 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,6 @@ pub mod environment; mod global; mod install_pypi; pub mod lock_file; -pub mod prefix; mod prompt; pub(crate) mod repodata; pub mod task; diff --git a/src/lock_file/update.rs b/src/lock_file/update.rs index 4b68e6656c..4af46cdd12 100644 --- a/src/lock_file/update.rs +++ b/src/lock_file/update.rs @@ -15,7 +15,6 @@ use crate::{ self, PypiRecord, reporter::SolveProgressBar, virtual_packages::validate_system_meets_environment_requirements, }, - prefix::Prefix, workspace::{ Environment, EnvironmentVars, HasWorkspaceRef, get_activated_environment_variables, grouped_environment::{GroupedEnvironment, GroupedEnvironmentName}, @@ -35,6 +34,7 @@ use pixi_glob::GlobHashCache; use pixi_manifest::{ChannelPriority, EnvironmentName, FeaturesExt}; use pixi_progress::global_multi_progress; use pixi_record::{ParseLockFileError, PixiRecord}; +use pixi_utils::Prefix; use pixi_uv_conversions::{ ConversionError, to_extra_name, to_marker_environment, to_normalize, to_uv_extra_name, to_uv_normalize, diff --git a/src/workspace/grouped_environment.rs b/src/workspace/grouped_environment.rs index 02f92c486f..34e670ea9a 100644 --- a/src/workspace/grouped_environment.rs +++ b/src/workspace/grouped_environment.rs @@ -8,11 +8,11 @@ use pixi_manifest::{ EnvironmentName, Feature, HasFeaturesIter, HasWorkspaceManifest, SystemRequirements, WorkspaceManifest, }; +use pixi_utils::Prefix; use rattler_conda_types::{ChannelConfig, GenericVirtualPackage, Platform}; use crate::{ Workspace, - prefix::Prefix, workspace::{ Environment, HasWorkspaceRef, SolveGroup, virtual_packages::get_minimal_virtual_packages, }, From 2923e3af43804ea18982d25183ee6784a5d13bfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20H=C3=A4cker?= Date: Fri, 8 Aug 2025 16:19:40 +0200 Subject: [PATCH 02/13] refactor: Move python_status, pypi_prefix into pixi_environment crate --- Cargo.lock | 110 ++++++++++++++++++ Cargo.toml | 2 + crates/pixi_environment/Cargo.toml | 21 ++++ crates/pixi_environment/src/lib.rs | 5 + .../pixi_environment/src}/pypi_prefix.rs | 0 .../pixi_environment/src}/python_status.rs | 8 +- src/environment/conda_prefix.rs | 2 +- src/environment/mod.rs | 4 - src/install_pypi/mod.rs | 3 +- src/lock_file/update.rs | 5 +- 10 files changed, 147 insertions(+), 13 deletions(-) create mode 100644 crates/pixi_environment/Cargo.toml create mode 100644 crates/pixi_environment/src/lib.rs rename {src/environment => crates/pixi_environment/src}/pypi_prefix.rs (100%) rename {src/environment => crates/pixi_environment/src}/python_status.rs (88%) diff --git a/Cargo.lock b/Cargo.lock index 96b9f23adf..a2128eaf22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4713,6 +4713,7 @@ dependencies = [ "pixi_config", "pixi_consts", "pixi_default_versions", + "pixi_environment", "pixi_git", "pixi_glob", "pixi_manifest", @@ -4988,6 +4989,22 @@ dependencies = [ "rattler_conda_types", ] +[[package]] +name = "pixi_environment" +version = "0.1.0" +dependencies = [ + "fs-err", + "miette 7.6.0", + "pixi_consts", + "pixi_utils", + "rattler", + "rattler_conda_types", + "rattler_lock", + "tracing", + "uv-distribution-types", + "uv-installer", +] + [[package]] name = "pixi_git" version = "0.0.1" @@ -5024,6 +5041,96 @@ dependencies = [ "wax", ] +[[package]] +name = "pixi_install_pypi" +version = "0.1.0" +dependencies = [ + "ahash", + "assert_matches", + "chrono", + "console 0.15.11", + "csv", + "dunce", + "fancy_display", + "fs-err", + "glob", + "indexmap 2.10.0", + "indicatif", + "insta", + "itertools 0.14.0", + "miette 7.6.0", + "minijinja", + "once_cell", + "pathdiff", + "pep440_rs", + "pep508_rs", + "percent-encoding", + "pixi_allocator", + "pixi_build_discovery", + "pixi_build_frontend", + "pixi_build_type_conversions", + "pixi_command_dispatcher", + "pixi_config", + "pixi_consts", + "pixi_default_versions", + "pixi_environment", + "pixi_git", + "pixi_glob", + "pixi_manifest", + "pixi_progress", + "pixi_pypi_spec", + "pixi_record", + "pixi_spec", + "pixi_spec_containers", + "pixi_test_utils", + "pixi_toml", + "pixi_utils", + "pixi_uv_conversions", + "pypi_modifiers", + "rattler", + "rattler_conda_types", + "rattler_lock", + "regex", + "rstest", + "serde", + "serde-value", + "serde_json", + "spdx", + "strsim", + "strum", + "tempfile", + "thiserror 2.0.12", + "tokio", + "toml-span", + "toml_edit", + "tracing", + "typed-path", + "url", + "uv-auth", + "uv-cache", + "uv-cache-info", + "uv-client", + "uv-configuration", + "uv-dispatch", + "uv-distribution", + "uv-distribution-filename", + "uv-distribution-types", + "uv-git-types", + "uv-install-wheel", + "uv-installer", + "uv-normalize", + "uv-pep440", + "uv-pep508", + "uv-pypi-types", + "uv-python", + "uv-redacted", + "uv-requirements", + "uv-requirements-txt", + "uv-resolver", + "uv-types", + "uv-workspace", +] + [[package]] name = "pixi_manifest" version = "0.1.0" @@ -5226,6 +5333,7 @@ dependencies = [ "fs-err", "indicatif", "insta", + "is_executable", "itertools 0.14.0", "miette 7.6.0", "pep508_rs", @@ -5233,6 +5341,7 @@ dependencies = [ "pixi_consts", "rattler_conda_types", "rattler_networking", + "rattler_shell", "reqwest", "reqwest-middleware", "reqwest-retry", @@ -5246,6 +5355,7 @@ dependencies = [ "tracing", "tracing-subscriber", "url", + "uv-configuration", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0d2ec648a2..544515b940 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -176,6 +176,7 @@ pixi_command_dispatcher = { path = "crates/pixi_command_dispatcher" } pixi_config = { path = "crates/pixi_config" } pixi_consts = { path = "crates/pixi_consts" } pixi_default_versions = { path = "crates/pixi_default_versions" } +pixi_environment = { path = "crates/pixi_environment" } pixi_git = { path = "crates/pixi_git" } pixi_glob = { path = "crates/pixi_glob" } pixi_manifest = { path = "crates/pixi_manifest" } @@ -306,6 +307,7 @@ pixi_command_dispatcher = { workspace = true } pixi_config = { workspace = true } pixi_consts = { workspace = true } pixi_default_versions = { workspace = true } +pixi_environment = { workspace = true } pixi_git = { workspace = true } pixi_glob = { workspace = true } pixi_manifest = { workspace = true, features = ["rattler_lock"] } diff --git a/crates/pixi_environment/Cargo.toml b/crates/pixi_environment/Cargo.toml new file mode 100644 index 0000000000..72ee7fb8d9 --- /dev/null +++ b/crates/pixi_environment/Cargo.toml @@ -0,0 +1,21 @@ +[package] +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +name = "pixi_environment" +readme.workspace = true +repository.workspace = true +version = "0.1.0" + +[dependencies] +fs-err = { workspace = true } +miette = { workspace = true } +pixi_consts = { workspace = true } +pixi_utils = { workspace = true, default-features = false } +rattler = { workspace = true } +rattler_conda_types = { workspace = true } +rattler_lock = { workspace = true } +tracing = { workspace = true } +uv-distribution-types = { workspace = true } +uv-installer = { workspace = true } diff --git a/crates/pixi_environment/src/lib.rs b/crates/pixi_environment/src/lib.rs new file mode 100644 index 0000000000..baa842d6b3 --- /dev/null +++ b/crates/pixi_environment/src/lib.rs @@ -0,0 +1,5 @@ +mod pypi_prefix; +mod python_status; + +pub use pypi_prefix::{ContinuePyPIPrefixUpdate, on_python_interpreter_change}; +pub use python_status::PythonStatus; diff --git a/src/environment/pypi_prefix.rs b/crates/pixi_environment/src/pypi_prefix.rs similarity index 100% rename from src/environment/pypi_prefix.rs rename to crates/pixi_environment/src/pypi_prefix.rs diff --git a/src/environment/python_status.rs b/crates/pixi_environment/src/python_status.rs similarity index 88% rename from src/environment/python_status.rs rename to crates/pixi_environment/src/python_status.rs index 0890b62bac..ca977dd061 100644 --- a/src/environment/python_status.rs +++ b/crates/pixi_environment/src/python_status.rs @@ -23,9 +23,7 @@ pub enum PythonStatus { impl PythonStatus { /// Determine the [`PythonStatus`] from a [`Transaction`]. - pub(crate) fn from_transaction( - transaction: &Transaction, - ) -> Self { + pub fn from_transaction(transaction: &Transaction) -> Self { match ( transaction.current_python_info.as_ref(), transaction.python_info.as_ref(), @@ -45,7 +43,7 @@ impl PythonStatus { /// Returns the info of the current situation (e.g. after the transaction /// completed). - pub(crate) fn current_info(&self) -> Option<&PythonInfo> { + pub fn current_info(&self) -> Option<&PythonInfo> { match self { PythonStatus::Changed { new, .. } | PythonStatus::Unchanged(new) @@ -56,7 +54,7 @@ impl PythonStatus { /// Returns the location of the python interpreter relative to the root of /// the prefix. - pub(crate) fn location(&self) -> Option<&Path> { + pub fn location(&self) -> Option<&Path> { Some(&self.current_info()?.path) } } diff --git a/src/environment/conda_prefix.rs b/src/environment/conda_prefix.rs index 0e354bcf48..3db72dde4c 100644 --- a/src/environment/conda_prefix.rs +++ b/src/environment/conda_prefix.rs @@ -3,6 +3,7 @@ use std::{collections::HashSet, sync::Arc}; use async_once_cell::OnceCell as AsyncOnceCell; use miette::IntoDiagnostic; use pixi_command_dispatcher::{BuildEnvironment, CommandDispatcher, InstallPixiEnvironmentSpec}; +use pixi_environment::PythonStatus; use pixi_manifest::FeaturesExt; use pixi_record::PixiRecord; use pixi_utils::Prefix; @@ -16,7 +17,6 @@ use super::{ try_increase_rlimit_to_sensible, }; use crate::{ - environment::PythonStatus, variants::VariantConfig, workspace::{ HasWorkspaceRef, diff --git a/src/environment/mod.rs b/src/environment/mod.rs index 06cf7655c6..8a22af1c4c 100644 --- a/src/environment/mod.rs +++ b/src/environment/mod.rs @@ -1,8 +1,6 @@ pub(crate) mod conda_metadata; mod conda_prefix; pub mod list; -mod pypi_prefix; -mod python_status; pub use conda_prefix::{CondaPrefixUpdated, CondaPrefixUpdater, CondaPrefixUpdaterBuilder}; use dialoguer::theme::ColorfulTheme; use futures::{FutureExt, StreamExt, TryStreamExt, stream}; @@ -14,8 +12,6 @@ use pixi_progress::await_in_progress; use pixi_pypi_spec::PixiPypiSpec; use pixi_spec::{GitSpec, PixiSpec}; use pixi_utils::Prefix; -pub use pypi_prefix::{ContinuePyPIPrefixUpdate, on_python_interpreter_change}; -pub use python_status::PythonStatus; use rattler_conda_types::Platform; use rattler_lock::LockedPackageRef; use serde::{Deserialize, Serialize}; diff --git a/src/install_pypi/mod.rs b/src/install_pypi/mod.rs index f40a5f5824..4eb4e3fe67 100644 --- a/src/install_pypi/mod.rs +++ b/src/install_pypi/mod.rs @@ -7,6 +7,7 @@ use fancy_display::FancyDisplay; use itertools::Itertools; use miette::{IntoDiagnostic, WrapErr}; use pixi_consts::consts; +use pixi_environment::{ContinuePyPIPrefixUpdate, on_python_interpreter_change}; use pixi_manifest::{ EnvironmentName, SystemRequirements, pypi::pypi_options::{NoBinary, NoBuild, NoBuildIsolation}, @@ -132,7 +133,7 @@ impl<'a> PyPIEnvironmentUpdater<'a> { &self, pixi_records: &[PixiRecord], pypi_records: &[PyPIRecords], - python_status: &crate::environment::PythonStatus, + python_status: &pixi_environment::PythonStatus, ) -> miette::Result<()> { // Determine global site-packages status let python_info = diff --git a/src/lock_file/update.rs b/src/lock_file/update.rs index 4af46cdd12..e30187ca7c 100644 --- a/src/lock_file/update.rs +++ b/src/lock_file/update.rs @@ -7,8 +7,8 @@ use crate::{ activation::CurrentEnvVarBehavior, environment::{ CondaPrefixUpdated, EnvironmentFile, LockFileUsage, LockedEnvironmentHash, - PerEnvironmentAndPlatform, PerGroup, PerGroupAndPlatform, PythonStatus, - read_environment_file, write_environment_file, + PerEnvironmentAndPlatform, PerGroup, PerGroupAndPlatform, read_environment_file, + write_environment_file, }, install_pypi::{PyPIBuildConfig, PyPIContextConfig, PyPIEnvironmentUpdater, PyPIUpdateConfig}, lock_file::{ @@ -30,6 +30,7 @@ use itertools::{Either, Itertools}; use miette::{Diagnostic, IntoDiagnostic, MietteDiagnostic, Report, WrapErr}; use pixi_command_dispatcher::{BuildEnvironment, CommandDispatcher, PixiEnvironmentSpec}; use pixi_consts::consts; +use pixi_environment::PythonStatus; use pixi_glob::GlobHashCache; use pixi_manifest::{ChannelPriority, EnvironmentName, FeaturesExt}; use pixi_progress::global_multi_progress; From 7fe1dbde2e1239d56b81170f960f18d5a07be3c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20H=C3=A4cker?= Date: Fri, 8 Aug 2025 17:02:06 +0200 Subject: [PATCH 03/13] refactor: Move UvResolutionContext into pixi_lockfile crate --- Cargo.lock | 23 ++++++++++++++++ Cargo.toml | 2 ++ crates/pixi_lockfile/Cargo.toml | 27 +++++++++++++++++++ crates/pixi_lockfile/src/lib.rs | 3 +++ crates/pixi_lockfile/src/resolve/mod.rs | 5 ++++ .../src}/resolve/uv_resolution_context.rs | 14 +++++----- src/cli/list.rs | 9 +++---- src/install_pypi/mod.rs | 2 +- src/lock_file/mod.rs | 2 +- src/lock_file/resolve/mod.rs | 1 - src/lock_file/resolve/pypi.rs | 3 ++- src/lock_file/update.rs | 11 +++++--- 12 files changed, 80 insertions(+), 22 deletions(-) create mode 100644 crates/pixi_lockfile/Cargo.toml create mode 100644 crates/pixi_lockfile/src/lib.rs create mode 100644 crates/pixi_lockfile/src/resolve/mod.rs rename {src/lock_file => crates/pixi_lockfile/src}/resolve/uv_resolution_context.rs (90%) diff --git a/Cargo.lock b/Cargo.lock index a2128eaf22..1c8102fb9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4716,6 +4716,7 @@ dependencies = [ "pixi_environment", "pixi_git", "pixi_glob", + "pixi_lockfile", "pixi_manifest", "pixi_progress", "pixi_pty", @@ -5076,6 +5077,7 @@ dependencies = [ "pixi_environment", "pixi_git", "pixi_glob", + "pixi_lockfile", "pixi_manifest", "pixi_progress", "pixi_pypi_spec", @@ -5131,6 +5133,27 @@ dependencies = [ "uv-workspace", ] +[[package]] +name = "pixi_lockfile" +version = "0.1.0" +dependencies = [ + "fs-err", + "miette 7.6.0", + "pixi_config", + "pixi_consts", + "pixi_utils", + "pixi_uv_conversions", + "reqwest", + "tracing", + "uv-cache", + "uv-client", + "uv-configuration", + "uv-dispatch", + "uv-distribution-types", + "uv-pep508", + "uv-types", +] + [[package]] name = "pixi_manifest" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 544515b940..9fa392b8c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -179,6 +179,7 @@ pixi_default_versions = { path = "crates/pixi_default_versions" } pixi_environment = { path = "crates/pixi_environment" } pixi_git = { path = "crates/pixi_git" } pixi_glob = { path = "crates/pixi_glob" } +pixi_lockfile = { path = "crates/pixi_lockfile" } pixi_manifest = { path = "crates/pixi_manifest" } pixi_progress = { path = "crates/pixi_progress" } pixi_pypi_spec = { path = "crates/pixi_pypi_spec" } @@ -310,6 +311,7 @@ pixi_default_versions = { workspace = true } pixi_environment = { workspace = true } pixi_git = { workspace = true } pixi_glob = { workspace = true } +pixi_lockfile = { workspace = true } pixi_manifest = { workspace = true, features = ["rattler_lock"] } pixi_progress = { workspace = true } pixi_pypi_spec = { workspace = true } diff --git a/crates/pixi_lockfile/Cargo.toml b/crates/pixi_lockfile/Cargo.toml new file mode 100644 index 0000000000..395a64abbd --- /dev/null +++ b/crates/pixi_lockfile/Cargo.toml @@ -0,0 +1,27 @@ +[package] +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +name = "pixi_lockfile" +readme.workspace = true +repository.workspace = true +version = "0.1.0" + +[dependencies] +fs-err = { workspace = true } +miette = { workspace = true } +pixi_config = { workspace = true } +pixi_consts = { workspace = true } +pixi_utils = { workspace = true, default-features = false } +pixi_uv_conversions = { workspace = true } +reqwest = { workspace = true } +tracing = { workspace = true } +uv-cache = { workspace = true } +uv-client = { workspace = true } +uv-configuration = { workspace = true } +uv-dispatch = { workspace = true } +uv-distribution-types = { workspace = true } +uv-pep508 = { workspace = true } +uv-types = { workspace = true } +uv-workspace = { workspace = true } diff --git a/crates/pixi_lockfile/src/lib.rs b/crates/pixi_lockfile/src/lib.rs new file mode 100644 index 0000000000..736a65fb7b --- /dev/null +++ b/crates/pixi_lockfile/src/lib.rs @@ -0,0 +1,3 @@ +mod resolve; + +pub use resolve::uv_resolution_context::UvResolutionContext; diff --git a/crates/pixi_lockfile/src/resolve/mod.rs b/crates/pixi_lockfile/src/resolve/mod.rs new file mode 100644 index 0000000000..3468b7b4a3 --- /dev/null +++ b/crates/pixi_lockfile/src/resolve/mod.rs @@ -0,0 +1,5 @@ +//! This module contains code to resolve python package from PyPi or Conda packages. +//! +//! See [`resolve_pypi`] and [`resolve_conda`] for more information. + +pub(crate) mod uv_resolution_context; diff --git a/src/lock_file/resolve/uv_resolution_context.rs b/crates/pixi_lockfile/src/resolve/uv_resolution_context.rs similarity index 90% rename from src/lock_file/resolve/uv_resolution_context.rs rename to crates/pixi_lockfile/src/resolve/uv_resolution_context.rs index 5277076240..acf0e69c65 100644 --- a/src/lock_file/resolve/uv_resolution_context.rs +++ b/crates/pixi_lockfile/src/resolve/uv_resolution_context.rs @@ -7,8 +7,7 @@ use uv_distribution_types::{ExtraBuildRequires, IndexCapabilities}; use uv_types::{HashStrategy, InFlight}; use uv_workspace::WorkspaceCache; -use crate::Workspace; -use pixi_config::{self, get_cache_dir}; +use pixi_config::{self, Config, get_cache_dir}; use pixi_consts::consts; use pixi_utils::reqwest::uv_middlewares; use pixi_uv_conversions::{ConversionError, to_uv_trusted_host}; @@ -34,7 +33,7 @@ pub struct UvResolutionContext { } impl UvResolutionContext { - pub(crate) fn from_workspace(project: &Workspace) -> miette::Result { + pub fn from_workspace_config(project_config: &Config) -> miette::Result { let uv_cache = get_cache_dir()?.join(consts::PYPI_CACHE_DIR); if !uv_cache.exists() { fs_err::create_dir_all(&uv_cache) @@ -44,7 +43,7 @@ impl UvResolutionContext { let cache = Cache::from_path(uv_cache); - let keyring_provider = match project.config().pypi_config().use_keyring() { + let keyring_provider = match project_config.pypi_config().use_keyring() { pixi_config::KeyringProvider::Subprocess => { tracing::debug!("using uv keyring (subprocess) provider"); uv_configuration::KeyringProviderType::Subprocess @@ -55,8 +54,7 @@ impl UvResolutionContext { } }; - let allow_insecure_host = project - .config() + let allow_insecure_host = project_config .pypi_config .allow_insecure_host .iter() @@ -80,8 +78,8 @@ impl UvResolutionContext { capabilities: IndexCapabilities::default(), allow_insecure_host, shared_state: SharedState::default(), - extra_middleware: ExtraMiddleware(uv_middlewares(project.config())), - proxies: project.config().get_proxies().into_diagnostic()?, + extra_middleware: ExtraMiddleware(uv_middlewares(project_config)), + proxies: project_config.get_proxies().into_diagnostic()?, package_config_settings: PackageConfigSettings::default(), extra_build_requires: ExtraBuildRequires::default(), preview: Preview::default(), diff --git a/src/cli/list.rs b/src/cli/list.rs index 6f20df1416..0b9b7f052b 100644 --- a/src/cli/list.rs +++ b/src/cli/list.rs @@ -11,6 +11,7 @@ use human_bytes::human_bytes; use itertools::Itertools; use miette::IntoDiagnostic; use pixi_consts::consts; +use pixi_lockfile::UvResolutionContext; use pixi_manifest::FeaturesExt; use pixi_uv_conversions::{ ConversionError, pypi_options_to_index_locations, to_uv_normalize, to_uv_version, @@ -23,11 +24,7 @@ use uv_configuration::ConfigSettings; use uv_distribution::RegistryWheelIndex; use super::cli_config::LockFileUpdateConfig; -use crate::{ - WorkspaceLocator, - cli::cli_config::WorkspaceConfig, - lock_file::{UpdateLockFileOptions, UvResolutionContext}, -}; +use crate::{WorkspaceLocator, cli::cli_config::WorkspaceConfig, lock_file::UpdateLockFileOptions}; // an enum to sort by size or name #[derive(clap::ValueEnum, Clone, Debug, Serialize)] @@ -223,7 +220,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { let config_settings = ConfigSettings::default(); let mut registry_index = if let Some(python_record) = python_record { if environment.has_pypi_dependencies() { - uv_context = UvResolutionContext::from_workspace(&workspace)?; + uv_context = UvResolutionContext::from_workspace_config(workspace.config())?; index_locations = pypi_options_to_index_locations(&environment.pypi_options(), workspace.root()) .into_diagnostic()?; diff --git a/src/install_pypi/mod.rs b/src/install_pypi/mod.rs index 4eb4e3fe67..c952d0e41c 100644 --- a/src/install_pypi/mod.rs +++ b/src/install_pypi/mod.rs @@ -8,6 +8,7 @@ use itertools::Itertools; use miette::{IntoDiagnostic, WrapErr}; use pixi_consts::consts; use pixi_environment::{ContinuePyPIPrefixUpdate, on_python_interpreter_change}; +use pixi_lockfile::UvResolutionContext; use pixi_manifest::{ EnvironmentName, SystemRequirements, pypi::pypi_options::{NoBinary, NoBuild, NoBuildIsolation}, @@ -45,7 +46,6 @@ use uv_resolver::{ExcludeNewer, FlatIndex}; use crate::{ install_pypi::plan::{CachedWheels, RequiredDists}, - lock_file::UvResolutionContext, uv_reporter::{UvReporter, UvReporterOptions}, }; use pixi_reporters::{UvReporter, UvReporterOptions}; diff --git a/src/lock_file/mod.rs b/src/lock_file/mod.rs index b164e0867a..542c207fd2 100644 --- a/src/lock_file/mod.rs +++ b/src/lock_file/mod.rs @@ -13,7 +13,7 @@ pub(crate) use package_identifier::PypiPackageIdentifier; use pixi_record::PixiRecord; use rattler_lock::{PypiPackageData, PypiPackageEnvironmentData}; pub(crate) use records_by_name::{PixiRecordsByName, PypiRecordsByName}; -pub(crate) use resolve::{pypi::resolve_pypi, uv_resolution_context::UvResolutionContext}; +pub(crate) use resolve::pypi::resolve_pypi; pub use satisfiability::{ EnvironmentUnsat, PlatformUnsat, verify_environment_satisfiability, verify_platform_satisfiability, diff --git a/src/lock_file/resolve/mod.rs b/src/lock_file/resolve/mod.rs index c1f724ffd9..3f1472a5f7 100644 --- a/src/lock_file/resolve/mod.rs +++ b/src/lock_file/resolve/mod.rs @@ -5,4 +5,3 @@ pub(crate) mod build_dispatch; pub(crate) mod pypi; mod resolver_provider; -pub(crate) mod uv_resolution_context; diff --git a/src/lock_file/resolve/pypi.rs b/src/lock_file/resolve/pypi.rs index bb912332de..368aeae990 100644 --- a/src/lock_file/resolve/pypi.rs +++ b/src/lock_file/resolve/pypi.rs @@ -15,6 +15,7 @@ use indicatif::ProgressBar; use itertools::{Either, Itertools}; use miette::{Context, IntoDiagnostic}; use pixi_consts::consts; +use pixi_lockfile::UvResolutionContext; use pixi_manifest::{EnvironmentName, SystemRequirements, pypi::pypi_options::PypiOptions}; use pixi_pypi_spec::PixiPypiSpec; use pixi_record::PixiRecord; @@ -55,7 +56,7 @@ use crate::{ environment::CondaPrefixUpdated, lock_file::{ CondaPrefixUpdater, LockedPypiPackages, PixiRecordsByName, PypiPackageIdentifier, - PypiRecord, UvResolutionContext, + PypiRecord, records_by_name::HasNameVersion, resolve::{ build_dispatch::{ diff --git a/src/lock_file/update.rs b/src/lock_file/update.rs index e30187ca7c..d259589bcb 100644 --- a/src/lock_file/update.rs +++ b/src/lock_file/update.rs @@ -1,6 +1,6 @@ use super::{ - CondaPrefixUpdater, PixiRecordsByName, PypiRecordsByName, UvResolutionContext, - outdated::OutdatedEnvironments, utils::IoConcurrencyLimit, + CondaPrefixUpdater, PixiRecordsByName, PypiRecordsByName, outdated::OutdatedEnvironments, + utils::IoConcurrencyLimit, }; use crate::{ Workspace, @@ -32,6 +32,7 @@ use pixi_command_dispatcher::{BuildEnvironment, CommandDispatcher, PixiEnvironme use pixi_consts::consts; use pixi_environment::PythonStatus; use pixi_glob::GlobHashCache; +use pixi_lockfile::UvResolutionContext; use pixi_manifest::{ChannelPriority, EnvironmentName, FeaturesExt}; use pixi_progress::global_multi_progress; use pixi_record::{ParseLockFileError, PixiRecord}; @@ -493,7 +494,9 @@ impl<'p> LockFileDerivedData<'p> { let uv_context = self .uv_context - .get_or_try_init(|| UvResolutionContext::from_workspace(self.workspace))? + .get_or_try_init(|| { + UvResolutionContext::from_workspace_config(self.workspace.config()) + })? .clone() .set_cache_refresh(uv_reinstall, uv_packages); @@ -1396,7 +1399,7 @@ impl<'p> UpdateContext<'p> { .finish()?; let uv_context = uv_context - .get_or_try_init(|| UvResolutionContext::from_workspace(project))? + .get_or_try_init(|| UvResolutionContext::from_workspace_config(project.config()))? .clone(); let locked_group_records = self From 5eba21d0339f0be576c22583f8f43d7f72599e53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20H=C3=A4cker?= Date: Mon, 11 Aug 2025 09:52:36 +0200 Subject: [PATCH 04/13] refactor: Move install_pypi into pixi_install_pypi crate --- .../pixi_install_pypi/src}/conda_pypi_clobber.rs | 0 {src/install_pypi => crates/pixi_install_pypi/src}/conversions.rs | 0 .../pixi_install_pypi/src}/install_wheel.rs | 0 {src/install_pypi => crates/pixi_install_pypi/src}/mod.rs | 0 {src/install_pypi => crates/pixi_install_pypi/src}/plan/cache.rs | 0 .../pixi_install_pypi/src}/plan/installation_source.rs | 0 .../pixi_install_pypi/src}/plan/installed_dists.rs | 0 {src/install_pypi => crates/pixi_install_pypi/src}/plan/mod.rs | 0 {src/install_pypi => crates/pixi_install_pypi/src}/plan/models.rs | 0 .../install_pypi => crates/pixi_install_pypi/src}/plan/planner.rs | 0 .../install_pypi => crates/pixi_install_pypi/src}/plan/reasons.rs | 0 .../pixi_install_pypi/src}/plan/required_dists.rs | 0 .../pixi_install_pypi/src}/plan/test/harness.rs | 0 .../pixi_install_pypi/src}/plan/test/mod.rs | 0 .../pixi_install_pypi/src}/plan/validation.rs | 0 {src/install_pypi => crates/pixi_install_pypi/src}/utils.rs | 0 16 files changed, 0 insertions(+), 0 deletions(-) rename {src/install_pypi => crates/pixi_install_pypi/src}/conda_pypi_clobber.rs (100%) rename {src/install_pypi => crates/pixi_install_pypi/src}/conversions.rs (100%) rename {src/install_pypi => crates/pixi_install_pypi/src}/install_wheel.rs (100%) rename {src/install_pypi => crates/pixi_install_pypi/src}/mod.rs (100%) rename {src/install_pypi => crates/pixi_install_pypi/src}/plan/cache.rs (100%) rename {src/install_pypi => crates/pixi_install_pypi/src}/plan/installation_source.rs (100%) rename {src/install_pypi => crates/pixi_install_pypi/src}/plan/installed_dists.rs (100%) rename {src/install_pypi => crates/pixi_install_pypi/src}/plan/mod.rs (100%) rename {src/install_pypi => crates/pixi_install_pypi/src}/plan/models.rs (100%) rename {src/install_pypi => crates/pixi_install_pypi/src}/plan/planner.rs (100%) rename {src/install_pypi => crates/pixi_install_pypi/src}/plan/reasons.rs (100%) rename {src/install_pypi => crates/pixi_install_pypi/src}/plan/required_dists.rs (100%) rename {src/install_pypi => crates/pixi_install_pypi/src}/plan/test/harness.rs (100%) rename {src/install_pypi => crates/pixi_install_pypi/src}/plan/test/mod.rs (100%) rename {src/install_pypi => crates/pixi_install_pypi/src}/plan/validation.rs (100%) rename {src/install_pypi => crates/pixi_install_pypi/src}/utils.rs (100%) diff --git a/src/install_pypi/conda_pypi_clobber.rs b/crates/pixi_install_pypi/src/conda_pypi_clobber.rs similarity index 100% rename from src/install_pypi/conda_pypi_clobber.rs rename to crates/pixi_install_pypi/src/conda_pypi_clobber.rs diff --git a/src/install_pypi/conversions.rs b/crates/pixi_install_pypi/src/conversions.rs similarity index 100% rename from src/install_pypi/conversions.rs rename to crates/pixi_install_pypi/src/conversions.rs diff --git a/src/install_pypi/install_wheel.rs b/crates/pixi_install_pypi/src/install_wheel.rs similarity index 100% rename from src/install_pypi/install_wheel.rs rename to crates/pixi_install_pypi/src/install_wheel.rs diff --git a/src/install_pypi/mod.rs b/crates/pixi_install_pypi/src/mod.rs similarity index 100% rename from src/install_pypi/mod.rs rename to crates/pixi_install_pypi/src/mod.rs diff --git a/src/install_pypi/plan/cache.rs b/crates/pixi_install_pypi/src/plan/cache.rs similarity index 100% rename from src/install_pypi/plan/cache.rs rename to crates/pixi_install_pypi/src/plan/cache.rs diff --git a/src/install_pypi/plan/installation_source.rs b/crates/pixi_install_pypi/src/plan/installation_source.rs similarity index 100% rename from src/install_pypi/plan/installation_source.rs rename to crates/pixi_install_pypi/src/plan/installation_source.rs diff --git a/src/install_pypi/plan/installed_dists.rs b/crates/pixi_install_pypi/src/plan/installed_dists.rs similarity index 100% rename from src/install_pypi/plan/installed_dists.rs rename to crates/pixi_install_pypi/src/plan/installed_dists.rs diff --git a/src/install_pypi/plan/mod.rs b/crates/pixi_install_pypi/src/plan/mod.rs similarity index 100% rename from src/install_pypi/plan/mod.rs rename to crates/pixi_install_pypi/src/plan/mod.rs diff --git a/src/install_pypi/plan/models.rs b/crates/pixi_install_pypi/src/plan/models.rs similarity index 100% rename from src/install_pypi/plan/models.rs rename to crates/pixi_install_pypi/src/plan/models.rs diff --git a/src/install_pypi/plan/planner.rs b/crates/pixi_install_pypi/src/plan/planner.rs similarity index 100% rename from src/install_pypi/plan/planner.rs rename to crates/pixi_install_pypi/src/plan/planner.rs diff --git a/src/install_pypi/plan/reasons.rs b/crates/pixi_install_pypi/src/plan/reasons.rs similarity index 100% rename from src/install_pypi/plan/reasons.rs rename to crates/pixi_install_pypi/src/plan/reasons.rs diff --git a/src/install_pypi/plan/required_dists.rs b/crates/pixi_install_pypi/src/plan/required_dists.rs similarity index 100% rename from src/install_pypi/plan/required_dists.rs rename to crates/pixi_install_pypi/src/plan/required_dists.rs diff --git a/src/install_pypi/plan/test/harness.rs b/crates/pixi_install_pypi/src/plan/test/harness.rs similarity index 100% rename from src/install_pypi/plan/test/harness.rs rename to crates/pixi_install_pypi/src/plan/test/harness.rs diff --git a/src/install_pypi/plan/test/mod.rs b/crates/pixi_install_pypi/src/plan/test/mod.rs similarity index 100% rename from src/install_pypi/plan/test/mod.rs rename to crates/pixi_install_pypi/src/plan/test/mod.rs diff --git a/src/install_pypi/plan/validation.rs b/crates/pixi_install_pypi/src/plan/validation.rs similarity index 100% rename from src/install_pypi/plan/validation.rs rename to crates/pixi_install_pypi/src/plan/validation.rs diff --git a/src/install_pypi/utils.rs b/crates/pixi_install_pypi/src/utils.rs similarity index 100% rename from src/install_pypi/utils.rs rename to crates/pixi_install_pypi/src/utils.rs From fce8ec9bc44f223e04ca58eabe4778d2f03dc136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20H=C3=A4cker?= Date: Mon, 11 Aug 2025 10:45:58 +0200 Subject: [PATCH 05/13] refactor: Adapt code to point to new pixi_install_pypi crate --- Cargo.lock | 40 +----------- Cargo.toml | 6 +- crates/pixi_install_pypi/Cargo.toml | 64 +++++++++++++++++++ .../pixi_install_pypi/src/{mod.rs => lib.rs} | 6 +- crates/pixi_install_pypi/src/plan/planner.rs | 2 +- .../src/plan/required_dists.rs | 2 +- .../src/plan/test/harness.rs | 6 +- crates/pixi_install_pypi/src/plan/test/mod.rs | 4 +- .../pixi_install_pypi/src/plan/validation.rs | 2 +- src/lib.rs | 1 - src/lock_file/update.rs | 4 +- tests/integration_rust/install_tests.rs | 2 +- 12 files changed, 82 insertions(+), 57 deletions(-) create mode 100644 crates/pixi_install_pypi/Cargo.toml rename crates/pixi_install_pypi/src/{mod.rs => lib.rs} (99%) diff --git a/Cargo.lock b/Cargo.lock index 1c8102fb9b..f8c40a5536 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4675,7 +4675,6 @@ dependencies = [ "console 0.15.11", "console-subscriber", "crossbeam-channel", - "csv", "ctrlc", "dashmap", "deno_task_shell", @@ -4704,7 +4703,6 @@ dependencies = [ "pathdiff", "pep440_rs", "pep508_rs", - "percent-encoding", "pixi_allocator", "pixi_build_discovery", "pixi_build_frontend", @@ -4716,6 +4714,7 @@ dependencies = [ "pixi_environment", "pixi_git", "pixi_glob", + "pixi_install_pypi", "pixi_lockfile", "pixi_manifest", "pixi_progress", @@ -4769,10 +4768,8 @@ dependencies = [ "tracing-subscriber", "typed-path", "url", - "uv-auth", "uv-build-frontend", "uv-cache", - "uv-cache-info", "uv-client", "uv-configuration", "uv-dispatch", @@ -5051,60 +5048,32 @@ dependencies = [ "chrono", "console 0.15.11", "csv", - "dunce", "fancy_display", "fs-err", - "glob", - "indexmap 2.10.0", - "indicatif", - "insta", "itertools 0.14.0", "miette 7.6.0", - "minijinja", - "once_cell", - "pathdiff", "pep440_rs", "pep508_rs", "percent-encoding", - "pixi_allocator", - "pixi_build_discovery", - "pixi_build_frontend", - "pixi_build_type_conversions", - "pixi_command_dispatcher", - "pixi_config", "pixi_consts", - "pixi_default_versions", "pixi_environment", "pixi_git", - "pixi_glob", "pixi_lockfile", "pixi_manifest", "pixi_progress", - "pixi_pypi_spec", "pixi_record", - "pixi_spec", - "pixi_spec_containers", - "pixi_test_utils", - "pixi_toml", + "pixi_reporters", "pixi_utils", "pixi_uv_conversions", "pypi_modifiers", "rattler", "rattler_conda_types", "rattler_lock", - "regex", - "rstest", "serde", - "serde-value", "serde_json", - "spdx", - "strsim", - "strum", "tempfile", "thiserror 2.0.12", "tokio", - "toml-span", - "toml_edit", "tracing", "typed-path", "url", @@ -5117,7 +5086,6 @@ dependencies = [ "uv-distribution", "uv-distribution-filename", "uv-distribution-types", - "uv-git-types", "uv-install-wheel", "uv-installer", "uv-normalize", @@ -5126,11 +5094,8 @@ dependencies = [ "uv-pypi-types", "uv-python", "uv-redacted", - "uv-requirements", - "uv-requirements-txt", "uv-resolver", "uv-types", - "uv-workspace", ] [[package]] @@ -5152,6 +5117,7 @@ dependencies = [ "uv-distribution-types", "uv-pep508", "uv-types", + "uv-workspace", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 9fa392b8c6..dcd062f47b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -179,6 +179,7 @@ pixi_default_versions = { path = "crates/pixi_default_versions" } pixi_environment = { path = "crates/pixi_environment" } pixi_git = { path = "crates/pixi_git" } pixi_glob = { path = "crates/pixi_glob" } +pixi_install_pypi = { path = "crates/pixi_install_pypi" } pixi_lockfile = { path = "crates/pixi_lockfile" } pixi_manifest = { path = "crates/pixi_manifest" } pixi_progress = { path = "crates/pixi_progress" } @@ -251,7 +252,6 @@ clap_complete_nushell = { workspace = true } console = { workspace = true, features = ["windows-console-colors"] } console-subscriber = { workspace = true, optional = true } crossbeam-channel = { workspace = true } -csv = { workspace = true } dashmap = { workspace = true } deno_task_shell = { workspace = true } dialoguer = { workspace = true } @@ -279,7 +279,6 @@ uv-install-wheel = { workspace = true } pep440_rs = { workspace = true } pep508_rs = { workspace = true } -percent-encoding = { workspace = true } rattler = { workspace = true, features = ["cli-tools", "indicatif"] } rattler_conda_types = { workspace = true } rattler_digest = { workspace = true } @@ -311,6 +310,7 @@ pixi_default_versions = { workspace = true } pixi_environment = { workspace = true } pixi_git = { workspace = true } pixi_glob = { workspace = true } +pixi_install_pypi = { workspace = true } pixi_lockfile = { workspace = true } pixi_manifest = { workspace = true, features = ["rattler_lock"] } pixi_progress = { workspace = true } @@ -353,9 +353,7 @@ tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter"] } typed-path = { workspace = true } url = { workspace = true } -uv-auth = { workspace = true } uv-cache = { workspace = true } -uv-cache-info = { workspace = true } uv-client = { workspace = true } uv-configuration = { workspace = true } uv-dispatch = { workspace = true } diff --git a/crates/pixi_install_pypi/Cargo.toml b/crates/pixi_install_pypi/Cargo.toml new file mode 100644 index 0000000000..3c5c69f99f --- /dev/null +++ b/crates/pixi_install_pypi/Cargo.toml @@ -0,0 +1,64 @@ +[package] +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +name = "pixi_install_pypi" +readme.workspace = true +repository.workspace = true +version = "0.1.0" + +[dependencies] +ahash = { workspace = true } +assert_matches = { workspace = true } +chrono = { workspace = true } +console = { workspace = true } +csv = { workspace = true } +fancy_display = { workspace = true } +fs-err = { workspace = true } +itertools = { workspace = true } +miette = { workspace = true } +pep440_rs = { workspace = true } +pep508_rs = { workspace = true } +percent-encoding = { workspace = true } +pixi_consts = { workspace = true } +pixi_environment = { workspace = true } +pixi_git = { workspace = true } +pixi_lockfile = { workspace = true } +pixi_manifest = { workspace = true } +pixi_progress = { workspace = true } +pixi_record = { workspace = true } +pixi_reporters = { workspace = true } +pixi_utils = { workspace = true, default-features = false } +pixi_uv_conversions = { workspace = true } +pypi_modifiers = { workspace = true } +rattler = { workspace = true } +rattler_conda_types = { workspace = true } +rattler_lock = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +tempfile = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } +typed-path = { workspace = true } +url = { workspace = true } +uv-auth = { workspace = true } +uv-cache = { workspace = true } +uv-cache-info = { workspace = true } +uv-client = { workspace = true } +uv-configuration = { workspace = true } +uv-dispatch = { workspace = true } +uv-distribution = { workspace = true } +uv-distribution-filename = { workspace = true } +uv-distribution-types = { workspace = true } +uv-install-wheel = { workspace = true } +uv-installer = { workspace = true } +uv-normalize = { workspace = true } +uv-pep440 = { workspace = true } +uv-pep508 = { workspace = true } +uv-pypi-types = { workspace = true } +uv-python = { workspace = true } +uv-redacted = { workspace = true } +uv-resolver = { workspace = true } +uv-types = { workspace = true } diff --git a/crates/pixi_install_pypi/src/mod.rs b/crates/pixi_install_pypi/src/lib.rs similarity index 99% rename from crates/pixi_install_pypi/src/mod.rs rename to crates/pixi_install_pypi/src/lib.rs index c952d0e41c..a6acc740fc 100644 --- a/crates/pixi_install_pypi/src/mod.rs +++ b/crates/pixi_install_pypi/src/lib.rs @@ -1,6 +1,5 @@ use std::{collections::HashMap, path::Path, sync::Arc}; -use crate::environment::{ContinuePyPIPrefixUpdate, on_python_interpreter_change}; use chrono::{DateTime, Utc}; use conda_pypi_clobber::PypiCondaClobberRegistry; use fancy_display::FancyDisplay; @@ -44,10 +43,7 @@ use uv_installer::{Preparer, SitePackages, UninstallError}; use uv_python::{Interpreter, PythonEnvironment}; use uv_resolver::{ExcludeNewer, FlatIndex}; -use crate::{ - install_pypi::plan::{CachedWheels, RequiredDists}, - uv_reporter::{UvReporter, UvReporterOptions}, -}; +use crate::plan::{CachedWheels, RequiredDists}; use pixi_reporters::{UvReporter, UvReporterOptions}; pub(crate) mod conda_pypi_clobber; diff --git a/crates/pixi_install_pypi/src/plan/planner.rs b/crates/pixi_install_pypi/src/plan/planner.rs index 84f5a5fa52..a51e8f3c64 100644 --- a/crates/pixi_install_pypi/src/plan/planner.rs +++ b/crates/pixi_install_pypi/src/plan/planner.rs @@ -13,7 +13,7 @@ use std::collections::HashSet; use uv_cache::Cache; use uv_distribution_types::{InstalledDist, Name}; -use crate::install_pypi::conversions::ConvertToUvDistError; +use crate::conversions::ConvertToUvDistError; use super::{ NeedReinstall, PyPIInstallationPlan, RequiredDists, cache::DistCache, diff --git a/crates/pixi_install_pypi/src/plan/required_dists.rs b/crates/pixi_install_pypi/src/plan/required_dists.rs index 194905eba6..ca9354b698 100644 --- a/crates/pixi_install_pypi/src/plan/required_dists.rs +++ b/crates/pixi_install_pypi/src/plan/required_dists.rs @@ -11,7 +11,7 @@ use rattler_lock::PypiPackageData; use uv_distribution_types::Dist; use uv_normalize::PackageName; -use crate::install_pypi::conversions::{ConvertToUvDistError, convert_to_dist}; +use crate::conversions::{ConvertToUvDistError, convert_to_dist}; /// A collection of required distributions with their associated package data. /// This struct owns the Dist objects to ensure proper lifetimes for the install planner. diff --git a/crates/pixi_install_pypi/src/plan/test/harness.rs b/crates/pixi_install_pypi/src/plan/test/harness.rs index 48cf3362bc..755072dc11 100644 --- a/crates/pixi_install_pypi/src/plan/test/harness.rs +++ b/crates/pixi_install_pypi/src/plan/test/harness.rs @@ -1,6 +1,6 @@ -use crate::install_pypi::plan::InstallPlanner; -use crate::install_pypi::plan::cache::DistCache; -use crate::install_pypi::plan::installed_dists::InstalledDists; +use crate::plan::InstallPlanner; +use crate::plan::cache::DistCache; +use crate::plan::installed_dists::InstalledDists; use pixi_consts::consts; use pixi_uv_conversions::GitUrlWithPrefix; use rattler_lock::{PypiPackageData, UrlOrPath}; diff --git a/crates/pixi_install_pypi/src/plan/test/mod.rs b/crates/pixi_install_pypi/src/plan/test/mod.rs index 71f130acd4..24f6b5e195 100644 --- a/crates/pixi_install_pypi/src/plan/test/mod.rs +++ b/crates/pixi_install_pypi/src/plan/test/mod.rs @@ -1,6 +1,6 @@ use self::harness::{InstalledDistOptions, MockedSitePackages, NoCache, RequiredPackages}; -use crate::install_pypi::NeedReinstall; -use crate::install_pypi::plan::test::harness::AllCached; +use crate::NeedReinstall; +use crate::plan::test::harness::AllCached; use assert_matches::assert_matches; use harness::empty_wheel; use std::path::PathBuf; diff --git a/crates/pixi_install_pypi/src/plan/validation.rs b/crates/pixi_install_pypi/src/plan/validation.rs index abdb4d8462..f89a5dc40a 100644 --- a/crates/pixi_install_pypi/src/plan/validation.rs +++ b/crates/pixi_install_pypi/src/plan/validation.rs @@ -8,7 +8,7 @@ use url::Url; use uv_distribution_types::InstalledDist; use uv_pypi_types::{ParsedGitUrl, ParsedUrlError}; -use crate::install_pypi::utils::{check_url_freshness, strip_direct_scheme}; +use crate::utils::{check_url_freshness, strip_direct_scheme}; use super::{NeedReinstall, models::ValidateCurrentInstall}; use pixi_uv_conversions::ConversionError; diff --git a/src/lib.rs b/src/lib.rs index 80a2f38a55..3db1c60e87 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,6 @@ pub mod cli; pub mod diff; pub mod environment; mod global; -mod install_pypi; pub mod lock_file; mod prompt; pub(crate) mod repodata; diff --git a/src/lock_file/update.rs b/src/lock_file/update.rs index d259589bcb..9e3d4de1c0 100644 --- a/src/lock_file/update.rs +++ b/src/lock_file/update.rs @@ -10,7 +10,6 @@ use crate::{ PerEnvironmentAndPlatform, PerGroup, PerGroupAndPlatform, read_environment_file, write_environment_file, }, - install_pypi::{PyPIBuildConfig, PyPIContextConfig, PyPIEnvironmentUpdater, PyPIUpdateConfig}, lock_file::{ self, PypiRecord, reporter::SolveProgressBar, virtual_packages::validate_system_meets_environment_requirements, @@ -32,6 +31,9 @@ use pixi_command_dispatcher::{BuildEnvironment, CommandDispatcher, PixiEnvironme use pixi_consts::consts; use pixi_environment::PythonStatus; use pixi_glob::GlobHashCache; +use pixi_install_pypi::{ + PyPIBuildConfig, PyPIContextConfig, PyPIEnvironmentUpdater, PyPIUpdateConfig, +}; use pixi_lockfile::UvResolutionContext; use pixi_manifest::{ChannelPriority, EnvironmentName, FeaturesExt}; use pixi_progress::global_multi_progress; diff --git a/tests/integration_rust/install_tests.rs b/tests/integration_rust/install_tests.rs index 066f63264c..6a81e95a24 100644 --- a/tests/integration_rust/install_tests.rs +++ b/tests/integration_rust/install_tests.rs @@ -333,7 +333,7 @@ fn is_pypi_package_installed(env: &PythonEnvironment, package_name: &str) -> boo // Helper to check if a conda package is installed. async fn is_conda_package_installed(prefix_path: &Path, package_name: &str) -> bool { - let conda_prefix = pixi::prefix::Prefix::new(prefix_path.to_path_buf()); + let conda_prefix = pixi_utils::Prefix::new(prefix_path.to_path_buf()); conda_prefix .find_designated_package(&rattler_conda_types::PackageName::try_from(package_name).unwrap()) .await From 37507eda8e76dbcb7f1d8adc3a129f710dd882b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20H=C3=A4cker?= Date: Mon, 11 Aug 2025 10:56:33 +0200 Subject: [PATCH 06/13] fix: Set correct target_family for pixi_utils::Prefix --- src/cli/shell.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cli/shell.rs b/src/cli/shell.rs index 15e53d6ef1..dbd1ce7ee9 100644 --- a/src/cli/shell.rs +++ b/src/cli/shell.rs @@ -21,9 +21,10 @@ use crate::{ use pixi_config::{ConfigCli, ConfigCliActivation, ConfigCliPrompt}; #[cfg(target_family = "unix")] use pixi_pty::unix::PtySession; -use pixi_utils::Prefix; #[cfg(target_family = "unix")] +use pixi_utils::Prefix; + use super::cli_config::LockFileUpdateConfig; /// Start a shell in a pixi environment, run `exit` to leave the shell. From 7554c21d427e8e8eee2765459e066f96935ca343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20H=C3=A4cker?= Date: Mon, 11 Aug 2025 14:07:08 +0200 Subject: [PATCH 07/13] refactor: Move conda_metadata and list into pixi_environment crate --- Cargo.lock | 4 ++++ crates/pixi_environment/Cargo.toml | 4 ++++ .../pixi_environment/src}/conda_metadata.rs | 0 crates/pixi_environment/src/lib.rs | 2 ++ {src/environment => crates/pixi_environment/src}/list.rs | 0 src/cli/exec.rs | 2 +- src/environment/mod.rs | 2 -- src/global/list.rs | 6 ++---- 8 files changed, 13 insertions(+), 7 deletions(-) rename {src/environment => crates/pixi_environment/src}/conda_metadata.rs (100%) rename {src/environment => crates/pixi_environment/src}/list.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index f8c40a5536..2ca4341b89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4991,13 +4991,17 @@ dependencies = [ name = "pixi_environment" version = "0.1.0" dependencies = [ + "console 0.15.11", "fs-err", + "human_bytes", "miette 7.6.0", "pixi_consts", "pixi_utils", "rattler", "rattler_conda_types", "rattler_lock", + "serde", + "tabwriter", "tracing", "uv-distribution-types", "uv-installer", diff --git a/crates/pixi_environment/Cargo.toml b/crates/pixi_environment/Cargo.toml index 72ee7fb8d9..21c7d88aa8 100644 --- a/crates/pixi_environment/Cargo.toml +++ b/crates/pixi_environment/Cargo.toml @@ -9,13 +9,17 @@ repository.workspace = true version = "0.1.0" [dependencies] +console = { workspace = true } fs-err = { workspace = true } +human_bytes = { workspace = true } miette = { workspace = true } pixi_consts = { workspace = true } pixi_utils = { workspace = true, default-features = false } rattler = { workspace = true } rattler_conda_types = { workspace = true } rattler_lock = { workspace = true } +serde = { workspace = true } +tabwriter = { workspace = true } tracing = { workspace = true } uv-distribution-types = { workspace = true } uv-installer = { workspace = true } diff --git a/src/environment/conda_metadata.rs b/crates/pixi_environment/src/conda_metadata.rs similarity index 100% rename from src/environment/conda_metadata.rs rename to crates/pixi_environment/src/conda_metadata.rs diff --git a/crates/pixi_environment/src/lib.rs b/crates/pixi_environment/src/lib.rs index baa842d6b3..4fd6df28a4 100644 --- a/crates/pixi_environment/src/lib.rs +++ b/crates/pixi_environment/src/lib.rs @@ -1,3 +1,5 @@ +pub mod conda_metadata; +pub mod list; mod pypi_prefix; mod python_status; diff --git a/src/environment/list.rs b/crates/pixi_environment/src/list.rs similarity index 100% rename from src/environment/list.rs rename to crates/pixi_environment/src/list.rs diff --git a/src/cli/exec.rs b/src/cli/exec.rs index 75f67b9b87..2edca61c61 100644 --- a/src/cli/exec.rs +++ b/src/cli/exec.rs @@ -4,6 +4,7 @@ use clap::{Parser, ValueHint}; use itertools::Itertools; use miette::{Context, IntoDiagnostic}; use pixi_config::{self, Config, ConfigCli}; +use pixi_environment::list::{PackageToOutput, print_package_table}; use pixi_progress::{await_in_progress, global_multi_progress, wrap_in_progress}; use pixi_utils::{AsyncPrefixGuard, EnvironmentHash, Prefix, reqwest::build_reqwest_clients}; use rattler::{ @@ -17,7 +18,6 @@ use reqwest_middleware::ClientWithMiddleware; use uv_configuration::RAYON_INITIALIZE; use super::cli_config::ChannelsConfig; -use crate::environment::list::{PackageToOutput, print_package_table}; /// Run a command and install it in a temporary environment. /// diff --git a/src/environment/mod.rs b/src/environment/mod.rs index 8a22af1c4c..0991c7e17d 100644 --- a/src/environment/mod.rs +++ b/src/environment/mod.rs @@ -1,6 +1,4 @@ -pub(crate) mod conda_metadata; mod conda_prefix; -pub mod list; pub use conda_prefix::{CondaPrefixUpdated, CondaPrefixUpdater, CondaPrefixUpdaterBuilder}; use dialoguer::theme::ColorfulTheme; use futures::{FutureExt, StreamExt, TryStreamExt, stream}; diff --git a/src/global/list.rs b/src/global/list.rs index 3a89b687fb..6b7afe1560 100644 --- a/src/global/list.rs +++ b/src/global/list.rs @@ -2,16 +2,14 @@ use fancy_display::FancyDisplay; use indexmap::{IndexMap, IndexSet}; use itertools::Itertools; use pixi_consts::consts; +use pixi_environment::list::{PackageToOutput, print_package_table}; use pixi_spec::PixiSpec; use rattler_conda_types::{PackageName, PrefixRecord, Version}; use serde::Serialize; use miette::{IntoDiagnostic, miette}; -use crate::{ - environment::list::{PackageToOutput, print_package_table}, - global::common::find_package_records, -}; +use crate::global::common::find_package_records; use super::{EnvChanges, EnvState, EnvironmentName, Mapping, Project, project::ParsedEnvironment}; From 7679104b556dee010079a69b5ee5b9b5d3850acb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20H=C3=A4cker?= Date: Mon, 11 Aug 2025 14:20:26 +0200 Subject: [PATCH 08/13] refactor: Move rlimit / variants into pixi_utils crate --- Cargo.lock | 1 + crates/pixi_utils/Cargo.toml | 1 + crates/pixi_utils/src/lib.rs | 2 ++ {src => crates/pixi_utils/src}/rlimit.rs | 4 ++-- {src => crates/pixi_utils/src}/variants.rs | 0 src/environment/conda_prefix.rs | 17 ++++++----------- src/environment/mod.rs | 1 - src/global/project/mod.rs | 3 +-- src/lib.rs | 2 -- src/workspace/mod.rs | 2 +- 10 files changed, 14 insertions(+), 19 deletions(-) rename {src => crates/pixi_utils/src}/rlimit.rs (94%) rename {src => crates/pixi_utils/src}/variants.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 2ca4341b89..12403165f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5338,6 +5338,7 @@ dependencies = [ "reqwest", "reqwest-middleware", "reqwest-retry", + "rlimit", "rstest", "serde", "serde_json", diff --git a/crates/pixi_utils/Cargo.toml b/crates/pixi_utils/Cargo.toml index 44d7d265d7..d20d554d4b 100644 --- a/crates/pixi_utils/Cargo.toml +++ b/crates/pixi_utils/Cargo.toml @@ -44,6 +44,7 @@ rattler_shell = { workspace = true } reqwest = { workspace = true } reqwest-middleware = { workspace = true } reqwest-retry = { workspace = true } +rlimit = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } serde_yaml = { workspace = true } diff --git a/crates/pixi_utils/src/lib.rs b/crates/pixi_utils/src/lib.rs index be1810339e..42b503b343 100644 --- a/crates/pixi_utils/src/lib.rs +++ b/crates/pixi_utils/src/lib.rs @@ -4,6 +4,8 @@ pub mod indicatif; pub mod prefix; mod prefix_guard; pub mod reqwest; +pub mod rlimit; +pub mod variants; mod executable_utils; pub use executable_utils::{ diff --git a/src/rlimit.rs b/crates/pixi_utils/src/rlimit.rs similarity index 94% rename from src/rlimit.rs rename to crates/pixi_utils/src/rlimit.rs index 3331d39a5f..23ebffc7a2 100644 --- a/src/rlimit.rs +++ b/crates/pixi_utils/src/rlimit.rs @@ -7,7 +7,7 @@ pub const DESIRED_RLIMIT_NOFILE: u64 = 1024; /// for pixi. The desired value is defined by the `DESIRED_RLIMIT_NOFILE` /// constant and should suffice for most use cases. #[cfg(not(target_os = "windows"))] -pub(crate) fn try_increase_rlimit_to_sensible() { +pub fn try_increase_rlimit_to_sensible() { static INIT: std::sync::Once = std::sync::Once::new(); INIT.call_once( || match rlimit::increase_nofile_limit(DESIRED_RLIMIT_NOFILE) { @@ -40,7 +40,7 @@ pub(crate) fn try_increase_rlimit_to_sensible() { } #[cfg(target_os = "windows")] -pub(crate) fn try_increase_rlimit_to_sensible() { +pub fn try_increase_rlimit_to_sensible() { // On Windows, there is no need to increase the RLIMIT_NOFILE resource // limit. } diff --git a/src/variants.rs b/crates/pixi_utils/src/variants.rs similarity index 100% rename from src/variants.rs rename to crates/pixi_utils/src/variants.rs diff --git a/src/environment/conda_prefix.rs b/src/environment/conda_prefix.rs index 3db72dde4c..02763f3a7b 100644 --- a/src/environment/conda_prefix.rs +++ b/src/environment/conda_prefix.rs @@ -4,24 +4,19 @@ use async_once_cell::OnceCell as AsyncOnceCell; use miette::IntoDiagnostic; use pixi_command_dispatcher::{BuildEnvironment, CommandDispatcher, InstallPixiEnvironmentSpec}; use pixi_environment::PythonStatus; +use pixi_environment::conda_metadata::{create_history_file, create_prefix_location_file}; use pixi_manifest::FeaturesExt; use pixi_record::PixiRecord; -use pixi_utils::Prefix; +use pixi_utils::variants::VariantConfig; +use pixi_utils::{Prefix, rlimit::try_increase_rlimit_to_sensible}; use rattler::install::link_script::LinkScriptType; use rattler_conda_types::{ ChannelConfig, ChannelUrl, GenericVirtualPackage, PackageName, Platform, }; -use super::{ - conda_metadata::{create_history_file, create_prefix_location_file}, - try_increase_rlimit_to_sensible, -}; -use crate::{ - variants::VariantConfig, - workspace::{ - HasWorkspaceRef, - grouped_environment::{GroupedEnvironment, GroupedEnvironmentName}, - }, +use crate::workspace::{ + HasWorkspaceRef, + grouped_environment::{GroupedEnvironment, GroupedEnvironmentName}, }; /// A struct that contains the result of updating a conda prefix. diff --git a/src/environment/mod.rs b/src/environment/mod.rs index 0991c7e17d..8f03aecb25 100644 --- a/src/environment/mod.rs +++ b/src/environment/mod.rs @@ -25,7 +25,6 @@ use xxhash_rust::xxh3::Xxh3; use crate::{ Workspace, lock_file::{LockFileDerivedData, ReinstallPackages, UpdateLockFileOptions, UpdateMode}, - rlimit::try_increase_rlimit_to_sensible, workspace::{Environment, HasWorkspaceRef, grouped_environment::GroupedEnvironment}, }; diff --git a/src/global/project/mod.rs b/src/global/project/mod.rs index 2540dadff0..9058c020dc 100644 --- a/src/global/project/mod.rs +++ b/src/global/project/mod.rs @@ -31,7 +31,7 @@ use pixi_manifest::PrioritizedChannel; use pixi_progress::global_multi_progress; use pixi_reporters::TopLevelProgress; use pixi_spec_containers::DependencyMap; -use pixi_utils::{Executable, Prefix}; +use pixi_utils::{Executable, Prefix, rlimit::try_increase_rlimit_to_sensible}; use pixi_utils::{executable_from_path, reqwest::build_reqwest_clients}; use rattler_conda_types::{ ChannelConfig, GenericVirtualPackage, MatchSpec, PackageName, Platform, PrefixRecord, @@ -63,7 +63,6 @@ use crate::{ project::environment::environment_specs_in_sync, }, repodata::Repodata, - rlimit::try_increase_rlimit_to_sensible, }; mod environment; diff --git a/src/lib.rs b/src/lib.rs index 3db1c60e87..4c1fc4f072 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,9 +11,7 @@ pub(crate) mod repodata; pub mod task; pub mod workspace; -mod rlimit; mod signals; -pub mod variants; pub use lock_file::UpdateLockFileOptions; pub use workspace::{DependencyType, Workspace, WorkspaceLocator}; diff --git a/src/workspace/mod.rs b/src/workspace/mod.rs index eb67a8f7f2..6939cc1ccd 100644 --- a/src/workspace/mod.rs +++ b/src/workspace/mod.rs @@ -40,6 +40,7 @@ use pixi_manifest::{ use pixi_pypi_spec::{PixiPypiSpec, PypiPackageName}; use pixi_spec::SourceSpec; use pixi_utils::reqwest::build_reqwest_clients; +use pixi_utils::variants::VariantConfig; use pypi_mapping::{ChannelName, CustomMapping, MappingLocation, MappingSource}; use rattler_conda_types::{Channel, ChannelConfig, MatchSpec, PackageName, Platform, Version}; use rattler_lock::{LockFile, LockedPackageRef}; @@ -58,7 +59,6 @@ use crate::{ diff::LockFileDiff, lock_file::filter_lock_file, repodata::Repodata, - variants::VariantConfig, }; static CUSTOM_TARGET_DIR_WARN: OnceCell<()> = OnceCell::new(); From 24ce357b9985a41207df16458a7d1ce0d8d850d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20H=C3=A4cker?= Date: Mon, 11 Aug 2025 14:35:44 +0200 Subject: [PATCH 09/13] refactor: Move virtual_package into pixi_lockfile crate --- Cargo.lock | 11 +++++++++++ crates/pixi_lockfile/Cargo.toml | 11 +++++++++++ crates/pixi_lockfile/src/lib.rs | 1 + .../pixi_lockfile/src}/virtual_packages.rs | 2 +- src/lock_file/mod.rs | 1 - src/lock_file/update.rs | 6 ++---- src/workspace/virtual_packages.rs | 6 +++--- 7 files changed, 29 insertions(+), 9 deletions(-) rename {src/lock_file => crates/pixi_lockfile/src}/virtual_packages.rs (99%) diff --git a/Cargo.lock b/Cargo.lock index 12403165f7..0511f54152 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5106,18 +5106,29 @@ dependencies = [ name = "pixi_lockfile" version = "0.1.0" dependencies = [ + "fancy_display", "fs-err", + "insta", + "itertools 0.14.0", "miette 7.6.0", "pixi_config", "pixi_consts", + "pixi_manifest", + "pixi_test_utils", "pixi_utils", "pixi_uv_conversions", + "pypi_modifiers", + "rattler_conda_types", + "rattler_lock", + "rattler_virtual_packages", "reqwest", + "thiserror 2.0.12", "tracing", "uv-cache", "uv-client", "uv-configuration", "uv-dispatch", + "uv-distribution-filename", "uv-distribution-types", "uv-pep508", "uv-types", diff --git a/crates/pixi_lockfile/Cargo.toml b/crates/pixi_lockfile/Cargo.toml index 395a64abbd..ca8df5d45f 100644 --- a/crates/pixi_lockfile/Cargo.toml +++ b/crates/pixi_lockfile/Cargo.toml @@ -9,18 +9,29 @@ repository.workspace = true version = "0.1.0" [dependencies] +fancy_display = { workspace = true } fs-err = { workspace = true } +insta = { workspace = true } +itertools = { workspace = true } miette = { workspace = true } pixi_config = { workspace = true } pixi_consts = { workspace = true } +pixi_manifest = { workspace = true } +pixi_test_utils = { workspace = true } pixi_utils = { workspace = true, default-features = false } pixi_uv_conversions = { workspace = true } +pypi_modifiers = { workspace = true } +rattler_conda_types = { workspace = true } +rattler_lock = { workspace = true } +rattler_virtual_packages = { workspace = true } reqwest = { workspace = true } +thiserror = { workspace = true } tracing = { workspace = true } uv-cache = { workspace = true } uv-client = { workspace = true } uv-configuration = { workspace = true } uv-dispatch = { workspace = true } +uv-distribution-filename = { workspace = true } uv-distribution-types = { workspace = true } uv-pep508 = { workspace = true } uv-types = { workspace = true } diff --git a/crates/pixi_lockfile/src/lib.rs b/crates/pixi_lockfile/src/lib.rs index 736a65fb7b..8d91fbabfd 100644 --- a/crates/pixi_lockfile/src/lib.rs +++ b/crates/pixi_lockfile/src/lib.rs @@ -1,3 +1,4 @@ mod resolve; +pub mod virtual_packages; pub use resolve::uv_resolution_context::UvResolutionContext; diff --git a/src/lock_file/virtual_packages.rs b/crates/pixi_lockfile/src/virtual_packages.rs similarity index 99% rename from src/lock_file/virtual_packages.rs rename to crates/pixi_lockfile/src/virtual_packages.rs index 114c559304..9e5dc87883 100644 --- a/src/lock_file/virtual_packages.rs +++ b/crates/pixi_lockfile/src/virtual_packages.rs @@ -146,7 +146,7 @@ fn get_wheels_from_pypi_package_data(pypi_packages: Vec) -> Vec } /// Validate that current machine has all the required virtual packages for the given environment -pub(crate) fn validate_system_meets_environment_requirements( +pub fn validate_system_meets_environment_requirements( lock_file: &LockFile, platform: Platform, environment_name: &EnvironmentName, diff --git a/src/lock_file/mod.rs b/src/lock_file/mod.rs index 542c207fd2..6df10ad07f 100644 --- a/src/lock_file/mod.rs +++ b/src/lock_file/mod.rs @@ -6,7 +6,6 @@ mod resolve; mod satisfiability; mod update; mod utils; -pub mod virtual_packages; pub use crate::environment::CondaPrefixUpdater; pub(crate) use package_identifier::PypiPackageIdentifier; diff --git a/src/lock_file/update.rs b/src/lock_file/update.rs index 9e3d4de1c0..028d7fbd96 100644 --- a/src/lock_file/update.rs +++ b/src/lock_file/update.rs @@ -10,10 +10,7 @@ use crate::{ PerEnvironmentAndPlatform, PerGroup, PerGroupAndPlatform, read_environment_file, write_environment_file, }, - lock_file::{ - self, PypiRecord, reporter::SolveProgressBar, - virtual_packages::validate_system_meets_environment_requirements, - }, + lock_file::{self, PypiRecord, reporter::SolveProgressBar}, workspace::{ Environment, EnvironmentVars, HasWorkspaceRef, get_activated_environment_variables, grouped_environment::{GroupedEnvironment, GroupedEnvironmentName}, @@ -35,6 +32,7 @@ use pixi_install_pypi::{ PyPIBuildConfig, PyPIContextConfig, PyPIEnvironmentUpdater, PyPIUpdateConfig, }; use pixi_lockfile::UvResolutionContext; +use pixi_lockfile::virtual_packages::validate_system_meets_environment_requirements; use pixi_manifest::{ChannelPriority, EnvironmentName, FeaturesExt}; use pixi_progress::global_multi_progress; use pixi_record::{ParseLockFileError, PixiRecord}; diff --git a/src/workspace/virtual_packages.rs b/src/workspace/virtual_packages.rs index c4d3281fa7..67207f498e 100644 --- a/src/workspace/virtual_packages.rs +++ b/src/workspace/virtual_packages.rs @@ -1,12 +1,12 @@ -use crate::lock_file::virtual_packages::{ - MachineValidationError, validate_system_meets_environment_requirements, -}; use crate::workspace::{Environment, errors::UnsupportedPlatformError}; use itertools::Itertools; use miette::Diagnostic; use pixi_default_versions::{ default_glibc_version, default_linux_version, default_mac_os_version, default_windows_version, }; +use pixi_lockfile::virtual_packages::{ + MachineValidationError, validate_system_meets_environment_requirements, +}; use pixi_manifest::{FeaturesExt, LibCSystemRequirement, SystemRequirements}; use rattler_conda_types::Platform; use rattler_lock::LockFile; From f4875ec23fa8b07edfac749049c80f2cc9b4fd41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20H=C3=A4cker?= Date: Mon, 11 Aug 2025 15:40:31 +0200 Subject: [PATCH 10/13] refactor: Move EditablePackagesMismatch, SourceTreeHashMismatch, PlatformUnsat, LockedCondaPackages, LockedPypiPackages, PypiRecord, PypiRecordsByName, PixiRecordsByName into pixi_lockfile crate --- Cargo.lock | 11 + crates/pixi_lockfile/Cargo.toml | 11 + crates/pixi_lockfile/src/lib.rs | 19 + .../pixi_lockfile/src}/package_identifier.rs | 14 +- .../pixi_lockfile/src}/records_by_name.rs | 32 +- .../editable_packages_mismatch.rs | 75 ++++ .../pixi_lockfile/src/satisfiability/mod.rs | 7 + .../src/satisfiability/platform_unsat.rs | 217 +++++++++++ .../source_tree_hash_mismatch.rs | 46 +++ src/lock_file/mod.rs | 19 +- src/lock_file/resolve/pypi.rs | 9 +- src/lock_file/resolve/resolver_provider.rs | 3 +- src/lock_file/satisfiability/mod.rs | 337 +----------------- src/lock_file/update.rs | 9 +- 14 files changed, 429 insertions(+), 380 deletions(-) rename {src/lock_file => crates/pixi_lockfile/src}/package_identifier.rs (96%) rename {src/lock_file => crates/pixi_lockfile/src}/records_by_name.rs (87%) create mode 100644 crates/pixi_lockfile/src/satisfiability/editable_packages_mismatch.rs create mode 100644 crates/pixi_lockfile/src/satisfiability/mod.rs create mode 100644 crates/pixi_lockfile/src/satisfiability/platform_unsat.rs create mode 100644 crates/pixi_lockfile/src/satisfiability/source_tree_hash_mismatch.rs diff --git a/Cargo.lock b/Cargo.lock index 0511f54152..9ffcf6a3bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5111,12 +5111,20 @@ dependencies = [ "insta", "itertools 0.14.0", "miette 7.6.0", + "pep440_rs", + "pep508_rs", + "pixi_build_discovery", "pixi_config", "pixi_consts", + "pixi_glob", "pixi_manifest", + "pixi_pypi_spec", + "pixi_record", + "pixi_spec", "pixi_test_utils", "pixi_utils", "pixi_uv_conversions", + "pypi_mapping", "pypi_modifiers", "rattler_conda_types", "rattler_lock", @@ -5124,13 +5132,16 @@ dependencies = [ "reqwest", "thiserror 2.0.12", "tracing", + "url", "uv-cache", "uv-client", "uv-configuration", "uv-dispatch", "uv-distribution-filename", "uv-distribution-types", + "uv-normalize", "uv-pep508", + "uv-pypi-types", "uv-types", "uv-workspace", ] diff --git a/crates/pixi_lockfile/Cargo.toml b/crates/pixi_lockfile/Cargo.toml index ca8df5d45f..9c188299be 100644 --- a/crates/pixi_lockfile/Cargo.toml +++ b/crates/pixi_lockfile/Cargo.toml @@ -14,12 +14,20 @@ fs-err = { workspace = true } insta = { workspace = true } itertools = { workspace = true } miette = { workspace = true } +pep440_rs = { workspace = true } +pep508_rs = { workspace = true } +pixi_build_discovery = { workspace = true } pixi_config = { workspace = true } pixi_consts = { workspace = true } +pixi_glob = { workspace = true } pixi_manifest = { workspace = true } +pixi_pypi_spec = { workspace = true } +pixi_record = { workspace = true } +pixi_spec = { workspace = true } pixi_test_utils = { workspace = true } pixi_utils = { workspace = true, default-features = false } pixi_uv_conversions = { workspace = true } +pypi_mapping = { workspace = true } pypi_modifiers = { workspace = true } rattler_conda_types = { workspace = true } rattler_lock = { workspace = true } @@ -27,12 +35,15 @@ rattler_virtual_packages = { workspace = true } reqwest = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } +url = { workspace = true } uv-cache = { workspace = true } uv-client = { workspace = true } uv-configuration = { workspace = true } uv-dispatch = { workspace = true } uv-distribution-filename = { workspace = true } uv-distribution-types = { workspace = true } +uv-normalize = { workspace = true } uv-pep508 = { workspace = true } +uv-pypi-types = { workspace = true } uv-types = { workspace = true } uv-workspace = { workspace = true } diff --git a/crates/pixi_lockfile/src/lib.rs b/crates/pixi_lockfile/src/lib.rs index 8d91fbabfd..bf84a11681 100644 --- a/crates/pixi_lockfile/src/lib.rs +++ b/crates/pixi_lockfile/src/lib.rs @@ -1,4 +1,23 @@ +use pixi_record::PixiRecord; +use rattler_lock::PypiPackageData; +use rattler_lock::PypiPackageEnvironmentData; + +mod package_identifier; +mod records_by_name; mod resolve; +pub mod satisfiability; pub mod virtual_packages; +pub use package_identifier::{ConversionError, PypiPackageIdentifier}; +pub use records_by_name::{HasNameVersion, PixiRecordsByName, PypiRecordsByName}; pub use resolve::uv_resolution_context::UvResolutionContext; + +/// A list of conda packages that are locked for a specific platform. +pub type LockedCondaPackages = Vec; + +/// A list of Pypi packages that are locked for a specific platform. +pub type LockedPypiPackages = Vec; + +/// A single Pypi record that contains both the package data and the environment +/// data. In Pixi we basically always need both. +pub type PypiRecord = (PypiPackageData, PypiPackageEnvironmentData); diff --git a/src/lock_file/package_identifier.rs b/crates/pixi_lockfile/src/package_identifier.rs similarity index 96% rename from src/lock_file/package_identifier.rs rename to crates/pixi_lockfile/src/package_identifier.rs index 069375a373..31b33d8c6c 100644 --- a/src/lock_file/package_identifier.rs +++ b/crates/pixi_lockfile/src/package_identifier.rs @@ -5,8 +5,8 @@ use rattler_conda_types::{PackageRecord, PackageUrl, RepoDataRecord}; use std::{collections::HashSet, str::FromStr}; use thiserror::Error; -use crate::lock_file::PlatformUnsat; -use crate::lock_file::PlatformUnsat::{ +use crate::satisfiability::PlatformUnsat; +use crate::satisfiability::PlatformUnsat::{ DirectUrlDependencyOnCondaInstalledPackage, DirectoryDependencyOnCondaInstalledPackage, GitDependencyOnCondaInstalledPackage, PathDependencyOnCondaInstalledPackage, }; @@ -26,9 +26,7 @@ pub struct PypiPackageIdentifier { impl PypiPackageIdentifier { /// Extracts the python packages that will be installed when the specified /// conda package is installed. - pub(crate) fn from_repodata_record( - record: &RepoDataRecord, - ) -> Result, ConversionError> { + pub fn from_repodata_record(record: &RepoDataRecord) -> Result, ConversionError> { let mut result = Vec::new(); Self::from_record_into(record, &mut result)?; @@ -99,7 +97,7 @@ impl PypiPackageIdentifier { /// Tries to construct an instance from a generic PURL. /// /// The `fallback_version` is used if the PURL does not contain a version. - pub(crate) fn convert_from_purl( + pub fn convert_from_purl( package_url: &PackageUrl, fallback_version: &str, ) -> Result, ConversionError> { @@ -113,7 +111,7 @@ impl PypiPackageIdentifier { /// Constructs a new instance from a PyPI package URL. /// /// The `fallback_version` is used if the PURL does not contain a version. - pub(crate) fn from_pypi_purl( + pub fn from_pypi_purl( package_url: &PackageUrl, fallback_version: &str, ) -> Result { @@ -139,7 +137,7 @@ impl PypiPackageIdentifier { /// Checks of a found pypi requirement satisfies with the information /// in this package identifier. - pub(crate) fn satisfies( + pub fn satisfies( &self, requirement: &uv_distribution_types::Requirement, ) -> Result> { diff --git a/src/lock_file/records_by_name.rs b/crates/pixi_lockfile/src/records_by_name.rs similarity index 87% rename from src/lock_file/records_by_name.rs rename to crates/pixi_lockfile/src/records_by_name.rs index 453b858c60..e05b4455f4 100644 --- a/src/lock_file/records_by_name.rs +++ b/crates/pixi_lockfile/src/records_by_name.rs @@ -1,5 +1,5 @@ use super::package_identifier::ConversionError; -use crate::lock_file::{PypiPackageIdentifier, PypiRecord}; +use crate::{PypiPackageIdentifier, PypiRecord}; use pixi_record::PixiRecord; use pixi_uv_conversions::to_uv_normalize; use pypi_modifiers::pypi_tags::is_python_record; @@ -8,11 +8,11 @@ use std::collections::HashMap; use std::collections::hash_map::Entry; use std::hash::Hash; -pub(crate) type PypiRecordsByName = DependencyRecordsByName; -pub(crate) type PixiRecordsByName = DependencyRecordsByName; +pub type PypiRecordsByName = DependencyRecordsByName; +pub type PixiRecordsByName = DependencyRecordsByName; /// A trait required from the dependencies stored in DependencyRecordsByName -pub(crate) trait HasNameVersion { +pub trait HasNameVersion { // Name type of the dependency type N: Hash + Eq + Clone; // Version type of the dependency @@ -64,8 +64,8 @@ impl HasNameVersion for PixiRecord { /// A struct that holds both a ``Vec` of `DependencyRecord` and a mapping from /// name to index. #[derive(Clone, Debug)] -pub(crate) struct DependencyRecordsByName { - pub(crate) records: Vec, +pub struct DependencyRecordsByName { + pub records: Vec, by_name: HashMap, } @@ -92,40 +92,40 @@ impl From> for DependencyRecordsByName { impl DependencyRecordsByName { /// Returns the record with the given name or `None` if no such record /// exists. - pub(crate) fn by_name(&self, key: &D::N) -> Option<&D> { + pub fn by_name(&self, key: &D::N) -> Option<&D> { self.by_name.get(key).map(|idx| &self.records[*idx]) } /// Returns the index of the record with the given name or `None` if no such /// record exists. - pub(crate) fn index_by_name(&self, key: &D::N) -> Option { + pub fn index_by_name(&self, key: &D::N) -> Option { self.by_name.get(key).copied() } /// Returns true if there are no records stored in this instance - pub(crate) fn is_empty(&self) -> bool { + pub fn is_empty(&self) -> bool { self.records.is_empty() } /// Returns the number of entries in the mapping. - pub(crate) fn len(&self) -> usize { + pub fn len(&self) -> usize { self.records.len() } /// Converts this instance into the internally stored records. - pub(crate) fn into_inner(self) -> Vec { + pub fn into_inner(self) -> Vec { self.records } /// Returns an iterator over the names of the records stored in this /// instance. - pub(crate) fn names(&self) -> impl Iterator { + pub fn names(&self) -> impl Iterator { // Iterate over the records to retain the index of the original record. self.records.iter().map(|r| r.name()) } /// Constructs a new instance from an iterator of pypi records. If multiple /// records exist for the same package name an error is returned. - pub(crate) fn from_unique_iter>(iter: I) -> Result> { + pub fn from_unique_iter>(iter: I) -> Result> { let iter = iter.into_iter(); let min_size = iter.size_hint().0; let mut by_name = HashMap::with_capacity(min_size); @@ -148,7 +148,7 @@ impl DependencyRecordsByName { /// Constructs a new instance from an iterator of repodata records. The /// records are deduplicated where the record with the highest version /// wins. - pub(crate) fn from_iter>(iter: I) -> Self { + pub fn from_iter>(iter: I) -> Self { let iter = iter.into_iter(); let min_size = iter.size_hint().0; let mut by_name = HashMap::with_capacity(min_size); @@ -177,7 +177,7 @@ impl DependencyRecordsByName { impl PixiRecordsByName { /// Returns the record that represents the python interpreter or `None` if /// no such record exists. - pub(crate) fn python_interpreter_record(&self) -> Option<&RepoDataRecord> { + pub fn python_interpreter_record(&self) -> Option<&RepoDataRecord> { self.records.iter().find_map(|record| match record { PixiRecord::Binary(record) if is_python_record(record) => Some(record), _ => None, @@ -186,7 +186,7 @@ impl PixiRecordsByName { /// Convert the records into a map of pypi package identifiers mapped to the /// records they were extracted from. - pub(crate) fn by_pypi_name( + pub fn by_pypi_name( &self, ) -> Result< HashMap, diff --git a/crates/pixi_lockfile/src/satisfiability/editable_packages_mismatch.rs b/crates/pixi_lockfile/src/satisfiability/editable_packages_mismatch.rs new file mode 100644 index 0000000000..f52ef840df --- /dev/null +++ b/crates/pixi_lockfile/src/satisfiability/editable_packages_mismatch.rs @@ -0,0 +1,75 @@ +use std::fmt::Display; + +use thiserror::Error; + +#[derive(Debug, Error)] +pub struct EditablePackagesMismatch { + pub expected_editable: Vec, + pub unexpected_editable: Vec, +} + +impl Display for EditablePackagesMismatch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if !self.expected_editable.is_empty() && self.unexpected_editable.is_empty() { + write!(f, "expected ")?; + format_package_list(f, &self.expected_editable)?; + write!( + f, + " to be editable but in the lock-file {they} {are} not", + they = it_they(self.expected_editable.len()), + are = is_are(self.expected_editable.len()) + )? + } else if self.expected_editable.is_empty() && !self.unexpected_editable.is_empty() { + write!(f, "expected ")?; + format_package_list(f, &self.unexpected_editable)?; + write!( + f, + "NOT to be editable but in the lock-file {they} {are}", + they = it_they(self.unexpected_editable.len()), + are = is_are(self.unexpected_editable.len()) + )? + } else { + write!(f, "expected ")?; + format_package_list(f, &self.expected_editable)?; + write!( + f, + " to be editable but in the lock-file but {they} {are} not, whereas ", + they = it_they(self.expected_editable.len()), + are = is_are(self.expected_editable.len()) + )?; + format_package_list(f, &self.unexpected_editable)?; + write!( + f, + " {are} NOT expected to be editable which in the lock-file {they} {are}", + they = it_they(self.unexpected_editable.len()), + are = is_are(self.unexpected_editable.len()) + )? + } + + return Ok(()); + + fn format_package_list( + f: &mut std::fmt::Formatter<'_>, + packages: &[uv_normalize::PackageName], + ) -> std::fmt::Result { + for (idx, package) in packages.iter().enumerate() { + if idx == packages.len() - 1 && idx > 0 { + write!(f, " and ")?; + } else if idx > 0 { + write!(f, ", ")?; + } + write!(f, "{}", package)?; + } + + Ok(()) + } + + fn is_are(count: usize) -> &'static str { + if count == 1 { "is" } else { "are" } + } + + fn it_they(count: usize) -> &'static str { + if count == 1 { "it" } else { "they" } + } + } +} diff --git a/crates/pixi_lockfile/src/satisfiability/mod.rs b/crates/pixi_lockfile/src/satisfiability/mod.rs new file mode 100644 index 0000000000..2208489b4c --- /dev/null +++ b/crates/pixi_lockfile/src/satisfiability/mod.rs @@ -0,0 +1,7 @@ +mod editable_packages_mismatch; +mod platform_unsat; +mod source_tree_hash_mismatch; + +pub use editable_packages_mismatch::EditablePackagesMismatch; +pub use platform_unsat::PlatformUnsat; +pub use source_tree_hash_mismatch::SourceTreeHashMismatch; diff --git a/crates/pixi_lockfile/src/satisfiability/platform_unsat.rs b/crates/pixi_lockfile/src/satisfiability/platform_unsat.rs new file mode 100644 index 0000000000..c29ea24332 --- /dev/null +++ b/crates/pixi_lockfile/src/satisfiability/platform_unsat.rs @@ -0,0 +1,217 @@ +use std::path::PathBuf; + +use itertools::Itertools; +use miette::Diagnostic; +use pep440_rs::VersionSpecifiers; +use pixi_glob::GlobHashError; +use pixi_record::{ParseLockFileError, SourceMismatchError}; +use pixi_uv_conversions::AsPep508Error; +use rattler_conda_types::{MatchSpec, ParseMatchSpecError}; +use thiserror::Error; +use url::Url; +use uv_pypi_types::ParsedUrlError; + +use crate::{ + package_identifier::ConversionError, + satisfiability::{EditablePackagesMismatch, SourceTreeHashMismatch}, +}; + +#[derive(Debug, Error, Diagnostic)] +pub enum PlatformUnsat { + #[error("the requirement '{0}' could not be satisfied (required by '{1}')")] + UnsatisfiableMatchSpec(Box, String), + + #[error("no package named exists '{0}' (required by '{1}')")] + SourcePackageMissing(String, String), + + #[error("required source package '{0}' is locked as binary (required by '{1}')")] + RequiredSourceIsBinary(String, String), + + #[error("package '{0}' is locked as source, but is only required as binary")] + RequiredBinaryIsSource(String), + + #[error("the locked source package '{0}' does not match the requested source package, {1}")] + SourcePackageMismatch(String, SourceMismatchError), + + #[error("failed to convert the requirement for '{0}'")] + FailedToConvertRequirement(pep508_rs::PackageName, #[source] Box), + + #[error("the requirement '{0}' could not be satisfied (required by '{1}')")] + UnsatisfiableRequirement(Box, String), + + #[error("the conda package does not satisfy the pypi requirement '{0}' (required by '{1}')")] + CondaUnsatisfiableRequirement(Box, String), + + #[error("there was a duplicate entry for '{0}'")] + DuplicateEntry(String), + + #[error("the requirement '{0}' failed to parse")] + FailedToParseMatchSpec(String, #[source] ParseMatchSpecError), + + #[error("there are more conda packages in the lock-file than are used by the environment: {}", .0.iter().map(rattler_conda_types::PackageName::as_source).format(", "))] + TooManyCondaPackages(Vec), + + #[error("missing purls")] + MissingPurls, + + #[error("corrupted lock-file entry for '{0}'")] + CorruptedEntry(String, ParseLockFileError), + + #[error("there are more pypi packages in the lock-file than are used by the environment: {}", .0.iter().format(", ") + )] + TooManyPypiPackages(Vec), + + #[error("there are PyPi dependencies but a python interpreter is missing from the lock-file")] + MissingPythonInterpreter, + + #[error( + "a marker environment could not be derived from the python interpreter in the lock-file" + )] + FailedToDetermineMarkerEnvironment(#[source] Box), + + #[error( + "'{0}' requires python version {1} but the python interpreter in the lock-file has version {2}" + )] + PythonVersionMismatch( + pep508_rs::PackageName, + VersionSpecifiers, + Box, + ), + + #[error("when converting {0} into a pep508 requirement")] + AsPep508Error(pep508_rs::PackageName, #[source] AsPep508Error), + + #[error("editable pypi dependency on conda resolved package '{0}' is not supported")] + EditableDependencyOnCondaInstalledPackage( + uv_normalize::PackageName, + Box, + ), + + #[error("direct pypi url dependency to a conda installed package '{0}' is not supported")] + DirectUrlDependencyOnCondaInstalledPackage(uv_normalize::PackageName), + + #[error("git dependency on a conda installed package '{0}' is not supported")] + GitDependencyOnCondaInstalledPackage(uv_normalize::PackageName), + + #[error("path dependency on a conda installed package '{0}' is not supported")] + PathDependencyOnCondaInstalledPackage(uv_normalize::PackageName), + + #[error("directory dependency on a conda installed package '{0}' is not supported")] + DirectoryDependencyOnCondaInstalledPackage(uv_normalize::PackageName), + + #[error(transparent)] + EditablePackageMismatch(EditablePackagesMismatch), + + #[error( + "the editable package '{0}' was expected to be a directory but is a url, which cannot be editable: '{1}'" + )] + EditablePackageIsUrl(uv_normalize::PackageName, String), + + #[error("the editable package path '{0}', lock does not equal spec path '{1}' == '{2}'")] + EditablePackagePathMismatch(uv_normalize::PackageName, PathBuf, PathBuf), + + #[error("failed to determine pypi source tree hash for {0}")] + FailedToDetermineSourceTreeHash(pep508_rs::PackageName, std::io::Error), + + #[error("source tree hash for {0} does not match the hash in the lock-file")] + SourceTreeHashMismatch(pep508_rs::PackageName, #[source] SourceTreeHashMismatch), + + #[error("the path '{0}, cannot be canonicalized")] + FailedToCanonicalizePath(PathBuf, #[source] std::io::Error), + + #[error(transparent)] + FailedToComputeInputHash(#[from] GlobHashError), + + #[error("the input hash for '{0}' ({1}) does not match the hash in the lock-file ({2})")] + InputHashMismatch(String, String, String), + + #[error("expect pypi package name '{expected}' but found '{found}'")] + LockedPyPINamesMismatch { expected: String, found: String }, + + #[error( + "'{name}' with specifiers '{specifiers}' does not match the locked version '{version}' " + )] + LockedPyPIVersionsMismatch { + name: String, + specifiers: String, + version: String, + }, + + #[error("the direct url should start with `direct+` or `git+` but found '{0}'")] + LockedPyPIMalformedUrl(Url), + + #[error("the spec for '{0}' required a direct url but it was not locked as such")] + LockedPyPIRequiresDirectUrl(String), + + #[error("'{name}' has mismatching url: '{spec_url} != {lock_url}'")] + LockedPyPIDirectUrlMismatch { + name: String, + spec_url: String, + lock_url: String, + }, + + #[error("'{name}' has mismatching git url: '{spec_url} != {lock_url}'")] + LockedPyPIGitUrlMismatch { + name: String, + spec_url: String, + lock_url: String, + }, + + #[error( + "'{name}' has mismatching git subdirectory: '{spec_subdirectory} != {lock_subdirectory}'" + )] + LockedPyPIGitSubdirectoryMismatch { + name: String, + spec_subdirectory: String, + lock_subdirectory: String, + }, + + #[error("'{name}' has mismatching git ref: '{expected_ref} != {found_ref}'")] + LockedPyPIGitRefMismatch { + name: String, + expected_ref: String, + found_ref: String, + }, + + #[error("'{0}' expected a git url but the lock file has: '{1}'")] + LockedPyPIRequiresGitUrl(String, String), + + #[error("'{0}' expected a path but the lock file has a url")] + LockedPyPIRequiresPath(String), + + #[error( + "'{name}' absolute required path is {install_path} but currently locked at {locked_path}" + )] + LockedPyPIPathMismatch { + name: String, + install_path: String, + locked_path: String, + }, + + #[error("failed to convert between pep508 and uv types: {0}")] + UvTypesConversionError(#[from] ConversionError), + + #[error(transparent)] + #[diagnostic(transparent)] + BackendDiscovery(#[from] pixi_build_discovery::DiscoveryError), + + #[error("'{name}' is locked as a conda package but only requested by pypi dependencies")] + CondaPackageShouldBePypi { name: String }, +} + +impl PlatformUnsat { + /// Returns true if this is a problem with pypi packages only. This means + /// the conda packages are still considered valid. + pub fn is_pypi_only(&self) -> bool { + matches!( + self, + PlatformUnsat::UnsatisfiableRequirement(_, _) + | PlatformUnsat::TooManyPypiPackages(_) + | PlatformUnsat::AsPep508Error(_, _) + | PlatformUnsat::FailedToDetermineSourceTreeHash(_, _) + | PlatformUnsat::PythonVersionMismatch(_, _, _) + | PlatformUnsat::EditablePackageMismatch(_) + | PlatformUnsat::SourceTreeHashMismatch(..), + ) + } +} diff --git a/crates/pixi_lockfile/src/satisfiability/source_tree_hash_mismatch.rs b/crates/pixi_lockfile/src/satisfiability/source_tree_hash_mismatch.rs new file mode 100644 index 0000000000..89dbab7703 --- /dev/null +++ b/crates/pixi_lockfile/src/satisfiability/source_tree_hash_mismatch.rs @@ -0,0 +1,46 @@ +use std::fmt::{Display, Formatter}; + +use rattler_lock::PackageHashes; +use thiserror::Error; + +#[derive(Debug, Error)] +pub struct SourceTreeHashMismatch { + pub computed: PackageHashes, + pub locked: Option, +} + +impl Display for SourceTreeHashMismatch { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let computed_hash = self + .computed + .sha256() + .map(|hash| format!("{:x}", hash)) + .or(self.computed.md5().map(|hash| format!("{:x}", hash))); + let locked_hash = self.locked.as_ref().and_then(|hash| { + hash.sha256() + .map(|hash| format!("{:x}", hash)) + .or(hash.md5().map(|hash| format!("{:x}", hash))) + }); + + match (computed_hash, locked_hash) { + (None, None) => write!(f, "could not compute a source tree hash"), + (Some(computed), None) => { + write!( + f, + "the computed source tree hash is '{}', but the lock-file does not contain a hash", + computed + ) + } + (Some(computed), Some(locked)) => write!( + f, + "the computed source tree hash is '{}', but the lock-file contains '{}'", + computed, locked + ), + (None, Some(locked)) => write!( + f, + "could not compute a source tree hash, but the lock-file contains '{}'", + locked + ), + } + } +} diff --git a/src/lock_file/mod.rs b/src/lock_file/mod.rs index 6df10ad07f..b4b7d6a3aa 100644 --- a/src/lock_file/mod.rs +++ b/src/lock_file/mod.rs @@ -1,6 +1,4 @@ mod outdated; -mod package_identifier; -mod records_by_name; mod reporter; mod resolve; mod satisfiability; @@ -8,14 +6,9 @@ mod update; mod utils; pub use crate::environment::CondaPrefixUpdater; -pub(crate) use package_identifier::PypiPackageIdentifier; -use pixi_record::PixiRecord; -use rattler_lock::{PypiPackageData, PypiPackageEnvironmentData}; -pub(crate) use records_by_name::{PixiRecordsByName, PypiRecordsByName}; pub(crate) use resolve::pypi::resolve_pypi; pub use satisfiability::{ - EnvironmentUnsat, PlatformUnsat, verify_environment_satisfiability, - verify_platform_satisfiability, + EnvironmentUnsat, verify_environment_satisfiability, verify_platform_satisfiability, }; pub use update::{LockFileDerivedData, ReinstallPackages, UpdateContext}; pub use update::{UpdateLockFileOptions, UpdateMode}; @@ -23,16 +16,6 @@ pub(crate) use utils::filter_lock_file; pub use utils::IoConcurrencyLimit; -/// A list of conda packages that are locked for a specific platform. -pub type LockedCondaPackages = Vec; - -/// A list of Pypi packages that are locked for a specific platform. -pub type LockedPypiPackages = Vec; - -/// A single Pypi record that contains both the package data and the environment -/// data. In Pixi we basically always need both. -pub type PypiRecord = (PypiPackageData, PypiPackageEnvironmentData); - #[cfg(test)] mod tests { use crate::Workspace; diff --git a/src/lock_file/resolve/pypi.rs b/src/lock_file/resolve/pypi.rs index 368aeae990..ebb122e2d3 100644 --- a/src/lock_file/resolve/pypi.rs +++ b/src/lock_file/resolve/pypi.rs @@ -15,7 +15,10 @@ use indicatif::ProgressBar; use itertools::{Either, Itertools}; use miette::{Context, IntoDiagnostic}; use pixi_consts::consts; -use pixi_lockfile::UvResolutionContext; +use pixi_lockfile::{ + HasNameVersion, LockedPypiPackages, PixiRecordsByName, PypiPackageIdentifier, PypiRecord, + UvResolutionContext, +}; use pixi_manifest::{EnvironmentName, SystemRequirements, pypi::pypi_options::PypiOptions}; use pixi_pypi_spec::PixiPypiSpec; use pixi_record::PixiRecord; @@ -55,9 +58,7 @@ use uv_types::EmptyInstalledPackages; use crate::{ environment::CondaPrefixUpdated, lock_file::{ - CondaPrefixUpdater, LockedPypiPackages, PixiRecordsByName, PypiPackageIdentifier, - PypiRecord, - records_by_name::HasNameVersion, + CondaPrefixUpdater, resolve::{ build_dispatch::{ LazyBuildDispatch, LazyBuildDispatchDependencies, UvBuildDispatchParams, diff --git a/src/lock_file/resolve/resolver_provider.rs b/src/lock_file/resolve/resolver_provider.rs index be9b3f62ce..8c6f671b6d 100644 --- a/src/lock_file/resolve/resolver_provider.rs +++ b/src/lock_file/resolve/resolver_provider.rs @@ -9,6 +9,7 @@ use std::{ use futures::{Future, FutureExt}; use pixi_consts::consts; +use pixi_lockfile::PypiPackageIdentifier; use pixi_record::PixiRecord; use uv_distribution::{ArchiveMetadata, Metadata}; use uv_distribution_filename::SourceDistExtension; @@ -23,8 +24,6 @@ use uv_resolver::{ }; use uv_types::BuildContext; -use crate::lock_file::PypiPackageIdentifier; - pub(super) struct CondaResolverProvider<'a, Context: BuildContext> { pub(super) fallback: DefaultResolverProvider<'a, Context>, pub(super) conda_python_identifiers: diff --git a/src/lock_file/satisfiability/mod.rs b/src/lock_file/satisfiability/mod.rs index f16ef01453..12f39585b7 100644 --- a/src/lock_file/satisfiability/mod.rs +++ b/src/lock_file/satisfiability/mod.rs @@ -4,23 +4,26 @@ use std::{ fmt::{Display, Formatter}, hash::Hash, ops::Sub, - path::{Path, PathBuf}, + path::Path, str::FromStr, }; use itertools::{Either, Itertools}; use miette::Diagnostic; -use pep440_rs::VersionSpecifiers; use pixi_build_discovery::{DiscoveredBackend, EnabledProtocols}; use pixi_build_type_conversions::compute_project_model_hash; use pixi_git::url::RepositoryUrl; -use pixi_glob::{GlobHashCache, GlobHashError, GlobHashKey}; +use pixi_glob::{GlobHashCache, GlobHashKey}; +use pixi_lockfile::satisfiability::{ + EditablePackagesMismatch, PlatformUnsat, SourceTreeHashMismatch, +}; +use pixi_lockfile::{ConversionError, PixiRecordsByName, PypiRecord, PypiRecordsByName}; use pixi_manifest::{FeaturesExt, pypi::pypi_options::NoBuild}; -use pixi_record::{LockedGitUrl, ParseLockFileError, PixiRecord, SourceMismatchError}; +use pixi_record::{LockedGitUrl, PixiRecord}; use pixi_spec::{PixiSpec, SourceAnchor, SourceSpec, SpecConversionError}; use pixi_uv_conversions::{ - AsPep508Error, as_uv_req, into_pixi_reference, pep508_requirement_to_uv_requirement, - to_normalize, to_uv_specifiers, to_uv_version, + as_uv_req, into_pixi_reference, pep508_requirement_to_uv_requirement, to_normalize, + to_uv_specifiers, to_uv_version, }; use pypi_modifiers::pypi_marker_env::determine_marker_environment; use rattler_conda_types::{ @@ -28,8 +31,7 @@ use rattler_conda_types::{ ParseMatchSpecError, ParseStrictness::Lenient, Platform, }; use rattler_lock::{ - LockedPackageRef, PackageHashes, PypiIndexes, PypiPackageData, PypiSourceTreeHashable, - UrlOrPath, + LockedPackageRef, PypiIndexes, PypiPackageData, PypiSourceTreeHashable, UrlOrPath, }; use thiserror::Error; use typed_path::Utf8TypedPathBuf; @@ -38,11 +40,7 @@ use uv_distribution_filename::{DistExtension, ExtensionError, SourceDistExtensio use uv_distribution_types::RequirementSource; use uv_distribution_types::RequiresPython; use uv_git_types::GitReference; -use uv_pypi_types::ParsedUrlError; -use super::{ - PixiRecordsByName, PypiRecord, PypiRecordsByName, package_identifier::ConversionError, -}; use crate::workspace::{Environment, grouped_environment::GroupedEnvironment}; #[derive(Debug, Error, Diagnostic)] @@ -164,260 +162,12 @@ impl Display for IndexesMismatch { } } -#[derive(Debug, Error)] -pub struct EditablePackagesMismatch { - pub expected_editable: Vec, - pub unexpected_editable: Vec, -} - -#[derive(Debug, Error)] -pub struct SourceTreeHashMismatch { - pub computed: PackageHashes, - pub locked: Option, -} - -impl Display for SourceTreeHashMismatch { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let computed_hash = self - .computed - .sha256() - .map(|hash| format!("{:x}", hash)) - .or(self.computed.md5().map(|hash| format!("{:x}", hash))); - let locked_hash = self.locked.as_ref().and_then(|hash| { - hash.sha256() - .map(|hash| format!("{:x}", hash)) - .or(hash.md5().map(|hash| format!("{:x}", hash))) - }); - - match (computed_hash, locked_hash) { - (None, None) => write!(f, "could not compute a source tree hash"), - (Some(computed), None) => { - write!( - f, - "the computed source tree hash is '{}', but the lock-file does not contain a hash", - computed - ) - } - (Some(computed), Some(locked)) => write!( - f, - "the computed source tree hash is '{}', but the lock-file contains '{}'", - computed, locked - ), - (None, Some(locked)) => write!( - f, - "could not compute a source tree hash, but the lock-file contains '{}'", - locked - ), - } - } -} - -#[derive(Debug, Error, Diagnostic)] -pub enum PlatformUnsat { - #[error("the requirement '{0}' could not be satisfied (required by '{1}')")] - UnsatisfiableMatchSpec(Box, String), - - #[error("no package named exists '{0}' (required by '{1}')")] - SourcePackageMissing(String, String), - - #[error("required source package '{0}' is locked as binary (required by '{1}')")] - RequiredSourceIsBinary(String, String), - - #[error("package '{0}' is locked as source, but is only required as binary")] - RequiredBinaryIsSource(String), - - #[error("the locked source package '{0}' does not match the requested source package, {1}")] - SourcePackageMismatch(String, SourceMismatchError), - - #[error("failed to convert the requirement for '{0}'")] - FailedToConvertRequirement(pep508_rs::PackageName, #[source] Box), - - #[error("the requirement '{0}' could not be satisfied (required by '{1}')")] - UnsatisfiableRequirement(Box, String), - - #[error("the conda package does not satisfy the pypi requirement '{0}' (required by '{1}')")] - CondaUnsatisfiableRequirement(Box, String), - - #[error("there was a duplicate entry for '{0}'")] - DuplicateEntry(String), - - #[error("the requirement '{0}' failed to parse")] - FailedToParseMatchSpec(String, #[source] ParseMatchSpecError), - - #[error("there are more conda packages in the lock-file than are used by the environment: {}", .0.iter().map(rattler_conda_types::PackageName::as_source).format(", "))] - TooManyCondaPackages(Vec), - - #[error("missing purls")] - MissingPurls, - - #[error("corrupted lock-file entry for '{0}'")] - CorruptedEntry(String, ParseLockFileError), - - #[error("there are more pypi packages in the lock-file than are used by the environment: {}", .0.iter().format(", ") - )] - TooManyPypiPackages(Vec), - - #[error("there are PyPi dependencies but a python interpreter is missing from the lock-file")] - MissingPythonInterpreter, - - #[error( - "a marker environment could not be derived from the python interpreter in the lock-file" - )] - FailedToDetermineMarkerEnvironment(#[source] Box), - - #[error( - "'{0}' requires python version {1} but the python interpreter in the lock-file has version {2}" - )] - PythonVersionMismatch( - pep508_rs::PackageName, - VersionSpecifiers, - Box, - ), - - #[error("when converting {0} into a pep508 requirement")] - AsPep508Error(pep508_rs::PackageName, #[source] AsPep508Error), - - #[error("editable pypi dependency on conda resolved package '{0}' is not supported")] - EditableDependencyOnCondaInstalledPackage( - uv_normalize::PackageName, - Box, - ), - - #[error("direct pypi url dependency to a conda installed package '{0}' is not supported")] - DirectUrlDependencyOnCondaInstalledPackage(uv_normalize::PackageName), - - #[error("git dependency on a conda installed package '{0}' is not supported")] - GitDependencyOnCondaInstalledPackage(uv_normalize::PackageName), - - #[error("path dependency on a conda installed package '{0}' is not supported")] - PathDependencyOnCondaInstalledPackage(uv_normalize::PackageName), - - #[error("directory dependency on a conda installed package '{0}' is not supported")] - DirectoryDependencyOnCondaInstalledPackage(uv_normalize::PackageName), - - #[error(transparent)] - EditablePackageMismatch(EditablePackagesMismatch), - - #[error( - "the editable package '{0}' was expected to be a directory but is a url, which cannot be editable: '{1}'" - )] - EditablePackageIsUrl(uv_normalize::PackageName, String), - - #[error("the editable package path '{0}', lock does not equal spec path '{1}' == '{2}'")] - EditablePackagePathMismatch(uv_normalize::PackageName, PathBuf, PathBuf), - - #[error("failed to determine pypi source tree hash for {0}")] - FailedToDetermineSourceTreeHash(pep508_rs::PackageName, std::io::Error), - - #[error("source tree hash for {0} does not match the hash in the lock-file")] - SourceTreeHashMismatch(pep508_rs::PackageName, #[source] SourceTreeHashMismatch), - - #[error("the path '{0}, cannot be canonicalized")] - FailedToCanonicalizePath(PathBuf, #[source] std::io::Error), - - #[error(transparent)] - FailedToComputeInputHash(#[from] GlobHashError), - - #[error("the input hash for '{0}' ({1}) does not match the hash in the lock-file ({2})")] - InputHashMismatch(String, String, String), - - #[error("expect pypi package name '{expected}' but found '{found}'")] - LockedPyPINamesMismatch { expected: String, found: String }, - - #[error( - "'{name}' with specifiers '{specifiers}' does not match the locked version '{version}' " - )] - LockedPyPIVersionsMismatch { - name: String, - specifiers: String, - version: String, - }, - - #[error("the direct url should start with `direct+` or `git+` but found '{0}'")] - LockedPyPIMalformedUrl(Url), - - #[error("the spec for '{0}' required a direct url but it was not locked as such")] - LockedPyPIRequiresDirectUrl(String), - - #[error("'{name}' has mismatching url: '{spec_url} != {lock_url}'")] - LockedPyPIDirectUrlMismatch { - name: String, - spec_url: String, - lock_url: String, - }, - - #[error("'{name}' has mismatching git url: '{spec_url} != {lock_url}'")] - LockedPyPIGitUrlMismatch { - name: String, - spec_url: String, - lock_url: String, - }, - - #[error( - "'{name}' has mismatching git subdirectory: '{spec_subdirectory} != {lock_subdirectory}'" - )] - LockedPyPIGitSubdirectoryMismatch { - name: String, - spec_subdirectory: String, - lock_subdirectory: String, - }, - - #[error("'{name}' has mismatching git ref: '{expected_ref} != {found_ref}'")] - LockedPyPIGitRefMismatch { - name: String, - expected_ref: String, - found_ref: String, - }, - - #[error("'{0}' expected a git url but the lock file has: '{1}'")] - LockedPyPIRequiresGitUrl(String, String), - - #[error("'{0}' expected a path but the lock file has a url")] - LockedPyPIRequiresPath(String), - - #[error( - "'{name}' absolute required path is {install_path} but currently locked at {locked_path}" - )] - LockedPyPIPathMismatch { - name: String, - install_path: String, - locked_path: String, - }, - - #[error("failed to convert between pep508 and uv types: {0}")] - UvTypesConversionError(#[from] ConversionError), - - #[error(transparent)] - #[diagnostic(transparent)] - BackendDiscovery(#[from] pixi_build_discovery::DiscoveryError), - - #[error("'{name}' is locked as a conda package but only requested by pypi dependencies")] - CondaPackageShouldBePypi { name: String }, -} - #[derive(Debug, Error, Diagnostic)] pub enum SolveGroupUnsat { #[error("'{name}' is locked as a conda package but only requested by pypi dependencies")] CondaPackageShouldBePypi { name: String }, } -impl PlatformUnsat { - /// Returns true if this is a problem with pypi packages only. This means - /// the conda packages are still considered valid. - pub(crate) fn is_pypi_only(&self) -> bool { - matches!( - self, - PlatformUnsat::UnsatisfiableRequirement(_, _) - | PlatformUnsat::TooManyPypiPackages(_) - | PlatformUnsat::AsPep508Error(_, _) - | PlatformUnsat::FailedToDetermineSourceTreeHash(_, _) - | PlatformUnsat::PythonVersionMismatch(_, _, _) - | PlatformUnsat::EditablePackageMismatch(_) - | PlatformUnsat::SourceTreeHashMismatch(..), - ) - } -} - /// Verifies that all the requirements of the specified `environment` can be /// satisfied with the packages present in the lock-file. /// @@ -1738,72 +1488,6 @@ pub fn verify_solve_group_satisfiability( Ok(()) } -impl Display for EditablePackagesMismatch { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if !self.expected_editable.is_empty() && self.unexpected_editable.is_empty() { - write!(f, "expected ")?; - format_package_list(f, &self.expected_editable)?; - write!( - f, - " to be editable but in the lock-file {they} {are} not", - they = it_they(self.expected_editable.len()), - are = is_are(self.expected_editable.len()) - )? - } else if self.expected_editable.is_empty() && !self.unexpected_editable.is_empty() { - write!(f, "expected ")?; - format_package_list(f, &self.unexpected_editable)?; - write!( - f, - "NOT to be editable but in the lock-file {they} {are}", - they = it_they(self.unexpected_editable.len()), - are = is_are(self.unexpected_editable.len()) - )? - } else { - write!(f, "expected ")?; - format_package_list(f, &self.expected_editable)?; - write!( - f, - " to be editable but in the lock-file but {they} {are} not, whereas ", - they = it_they(self.expected_editable.len()), - are = is_are(self.expected_editable.len()) - )?; - format_package_list(f, &self.unexpected_editable)?; - write!( - f, - " {are} NOT expected to be editable which in the lock-file {they} {are}", - they = it_they(self.unexpected_editable.len()), - are = is_are(self.unexpected_editable.len()) - )? - } - - return Ok(()); - - fn format_package_list( - f: &mut std::fmt::Formatter<'_>, - packages: &[uv_normalize::PackageName], - ) -> std::fmt::Result { - for (idx, package) in packages.iter().enumerate() { - if idx == packages.len() - 1 && idx > 0 { - write!(f, " and ")?; - } else if idx > 0 { - write!(f, ", ")?; - } - write!(f, "{}", package)?; - } - - Ok(()) - } - - fn is_are(count: usize) -> &'static str { - if count == 1 { "is" } else { "are" } - } - - fn it_they(count: usize) -> &'static str { - if count == 1 { "it" } else { "they" } - } - } -} - #[cfg(test)] mod tests { use std::{ @@ -1814,6 +1498,7 @@ mod tests { use insta::Settings; use miette::{IntoDiagnostic, NarratableReportHandler}; + use pep440_rs::VersionSpecifiers; use pep440_rs::{Operator, Version}; use rattler_lock::LockFile; use rstest::rstest; diff --git a/src/lock_file/update.rs b/src/lock_file/update.rs index 028d7fbd96..b28b1c8761 100644 --- a/src/lock_file/update.rs +++ b/src/lock_file/update.rs @@ -1,7 +1,4 @@ -use super::{ - CondaPrefixUpdater, PixiRecordsByName, PypiRecordsByName, outdated::OutdatedEnvironments, - utils::IoConcurrencyLimit, -}; +use super::{CondaPrefixUpdater, outdated::OutdatedEnvironments, utils::IoConcurrencyLimit}; use crate::{ Workspace, activation::CurrentEnvVarBehavior, @@ -10,7 +7,7 @@ use crate::{ PerEnvironmentAndPlatform, PerGroup, PerGroupAndPlatform, read_environment_file, write_environment_file, }, - lock_file::{self, PypiRecord, reporter::SolveProgressBar}, + lock_file::{self, reporter::SolveProgressBar}, workspace::{ Environment, EnvironmentVars, HasWorkspaceRef, get_activated_environment_variables, grouped_environment::{GroupedEnvironment, GroupedEnvironmentName}, @@ -31,8 +28,8 @@ use pixi_glob::GlobHashCache; use pixi_install_pypi::{ PyPIBuildConfig, PyPIContextConfig, PyPIEnvironmentUpdater, PyPIUpdateConfig, }; -use pixi_lockfile::UvResolutionContext; use pixi_lockfile::virtual_packages::validate_system_meets_environment_requirements; +use pixi_lockfile::{PixiRecordsByName, PypiRecord, PypiRecordsByName, UvResolutionContext}; use pixi_manifest::{ChannelPriority, EnvironmentName, FeaturesExt}; use pixi_progress::global_multi_progress; use pixi_record::{ParseLockFileError, PixiRecord}; From 72686ed1130976ac0b2c8a19ce82d2ebb522e342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20H=C3=A4cker?= Date: Mon, 11 Aug 2025 15:58:12 +0200 Subject: [PATCH 11/13] refactor: Move resolver_provider into pixi_lockfile crate --- Cargo.lock | 7 +++++-- Cargo.toml | 1 - crates/pixi_lockfile/Cargo.toml | 6 +++++- crates/pixi_lockfile/src/lib.rs | 4 +++- crates/pixi_lockfile/src/resolve/mod.rs | 1 + .../pixi_lockfile/src}/resolve/resolver_provider.rs | 10 +++++----- src/lock_file/resolve/mod.rs | 1 - src/lock_file/resolve/pypi.rs | 11 ++++------- 8 files changed, 23 insertions(+), 18 deletions(-) rename {src/lock_file => crates/pixi_lockfile/src}/resolve/resolver_provider.rs (95%) diff --git a/Cargo.lock b/Cargo.lock index 9ffcf6a3bf..2f43d24b91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4785,7 +4785,6 @@ dependencies = [ "uv-pep508", "uv-pypi-types", "uv-python", - "uv-redacted", "uv-requirements", "uv-requirements-txt", "uv-resolver", @@ -5108,6 +5107,7 @@ version = "0.1.0" dependencies = [ "fancy_display", "fs-err", + "futures", "insta", "itertools 0.14.0", "miette 7.6.0", @@ -5120,7 +5120,6 @@ dependencies = [ "pixi_manifest", "pixi_pypi_spec", "pixi_record", - "pixi_spec", "pixi_test_utils", "pixi_utils", "pixi_uv_conversions", @@ -5137,11 +5136,15 @@ dependencies = [ "uv-client", "uv-configuration", "uv-dispatch", + "uv-distribution", "uv-distribution-filename", "uv-distribution-types", "uv-normalize", + "uv-pep440", "uv-pep508", "uv-pypi-types", + "uv-redacted", + "uv-resolver", "uv-types", "uv-workspace", ] diff --git a/Cargo.toml b/Cargo.toml index dcd062f47b..cadcb5be4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -362,7 +362,6 @@ uv-git-types = { workspace = true } uv-installer = { workspace = true } uv-normalize = { workspace = true } uv-python = { workspace = true } -uv-redacted = { workspace = true } uv-requirements = { workspace = true } uv-requirements-txt = { workspace = true } uv-resolver = { workspace = true } diff --git a/crates/pixi_lockfile/Cargo.toml b/crates/pixi_lockfile/Cargo.toml index 9c188299be..7b01f0839f 100644 --- a/crates/pixi_lockfile/Cargo.toml +++ b/crates/pixi_lockfile/Cargo.toml @@ -11,6 +11,7 @@ version = "0.1.0" [dependencies] fancy_display = { workspace = true } fs-err = { workspace = true } +futures = { workspace = true } insta = { workspace = true } itertools = { workspace = true } miette = { workspace = true } @@ -23,7 +24,6 @@ pixi_glob = { workspace = true } pixi_manifest = { workspace = true } pixi_pypi_spec = { workspace = true } pixi_record = { workspace = true } -pixi_spec = { workspace = true } pixi_test_utils = { workspace = true } pixi_utils = { workspace = true, default-features = false } pixi_uv_conversions = { workspace = true } @@ -40,10 +40,14 @@ uv-cache = { workspace = true } uv-client = { workspace = true } uv-configuration = { workspace = true } uv-dispatch = { workspace = true } +uv-distribution = { workspace = true } uv-distribution-filename = { workspace = true } uv-distribution-types = { workspace = true } uv-normalize = { workspace = true } +uv-pep440 = { workspace = true } uv-pep508 = { workspace = true } uv-pypi-types = { workspace = true } +uv-redacted = { workspace = true } +uv-resolver = { workspace = true } uv-types = { workspace = true } uv-workspace = { workspace = true } diff --git a/crates/pixi_lockfile/src/lib.rs b/crates/pixi_lockfile/src/lib.rs index bf84a11681..81393afb32 100644 --- a/crates/pixi_lockfile/src/lib.rs +++ b/crates/pixi_lockfile/src/lib.rs @@ -10,7 +10,9 @@ pub mod virtual_packages; pub use package_identifier::{ConversionError, PypiPackageIdentifier}; pub use records_by_name::{HasNameVersion, PixiRecordsByName, PypiRecordsByName}; -pub use resolve::uv_resolution_context::UvResolutionContext; +pub use resolve::{ + resolver_provider::CondaResolverProvider, uv_resolution_context::UvResolutionContext, +}; /// A list of conda packages that are locked for a specific platform. pub type LockedCondaPackages = Vec; diff --git a/crates/pixi_lockfile/src/resolve/mod.rs b/crates/pixi_lockfile/src/resolve/mod.rs index 3468b7b4a3..3d22fd77f0 100644 --- a/crates/pixi_lockfile/src/resolve/mod.rs +++ b/crates/pixi_lockfile/src/resolve/mod.rs @@ -2,4 +2,5 @@ //! //! See [`resolve_pypi`] and [`resolve_conda`] for more information. +pub(crate) mod resolver_provider; pub(crate) mod uv_resolution_context; diff --git a/src/lock_file/resolve/resolver_provider.rs b/crates/pixi_lockfile/src/resolve/resolver_provider.rs similarity index 95% rename from src/lock_file/resolve/resolver_provider.rs rename to crates/pixi_lockfile/src/resolve/resolver_provider.rs index 8c6f671b6d..442f4d22e5 100644 --- a/src/lock_file/resolve/resolver_provider.rs +++ b/crates/pixi_lockfile/src/resolve/resolver_provider.rs @@ -7,9 +7,9 @@ use std::{ sync::Arc, }; +use crate::PypiPackageIdentifier; use futures::{Future, FutureExt}; use pixi_consts::consts; -use pixi_lockfile::PypiPackageIdentifier; use pixi_record::PixiRecord; use uv_distribution::{ArchiveMetadata, Metadata}; use uv_distribution_filename::SourceDistExtension; @@ -24,13 +24,13 @@ use uv_resolver::{ }; use uv_types::BuildContext; -pub(super) struct CondaResolverProvider<'a, Context: BuildContext> { - pub(super) fallback: DefaultResolverProvider<'a, Context>, - pub(super) conda_python_identifiers: +pub struct CondaResolverProvider<'a, Context: BuildContext> { + pub fallback: DefaultResolverProvider<'a, Context>, + pub conda_python_identifiers: &'a HashMap, /// Saves the number of requests by the uv solver per package - pub(super) package_requests: Rc>>, + pub package_requests: Rc>>, } impl ResolverProvider for CondaResolverProvider<'_, Context> { diff --git a/src/lock_file/resolve/mod.rs b/src/lock_file/resolve/mod.rs index 3f1472a5f7..a09005b2be 100644 --- a/src/lock_file/resolve/mod.rs +++ b/src/lock_file/resolve/mod.rs @@ -4,4 +4,3 @@ pub(crate) mod build_dispatch; pub(crate) mod pypi; -mod resolver_provider; diff --git a/src/lock_file/resolve/pypi.rs b/src/lock_file/resolve/pypi.rs index ebb122e2d3..cd6e214864 100644 --- a/src/lock_file/resolve/pypi.rs +++ b/src/lock_file/resolve/pypi.rs @@ -16,8 +16,8 @@ use itertools::{Either, Itertools}; use miette::{Context, IntoDiagnostic}; use pixi_consts::consts; use pixi_lockfile::{ - HasNameVersion, LockedPypiPackages, PixiRecordsByName, PypiPackageIdentifier, PypiRecord, - UvResolutionContext, + CondaResolverProvider, HasNameVersion, LockedPypiPackages, PixiRecordsByName, + PypiPackageIdentifier, PypiRecord, UvResolutionContext, }; use pixi_manifest::{EnvironmentName, SystemRequirements, pypi::pypi_options::PypiOptions}; use pixi_pypi_spec::PixiPypiSpec; @@ -59,11 +59,8 @@ use crate::{ environment::CondaPrefixUpdated, lock_file::{ CondaPrefixUpdater, - resolve::{ - build_dispatch::{ - LazyBuildDispatch, LazyBuildDispatchDependencies, UvBuildDispatchParams, - }, - resolver_provider::CondaResolverProvider, + resolve::build_dispatch::{ + LazyBuildDispatch, LazyBuildDispatchDependencies, UvBuildDispatchParams, }, }, workspace::{Environment, EnvironmentVars}, From 53b5dcd200e075bc8d3b16433014eb4c3a9577a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20H=C3=A4cker?= Date: Mon, 11 Aug 2025 16:32:25 +0200 Subject: [PATCH 12/13] refactor: Move EnvironmentUnsat, ExcludeNewerMismatch, IndexesMismatch into pixi_lockfile crate --- Cargo.lock | 2 + crates/pixi_lockfile/Cargo.toml | 2 + .../src/satisfiability/environment_unsat.rs | 71 ++++++++++ .../satisfiability/exclude_newer_mismatch.rs | 35 +++++ .../src/satisfiability/indexes_mismatch.rs | 29 +++++ .../pixi_lockfile/src/satisfiability/mod.rs | 6 + src/lock_file/mod.rs | 4 +- src/lock_file/outdated.rs | 3 +- src/lock_file/satisfiability/mod.rs | 123 +----------------- 9 files changed, 150 insertions(+), 125 deletions(-) create mode 100644 crates/pixi_lockfile/src/satisfiability/environment_unsat.rs create mode 100644 crates/pixi_lockfile/src/satisfiability/exclude_newer_mismatch.rs create mode 100644 crates/pixi_lockfile/src/satisfiability/indexes_mismatch.rs diff --git a/Cargo.lock b/Cargo.lock index 2f43d24b91..f03054198f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5105,6 +5105,7 @@ dependencies = [ name = "pixi_lockfile" version = "0.1.0" dependencies = [ + "chrono", "fancy_display", "fs-err", "futures", @@ -5127,6 +5128,7 @@ dependencies = [ "pypi_modifiers", "rattler_conda_types", "rattler_lock", + "rattler_solve", "rattler_virtual_packages", "reqwest", "thiserror 2.0.12", diff --git a/crates/pixi_lockfile/Cargo.toml b/crates/pixi_lockfile/Cargo.toml index 7b01f0839f..f4cfca4767 100644 --- a/crates/pixi_lockfile/Cargo.toml +++ b/crates/pixi_lockfile/Cargo.toml @@ -9,6 +9,7 @@ repository.workspace = true version = "0.1.0" [dependencies] +chrono = { workspace = true } fancy_display = { workspace = true } fs-err = { workspace = true } futures = { workspace = true } @@ -31,6 +32,7 @@ pypi_mapping = { workspace = true } pypi_modifiers = { workspace = true } rattler_conda_types = { workspace = true } rattler_lock = { workspace = true } +rattler_solve = { workspace = true } rattler_virtual_packages = { workspace = true } reqwest = { workspace = true } thiserror = { workspace = true } diff --git a/crates/pixi_lockfile/src/satisfiability/environment_unsat.rs b/crates/pixi_lockfile/src/satisfiability/environment_unsat.rs new file mode 100644 index 0000000000..8f3754f695 --- /dev/null +++ b/crates/pixi_lockfile/src/satisfiability/environment_unsat.rs @@ -0,0 +1,71 @@ +use std::collections::HashSet; + +use itertools::Itertools; +use miette::Diagnostic; +use rattler_conda_types::{ParseChannelError, Platform}; +use thiserror::Error; +use uv_distribution_filename::ExtensionError; + +use crate::satisfiability::{ExcludeNewerMismatch, IndexesMismatch}; + +#[derive(Debug, Error, Diagnostic)] +pub enum EnvironmentUnsat { + #[error("the channels in the lock-file do not match the environments channels")] + ChannelsMismatch, + + #[error("platform(s) '{platforms}' present in the lock-file but not in the environment", platforms = .0.iter().map(|p| p.as_str()).join(", ") + )] + AdditionalPlatformsInLockFile(HashSet), + + #[error(transparent)] + IndexesMismatch(#[from] IndexesMismatch), + + #[error(transparent)] + InvalidChannel(#[from] ParseChannelError), + + #[error(transparent)] + InvalidDistExtensionInNoBuild(#[from] ExtensionError), + + #[error( + "the lock-file contains non-binary package: '{0}', but the pypi-option `no-build` is set" + )] + NoBuildWithNonBinaryPackages(String), + + #[error( + "the lock-file was solved with a different strategy ({locked_strategy}) than the one selected ({expected_strategy})", + locked_strategy = fmt_solve_strategy(*.locked_strategy), + expected_strategy = fmt_solve_strategy(*.expected_strategy), + )] + SolveStrategyMismatch { + locked_strategy: rattler_solve::SolveStrategy, + expected_strategy: rattler_solve::SolveStrategy, + }, + + #[error( + "the lock-file was solved with a different channel priority ({locked_priority}) than the one selected ({expected_priority})", + locked_priority = fmt_channel_priority(*.locked_priority), + expected_priority = fmt_channel_priority(*.expected_priority), + )] + ChannelPriorityMismatch { + locked_priority: rattler_solve::ChannelPriority, + expected_priority: rattler_solve::ChannelPriority, + }, + + #[error(transparent)] + ExcludeNewerMismatch(#[from] ExcludeNewerMismatch), +} + +fn fmt_solve_strategy(strategy: rattler_solve::SolveStrategy) -> &'static str { + match strategy { + rattler_solve::SolveStrategy::Highest => "highest", + rattler_solve::SolveStrategy::LowestVersion => "lowest-version", + rattler_solve::SolveStrategy::LowestVersionDirect => "lowest-version-direct", + } +} + +fn fmt_channel_priority(priority: rattler_solve::ChannelPriority) -> &'static str { + match priority { + rattler_solve::ChannelPriority::Strict => "strict", + rattler_solve::ChannelPriority::Disabled => "disabled", + } +} diff --git a/crates/pixi_lockfile/src/satisfiability/exclude_newer_mismatch.rs b/crates/pixi_lockfile/src/satisfiability/exclude_newer_mismatch.rs new file mode 100644 index 0000000000..1f0b911338 --- /dev/null +++ b/crates/pixi_lockfile/src/satisfiability/exclude_newer_mismatch.rs @@ -0,0 +1,35 @@ +use std::fmt::{Display, Formatter}; + +use thiserror::Error; + +#[derive(Debug, Error)] +pub struct ExcludeNewerMismatch { + pub locked_exclude_newer: Option>, + pub expected_exclude_newer: Option>, +} + +impl Display for ExcludeNewerMismatch { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match (self.locked_exclude_newer, self.expected_exclude_newer) { + (Some(locked), None) => { + write!( + f, + "the lock-file was solved with exclude-newer set to {locked}, but the environment does not have this option set" + ) + } + (None, Some(expected)) => { + write!( + f, + "the lock-file was solved without exclude-newer, but the environment has this option set to {expected}" + ) + } + (Some(locked), Some(expected)) if locked != expected => { + write!( + f, + "the lock-file was solved with exclude-newer set to {locked}, but the environment has this option set to {expected}" + ) + } + _ => unreachable!("if we get here the values are the same"), + } + } +} diff --git a/crates/pixi_lockfile/src/satisfiability/indexes_mismatch.rs b/crates/pixi_lockfile/src/satisfiability/indexes_mismatch.rs new file mode 100644 index 0000000000..6aa8fa5652 --- /dev/null +++ b/crates/pixi_lockfile/src/satisfiability/indexes_mismatch.rs @@ -0,0 +1,29 @@ +use std::fmt::Display; + +use rattler_lock::PypiIndexes; +use thiserror::Error; + +#[derive(Debug, Error)] +pub struct IndexesMismatch { + pub current: PypiIndexes, + pub previous: Option, +} + +impl Display for IndexesMismatch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(previous) = &self.previous { + write!( + f, + "the indexes used to previously solve to lock file do not match the environments indexes.\n \ + Expected: {expected:#?}\n Found: {found:#?}", + expected = previous, + found = self.current + ) + } else { + write!( + f, + "the indexes used to previously solve to lock file are missing" + ) + } + } +} diff --git a/crates/pixi_lockfile/src/satisfiability/mod.rs b/crates/pixi_lockfile/src/satisfiability/mod.rs index 2208489b4c..43f9cd7839 100644 --- a/crates/pixi_lockfile/src/satisfiability/mod.rs +++ b/crates/pixi_lockfile/src/satisfiability/mod.rs @@ -1,7 +1,13 @@ mod editable_packages_mismatch; +mod environment_unsat; +mod exclude_newer_mismatch; +mod indexes_mismatch; mod platform_unsat; mod source_tree_hash_mismatch; pub use editable_packages_mismatch::EditablePackagesMismatch; +pub use environment_unsat::EnvironmentUnsat; +pub use exclude_newer_mismatch::ExcludeNewerMismatch; +pub use indexes_mismatch::IndexesMismatch; pub use platform_unsat::PlatformUnsat; pub use source_tree_hash_mismatch::SourceTreeHashMismatch; diff --git a/src/lock_file/mod.rs b/src/lock_file/mod.rs index b4b7d6a3aa..5f90ccaa7a 100644 --- a/src/lock_file/mod.rs +++ b/src/lock_file/mod.rs @@ -7,9 +7,7 @@ mod utils; pub use crate::environment::CondaPrefixUpdater; pub(crate) use resolve::pypi::resolve_pypi; -pub use satisfiability::{ - EnvironmentUnsat, verify_environment_satisfiability, verify_platform_satisfiability, -}; +pub use satisfiability::{verify_environment_satisfiability, verify_platform_satisfiability}; pub use update::{LockFileDerivedData, ReinstallPackages, UpdateContext}; pub use update::{UpdateLockFileOptions, UpdateMode}; pub(crate) use utils::filter_lock_file; diff --git a/src/lock_file/outdated.rs b/src/lock_file/outdated.rs index fd2de51405..2f2cbcf221 100644 --- a/src/lock_file/outdated.rs +++ b/src/lock_file/outdated.rs @@ -3,13 +3,14 @@ use std::collections::{HashMap, HashSet}; use super::{verify_environment_satisfiability, verify_platform_satisfiability}; use crate::{ Workspace, - lock_file::satisfiability::{EnvironmentUnsat, verify_solve_group_satisfiability}, + lock_file::satisfiability::verify_solve_group_satisfiability, workspace::{Environment, SolveGroup}, }; use fancy_display::FancyDisplay; use itertools::Itertools; use pixi_consts::consts; use pixi_glob::GlobHashCache; +use pixi_lockfile::satisfiability::EnvironmentUnsat; use pixi_manifest::FeaturesExt; use rattler_conda_types::Platform; use rattler_lock::{LockFile, LockedPackageRef}; diff --git a/src/lock_file/satisfiability/mod.rs b/src/lock_file/satisfiability/mod.rs index 12f39585b7..34dd1ef3a6 100644 --- a/src/lock_file/satisfiability/mod.rs +++ b/src/lock_file/satisfiability/mod.rs @@ -1,7 +1,6 @@ use std::{ borrow::Cow, collections::{HashMap, HashSet}, - fmt::{Display, Formatter}, hash::Hash, ops::Sub, path::Path, @@ -15,7 +14,8 @@ use pixi_build_type_conversions::compute_project_model_hash; use pixi_git::url::RepositoryUrl; use pixi_glob::{GlobHashCache, GlobHashKey}; use pixi_lockfile::satisfiability::{ - EditablePackagesMismatch, PlatformUnsat, SourceTreeHashMismatch, + EditablePackagesMismatch, EnvironmentUnsat, ExcludeNewerMismatch, IndexesMismatch, + PlatformUnsat, SourceTreeHashMismatch, }; use pixi_lockfile::{ConversionError, PixiRecordsByName, PypiRecord, PypiRecordsByName}; use pixi_manifest::{FeaturesExt, pypi::pypi_options::NoBuild}; @@ -43,125 +43,6 @@ use uv_git_types::GitReference; use crate::workspace::{Environment, grouped_environment::GroupedEnvironment}; -#[derive(Debug, Error, Diagnostic)] -pub enum EnvironmentUnsat { - #[error("the channels in the lock-file do not match the environments channels")] - ChannelsMismatch, - - #[error("platform(s) '{platforms}' present in the lock-file but not in the environment", platforms = .0.iter().map(|p| p.as_str()).join(", ") - )] - AdditionalPlatformsInLockFile(HashSet), - - #[error(transparent)] - IndexesMismatch(#[from] IndexesMismatch), - - #[error(transparent)] - InvalidChannel(#[from] ParseChannelError), - - #[error(transparent)] - InvalidDistExtensionInNoBuild(#[from] ExtensionError), - - #[error( - "the lock-file contains non-binary package: '{0}', but the pypi-option `no-build` is set" - )] - NoBuildWithNonBinaryPackages(String), - - #[error( - "the lock-file was solved with a different strategy ({locked_strategy}) than the one selected ({expected_strategy})", - locked_strategy = fmt_solve_strategy(*.locked_strategy), - expected_strategy = fmt_solve_strategy(*.expected_strategy), - )] - SolveStrategyMismatch { - locked_strategy: rattler_solve::SolveStrategy, - expected_strategy: rattler_solve::SolveStrategy, - }, - - #[error( - "the lock-file was solved with a different channel priority ({locked_priority}) than the one selected ({expected_priority})", - locked_priority = fmt_channel_priority(*.locked_priority), - expected_priority = fmt_channel_priority(*.expected_priority), - )] - ChannelPriorityMismatch { - locked_priority: rattler_solve::ChannelPriority, - expected_priority: rattler_solve::ChannelPriority, - }, - - #[error(transparent)] - ExcludeNewerMismatch(#[from] ExcludeNewerMismatch), -} - -fn fmt_channel_priority(priority: rattler_solve::ChannelPriority) -> &'static str { - match priority { - rattler_solve::ChannelPriority::Strict => "strict", - rattler_solve::ChannelPriority::Disabled => "disabled", - } -} - -fn fmt_solve_strategy(strategy: rattler_solve::SolveStrategy) -> &'static str { - match strategy { - rattler_solve::SolveStrategy::Highest => "highest", - rattler_solve::SolveStrategy::LowestVersion => "lowest-version", - rattler_solve::SolveStrategy::LowestVersionDirect => "lowest-version-direct", - } -} - -#[derive(Debug, Error)] -pub struct ExcludeNewerMismatch { - locked_exclude_newer: Option>, - expected_exclude_newer: Option>, -} - -impl Display for ExcludeNewerMismatch { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match (self.locked_exclude_newer, self.expected_exclude_newer) { - (Some(locked), None) => { - write!( - f, - "the lock-file was solved with exclude-newer set to {locked}, but the environment does not have this option set" - ) - } - (None, Some(expected)) => { - write!( - f, - "the lock-file was solved without exclude-newer, but the environment has this option set to {expected}" - ) - } - (Some(locked), Some(expected)) if locked != expected => { - write!( - f, - "the lock-file was solved with exclude-newer set to {locked}, but the environment has this option set to {expected}" - ) - } - _ => unreachable!("if we get here the values are the same"), - } - } -} - -#[derive(Debug, Error)] -pub struct IndexesMismatch { - current: PypiIndexes, - previous: Option, -} - -impl Display for IndexesMismatch { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Some(previous) = &self.previous { - write!( - f, - "the indexes used to previously solve to lock file do not match the environments indexes.\n \ - Expected: {expected:#?}\n Found: {found:#?}", - expected = previous, - found = self.current - ) - } else { - write!( - f, - "the indexes used to previously solve to lock file are missing" - ) - } - } -} - #[derive(Debug, Error, Diagnostic)] pub enum SolveGroupUnsat { #[error("'{name}' is locked as a conda package but only requested by pypi dependencies")] From aed7f2ef6759060236c9afbf874c79bce4792d15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20H=C3=A4cker?= Date: Mon, 11 Aug 2025 16:48:22 +0200 Subject: [PATCH 13/13] refactor: Update tests for virtual package --- ...ages__test__virtual_package_not_found_error.snap | 2 +- crates/pixi_lockfile/src/virtual_packages.rs | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) rename src/lock_file/snapshots/pixi__lock_file__virtual_packages__test__virtual_package_not_found_error.snap => crates/pixi_lockfile/src/snapshots/pixi_lockfile__virtual_packages__test__virtual_package_not_found_error.snap (91%) diff --git a/src/lock_file/snapshots/pixi__lock_file__virtual_packages__test__virtual_package_not_found_error.snap b/crates/pixi_lockfile/src/snapshots/pixi_lockfile__virtual_packages__test__virtual_package_not_found_error.snap similarity index 91% rename from src/lock_file/snapshots/pixi__lock_file__virtual_packages__test__virtual_package_not_found_error.snap rename to crates/pixi_lockfile/src/snapshots/pixi_lockfile__virtual_packages__test__virtual_package_not_found_error.snap index 476d66d607..39da783cf3 100644 --- a/src/lock_file/snapshots/pixi__lock_file__virtual_packages__test__virtual_package_not_found_error.snap +++ b/crates/pixi_lockfile/src/snapshots/pixi_lockfile__virtual_packages__test__virtual_package_not_found_error.snap @@ -1,5 +1,5 @@ --- -source: src/lock_file/virtual_packages.rs +source: crates/pixi_lockfile/src/virtual_packages.rs expression: "format!(\"With override:\\n{}\\nWithout override:\\n{}\",\nformat_diagnostic(&error1), format_diagnostic(&error2))" --- With override: diff --git a/crates/pixi_lockfile/src/virtual_packages.rs b/crates/pixi_lockfile/src/virtual_packages.rs index 9e5dc87883..a11dae3b7f 100644 --- a/crates/pixi_lockfile/src/virtual_packages.rs +++ b/crates/pixi_lockfile/src/virtual_packages.rs @@ -300,7 +300,8 @@ mod test { #[test] fn test_get_minimal_virtual_packages() { let root_dir = Path::new(env!("CARGO_MANIFEST_DIR")); - let lockfile_path = root_dir.join("tests/data/lockfiles/cuda_virtual_dependency.lock"); + let lockfile_path = + root_dir.join("../../tests/data/lockfiles/cuda_virtual_dependency.lock"); let lockfile = LockFile::from_path(&lockfile_path).unwrap(); let platform = Platform::Linux64; let env = lockfile.default_environment().unwrap(); @@ -328,7 +329,8 @@ mod test { #[test] fn test_validate_virtual_packages() { let root_dir = Path::new(env!("CARGO_MANIFEST_DIR")); - let lockfile_path = root_dir.join("tests/data/lockfiles/cuda_virtual_dependency.lock"); + let lockfile_path = + root_dir.join("../../tests/data/lockfiles/cuda_virtual_dependency.lock"); let lockfile = LockFile::from_path(&lockfile_path).unwrap(); let platform = Platform::Linux64; @@ -364,7 +366,7 @@ mod test { #[test] fn test_validate_wheel_tags() { let root_dir = Path::new(env!("CARGO_MANIFEST_DIR")); - let lockfile_path = root_dir.join("tests/data/lockfiles/pypi-numpy.lock"); + let lockfile_path = root_dir.join("../../tests/data/lockfiles/pypi-numpy.lock"); let lockfile = LockFile::from_path(&lockfile_path).unwrap(); let platform = Platform::current(); @@ -466,7 +468,7 @@ mod test { #[test] fn test_archspec_skip() { let root_dir = Path::new(env!("CARGO_MANIFEST_DIR")); - let lockfile_path = root_dir.join("tests/data/lockfiles/archspec.lock"); + let lockfile_path = root_dir.join("../../tests/data/lockfiles/archspec.lock"); let lockfile = LockFile::from_path(&lockfile_path).unwrap(); let platform = Platform::Linux64; @@ -488,7 +490,8 @@ mod test { #[test] fn test_ignored_virtual_packages() { let root_dir = Path::new(env!("CARGO_MANIFEST_DIR")); - let lockfile_path = root_dir.join("tests/data/lockfiles/ignored_virtual_packages.lock"); + let lockfile_path = + root_dir.join("../../tests/data/lockfiles/ignored_virtual_packages.lock"); let lockfile = LockFile::from_path(&lockfile_path).unwrap(); let platform = Platform::Linux64;