diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index 15529b4fde8d..b115c29bf743 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -22,7 +22,8 @@ use uv_installer::{SatisfiesResult, SitePackages}; use uv_pep508::PackageName; use uv_pypi_types::{Conflicts, Requirement}; use uv_python::{ - EnvironmentPreference, Prefix, PythonEnvironment, PythonRequest, PythonVersion, Target, + EnvironmentPreference, Prefix, PythonEnvironment, PythonInstallation, PythonPreference, + PythonRequest, PythonVersion, Target, }; use uv_requirements::{RequirementsSource, RequirementsSpecification}; use uv_resolver::{ @@ -32,8 +33,8 @@ use uv_resolver::{ use uv_types::{BuildIsolation, HashStrategy}; use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger, InstallLogger}; -use crate::commands::pip::operations::report_target_environment; use crate::commands::pip::operations::Modifications; +use crate::commands::pip::operations::{report_interpreter, report_target_environment}; use crate::commands::pip::{operations, resolution_markers, resolution_tags}; use crate::commands::{diagnostics, ExitStatus, SharedState}; use crate::printer::Printer; @@ -76,6 +77,7 @@ pub(crate) async fn pip_install( break_system_packages: bool, target: Option, prefix: Option, + python_preference: PythonPreference, concurrency: Concurrency, native_tls: bool, allow_insecure_host: &[TrustedHost], @@ -138,22 +140,31 @@ pub(crate) async fn pip_install( ) .collect(); - // Determine whether we're modifying the discovered environment, or a separate target. - let mutable = !(target.is_some() || prefix.is_some()); - // Detect the current Python interpreter. - let environment = PythonEnvironment::find( - &python - .as_deref() - .map(PythonRequest::parse) - .unwrap_or_default(), - EnvironmentPreference::from_system_flag(system, mutable), - &cache, - )?; - - if mutable { + let environment = if target.is_some() || prefix.is_some() { + let installation = PythonInstallation::find( + &python + .as_deref() + .map(PythonRequest::parse) + .unwrap_or_default(), + EnvironmentPreference::from_system_flag(system, false), + python_preference, + &cache, + )?; + report_interpreter(&installation, true, printer)?; + PythonEnvironment::from_installation(installation) + } else { + let environment = PythonEnvironment::find( + &python + .as_deref() + .map(PythonRequest::parse) + .unwrap_or_default(), + EnvironmentPreference::from_system_flag(system, true), + &cache, + )?; report_target_environment(&environment, &cache, printer)?; - } + environment + }; // Apply any `--target` or `--prefix` directories. let environment = if let Some(target) = target { diff --git a/crates/uv/src/commands/pip/operations.rs b/crates/uv/src/commands/pip/operations.rs index 694786cd9018..9c9e5c024861 100644 --- a/crates/uv/src/commands/pip/operations.rs +++ b/crates/uv/src/commands/pip/operations.rs @@ -30,7 +30,7 @@ use uv_installer::{Plan, Planner, Preparer, SitePackages}; use uv_normalize::{GroupName, PackageName}; use uv_platform_tags::Tags; use uv_pypi_types::{Conflicts, ResolverMarkerEnvironment}; -use uv_python::PythonEnvironment; +use uv_python::{PythonEnvironment, PythonInstallation}; use uv_requirements::{ LookaheadResolver, NamedRequirementsResolver, RequirementsSource, RequirementsSpecification, SourceTreeResolver, @@ -571,6 +571,63 @@ pub(crate) async fn install( Ok(changelog) } +/// Display a message about the interpreter that was selected for the operation. +pub(crate) fn report_interpreter( + python: &PythonInstallation, + dimmed: bool, + printer: Printer, +) -> Result<(), Error> { + let managed = python.source().is_managed(); + let implementation = python.implementation(); + let interpreter = python.interpreter(); + + if dimmed { + if managed { + writeln!( + printer.stderr(), + "{}", + format!( + "Using {} {}", + implementation.pretty(), + interpreter.python_version() + ) + .dimmed() + )?; + } else { + writeln!( + printer.stderr(), + "{}", + format!( + "Using {} {} interpreter at: {}", + implementation.pretty(), + interpreter.python_version(), + interpreter.sys_executable().user_display() + ) + .dimmed() + )?; + } + } else { + if managed { + writeln!( + printer.stderr(), + "Using {} {}", + implementation.pretty(), + interpreter.python_version().cyan() + )?; + } else { + writeln!( + printer.stderr(), + "Using {} {} interpreter at: {}", + implementation.pretty(), + interpreter.python_version(), + interpreter.sys_executable().user_display().cyan() + )?; + } + } + + Ok(()) +} + /// Display a message about the target environment for the operation. pub(crate) fn report_target_environment( env: &PythonEnvironment, @@ -578,7 +635,7 @@ pub(crate) fn report_target_environment( printer: Printer, ) -> Result<(), Error> { let message = format!( - "Using Python {} environment at {}", + "Using Python {} environment at: {}", env.interpreter().python_version(), env.root().user_display() ); diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 08ffb1181a26..df71c1e1a29e 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -19,7 +19,8 @@ use uv_installer::SitePackages; use uv_pep508::PackageName; use uv_pypi_types::Conflicts; use uv_python::{ - EnvironmentPreference, Prefix, PythonEnvironment, PythonRequest, PythonVersion, Target, + EnvironmentPreference, Prefix, PythonEnvironment, PythonInstallation, PythonPreference, + PythonRequest, PythonVersion, Target, }; use uv_requirements::{RequirementsSource, RequirementsSpecification}; use uv_resolver::{ @@ -29,8 +30,8 @@ use uv_resolver::{ use uv_types::{BuildIsolation, HashStrategy}; use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger}; -use crate::commands::pip::operations::report_target_environment; use crate::commands::pip::operations::Modifications; +use crate::commands::pip::operations::{report_interpreter, report_target_environment}; use crate::commands::pip::{operations, resolution_markers, resolution_tags}; use crate::commands::{diagnostics, ExitStatus, SharedState}; use crate::printer::Printer; @@ -65,6 +66,7 @@ pub(crate) async fn pip_sync( target: Option, prefix: Option, sources: SourceStrategy, + python_preference: PythonPreference, concurrency: Concurrency, native_tls: bool, allow_insecure_host: &[TrustedHost], @@ -122,22 +124,31 @@ pub(crate) async fn pip_sync( } } - // Determine whether we're modifying the discovered environment, or a separate target. - let mutable = !(target.is_some() || prefix.is_some()); - // Detect the current Python interpreter. - let environment = PythonEnvironment::find( - &python - .as_deref() - .map(PythonRequest::parse) - .unwrap_or_default(), - EnvironmentPreference::from_system_flag(system, mutable), - &cache, - )?; - - if mutable { + let environment = if target.is_some() || prefix.is_some() { + let installation = PythonInstallation::find( + &python + .as_deref() + .map(PythonRequest::parse) + .unwrap_or_default(), + EnvironmentPreference::from_system_flag(system, false), + python_preference, + &cache, + )?; + report_interpreter(&installation, true, printer)?; + PythonEnvironment::from_installation(installation) + } else { + let environment = PythonEnvironment::find( + &python + .as_deref() + .map(PythonRequest::parse) + .unwrap_or_default(), + EnvironmentPreference::from_system_flag(system, true), + &cache, + )?; report_target_environment(&environment, &cache, printer)?; - } + environment + }; // Apply any `--target` or `--prefix` directories. let environment = if let Some(target) = target { diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index 93cb6af415ae..957b440dd792 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -31,7 +31,7 @@ use uv_warnings::{warn_user, warn_user_once}; use uv_workspace::{DiscoveryOptions, VirtualProject, WorkspaceError}; use crate::commands::pip::loggers::{DefaultInstallLogger, InstallLogger}; -use crate::commands::pip::operations::Changelog; +use crate::commands::pip::operations::{report_interpreter, Changelog}; use crate::commands::project::{validate_requires_python, WorkspacePython}; use crate::commands::reporters::PythonDownloadReporter; use crate::commands::{ExitStatus, SharedState}; @@ -201,23 +201,23 @@ async fn venv_impl( .into_diagnostic()?; // Locate the Python interpreter to use in the environment - let python = PythonInstallation::find_or_download( - python_request.as_ref(), - EnvironmentPreference::OnlySystem, - python_preference, - python_downloads, - &client_builder, - cache, - Some(&reporter), - install_mirrors.python_install_mirror.as_deref(), - install_mirrors.pypy_install_mirror.as_deref(), - ) - .await - .into_diagnostic()?; - - let managed = python.source().is_managed(); - let implementation = python.implementation(); - let interpreter = python.into_interpreter(); + let interpreter = { + let python = PythonInstallation::find_or_download( + python_request.as_ref(), + EnvironmentPreference::OnlySystem, + python_preference, + python_downloads, + &client_builder, + cache, + Some(&reporter), + install_mirrors.python_install_mirror.as_deref(), + install_mirrors.pypy_install_mirror.as_deref(), + ) + .await + .into_diagnostic()?; + report_interpreter(&python, false, printer).into_diagnostic()?; + python.into_interpreter() + }; // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { @@ -226,25 +226,6 @@ async fn venv_impl( } } - if managed { - writeln!( - printer.stderr(), - "Using {} {}", - implementation.pretty(), - interpreter.python_version().cyan() - ) - .into_diagnostic()?; - } else { - writeln!( - printer.stderr(), - "Using {} {} interpreter at: {}", - implementation.pretty(), - interpreter.python_version(), - interpreter.sys_executable().user_display().cyan() - ) - .into_diagnostic()?; - } - // Check if the discovered Python version is incompatible with the current workspace if let Some(requires_python) = requires_python { match validate_requires_python( diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 642efa2e65ec..40c3fe587e5f 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -437,6 +437,7 @@ async fn run(mut cli: Cli) -> Result { args.settings.target, args.settings.prefix, args.settings.sources, + globals.python_preference, globals.concurrency, globals.native_tls, &globals.allow_insecure_host, @@ -525,6 +526,7 @@ async fn run(mut cli: Cli) -> Result { args.settings.break_system_packages, args.settings.target, args.settings.prefix, + globals.python_preference, globals.concurrency, globals.native_tls, &globals.allow_insecure_host, diff --git a/crates/uv/tests/it/export.rs b/crates/uv/tests/it/export.rs index e07ef64491f0..c5c68c323933 100644 --- a/crates/uv/tests/it/export.rs +++ b/crates/uv/tests/it/export.rs @@ -864,7 +864,7 @@ fn relative_path() -> Result<()> { ----- stdout ----- ----- stderr ----- - Using Python 3.12.[X] environment at [VENV]/ + Using Python 3.12.[X] environment at: [VENV]/ Resolved 3 packages in [TIME] Prepared 3 packages in [TIME] Installed 3 packages in [TIME] diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index 8bb7e7403851..75819052e755 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -1266,7 +1266,7 @@ fn install_editable_bare_cli() { ----- stdout ----- ----- stderr ----- - Using Python 3.12.[X] environment at [VENV]/ + Using Python 3.12.[X] environment at: [VENV]/ Resolved 1 package in [TIME] Prepared 1 package in [TIME] Installed 1 package in [TIME] @@ -1293,7 +1293,7 @@ fn install_editable_bare_requirements_txt() -> Result<()> { ----- stdout ----- ----- stderr ----- - Using Python 3.12.[X] environment at [VENV]/ + Using Python 3.12.[X] environment at: [VENV]/ Resolved 1 package in [TIME] Prepared 1 package in [TIME] Installed 1 package in [TIME] @@ -3445,7 +3445,7 @@ requires-python = ">=3.8" ----- stdout ----- ----- stderr ----- - Using Python 3.12.[X] environment at [VENV]/ + Using Python 3.12.[X] environment at: [VENV]/ Resolved 4 packages in [TIME] Prepared 4 packages in [TIME] Installed 4 packages in [TIME] @@ -3465,7 +3465,7 @@ requires-python = ">=3.8" ----- stdout ----- ----- stderr ----- - Using Python 3.12.[X] environment at [VENV]/ + Using Python 3.12.[X] environment at: [VENV]/ Audited 1 package in [TIME] "### ); @@ -3491,7 +3491,7 @@ requires-python = ">=3.8" ----- stdout ----- ----- stderr ----- - Using Python 3.12.[X] environment at [VENV]/ + Using Python 3.12.[X] environment at: [VENV]/ Resolved 4 packages in [TIME] Prepared 2 packages in [TIME] Uninstalled 2 packages in [TIME] @@ -3539,7 +3539,7 @@ fn invalidate_path_on_cache_key() -> Result<()> { ----- stdout ----- ----- stderr ----- - Using Python 3.12.[X] environment at [VENV]/ + Using Python 3.12.[X] environment at: [VENV]/ Resolved 4 packages in [TIME] Prepared 4 packages in [TIME] Installed 4 packages in [TIME] @@ -3559,7 +3559,7 @@ fn invalidate_path_on_cache_key() -> Result<()> { ----- stdout ----- ----- stderr ----- - Using Python 3.12.[X] environment at [VENV]/ + Using Python 3.12.[X] environment at: [VENV]/ Audited 1 package in [TIME] "### ); @@ -3576,7 +3576,7 @@ fn invalidate_path_on_cache_key() -> Result<()> { ----- stdout ----- ----- stderr ----- - Using Python 3.12.[X] environment at [VENV]/ + Using Python 3.12.[X] environment at: [VENV]/ Resolved 4 packages in [TIME] Prepared 1 package in [TIME] Uninstalled 1 package in [TIME] @@ -3597,7 +3597,7 @@ fn invalidate_path_on_cache_key() -> Result<()> { ----- stdout ----- ----- stderr ----- - Using Python 3.12.[X] environment at [VENV]/ + Using Python 3.12.[X] environment at: [VENV]/ Resolved 4 packages in [TIME] Prepared 1 package in [TIME] Uninstalled 1 package in [TIME] @@ -3628,7 +3628,7 @@ fn invalidate_path_on_cache_key() -> Result<()> { ----- stdout ----- ----- stderr ----- - Using Python 3.12.[X] environment at [VENV]/ + Using Python 3.12.[X] environment at: [VENV]/ Audited 1 package in [TIME] "### ); @@ -3661,7 +3661,7 @@ fn invalidate_path_on_cache_key() -> Result<()> { ----- stdout ----- ----- stderr ----- - Using Python 3.12.[X] environment at [VENV]/ + Using Python 3.12.[X] environment at: [VENV]/ Resolved 4 packages in [TIME] Prepared 1 package in [TIME] Uninstalled 1 package in [TIME] @@ -3682,7 +3682,7 @@ fn invalidate_path_on_cache_key() -> Result<()> { ----- stdout ----- ----- stderr ----- - Using Python 3.12.[X] environment at [VENV]/ + Using Python 3.12.[X] environment at: [VENV]/ Resolved 4 packages in [TIME] Prepared 1 package in [TIME] Uninstalled 1 package in [TIME] @@ -3738,7 +3738,7 @@ fn invalidate_path_on_commit() -> Result<()> { ----- stdout ----- ----- stderr ----- - Using Python 3.12.[X] environment at [VENV]/ + Using Python 3.12.[X] environment at: [VENV]/ Resolved 4 packages in [TIME] Prepared 4 packages in [TIME] Installed 4 packages in [TIME] @@ -3758,7 +3758,7 @@ fn invalidate_path_on_commit() -> Result<()> { ----- stdout ----- ----- stderr ----- - Using Python 3.12.[X] environment at [VENV]/ + Using Python 3.12.[X] environment at: [VENV]/ Audited 1 package in [TIME] "### ); @@ -3781,7 +3781,7 @@ fn invalidate_path_on_commit() -> Result<()> { ----- stdout ----- ----- stderr ----- - Using Python 3.12.[X] environment at [VENV]/ + Using Python 3.12.[X] environment at: [VENV]/ Resolved 4 packages in [TIME] Prepared 1 package in [TIME] Uninstalled 1 package in [TIME] @@ -4881,7 +4881,7 @@ fn deptry_gitignore() { ----- stdout ----- ----- stderr ----- - Using Python 3.12.[X] environment at [VENV]/ + Using Python 3.12.[X] environment at: [VENV]/ Resolved 3 packages in [TIME] Prepared 3 packages in [TIME] Installed 3 packages in [TIME] diff --git a/crates/uv/tests/it/pip_sync.rs b/crates/uv/tests/it/pip_sync.rs index de1004c55ac4..f1c90e12925e 100644 --- a/crates/uv/tests/it/pip_sync.rs +++ b/crates/uv/tests/it/pip_sync.rs @@ -5175,13 +5175,16 @@ fn require_hashes_url_unnamed() -> Result<()> { /// Sync to a `--target` directory with a built distribution. #[test] fn target_built_distribution() -> Result<()> { - let context = TestContext::new("3.12"); + let context = TestContext::new("3.12") + .with_filtered_python_names() + .with_filtered_virtualenv_bin() + .with_filtered_exe_suffix(); // Install `iniconfig` to the target directory. let requirements_in = context.temp_dir.child("requirements.in"); requirements_in.write_str("iniconfig==2.0.0")?; - uv_snapshot!(context.pip_sync() + uv_snapshot!(context.filters(), context.pip_sync() .arg("requirements.in") .arg("--target") .arg("target"), @r###" @@ -5190,6 +5193,7 @@ fn target_built_distribution() -> Result<()> { ----- stdout ----- ----- stderr ----- + Using CPython 3.12.[X] interpreter at: .venv/[BIN]/python Resolved 1 package in [TIME] Prepared 1 package in [TIME] Installed 1 package in [TIME] @@ -5216,7 +5220,7 @@ fn target_built_distribution() -> Result<()> { let requirements_in = context.temp_dir.child("requirements.in"); requirements_in.write_str("iniconfig==1.1.1")?; - uv_snapshot!(context.pip_sync() + uv_snapshot!(context.filters(), context.pip_sync() .arg("requirements.in") .arg("--target") .arg("target"), @r###" @@ -5225,6 +5229,7 @@ fn target_built_distribution() -> Result<()> { ----- stdout ----- ----- stderr ----- + Using CPython 3.12.[X] interpreter at: .venv/[BIN]/python Resolved 1 package in [TIME] Prepared 1 package in [TIME] Uninstalled 1 package in [TIME] @@ -5237,7 +5242,7 @@ fn target_built_distribution() -> Result<()> { let requirements_in = context.temp_dir.child("requirements.in"); requirements_in.write_str("flask")?; - uv_snapshot!(context.pip_sync() + uv_snapshot!(context.filters(), context.pip_sync() .arg("requirements.in") .arg("--target") .arg("target"), @r###" @@ -5246,6 +5251,7 @@ fn target_built_distribution() -> Result<()> { ----- stdout ----- ----- stderr ----- + Using CPython 3.12.[X] interpreter at: .venv/[BIN]/python Resolved 1 package in [TIME] Prepared 1 package in [TIME] Uninstalled 1 package in [TIME] @@ -5267,13 +5273,16 @@ fn target_built_distribution() -> Result<()> { /// Sync to a `--target` directory with a package that requires building from source. #[test] fn target_source_distribution() -> Result<()> { - let context = TestContext::new("3.12"); + let context = TestContext::new("3.12") + .with_filtered_python_names() + .with_filtered_virtualenv_bin() + .with_filtered_exe_suffix(); // Install `iniconfig` to the target directory. let requirements_in = context.temp_dir.child("requirements.in"); requirements_in.write_str("iniconfig==2.0.0")?; - uv_snapshot!(context.pip_sync() + uv_snapshot!(context.filters(), context.pip_sync() .arg("requirements.in") .arg("--no-binary") .arg("iniconfig") @@ -5284,6 +5293,7 @@ fn target_source_distribution() -> Result<()> { ----- stdout ----- ----- stderr ----- + Using CPython 3.12.[X] interpreter at: .venv/[BIN]/python Resolved 1 package in [TIME] Prepared 1 package in [TIME] Installed 1 package in [TIME] @@ -5316,13 +5326,16 @@ fn target_source_distribution() -> Result<()> { /// `--no-build-isolation`. #[test] fn target_no_build_isolation() -> Result<()> { - let context = TestContext::new("3.12"); + let context = TestContext::new("3.12") + .with_filtered_python_names() + .with_filtered_virtualenv_bin() + .with_filtered_exe_suffix(); // Install `hatchling` into the current environment. let requirements_in = context.temp_dir.child("requirements.in"); requirements_in.write_str("flit_core")?; - uv_snapshot!(context.pip_sync() + uv_snapshot!(context.filters(), context.pip_sync() .arg("requirements.in"), @r###" success: true exit_code: 0 @@ -5339,7 +5352,7 @@ fn target_no_build_isolation() -> Result<()> { let requirements_in = context.temp_dir.child("requirements.in"); requirements_in.write_str("wheel")?; - uv_snapshot!(context.pip_sync() + uv_snapshot!(context.filters(), context.pip_sync() .arg("requirements.in") .arg("--no-build-isolation") .arg("--no-binary") @@ -5351,6 +5364,7 @@ fn target_no_build_isolation() -> Result<()> { ----- stdout ----- ----- stderr ----- + Using CPython 3.12.[X] interpreter at: .venv/[BIN]/python Resolved 1 package in [TIME] Prepared 1 package in [TIME] Installed 1 package in [TIME] @@ -5397,6 +5411,7 @@ fn target_system() -> Result<()> { ----- stdout ----- ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] Resolved 1 package in [TIME] Prepared 1 package in [TIME] Installed 1 package in [TIME] @@ -5412,7 +5427,10 @@ fn target_system() -> Result<()> { /// Sync to a `--prefix` directory. #[test] fn prefix() -> Result<()> { - let context = TestContext::new("3.12"); + let context = TestContext::new("3.12") + .with_filtered_python_names() + .with_filtered_virtualenv_bin() + .with_filtered_exe_suffix(); // Install `iniconfig` to the target directory. let requirements_in = context.temp_dir.child("requirements.in"); @@ -5420,7 +5438,7 @@ fn prefix() -> Result<()> { let prefix = context.temp_dir.child("prefix"); - uv_snapshot!(context.pip_sync() + uv_snapshot!(context.filters(), context.pip_sync() .arg("requirements.in") .arg("--prefix") .arg(prefix.path()), @r###" @@ -5429,6 +5447,7 @@ fn prefix() -> Result<()> { ----- stdout ----- ----- stderr ----- + Using CPython 3.12.[X] interpreter at: .venv/[BIN]/python Resolved 1 package in [TIME] Prepared 1 package in [TIME] Installed 1 package in [TIME] @@ -5455,7 +5474,7 @@ fn prefix() -> Result<()> { let requirements_in = context.temp_dir.child("requirements.in"); requirements_in.write_str("iniconfig==1.1.1")?; - uv_snapshot!(context.pip_sync() + uv_snapshot!(context.filters(), context.pip_sync() .arg("requirements.in") .arg("--prefix") .arg(prefix.path()), @r###" @@ -5464,6 +5483,7 @@ fn prefix() -> Result<()> { ----- stdout ----- ----- stderr ----- + Using CPython 3.12.[X] interpreter at: .venv/[BIN]/python Resolved 1 package in [TIME] Prepared 1 package in [TIME] Uninstalled 1 package in [TIME]