Skip to content

Commit 7546f1e

Browse files
committed
Support for pyenv locator (#23356)
1 parent 9c29fd1 commit 7546f1e

File tree

4 files changed

+136
-14
lines changed

4 files changed

+136
-14
lines changed

native_locator/src/conda.rs

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
use crate::known;
55
use crate::messaging;
6+
use crate::utils::find_python_binary_path;
67
use regex::Regex;
78
use std::env;
89
use std::path::{Path, PathBuf};
@@ -145,19 +146,6 @@ fn find_conda_binary_on_path(environment: &impl known::Environment) -> Option<Pa
145146
None
146147
}
147148

148-
fn find_python_binary_path(env_path: &Path) -> Option<PathBuf> {
149-
let python_bin_name = if cfg!(windows) {
150-
"python.exe"
151-
} else {
152-
"python"
153-
};
154-
let path_1 = env_path.join("bin").join(python_bin_name);
155-
let path_2 = env_path.join("Scripts").join(python_bin_name);
156-
let path_3 = env_path.join(python_bin_name);
157-
let paths = vec![path_1, path_2, path_3];
158-
paths.into_iter().find(|path| path.exists())
159-
}
160-
161149
#[cfg(windows)]
162150
fn get_known_conda_locations(environment: &impl known::Environment) -> Vec<PathBuf> {
163151
let user_profile = environment.get_env_var("USERPROFILE".to_string()).unwrap();

native_locator/src/main.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ mod logging;
1313
mod messaging;
1414
mod utils;
1515
mod windows_python;
16+
mod pyenv;
1617

1718
fn main() {
1819
let mut dispatcher = create_dispatcher();
@@ -31,6 +32,8 @@ fn main() {
3132
#[cfg(windows)]
3233
windows_python::find_and_report(&mut dispatcher, &environment);
3334

35+
pyenv::find_and_report(&mut dispatcher, &environment);
36+
3437
match now.elapsed() {
3538
Ok(elapsed) => {
3639
dispatcher.log_info(&format!(

native_locator/src/pyenv.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use std::fs;
5+
use std::path::PathBuf;
6+
7+
use crate::known;
8+
use crate::messaging;
9+
use crate::utils::find_python_binary_path;
10+
11+
#[cfg(windows)]
12+
fn get_home_pyenv_dir(environment: &impl known::Environment) -> Option<String> {
13+
let home = environment.get_user_home()?;
14+
PathBuf::from(home)
15+
.join(".pyenv")
16+
.join("pyenv-win")
17+
.into_os_string()
18+
.into_string()
19+
.ok()
20+
}
21+
22+
#[cfg(unix)]
23+
fn get_home_pyenv_dir(environment: &impl known::Environment) -> Option<String> {
24+
let home = environment.get_user_home()?;
25+
PathBuf::from(home)
26+
.join(".pyenv")
27+
.into_os_string()
28+
.into_string()
29+
.ok()
30+
}
31+
32+
fn get_binary_from_known_paths(environment: &impl known::Environment) -> Option<String> {
33+
for known_path in environment.get_know_global_search_locations() {
34+
let bin = known_path.join("pyenv");
35+
if bin.exists() {
36+
return bin.into_os_string().into_string().ok();
37+
}
38+
}
39+
None
40+
}
41+
42+
fn get_pyenv_dir(environment: &impl known::Environment) -> Option<String> {
43+
// Check if the pyenv environment variables exist: PYENV on Windows, PYENV_ROOT on Unix.
44+
// They contain the path to pyenv's installation folder.
45+
// If they don't exist, use the default path: ~/.pyenv/pyenv-win on Windows, ~/.pyenv on Unix.
46+
// If the interpreter path starts with the path to the pyenv folder, then it is a pyenv environment.
47+
// See https://github.com/pyenv/pyenv#locating-the-python-installation for general usage,
48+
// And https://github.com/pyenv-win/pyenv-win for Windows specifics.
49+
50+
match environment.get_env_var("PYENV_ROOT".to_string()) {
51+
Some(dir) => Some(dir),
52+
None => match environment.get_env_var("PYENV".to_string()) {
53+
Some(dir) => Some(dir),
54+
None => get_home_pyenv_dir(environment),
55+
},
56+
}
57+
}
58+
59+
fn get_pyenv_binary(environment: &impl known::Environment) -> Option<String> {
60+
let dir = get_pyenv_dir(environment)?;
61+
let exe = PathBuf::from(dir).join("bin").join("pyenv");
62+
if fs::metadata(&exe).is_ok() {
63+
exe.into_os_string().into_string().ok()
64+
} else {
65+
get_binary_from_known_paths(environment)
66+
}
67+
}
68+
69+
pub fn find_and_report(
70+
dispatcher: &mut impl messaging::MessageDispatcher,
71+
environment: &impl known::Environment,
72+
) -> Option<()> {
73+
let pyenv_dir = get_pyenv_dir(environment)?;
74+
75+
if let Some(pyenv_binary) = get_pyenv_binary(environment) {
76+
let params = messaging::EnvManager::new(vec![pyenv_binary], None);
77+
let message = messaging::EnvManagerMessage::new(params);
78+
dispatcher.send_message(message);
79+
}
80+
81+
let versions_dir = PathBuf::from(&pyenv_dir)
82+
.join("versions")
83+
.into_os_string()
84+
.into_string()
85+
.ok()?;
86+
87+
let pyenv_binary_for_activation = match get_pyenv_binary(environment) {
88+
Some(binary) => binary,
89+
None => "pyenv".to_string(),
90+
};
91+
for entry in fs::read_dir(&versions_dir).ok()? {
92+
if let Ok(path) = entry {
93+
let path = path.path();
94+
if path.is_dir() {
95+
if let Some(executable) = find_python_binary_path(&path) {
96+
let version = path.file_name().unwrap().to_string_lossy().to_string();
97+
dispatcher.send_message(messaging::PythonEnvironment::new(
98+
"Python".to_string(),
99+
vec![executable.into_os_string().into_string().unwrap()],
100+
"Pyenv".to_string(),
101+
Some(version.clone()),
102+
Some(vec![
103+
pyenv_binary_for_activation.clone(),
104+
"shell".to_string(),
105+
version,
106+
]),
107+
Some(path.into_os_string().into_string().unwrap()),
108+
));
109+
}
110+
}
111+
}
112+
}
113+
114+
None
115+
}

native_locator/src/utils.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4-
use std::process::Command;
4+
use std::{
5+
path::{Path, PathBuf},
6+
process::Command,
7+
};
58

69
fn get_version_impl(path: &str) -> Option<String> {
710
let output = Command::new(path)
@@ -32,3 +35,16 @@ pub fn get_version(path: &str) -> Option<String> {
3235
}
3336
get_version_impl(path)
3437
}
38+
39+
pub fn find_python_binary_path(env_path: &Path) -> Option<PathBuf> {
40+
let python_bin_name = if cfg!(windows) {
41+
"python.exe"
42+
} else {
43+
"python"
44+
};
45+
let path_1 = env_path.join("bin").join(python_bin_name);
46+
let path_2 = env_path.join("Scripts").join(python_bin_name);
47+
let path_3 = env_path.join(python_bin_name);
48+
let paths = vec![path_1, path_2, path_3];
49+
paths.into_iter().find(|path| path.exists())
50+
}

0 commit comments

Comments
 (0)