diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 1bb579e5d218..12e8276de474 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -3,6 +3,7 @@ use crate::known; use crate::messaging; +use crate::utils::find_python_binary_path; use regex::Regex; use std::env; use std::path::{Path, PathBuf}; @@ -145,19 +146,6 @@ fn find_conda_binary_on_path(environment: &impl known::Environment) -> Option Option { - let python_bin_name = if cfg!(windows) { - "python.exe" - } else { - "python" - }; - let path_1 = env_path.join("bin").join(python_bin_name); - let path_2 = env_path.join("Scripts").join(python_bin_name); - let path_3 = env_path.join(python_bin_name); - let paths = vec![path_1, path_2, path_3]; - paths.into_iter().find(|path| path.exists()) -} - #[cfg(windows)] fn get_known_conda_locations(environment: &impl known::Environment) -> Vec { let user_profile = environment.get_env_var("USERPROFILE".to_string()).unwrap(); diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index fac7050259de..0d61547d081f 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -13,6 +13,7 @@ mod logging; mod messaging; mod utils; mod windows_python; +mod pyenv; fn main() { let mut dispatcher = create_dispatcher(); @@ -31,6 +32,8 @@ fn main() { #[cfg(windows)] windows_python::find_and_report(&mut dispatcher, &environment); + pyenv::find_and_report(&mut dispatcher, &environment); + match now.elapsed() { Ok(elapsed) => { dispatcher.log_info(&format!( diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs new file mode 100644 index 000000000000..2c34f8848381 --- /dev/null +++ b/native_locator/src/pyenv.rs @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::fs; +use std::path::PathBuf; + +use crate::known; +use crate::messaging; +use crate::utils::find_python_binary_path; + +#[cfg(windows)] +fn get_home_pyenv_dir(environment: &impl known::Environment) -> Option { + let home = environment.get_user_home()?; + PathBuf::from(home) + .join(".pyenv") + .join("pyenv-win") + .into_os_string() + .into_string() + .ok() +} + +#[cfg(unix)] +fn get_home_pyenv_dir(environment: &impl known::Environment) -> Option { + let home = environment.get_user_home()?; + PathBuf::from(home) + .join(".pyenv") + .into_os_string() + .into_string() + .ok() +} + +fn get_binary_from_known_paths(environment: &impl known::Environment) -> Option { + for known_path in environment.get_know_global_search_locations() { + let bin = known_path.join("pyenv"); + if bin.exists() { + return bin.into_os_string().into_string().ok(); + } + } + None +} + +fn get_pyenv_dir(environment: &impl known::Environment) -> Option { + // Check if the pyenv environment variables exist: PYENV on Windows, PYENV_ROOT on Unix. + // They contain the path to pyenv's installation folder. + // If they don't exist, use the default path: ~/.pyenv/pyenv-win on Windows, ~/.pyenv on Unix. + // If the interpreter path starts with the path to the pyenv folder, then it is a pyenv environment. + // See https://github.com/pyenv/pyenv#locating-the-python-installation for general usage, + // And https://github.com/pyenv-win/pyenv-win for Windows specifics. + + match environment.get_env_var("PYENV_ROOT".to_string()) { + Some(dir) => Some(dir), + None => match environment.get_env_var("PYENV".to_string()) { + Some(dir) => Some(dir), + None => get_home_pyenv_dir(environment), + }, + } +} + +fn get_pyenv_binary(environment: &impl known::Environment) -> Option { + let dir = get_pyenv_dir(environment)?; + let exe = PathBuf::from(dir).join("bin").join("pyenv"); + if fs::metadata(&exe).is_ok() { + exe.into_os_string().into_string().ok() + } else { + get_binary_from_known_paths(environment) + } +} + +pub fn find_and_report( + dispatcher: &mut impl messaging::MessageDispatcher, + environment: &impl known::Environment, +) -> Option<()> { + let pyenv_dir = get_pyenv_dir(environment)?; + + if let Some(pyenv_binary) = get_pyenv_binary(environment) { + let params = messaging::EnvManager::new(vec![pyenv_binary], None); + let message = messaging::EnvManagerMessage::new(params); + dispatcher.send_message(message); + } + + let versions_dir = PathBuf::from(&pyenv_dir) + .join("versions") + .into_os_string() + .into_string() + .ok()?; + + let pyenv_binary_for_activation = match get_pyenv_binary(environment) { + Some(binary) => binary, + None => "pyenv".to_string(), + }; + for entry in fs::read_dir(&versions_dir).ok()? { + if let Ok(path) = entry { + let path = path.path(); + if path.is_dir() { + if let Some(executable) = find_python_binary_path(&path) { + let version = path.file_name().unwrap().to_string_lossy().to_string(); + dispatcher.send_message(messaging::PythonEnvironment::new( + "Python".to_string(), + vec![executable.into_os_string().into_string().unwrap()], + "Pyenv".to_string(), + Some(version.clone()), + Some(vec![ + pyenv_binary_for_activation.clone(), + "shell".to_string(), + version, + ]), + Some(path.into_os_string().into_string().unwrap()), + )); + } + } + } + } + + None +} diff --git a/native_locator/src/utils.rs b/native_locator/src/utils.rs index d3573e3190a1..7a235a251942 100644 --- a/native_locator/src/utils.rs +++ b/native_locator/src/utils.rs @@ -1,7 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -use std::process::Command; +use std::{ + path::{Path, PathBuf}, + process::Command, +}; fn get_version_impl(path: &str) -> Option { let output = Command::new(path) @@ -32,3 +35,16 @@ pub fn get_version(path: &str) -> Option { } get_version_impl(path) } + +pub fn find_python_binary_path(env_path: &Path) -> Option { + let python_bin_name = if cfg!(windows) { + "python.exe" + } else { + "python" + }; + let path_1 = env_path.join("bin").join(python_bin_name); + let path_2 = env_path.join("Scripts").join(python_bin_name); + let path_3 = env_path.join(python_bin_name); + let paths = vec![path_1, path_2, path_3]; + paths.into_iter().find(|path| path.exists()) +}