Improve interpreter discovery logging (#1909)

We had several cases where interpreter discovery fails. This PR improves
the verbose output to ensure interpreter discovery is debuggable for a
user.

In the process, i removed the custom gourgeist logic for the
uv_interpreter logic.

**venv creation**

```
$ uv venv -v -p 3.10
 uv_interpreter::python_query::find_requested_python request=3.10
      0.002389s   0ms DEBUG uv_interpreter::python_query Starting interpreter discovery for Python 3.10
   uv_interpreter::python_query::windows::py_list_paths
      0.016288s  14ms DEBUG uv_interpreter::interpreter Probing interpreter info for: C:\Users\Ferris\AppData\Local\Programs\Python\Python312\python.exe
      0.072860s  70ms DEBUG uv_interpreter::interpreter Found Python 3.12.1 for: C:\Users\Ferris\AppData\Local\Programs\Python\Python312\python.exe
      0.074303s  72ms DEBUG uv_interpreter::interpreter Probing interpreter info for: C:\Users\Ferris\AppData\Local\Programs\Python\Python38\python.exe
      0.134311s 132ms DEBUG uv_interpreter::interpreter Found Python 3.8.10 for: C:\Users\Ferris\AppData\Local\Programs\Python\Python38\python.exe
  x No Python 3.10 found through `py --list-paths` or in `PATH`. Is Python 3.10 installed?
error: process didn't exit successfully: `target\debug\uv.exe venv -v -p 3.10` (exit code: 1)
```

```
$ uv venv -v -p 3.10
 uv_interpreter::python_query::find_requested_python request=3.10
      0.001889s   0ms DEBUG uv_interpreter::python_query Starting interpreter discovery for Python 3.10
   uv_interpreter::python_query::windows::py_list_paths
      0.021488s  19ms DEBUG uv_interpreter::interpreter Cached interpreter info for Python 3.12.1, skipping probing: C:\Users\Ferris\AppData\Local\Programs\Python\Python312\python.exe
      0.021945s  20ms DEBUG uv_interpreter::interpreter Cached interpreter info for Python 3.8.10, skipping probing: C:\Users\Ferris\AppData\Local\Programs\Python\Python38\python.exe
  x No Python 3.10 found through `py --list-paths` or in `PATH`. Is Python 3.10 installed?
error: process didn't exit successfully: `target\debug\uv.exe venv -v -p 3.10` (exit code: 1)
```

```
$ uv venv -v -p 3.8
 uv_interpreter::python_query::find_requested_python request=3.8
      0.001896s   0ms DEBUG uv_interpreter::python_query Starting interpreter discovery for Python 3.8
   uv_interpreter::python_query::windows::py_list_paths
      0.013541s  11ms DEBUG uv_interpreter::interpreter Cached interpreter info for Python 3.8.10, skipping probing: C:\Users\Ferris\AppData\Local\Programs\Python\Python38\python.exe
Using Python 3.8.10 interpreter at C:\Users\Ferris\AppData\Local\Programs\Python\Python38\python.exe
Creating virtualenv at: .venv
Activate with: .venv\Scripts\activate
```

```
$ uv venv -v -p 3.12
 uv_interpreter::python_query::find_requested_python request=3.12
      0.001741s   0ms DEBUG uv_interpreter::python_query Starting interpreter discovery for Python 3.12
   uv_interpreter::python_query::windows::py_list_paths
      0.012807s  11ms DEBUG uv_interpreter::interpreter Cached interpreter info for Python 3.12.1, skipping probing: C:\Users\Ferris\AppData\Local\Programs\Python\Python312\python.exe
Using Python 3.12.1 interpreter at C:\Users\Ferris\AppData\Local\Programs\Python\Python312\python.exe
Creating virtualenv at: .venv
Activate with: .venv\Scripts\activate
```

**pip compile**

```
$ uv pip compile -v .\scripts\requirements\black.in
   uv::requirements::from_source source=.\scripts\requirements\black.in
   uv_interpreter::interpreter::find_best python_version=None
        0.002071s   0ms DEBUG uv_interpreter::interpreter Starting interpreter discovery for active Python
        0.002220s   0ms DEBUG uv_interpreter::virtual_env Found a virtualenv named .venv at: C:\Users\Ferris\projects\uv\.venv
        0.002483s   0ms DEBUG uv_interpreter::interpreter Cached interpreter info for Python 3.12.1, skipping probing: C:\Users\Ferris\projects\uv\.venv\Scripts\python.exe
      0.002581s DEBUG uv::commands::pip_compile Using Python 3.12.1 interpreter at C:\Users\Ferris\projects\uv\.venv\Scripts\python.exe for builds
```

```
$ uv pip compile -p 3.8 -v .\scripts\requirements\black.in
   uv::requirements::from_source source=.\scripts\requirements\black.in
   uv_interpreter::interpreter::find_best python_version=Some(PythonVersion(StringVersion { string: "3.8", version: "3.8" }))
        0.002001s   0ms DEBUG uv_interpreter::interpreter Starting interpreter discovery for Python 3.8
        0.002146s   0ms DEBUG uv_interpreter::virtual_env Found a virtualenv named .venv at: C:\Users\Ferris\projects\uv\.venv
        0.002378s   0ms DEBUG uv_interpreter::interpreter Cached interpreter info for Python 3.12.1, skipping probing: C:\Users\Ferris\projects\uv\.venv\Scripts\python.exe
     uv_interpreter::python_query::find_requested_python request=3.8
          0.002509s   0ms DEBUG uv_interpreter::python_query Starting interpreter discovery for Python 3.8
       uv_interpreter::python_query::windows::py_list_paths
          0.015989s  13ms DEBUG uv_interpreter::interpreter Cached interpreter info for Python 3.8.10, skipping probing: C:\Users\Ferris\AppData\Local\Programs\Python\Python38\python.exe
      0.016144s DEBUG uv::commands::pip_compile Using Python 3.8.10 interpreter at C:\Users\Ferris\AppData\Local\Programs\Python\Python38\python.exe for builds

```

```
$ uv pip compile -p 3.10 -v .\scripts\requirements\black.in
   uv::requirements::from_source source=.\scripts\requirements\black.in
   uv_interpreter::interpreter::find_best python_version=Some(PythonVersion(StringVersion { string: "3.10", version: "3.10" }))
        0.002086s   0ms DEBUG uv_interpreter::interpreter Starting interpreter discovery for Python 3.10
        0.002234s   0ms DEBUG uv_interpreter::virtual_env Found a virtualenv named .venv at: C:\Users\Ferris\projects\uv\.venv
        0.002462s   0ms DEBUG uv_interpreter::interpreter Cached interpreter info for Python 3.12.1, skipping probing: C:\Users\Ferris\projects\uv\.venv\Scripts\python.exe
     uv_interpreter::python_query::find_requested_python request=3.10
          0.002589s   0ms DEBUG uv_interpreter::python_query Starting interpreter discovery for Python 3.10
       uv_interpreter::python_query::windows::py_list_paths
          0.017299s  14ms DEBUG uv_interpreter::interpreter Cached interpreter info for Python 3.12.1, skipping probing: C:\Users\Ferris\AppData\Local\Programs\Python\Python312\python.exe
          0.018135s  15ms DEBUG uv_interpreter::interpreter Cached interpreter info for Python 3.8.10, skipping probing: C:\Users\Ferris\AppData\Local\Programs\Python\Python38\python.exe
        0.020176s  18ms DEBUG uv_interpreter::virtual_env Found a virtualenv named .venv at: C:\Users\Ferris\projects\uv\.venv
        0.020873s  18ms DEBUG uv_interpreter::interpreter Cached interpreter info for Python 3.12.1, skipping probing: C:\Users\Ferris\projects\uv\.venv\Scripts\python.exe
      0.021116s DEBUG uv::commands::pip_compile Using Python 3.12.1 interpreter at C:\Users\Ferris\projects\uv\.venv\Scripts\python.exe for builds
  warning: The requested Python version 3.10 is not available; 3.12.1 will be used to build dependencies instead.
```
This commit is contained in:
konsti 2024-02-23 19:43:46 +01:00 committed by GitHub
parent 11ed4f7183
commit 9cf7d113bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 64 additions and 69 deletions

View file

@ -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<Utf8PathBuf>) -> Result<Utf8PathBuf, crate::Error> {
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::<u8>().ok()?, minor.parse::<u8>().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)
}

