From 619ec8dcced503d0526b6e2ea1184d896f11f5d1 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 22 Nov 2024 17:06:43 -0500 Subject: [PATCH] Allow system Python discovery with `--target` and `--prefix` (#9371) ## Summary If we're installing with `--target` or `--prefix`, then it's not a mutable operation, so we should be allowed to discover system Pythons. I suspect this was hard to special-case in the past but is now trivial after @zanieb's various refactors. Closes https://github.com/astral-sh/uv/issues/9356. --- crates/uv/src/commands/pip/install.rs | 9 ++++++-- crates/uv/src/commands/pip/sync.rs | 9 ++++++-- crates/uv/tests/it/common/mod.rs | 2 +- crates/uv/tests/it/pip_sync.rs | 30 +++++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 5 deletions(-) diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index 01b82f6b5..15529b4fd 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -138,17 +138,22 @@ 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, true), + EnvironmentPreference::from_system_flag(system, mutable), &cache, )?; - report_target_environment(&environment, &cache, printer)?; + if mutable { + report_target_environment(&environment, &cache, printer)?; + } // Apply any `--target` or `--prefix` directories. let environment = if let Some(target) = target { diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index a65bd1b9a..08ffb1181 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -122,17 +122,22 @@ 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, true), + EnvironmentPreference::from_system_flag(system, mutable), &cache, )?; - report_target_environment(&environment, &cache, printer)?; + if mutable { + report_target_environment(&environment, &cache, printer)?; + } // Apply any `--target` or `--prefix` directories. let environment = if let Some(target) = target { diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index 82143317a..5201aae1a 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -315,7 +315,7 @@ impl TestContext { // Exclude `link-mode` on Windows since we set it in the remote test suite if cfg!(windows) { filters.push(("--link-mode ".to_string(), String::new())); - filters.push(((r#"link-mode = "copy"\n"#).to_string(), String::new())); + filters.push((r#"link-mode = "copy"\n"#.to_string(), String::new())); } filters.extend( diff --git a/crates/uv/tests/it/pip_sync.rs b/crates/uv/tests/it/pip_sync.rs index c3a51e7c7..dee1a6b01 100644 --- a/crates/uv/tests/it/pip_sync.rs +++ b/crates/uv/tests/it/pip_sync.rs @@ -5361,6 +5361,36 @@ fn target_no_build_isolation() -> Result<()> { Ok(()) } +/// Sync to a `--target` directory without a virtual environment. +#[test] +fn target_system() -> Result<()> { + let context = TestContext::new_with_versions(&["3.12"]); + + // 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.filters(), context.pip_sync() + .arg("requirements.in") + .arg("--target") + .arg("target"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + iniconfig==2.0.0 + "###); + + // Ensure that the package is present in the target directory. + assert!(context.temp_dir.child("target").child("iniconfig").is_dir()); + + Ok(()) +} + /// Sync to a `--prefix` directory. #[test] fn prefix() -> Result<()> {