diff --git a/crates/gourgeist/src/interpreter.rs b/crates/gourgeist/src/interpreter.rs deleted file mode 100644 index e7a271767..000000000 --- a/crates/gourgeist/src/interpreter.rs +++ /dev/null @@ -1,46 +0,0 @@ -use camino::Utf8PathBuf; -use tracing::debug; - -/// Parse the value of the `-p`/`--python` option, which can be e.g. `3.11`, `python3.11`, -/// `tools/bin/python3.11` or `/usr/bin/python3.11`. -pub fn parse_python_cli(cli_python: Option) -> Result { - let python = if let Some(python) = cli_python { - if let Some((major, minor)) = python - .as_str() - .split_once('.') - .and_then(|(major, minor)| Some((major.parse::().ok()?, minor.parse::().ok()?))) - { - if major != 3 { - return Err(crate::Error::InvalidPythonInterpreter( - "Only python 3 is supported".into(), - )); - } - debug!("Looking for python {major}.{minor}"); - Utf8PathBuf::from(format!("python{major}.{minor}")) - } else { - python - } - } else { - Utf8PathBuf::from("python3".to_string()) - }; - - // Call `which` to find it in path, if not given a path - let python = if python.components().count() > 1 { - // Does this path contain a slash (unix) or backslash (windows)? In that case, assume it's - // relative or absolute path that we don't need to resolve - debug!("Assuming {python} is a path"); - python - } else { - let python_in_path = which::which(python.as_std_path()) - .map_err(|err| { - crate::Error::InvalidPythonInterpreter( - format!("Can't find {python} ({err})").into(), - ) - })? - .try_into() - .map_err(camino::FromPathBufError::into_io_error)?; - debug!("Resolved {python} to {python_in_path}"); - python_in_path - }; - Ok(python) -} diff --git a/crates/gourgeist/src/lib.rs b/crates/gourgeist/src/lib.rs index c4c2ec83c..49e140f8c 100644 --- a/crates/gourgeist/src/lib.rs +++ b/crates/gourgeist/src/lib.rs @@ -4,21 +4,19 @@ use std::path::Path; use camino::{FromPathError, Utf8Path}; use thiserror::Error; -pub use interpreter::parse_python_cli; use platform_host::PlatformError; use uv_interpreter::{Interpreter, Virtualenv}; pub use crate::bare::create_bare_venv; mod bare; -mod interpreter; #[derive(Debug, Error)] pub enum Error { #[error(transparent)] IO(#[from] io::Error), #[error("Failed to determine python interpreter to use")] - InvalidPythonInterpreter(#[source] Box), + InterpreterError(#[from] uv_interpreter::Error), #[error(transparent)] Platform(#[from] PlatformError), #[error("Reserved key used for pyvenv.cfg: {0}")] diff --git a/crates/gourgeist/src/main.rs b/crates/gourgeist/src/main.rs index 13691a898..95af2f3a4 100644 --- a/crates/gourgeist/src/main.rs +++ b/crates/gourgeist/src/main.rs @@ -11,16 +11,16 @@ use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::{fmt, EnvFilter}; -use gourgeist::{create_bare_venv, parse_python_cli, Prompt}; +use gourgeist::{create_bare_venv, Prompt}; use platform_host::Platform; use uv_cache::Cache; -use uv_interpreter::Interpreter; +use uv_interpreter::{find_default_python, find_requested_python}; #[derive(Parser, Debug)] struct Cli { path: Option, #[clap(short, long)] - python: Option, + python: Option, #[clap(long)] prompt: Option, } @@ -28,15 +28,25 @@ struct Cli { fn run() -> Result<(), gourgeist::Error> { let cli = Cli::parse(); let location = cli.path.unwrap_or(Utf8PathBuf::from(".venv")); - let python = parse_python_cli(cli.python)?; let platform = Platform::current()?; let cache = if let Some(project_dirs) = ProjectDirs::from("", "", "gourgeist") { Cache::from_path(project_dirs.cache_dir())? } else { Cache::from_path(".gourgeist_cache")? }; - let info = Interpreter::query(python.as_std_path(), &platform, &cache).unwrap(); - create_bare_venv(&location, &info, Prompt::from_args(cli.prompt), Vec::new())?; + let interpreter = if let Some(python_request) = &cli.python { + find_requested_python(python_request, &platform, &cache)?.ok_or( + uv_interpreter::Error::NoSuchPython(python_request.to_string()), + )? + } else { + find_default_python(&platform, &cache)? + }; + create_bare_venv( + &location, + &interpreter, + Prompt::from_args(cli.prompt), + Vec::new(), + )?; Ok(()) } diff --git a/crates/uv-interpreter/src/interpreter.rs b/crates/uv-interpreter/src/interpreter.rs index 86fe93fbd..cb46f20f0 100644 --- a/crates/uv-interpreter/src/interpreter.rs +++ b/crates/uv-interpreter/src/interpreter.rs @@ -6,7 +6,7 @@ use std::process::Command; use fs_err as fs; use once_cell::sync::OnceCell; use serde::{Deserialize, Serialize}; -use tracing::{debug, warn}; +use tracing::{debug, instrument, warn}; use cache_key::digest; use pep440_rs::Version; @@ -35,7 +35,11 @@ pub struct Interpreter { impl Interpreter { /// Detect the interpreter info for the given Python executable. - pub fn query(executable: &Path, platform: &Platform, cache: &Cache) -> Result { + pub(crate) fn query( + executable: &Path, + platform: &Platform, + cache: &Cache, + ) -> Result { let info = InterpreterInfo::query_cached(executable, cache)?; debug_assert!( @@ -77,7 +81,7 @@ impl Interpreter { /// Return a new [`Interpreter`] with the given base prefix. #[must_use] - pub fn with_base_prefix(self, base_prefix: PathBuf) -> Self { + pub(crate) fn with_base_prefix(self, base_prefix: PathBuf) -> Self { Self { base_prefix, ..self @@ -94,11 +98,21 @@ impl Interpreter { /// the first available version. /// /// See [`Self::find_version`] for details on the precedence of Python lookup locations. + #[instrument(skip_all, fields(?python_version))] pub fn find_best( python_version: Option<&PythonVersion>, platform: &Platform, cache: &Cache, ) -> Result { + if let Some(python_version) = python_version { + debug!( + "Starting interpreter discovery for Python {}", + python_version + ); + } else { + debug!("Starting interpreter discovery for active Python"); + } + // First, check for an exact match (or the first available version if no Python version was provided) if let Some(interpreter) = Self::find_version(python_version, platform, cache)? { return Ok(interpreter); @@ -139,7 +153,7 @@ impl Interpreter { /// /// If a version is provided and an interpreter cannot be found with the given version, /// we will return [`None`]. - pub fn find_version( + pub(crate) fn find_version( python_version: Option<&PythonVersion>, platform: &Platform, cache: &Cache, @@ -184,7 +198,7 @@ impl Interpreter { /// Find the Python interpreter in `PATH`, respecting `UV_PYTHON_PATH`. /// /// Returns `Ok(None)` if not found. - pub fn find_executable + Into + Copy>( + pub(crate) fn find_executable + Into + Copy>( requested: R, ) -> Result, Error> { let result = if let Some(isolated) = std::env::var_os("UV_TEST_PYTHON_PATH") { @@ -403,7 +417,11 @@ impl InterpreterInfo { match rmp_serde::from_slice::>(&data) { Ok(cached) => { if cached.timestamp == modified { - debug!("Using cached markers for: {}", executable.display()); + debug!( + "Cached interpreter info for Python {}, skipping probing: {}", + cached.data.markers.python_full_version, + executable.display() + ); return Ok(cached.data); } @@ -424,8 +442,13 @@ impl InterpreterInfo { } // Otherwise, run the Python script. - debug!("Detecting markers for: {}", executable.display()); + debug!("Probing interpreter info for: {}", executable.display()); let info = Self::query(executable)?; + debug!( + "Found Python {} for: {}", + info.markers.python_full_version, + executable.display() + ); // If `executable` is a pyenv shim, a bash script that redirects to the activated // python executable at another path, we're not allowed to cache the interpreter info. diff --git a/crates/uv-interpreter/src/python_query.rs b/crates/uv-interpreter/src/python_query.rs index fda52300a..a23e5c1d2 100644 --- a/crates/uv-interpreter/src/python_query.rs +++ b/crates/uv-interpreter/src/python_query.rs @@ -4,7 +4,7 @@ use std::borrow::Cow; use std::env; use std::path::PathBuf; -use tracing::instrument; +use tracing::{debug, instrument}; use platform_host::Platform; use uv_cache::Cache; @@ -23,11 +23,13 @@ use crate::{Error, Interpreter}; /// version (e.g. `python3.12` on unix) and error when the version mismatches, as a binary with the /// patch version (e.g. `python3.12.1`) is often not in `PATH` and we make the simplifying /// assumption that the user has only this one patch version installed. +#[instrument(skip_all, fields(%request))] pub fn find_requested_python( request: &str, platform: &Platform, cache: &Cache, ) -> Result, Error> { + debug!("Starting interpreter discovery for Python {}", request); let versions = request .splitn(3, '.') .map(str::parse::) @@ -70,7 +72,9 @@ pub fn find_requested_python( /// /// We prefer the test overwrite `UV_TEST_PYTHON_PATH` if it is set, otherwise `python3`/`python` or /// `python.exe` respectively. +#[instrument(skip_all)] pub fn find_default_python(platform: &Platform, cache: &Cache) -> Result { + debug!("Starting interpreter discovery for default Python"); try_find_default_python(platform, cache)?.ok_or(if cfg!(windows) { Error::NoPythonInstalledWindows } else if cfg!(unix) { @@ -100,7 +104,6 @@ pub(crate) fn try_find_default_python( /// * (windows): For each of the above, test for the existence of `python.bat` shim (pyenv-windows) last. /// /// (Windows): Filter out the windows store shim (Enabled in Settings/Apps/Advanced app settings/App execution aliases). -#[instrument(skip_all, fields(? selector))] fn find_python( selector: PythonVersionSelector, platform: &Platform, diff --git a/crates/uv-interpreter/src/python_version.rs b/crates/uv-interpreter/src/python_version.rs index e2567e029..6b75d1e44 100644 --- a/crates/uv-interpreter/src/python_version.rs +++ b/crates/uv-interpreter/src/python_version.rs @@ -1,5 +1,6 @@ use pep440_rs::Version; use pep508_rs::{MarkerEnvironment, StringVersion}; +use std::fmt::{Display, Formatter}; use std::ops::Deref; use std::str::FromStr; @@ -41,6 +42,12 @@ impl FromStr for PythonVersion { } } +impl Display for PythonVersion { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.0, f) + } +} + impl PythonVersion { /// Return a [`MarkerEnvironment`] compatible with the given [`PythonVersion`], based on /// a base [`MarkerEnvironment`]. diff --git a/crates/uv/src/confirm.rs b/crates/uv/src/confirm.rs index 3dc19fcfe..3b111d331 100644 --- a/crates/uv/src/confirm.rs +++ b/crates/uv/src/confirm.rs @@ -3,7 +3,7 @@ use console::{style, Key, Term}; /// Prompt the user for confirmation in the given [`Term`]. /// -/// This is a slimmed-down version of [`dialoguer::Confirm`], with the post-confirmation report +/// This is a slimmed-down version of `dialoguer::Confirm`, with the post-confirmation report /// enabled. pub(crate) fn confirm(message: &str, term: &Term, default: bool) -> Result { ctrlc::set_handler(move || { diff --git a/crates/uv/src/main.rs b/crates/uv/src/main.rs index 0aae1cff0..fd7009b2d 100644 --- a/crates/uv/src/main.rs +++ b/crates/uv/src/main.rs @@ -272,7 +272,7 @@ struct PipCompileArgs { #[clap(long)] refresh_package: Vec, - /// The URL of the Python package index (by default: https://pypi.org/simple). + /// The URL of the Python package index (by default: ). #[clap(long, short, env = "UV_INDEX_URL")] index_url: Option, @@ -404,7 +404,7 @@ struct PipSyncArgs { #[clap(long, value_enum, default_value_t = install_wheel_rs::linker::LinkMode::default())] link_mode: install_wheel_rs::linker::LinkMode, - /// The URL of the Python package index (by default: https://pypi.org/simple). + /// The URL of the Python package index (by default: ). #[clap(long, short, env = "UV_INDEX_URL")] index_url: Option, @@ -573,7 +573,7 @@ struct PipInstallArgs { #[clap(long, short)] output_file: Option, - /// The URL of the Python package index (by default: https://pypi.org/simple). + /// The URL of the Python package index (by default: ). #[clap(long, short, env = "UV_INDEX_URL")] index_url: Option, @@ -711,7 +711,7 @@ struct VenvArgs { #[clap(long, verbatim_doc_comment)] prompt: Option, - /// The URL of the Python package index (by default: https://pypi.org/simple). + /// The URL of the Python package index (by default: ). #[clap(long, short, env = "UV_INDEX_URL")] index_url: Option,