From 428e4ef31c414b3aa54d52ac39ebc5067a61fda6 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 18 Apr 2024 00:43:25 -0400 Subject: [PATCH] Allow --python and --system on pip compile --- crates/uv-interpreter/src/find_python.rs | 26 ++++++++------ crates/uv/src/cli.rs | 43 ++++++++++++++++++++---- crates/uv/src/commands/pip_compile.rs | 11 ++++-- crates/uv/src/main.rs | 2 ++ crates/uv/src/settings.rs | 5 +++ 5 files changed, 69 insertions(+), 18 deletions(-) diff --git a/crates/uv-interpreter/src/find_python.rs b/crates/uv-interpreter/src/find_python.rs index c24b58b7bcd4..6d119a262c4d 100644 --- a/crates/uv-interpreter/src/find_python.rs +++ b/crates/uv-interpreter/src/find_python.rs @@ -425,6 +425,7 @@ impl PythonVersionSelector { #[instrument(skip_all, fields(?python_version))] pub fn find_best_python( python_version: Option<&PythonVersion>, + system: bool, cache: &Cache, ) -> Result { if let Some(python_version) = python_version { @@ -437,7 +438,7 @@ pub fn find_best_python( } // First, check for an exact match (or the first available version if no Python version was provided) - if let Some(interpreter) = find_version(python_version, cache)? { + if let Some(interpreter) = find_version(python_version, system, cache)? { return Ok(interpreter); } @@ -445,14 +446,16 @@ pub fn find_best_python( // If that fails, and a specific patch version was requested try again allowing a // different patch version if python_version.patch().is_some() { - if let Some(interpreter) = find_version(Some(&python_version.without_patch()), cache)? { + if let Some(interpreter) = + find_version(Some(&python_version.without_patch()), system, cache)? + { return Ok(interpreter); } } } // If a Python version was requested but cannot be fulfilled, just take any version - if let Some(interpreter) = find_version(None, cache)? { + if let Some(interpreter) = find_version(None, system, cache)? { return Ok(interpreter); } @@ -477,6 +480,7 @@ pub fn find_best_python( /// we will return [`None`]. fn find_version( python_version: Option<&PythonVersion>, + system: bool, cache: &Cache, ) -> Result, Error> { let version_matches = |interpreter: &Interpreter| -> bool { @@ -490,14 +494,16 @@ fn find_version( }; // Check if the venv Python matches. - if let Some(venv) = detect_virtual_env()? { - let executable = detect_python_executable(venv); - let interpreter = Interpreter::query(executable, cache)?; + if !system { + if let Some(venv) = detect_virtual_env()? { + let executable = detect_python_executable(venv); + let interpreter = Interpreter::query(executable, cache)?; - if version_matches(&interpreter) { - return Ok(Some(interpreter)); - } - }; + if version_matches(&interpreter) { + return Ok(Some(interpreter)); + } + }; + } // Look for the requested version with by search for `python{major}.{minor}` in `PATH` on // Unix and `py --list-paths` on Windows. diff --git a/crates/uv/src/cli.rs b/crates/uv/src/cli.rs index ca670844f9b0..7e93d451f49b 100644 --- a/crates/uv/src/cli.rs +++ b/crates/uv/src/cli.rs @@ -383,6 +383,15 @@ pub(crate) struct PipCompileArgs { #[arg(long, env = "UV_EXTRA_INDEX_URL", value_delimiter = ' ', value_parser = parse_index_url)] pub(crate) extra_index_url: Option>>, + /// Locations to search for candidate distributions, beyond those found in the indexes. + /// + /// If a path, the target must be a directory that contains package as wheel files (`.whl`) or + /// source distributions (`.tar.gz` or `.zip`) at the top level. + /// + /// If a URL, the page must contain a flat list of links to package files. + #[arg(long, short)] + pub(crate) find_links: Option>, + /// Ignore the registry index (e.g., PyPI), instead relying on direct URL dependencies and those /// discovered via `--find-links`. #[arg(long)] @@ -406,14 +415,36 @@ pub(crate) struct PipCompileArgs { #[arg(long, value_enum, env = "UV_KEYRING_PROVIDER")] pub(crate) keyring_provider: Option, - /// Locations to search for candidate distributions, beyond those found in the indexes. + /// The Python interpreter against which to compile the requirements. /// - /// If a path, the target must be a directory that contains package as wheel files (`.whl`) or - /// source distributions (`.tar.gz` or `.zip`) at the top level. + /// By default, `uv` uses the virtual environment in the current working directory or any parent + /// directory, falling back to searching for a Python executable in `PATH`. The `--python` + /// option allows you to specify a different interpreter. /// - /// If a URL, the page must contain a flat list of links to package files. - #[arg(long, short)] - pub(crate) find_links: Option>, + /// Supported formats: + /// - `3.10` looks for an installed Python 3.10 using `py --list-paths` on Windows, or + /// `python3.10` on Linux and macOS. + /// - `python3.10` or `python.exe` looks for a binary with the given name in `PATH`. + /// - `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path. + #[arg(long, verbatim_doc_comment, group = "discovery")] + pub(crate) python: Option, + + /// Install packages into the system Python. + /// + /// By default, `uv` uses the virtual environment in the current working directory or any parent + /// directory, falling back to searching for a Python executable in `PATH`. The `--system` + /// option instructs `uv` to avoid using a virtual environment Python and restrict its search to + /// the system path. + #[arg( + long, + env = "UV_SYSTEM_PYTHON", + group = "discovery", + overrides_with("no_system") + )] + pub(crate) system: bool, + + #[arg(long, overrides_with("system"))] + pub(crate) no_system: bool, /// Allow package upgrades, ignoring pinned versions in the existing output file. #[arg(long, short = 'U')] diff --git a/crates/uv/src/commands/pip_compile.rs b/crates/uv/src/commands/pip_compile.rs index 8f99152a83ca..006e80b04c58 100644 --- a/crates/uv/src/commands/pip_compile.rs +++ b/crates/uv/src/commands/pip_compile.rs @@ -27,7 +27,7 @@ use uv_configuration::{ use uv_dispatch::BuildDispatch; use uv_fs::Simplified; use uv_installer::Downloader; -use uv_interpreter::{find_best_python, PythonEnvironment}; +use uv_interpreter::{find_best_python, find_requested_python, PythonEnvironment}; use uv_normalize::{ExtraName, PackageName}; use uv_requirements::{ upgrade::read_lockfile, ExtrasSpecification, LookaheadResolver, NamedRequirementsResolver, @@ -80,6 +80,8 @@ pub(crate) async fn pip_compile( exclude_newer: Option, annotation_style: AnnotationStyle, link_mode: LinkMode, + python: Option, + system: bool, native_tls: bool, quiet: bool, cache: Cache, @@ -145,7 +147,12 @@ pub(crate) async fn pip_compile( } // Find an interpreter to use for building distributions - let interpreter = find_best_python(python_version.as_ref(), &cache)?; + let interpreter = if let Some(python) = python.as_ref() { + find_requested_python(python, &cache)? + .ok_or_else(|| uv_interpreter::Error::RequestedPythonNotFound(python.to_string()))? + } else { + find_best_python(python_version.as_ref(), system, &cache)? + }; debug!( "Using Python {} interpreter at {} for builds", interpreter.python_version(), diff --git a/crates/uv/src/main.rs b/crates/uv/src/main.rs index 83ccf1b3da4f..4e285ceea255 100644 --- a/crates/uv/src/main.rs +++ b/crates/uv/src/main.rs @@ -257,6 +257,8 @@ async fn run() -> Result { args.shared.exclude_newer, args.shared.annotation_style, args.shared.link_mode, + args.shared.python, + args.shared.system, globals.native_tls, globals.quiet, cache, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 63e79bd3261e..d6685ccd53bd 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -117,6 +117,9 @@ impl PipCompileSettings { index_strategy, keyring_provider, find_links, + python, + system, + no_system, upgrade, upgrade_package, generate_hashes, @@ -156,6 +159,8 @@ impl PipCompileSettings { // Shared settings. shared: PipSharedSettings::combine( PipOptions { + python, + system: flag(system, no_system), offline: flag(offline, no_offline), index_url: index_url.and_then(Maybe::into_option), extra_index_url: extra_index_url.map(|extra_index_urls| {