Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for pyenv locator #23356

Merged
merged 12 commits into from
May 7, 2024
14 changes: 1 addition & 13 deletions native_locator/src/conda.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -145,19 +146,6 @@ fn find_conda_binary_on_path(environment: &impl known::Environment) -> Option<Pa
None
}

fn find_python_binary_path(env_path: &Path) -> Option<PathBuf> {
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<PathBuf> {
let user_profile = environment.get_env_var("USERPROFILE".to_string()).unwrap();
Expand Down
3 changes: 3 additions & 0 deletions native_locator/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod logging;
mod messaging;
mod utils;
mod windows_python;
mod pyenv;

fn main() {
let mut dispatcher = create_dispatcher();
Expand All @@ -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!(
Expand Down
115 changes: 115 additions & 0 deletions native_locator/src/pyenv.rs
Original file line number Diff line number Diff line change
@@ -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<String> {
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<String> {
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<String> {
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<String> {
// 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<String> {
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
}
18 changes: 17 additions & 1 deletion native_locator/src/utils.rs
Original file line number Diff line number Diff line change
@@ -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<String> {
let output = Command::new(path)
Expand Down Expand Up @@ -32,3 +35,16 @@ pub fn get_version(path: &str) -> Option<String> {
}
get_version_impl(path)
}

pub fn find_python_binary_path(env_path: &Path) -> Option<PathBuf> {
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())
}
Loading