From 5ad373b2ec612727ac149643c89bbff970759f07 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 8 May 2024 23:25:21 -0400 Subject: [PATCH] Skip Python 2 versions when locating Python (#3476) ## Summary Unfortunately, the `-I` flag was added in Python 3.4. So if we query a Python version prior to 3.4 (e.g., Python 2.7), we can't run our script at all, and lose the ability to match against our structured error. This PR adds an additional check against the stderr output for these cases. Closes https://github.com/astral-sh/uv/issues/3474. ## Test Plan Installed Python 2.7, and verified that it was skipped (and that we instead found my `python3`). --- crates/uv-interpreter/src/find_python.rs | 87 ++++++++++++++++++------ crates/uv-interpreter/src/lib.rs | 2 + 2 files changed, 70 insertions(+), 19 deletions(-) diff --git a/crates/uv-interpreter/src/find_python.rs b/crates/uv-interpreter/src/find_python.rs index fd986874e..5378e30ab 100644 --- a/crates/uv-interpreter/src/find_python.rs +++ b/crates/uv-interpreter/src/find_python.rs @@ -150,19 +150,77 @@ fn find_python( let interpreter = match Interpreter::query(&path, cache) { Ok(interpreter) => interpreter, - Err( - err @ Error::QueryScript { - err: InterpreterInfoError::UnsupportedPythonVersion { .. }, - .. - }, - ) => { - if selector.major() <= Some(2) { - return Err(err); + + // If the Python version is < 3.4, the `-I` flag is not supported, so + // we can't run the script at all, and need to sniff it from the output. + Err(Error::PythonSubcommandOutput { stderr, .. }) + if stderr.contains("Unknown option: -I") + && stderr.contains("usage: python [option]") => + { + // If the user _requested_ a version prior to 3.4, raise an error, as + // 3.4 is the minimum supported version for invoking the interpreter + // query script at all. + match selector { + PythonVersionSelector::Major(major) if major < 3 => { + return Err(Error::UnsupportedPython(major.to_string())); + } + PythonVersionSelector::MajorMinor(major, minor) + if (major, minor) < (3, 4) => + { + return Err(Error::UnsupportedPython(format!( + "{major}.{minor}" + ))); + } + PythonVersionSelector::MajorMinorPatch(major, minor, patch) + if (major, minor) < (3, 4) => + { + return Err(Error::UnsupportedPython(format!( + "{major}.{minor}.{patch}" + ))); + } + _ => {} } - // Skip over Python 2 or older installation when querying for a recent python installation. - debug!("Found a Python 2 installation that isn't supported by uv, skipping."); + + debug!( + "Found a Python installation that isn't supported by uv, skipping." + ); continue; } + + Err(Error::QueryScript { + err: InterpreterInfoError::UnsupportedPythonVersion { .. }, + .. + }) => { + // If the user _requested_ a version prior to 3.7, raise an error, as + // 3.7 is the minimum supported version for running the interpreter + // query script. + match selector { + PythonVersionSelector::Major(major) if major < 3 => { + return Err(Error::UnsupportedPython(major.to_string())); + } + PythonVersionSelector::MajorMinor(major, minor) + if (major, minor) < (3, 7) => + { + return Err(Error::UnsupportedPython(format!( + "{major}.{minor}" + ))); + } + PythonVersionSelector::MajorMinorPatch(major, minor, patch) + if (major, minor) < (3, 7) => + { + return Err(Error::UnsupportedPython(format!( + "{major}.{minor}.{patch}" + ))); + } + _ => {} + } + + debug!( + "Found a Python installation that isn't supported by uv, skipping." + ); + continue; + } + Err(error) => return Err(error), }; @@ -400,15 +458,6 @@ impl PythonVersionSelector { ], } } - - fn major(self) -> Option { - match self { - Self::Default => None, - Self::Major(major) => Some(major), - Self::MajorMinor(major, _) => Some(major), - Self::MajorMinorPatch(major, _, _) => Some(major), - } - } } fn warn_on_unsupported_python(interpreter: &Interpreter) { diff --git a/crates/uv-interpreter/src/lib.rs b/crates/uv-interpreter/src/lib.rs index 10a29b4ac..7b02197ad 100644 --- a/crates/uv-interpreter/src/lib.rs +++ b/crates/uv-interpreter/src/lib.rs @@ -74,6 +74,8 @@ pub enum Error { stdout: String, stderr: String, }, + #[error("Requested Python version ({0}) is unsupported")] + UnsupportedPython(String), #[error("Failed to write to cache")] Encode(#[from] rmp_serde::encode::Error), #[error("Broken virtualenv: Failed to parse pyvenv.cfg")]