From 3b343ee9f50cae4be27b64a714ba6c57bab34e79 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 24 Jan 2024 12:29:27 -0500 Subject: [PATCH] Restructure interpreter queries --- crates/puffin-interpreter/src/interpreter.rs | 167 +++++++++---------- 1 file changed, 83 insertions(+), 84 deletions(-) diff --git a/crates/puffin-interpreter/src/interpreter.rs b/crates/puffin-interpreter/src/interpreter.rs index 9b5cf3ea4..cd285ad71 100644 --- a/crates/puffin-interpreter/src/interpreter.rs +++ b/crates/puffin-interpreter/src/interpreter.rs @@ -92,111 +92,78 @@ impl Interpreter { /// without comparing the patch version number. If that cannot be found, we fall back to /// the first available version. /// - /// See [`Self::find_strict`] for details on the precedence of Python lookup locations. - pub fn find_best( - python_version: Option<&PythonVersion>, - platform: &Platform, - cache: &Cache, - ) -> Result { - // 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); - } - - if let Some(python_version) = python_version { - // 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) = - Self::find_version(Some(&python_version.without_patch()), platform, cache)? - { - return Ok(interpreter); - } - } - - // If a Python version was requested but cannot be fulfilled, just take any version - if let Some(interpreter) = Self::find_version(None, platform, cache)? { - return Ok(interpreter); - } - } - - Err(Error::PythonNotFound) - } - - /// Find a Python interpreter. - /// /// We check, in order, the following locations: /// /// - `VIRTUAL_ENV` and `CONDA_PREFIX` /// - A `.venv` folder /// - If a python version is given: `pythonx.y` /// - `python3` (unix) or `python.exe` (windows) - /// - /// If a version is provided and an interpreter cannot be found with the given version, - /// we will return [`None`]. - pub fn find_version( + pub fn find_best( python_version: Option<&PythonVersion>, platform: &Platform, cache: &Cache, - ) -> Result, Error> { - let version_matches = |interpreter: &Self| -> bool { - if let Some(python_version) = python_version { - // If a patch version was provided, check for an exact match - python_version.is_satisfied_by(interpreter) - } else { - // The version always matches if one was not provided - true - } - }; - + ) -> Result { let platform = PythonPlatform::from(platform.to_owned()); - if let Some(venv) = detect_virtual_env(&platform)? { - let executable = platform.venv_python(venv); - let interpreter = Self::query(&executable, &platform.0, cache)?; - if version_matches(&interpreter) { - return Ok(Some(interpreter)); + // Locate the base interpreter, which is our first choice. + let base_interpreter = Self::find_generic(&platform, cache)?; + + // If a Python version was requested, look for an exact match. + if let Some(python_version) = python_version { + // If the base interpreter satisfies the requested Python version, we're done. + if let Some(interpreter) = + base_interpreter.filter(|interpreter| python_version.is_satisfied_by(interpreter)) + { + return Ok(interpreter); } - }; - if cfg!(unix) { - if let Some(python_version) = python_version { - let requested = format!( - "python{}.{}", - python_version.major(), - python_version.minor() - ); - if let Ok(executable) = which::which(&requested) { - debug!("Resolved {requested} to {}", executable.display()); - let interpreter = Interpreter::query(&executable, &platform.0, cache)?; - if version_matches(&interpreter) { - return Ok(Some(interpreter)); - } + // If we can find an interpreter with the requested Python version, we're done. + if let Some(interpreter) = Self::find_version(python_version, &platform, cache)? { + return Ok(interpreter); + } + + // 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) = + Self::find_version(&python_version.without_patch(), &platform, cache)? + { + return Ok(interpreter); } } + } else if let Some(interpreter) = base_interpreter { + return Ok(interpreter); + } - if let Ok(executable) = which::which("python3") { - debug!("Resolved python3 to {}", executable.display()); + Err(Error::PythonNotFound) + } + + /// Find a Python interpreter that matches the given version. + /// + /// If an interpreter cannot be found with the given version, we will return [`None`]. + fn find_version( + python_version: &PythonVersion, + platform: &PythonPlatform, + cache: &Cache, + ) -> Result, Error> { + if cfg!(unix) { + let requested = format!( + "python{}.{}", + python_version.major(), + python_version.minor() + ); + if let Ok(executable) = which::which(&requested) { + debug!("Resolved {requested} to {}", executable.display()); let interpreter = Interpreter::query(&executable, &platform.0, cache)?; - if version_matches(&interpreter) { + if python_version.is_satisfied_by(&interpreter) { return Ok(Some(interpreter)); } } } else if cfg!(windows) { - if let Some(python_version) = python_version { - if let Some(path) = - find_python_windows(python_version.major(), python_version.minor())? - { - let interpreter = Interpreter::query(&path, &platform.0, cache)?; - if version_matches(&interpreter) { - return Ok(Some(interpreter)); - } - } - } - - if let Ok(executable) = which::which("python.exe") { - let interpreter = Interpreter::query(&executable, &platform.0, cache)?; - if version_matches(&interpreter) { + if let Some(path) = find_python_windows(python_version.major(), python_version.minor())? + { + let interpreter = Interpreter::query(&path, &platform.0, cache)?; + if python_version.is_satisfied_by(&interpreter) { return Ok(Some(interpreter)); } } @@ -207,6 +174,38 @@ impl Interpreter { Ok(None) } + /// Find a generic Python interpreter. + /// + /// We check, in order, the following locations: + /// + /// - `VIRTUAL_ENV` and `CONDA_PREFIX` + /// - A `.venv` folder + /// - `python3` (unix) or `python.exe` (windows) + fn find_generic(platform: &PythonPlatform, cache: &Cache) -> Result, Error> { + if let Some(venv) = detect_virtual_env(platform)? { + let executable = platform.venv_python(venv); + let interpreter = Self::query(&executable, &platform.0, cache)?; + return Ok(Some(interpreter)); + }; + + if cfg!(unix) { + if let Ok(executable) = which::which("python3") { + debug!("Resolved python3 to {}", executable.display()); + let interpreter = Interpreter::query(&executable, &platform.0, cache)?; + return Ok(Some(interpreter)); + } + } else if cfg!(windows) { + if let Ok(executable) = which::which("python.exe") { + let interpreter = Interpreter::query(&executable, &platform.0, cache)?; + return Ok(Some(interpreter)); + } + } else { + unimplemented!("Only Windows and Unix are supported"); + } + + Ok(None) + } + /// Returns the path to the Python virtual environment. #[inline] pub fn platform(&self) -> &Platform {