View file

@ -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<dyn std::error::Error + Sync + Send>),
InterpreterError(#[from] uv_interpreter::Error),
#[error(transparent)]
Platform(#[from] PlatformError),
#[error("Reserved key used for pyvenv.cfg: {0}")]

View file

@ -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<Utf8PathBuf>,
#[clap(short, long)]
python: Option<Utf8PathBuf>,
python: Option<String>,
#[clap(long)]
prompt: Option<String>,
}
@ -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(())
}

View file

@ -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<Self, Error> {
pub(crate) fn query(
executable: &Path,
platform: &Platform,
cache: &Cache,
) -> Result<Self, Error> {
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<Self, Error> {
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<R: AsRef<OsStr> + Into<OsString> + Copy>(
pub(crate) fn find_executable<R: AsRef<OsStr> + Into<OsString> + Copy>(
requested: R,
) -> Result<Option<PathBuf>, 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::<CachedByTimestamp<Self>>(&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.

View file

@ -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<Option<Interpreter>, Error> {
debug!("Starting interpreter discovery for Python {}", request);
let versions = request
.splitn(3, '.')
.map(str::parse::<u8>)
@ -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<Interpreter, Error> {
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,

View file

@ -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`].

View file

@ -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<bool> {
ctrlc::set_handler(move || {

View file

@ -272,7 +272,7 @@ struct PipCompileArgs {
#[clap(long)]
refresh_package: Vec<PackageName>,
/// The URL of the Python package index (by default: https://pypi.org/simple).
/// The URL of the Python package index (by default: <https://pypi.org/simple>).
#[clap(long, short, env = "UV_INDEX_URL")]
index_url: Option<IndexUrl>,
@ -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: <https://pypi.org/simple>).
#[clap(long, short, env = "UV_INDEX_URL")]
index_url: Option<IndexUrl>,
@ -573,7 +573,7 @@ struct PipInstallArgs {
#[clap(long, short)]
output_file: Option<PathBuf>,
/// The URL of the Python package index (by default: https://pypi.org/simple).
/// The URL of the Python package index (by default: <https://pypi.org/simple>).
#[clap(long, short, env = "UV_INDEX_URL")]
index_url: Option<IndexUrl>,
@ -711,7 +711,7 @@ struct VenvArgs {
#[clap(long, verbatim_doc_comment)]
prompt: Option<String>,
/// The URL of the Python package index (by default: https://pypi.org/simple).
/// The URL of the Python package index (by default: <https://pypi.org/simple>).
#[clap(long, short, env = "UV_INDEX_URL")]
index_url: Option<IndexUrl>,