diff --git a/Cargo.lock b/Cargo.lock index 96b9f23adf..f03054198f 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", @@ -4713,8 +4711,11 @@ dependencies = [ "pixi_config", "pixi_consts", "pixi_default_versions", + "pixi_environment", "pixi_git", "pixi_glob", + "pixi_install_pypi", + "pixi_lockfile", "pixi_manifest", "pixi_progress", "pixi_pty", @@ -4767,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", @@ -4786,7 +4785,6 @@ dependencies = [ "uv-pep508", "uv-pypi-types", "uv-python", - "uv-redacted", "uv-requirements", "uv-requirements-txt", "uv-resolver", @@ -4988,6 +4986,26 @@ dependencies = [ "rattler_conda_types", ] +[[package]] +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", +] + [[package]] name = "pixi_git" version = "0.0.1" @@ -5024,6 +5042,115 @@ dependencies = [ "wax", ] +[[package]] +name = "pixi_install_pypi" +version = "0.1.0" +dependencies = [ + "ahash", + "assert_matches", + "chrono", + "console 0.15.11", + "csv", + "fancy_display", + "fs-err", + "itertools 0.14.0", + "miette 7.6.0", + "pep440_rs", + "pep508_rs", + "percent-encoding", + "pixi_consts", + "pixi_environment", + "pixi_git", + "pixi_lockfile", + "pixi_manifest", + "pixi_progress", + "pixi_record", + "pixi_reporters", + "pixi_utils", + "pixi_uv_conversions", + "pypi_modifiers", + "rattler", + "rattler_conda_types", + "rattler_lock", + "serde", + "serde_json", + "tempfile", + "thiserror 2.0.12", + "tokio", + "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-install-wheel", + "uv-installer", + "uv-normalize", + "uv-pep440", + "uv-pep508", + "uv-pypi-types", + "uv-python", + "uv-redacted", + "uv-resolver", + "uv-types", +] + +[[package]] +name = "pixi_lockfile" +version = "0.1.0" +dependencies = [ + "chrono", + "fancy_display", + "fs-err", + "futures", + "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_test_utils", + "pixi_utils", + "pixi_uv_conversions", + "pypi_mapping", + "pypi_modifiers", + "rattler_conda_types", + "rattler_lock", + "rattler_solve", + "rattler_virtual_packages", + "reqwest", + "thiserror 2.0.12", + "tracing", + "url", + "uv-cache", + "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", +] + [[package]] name = "pixi_manifest" version = "0.1.0" @@ -5226,6 +5353,7 @@ dependencies = [ "fs-err", "indicatif", "insta", + "is_executable", "itertools 0.14.0", "miette 7.6.0", "pep508_rs", @@ -5233,9 +5361,11 @@ dependencies = [ "pixi_consts", "rattler_conda_types", "rattler_networking", + "rattler_shell", "reqwest", "reqwest-middleware", "reqwest-retry", + "rlimit", "rstest", "serde", "serde_json", @@ -5246,6 +5376,7 @@ dependencies = [ "tracing", "tracing-subscriber", "url", + "uv-configuration", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0d2ec648a2..cadcb5be4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -176,8 +176,11 @@ 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_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" } pixi_pypi_spec = { path = "crates/pixi_pypi_spec" } @@ -249,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 } @@ -277,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 } @@ -306,8 +307,11 @@ 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_install_pypi = { workspace = true } +pixi_lockfile = { workspace = true } pixi_manifest = { workspace = true, features = ["rattler_lock"] } pixi_progress = { workspace = true } pixi_pypi_spec = { workspace = true } @@ -349,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 } @@ -360,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_environment/Cargo.toml b/crates/pixi_environment/Cargo.toml new file mode 100644 index 0000000000..21c7d88aa8 --- /dev/null +++ b/crates/pixi_environment/Cargo.toml @@ -0,0 +1,25 @@ +[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] +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 new file mode 100644 index 0000000000..4fd6df28a4 --- /dev/null +++ b/crates/pixi_environment/src/lib.rs @@ -0,0 +1,7 @@ +pub mod conda_metadata; +pub mod list; +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/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/environment/pypi_prefix.rs b/crates/pixi_environment/src/pypi_prefix.rs similarity index 99% rename from src/environment/pypi_prefix.rs rename to crates/pixi_environment/src/pypi_prefix.rs index 5ff78d20d2..61bf9d70fd 100644 --- a/src/environment/pypi_prefix.rs +++ b/crates/pixi_environment/src/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/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/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/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/lib.rs similarity index 99% rename from src/install_pypi/mod.rs rename to crates/pixi_install_pypi/src/lib.rs index 7c9b889976..a6acc740fc 100644 --- a/src/install_pypi/mod.rs +++ b/crates/pixi_install_pypi/src/lib.rs @@ -1,18 +1,20 @@ 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; 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}, }; 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, @@ -41,11 +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}, - lock_file::UvResolutionContext, - prefix::Prefix, -}; +use crate::plan::{CachedWheels, RequiredDists}; use pixi_reporters::{UvReporter, UvReporterOptions}; pub(crate) mod conda_pypi_clobber; @@ -131,7 +129,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/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 99% rename from src/install_pypi/plan/planner.rs rename to crates/pixi_install_pypi/src/plan/planner.rs index 84f5a5fa52..a51e8f3c64 100644 --- a/src/install_pypi/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/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 96% rename from src/install_pypi/plan/required_dists.rs rename to crates/pixi_install_pypi/src/plan/required_dists.rs index 194905eba6..ca9354b698 100644 --- a/src/install_pypi/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/src/install_pypi/plan/test/harness.rs b/crates/pixi_install_pypi/src/plan/test/harness.rs similarity index 99% rename from src/install_pypi/plan/test/harness.rs rename to crates/pixi_install_pypi/src/plan/test/harness.rs index 48cf3362bc..755072dc11 100644 --- a/src/install_pypi/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/src/install_pypi/plan/test/mod.rs b/crates/pixi_install_pypi/src/plan/test/mod.rs similarity index 99% rename from src/install_pypi/plan/test/mod.rs rename to crates/pixi_install_pypi/src/plan/test/mod.rs index 71f130acd4..24f6b5e195 100644 --- a/src/install_pypi/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/src/install_pypi/plan/validation.rs b/crates/pixi_install_pypi/src/plan/validation.rs similarity index 99% rename from src/install_pypi/plan/validation.rs rename to crates/pixi_install_pypi/src/plan/validation.rs index abdb4d8462..f89a5dc40a 100644 --- a/src/install_pypi/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/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 diff --git a/crates/pixi_lockfile/Cargo.toml b/crates/pixi_lockfile/Cargo.toml new file mode 100644 index 0000000000..f4cfca4767 --- /dev/null +++ b/crates/pixi_lockfile/Cargo.toml @@ -0,0 +1,55 @@ +[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] +chrono = { workspace = true } +fancy_display = { workspace = true } +fs-err = { workspace = true } +futures = { 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_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 } +rattler_solve = { workspace = true } +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 = { 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 new file mode 100644 index 0000000000..81393afb32 --- /dev/null +++ b/crates/pixi_lockfile/src/lib.rs @@ -0,0 +1,25 @@ +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::{ + resolver_provider::CondaResolverProvider, 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/resolve/mod.rs b/crates/pixi_lockfile/src/resolve/mod.rs new file mode 100644 index 0000000000..3d22fd77f0 --- /dev/null +++ b/crates/pixi_lockfile/src/resolve/mod.rs @@ -0,0 +1,6 @@ +//! 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 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 be9b3f62ce..442f4d22e5 100644 --- a/src/lock_file/resolve/resolver_provider.rs +++ b/crates/pixi_lockfile/src/resolve/resolver_provider.rs @@ -7,6 +7,7 @@ use std::{ sync::Arc, }; +use crate::PypiPackageIdentifier; use futures::{Future, FutureExt}; use pixi_consts::consts; use pixi_record::PixiRecord; @@ -23,15 +24,13 @@ 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: +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/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/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/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 new file mode 100644 index 0000000000..43f9cd7839 --- /dev/null +++ b/crates/pixi_lockfile/src/satisfiability/mod.rs @@ -0,0 +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/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/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/src/lock_file/virtual_packages.rs b/crates/pixi_lockfile/src/virtual_packages.rs similarity index 96% rename from src/lock_file/virtual_packages.rs rename to crates/pixi_lockfile/src/virtual_packages.rs index 114c559304..a11dae3b7f 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, @@ -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; diff --git a/crates/pixi_utils/Cargo.toml b/crates/pixi_utils/Cargo.toml index b7414bfcc4..d20d554d4b 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,9 +40,11 @@ rattler_networking = { workspace = true, features = [ "netrc-rs", "system-integration", ] } +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 } @@ -51,6 +54,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..42b503b343 100644 --- a/crates/pixi_utils/src/lib.rs +++ b/crates/pixi_utils/src/lib.rs @@ -1,8 +1,11 @@ pub mod cache; pub mod conda_environment_file; 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::{ @@ -10,4 +13,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/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/cli/exec.rs b/src/cli/exec.rs index 8b5e560e46..2edca61c61 100644 --- a/src/cli/exec.rs +++ b/src/cli/exec.rs @@ -4,8 +4,9 @@ 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, reqwest::build_reqwest_clients}; +use pixi_utils::{AsyncPrefixGuard, EnvironmentHash, Prefix, reqwest::build_reqwest_clients}; use rattler::{ install::{IndicatifReporter, Installer}, package_cache::PackageCache, @@ -17,10 +18,6 @@ use reqwest_middleware::ClientWithMiddleware; use uv_configuration::RAYON_INITIALIZE; use super::cli_config::ChannelsConfig; -use crate::{ - environment::list::{PackageToOutput, print_package_table}, - prefix::Prefix, -}; /// Run a command and install it in a temporary environment. /// 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/cli/shell.rs b/src/cli/shell.rs index 0549885421..dbd1ce7ee9 100644 --- a/src/cli/shell.rs +++ b/src/cli/shell.rs @@ -23,7 +23,7 @@ use pixi_config::{ConfigCli, ConfigCliActivation, ConfigCliPrompt}; use pixi_pty::unix::PtySession; #[cfg(target_family = "unix")] -use crate::prefix::Prefix; +use pixi_utils::Prefix; use super::cli_config::LockFileUpdateConfig; diff --git a/src/environment/conda_prefix.rs b/src/environment/conda_prefix.rs index 4cd2df654a..02763f3a7b 100644 --- a/src/environment/conda_prefix.rs +++ b/src/environment/conda_prefix.rs @@ -3,25 +3,20 @@ 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_environment::conda_metadata::{create_history_file, create_prefix_location_file}; use pixi_manifest::FeaturesExt; use pixi_record::PixiRecord; +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::{ - environment::PythonStatus, - prefix::Prefix, - 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 a6899b892c..8f03aecb25 100644 --- a/src/environment/mod.rs +++ b/src/environment/mod.rs @@ -1,8 +1,4 @@ -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}; @@ -13,8 +9,7 @@ use pixi_manifest::FeaturesExt; use pixi_progress::await_in_progress; use pixi_pypi_spec::PixiPypiSpec; use pixi_spec::{GitSpec, PixiSpec}; -pub use pypi_prefix::{ContinuePyPIPrefixUpdate, on_python_interpreter_change}; -pub use python_status::PythonStatus; +use pixi_utils::Prefix; use rattler_conda_types::Platform; use rattler_lock::LockedPackageRef; use serde::{Deserialize, Serialize}; @@ -30,8 +25,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/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/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}; 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..9058c020dc 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, 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, @@ -61,9 +62,7 @@ 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, }; mod environment; diff --git a/src/lib.rs b/src/lib.rs index a8b7b44d92..4c1fc4f072 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,17 +5,13 @@ pub mod cli; pub mod diff; pub mod environment; mod global; -mod install_pypi; pub mod lock_file; -pub mod prefix; mod prompt; 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/lock_file/mod.rs b/src/lock_file/mod.rs index b164e0867a..5f90ccaa7a 100644 --- a/src/lock_file/mod.rs +++ b/src/lock_file/mod.rs @@ -1,39 +1,19 @@ mod outdated; -mod package_identifier; -mod records_by_name; mod reporter; mod resolve; mod satisfiability; mod update; mod utils; -pub mod virtual_packages; 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, uv_resolution_context::UvResolutionContext}; -pub use satisfiability::{ - EnvironmentUnsat, PlatformUnsat, verify_environment_satisfiability, - verify_platform_satisfiability, -}; +pub(crate) use resolve::pypi::resolve_pypi; +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; 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/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/resolve/mod.rs b/src/lock_file/resolve/mod.rs index c1f724ffd9..a09005b2be 100644 --- a/src/lock_file/resolve/mod.rs +++ b/src/lock_file/resolve/mod.rs @@ -4,5 +4,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..cd6e214864 100644 --- a/src/lock_file/resolve/pypi.rs +++ b/src/lock_file/resolve/pypi.rs @@ -15,6 +15,10 @@ use indicatif::ProgressBar; use itertools::{Either, Itertools}; use miette::{Context, IntoDiagnostic}; use pixi_consts::consts; +use pixi_lockfile::{ + CondaResolverProvider, HasNameVersion, LockedPypiPackages, PixiRecordsByName, + PypiPackageIdentifier, PypiRecord, UvResolutionContext, +}; use pixi_manifest::{EnvironmentName, SystemRequirements, pypi::pypi_options::PypiOptions}; use pixi_pypi_spec::PixiPypiSpec; use pixi_record::PixiRecord; @@ -54,14 +58,9 @@ use uv_types::EmptyInstalledPackages; use crate::{ environment::CondaPrefixUpdated, lock_file::{ - CondaPrefixUpdater, LockedPypiPackages, PixiRecordsByName, PypiPackageIdentifier, - PypiRecord, UvResolutionContext, - records_by_name::HasNameVersion, - resolve::{ - build_dispatch::{ - LazyBuildDispatch, LazyBuildDispatchDependencies, UvBuildDispatchParams, - }, - resolver_provider::CondaResolverProvider, + CondaPrefixUpdater, + resolve::build_dispatch::{ + LazyBuildDispatch, LazyBuildDispatchDependencies, UvBuildDispatchParams, }, }, workspace::{Environment, EnvironmentVars}, diff --git a/src/lock_file/satisfiability/mod.rs b/src/lock_file/satisfiability/mod.rs index f16ef01453..34dd1ef3a6 100644 --- a/src/lock_file/satisfiability/mod.rs +++ b/src/lock_file/satisfiability/mod.rs @@ -1,26 +1,29 @@ use std::{ borrow::Cow, collections::{HashMap, HashSet}, - 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, EnvironmentUnsat, ExcludeNewerMismatch, IndexesMismatch, + 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,386 +40,15 @@ 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)] -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)] -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 +1369,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 +1379,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 4b68e6656c..b28b1c8761 100644 --- a/src/lock_file/update.rs +++ b/src/lock_file/update.rs @@ -1,21 +1,13 @@ -use super::{ - CondaPrefixUpdater, PixiRecordsByName, PypiRecordsByName, UvResolutionContext, - outdated::OutdatedEnvironments, utils::IoConcurrencyLimit, -}; +use super::{CondaPrefixUpdater, outdated::OutdatedEnvironments, utils::IoConcurrencyLimit}; use crate::{ Workspace, activation::CurrentEnvVarBehavior, environment::{ CondaPrefixUpdated, EnvironmentFile, LockFileUsage, LockedEnvironmentHash, - PerEnvironmentAndPlatform, PerGroup, PerGroupAndPlatform, PythonStatus, - 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, + PerEnvironmentAndPlatform, PerGroup, PerGroupAndPlatform, read_environment_file, + write_environment_file, }, - prefix::Prefix, + lock_file::{self, reporter::SolveProgressBar}, workspace::{ Environment, EnvironmentVars, HasWorkspaceRef, get_activated_environment_variables, grouped_environment::{GroupedEnvironment, GroupedEnvironmentName}, @@ -31,10 +23,17 @@ 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_install_pypi::{ + PyPIBuildConfig, PyPIContextConfig, PyPIEnvironmentUpdater, PyPIUpdateConfig, +}; +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}; +use pixi_utils::Prefix; use pixi_uv_conversions::{ ConversionError, to_extra_name, to_marker_environment, to_normalize, to_uv_extra_name, to_uv_normalize, @@ -492,7 +491,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); @@ -1395,7 +1396,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 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, }, 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(); 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; 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