Skip to content

Commit a2f167e

Browse files
authored
Better py env version extraction (#23368)
1 parent bc01bcb commit a2f167e

File tree

48 files changed

+146
-8
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+146
-8
lines changed

.github/workflows/pr-check.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ jobs:
332332
path: ${{ env.special-working-directory-relative }}
333333

334334
- name: Native Locator tests
335-
run: cargo test
335+
run: cargo test -- --nocapture
336336
working-directory: ${{ env.special-working-directory }}/native_locator
337337

338338
smoke-tests:

native_locator/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ pub mod common_python;
77
pub mod logging;
88
pub mod conda;
99
pub mod known;
10+
pub mod pyenv;

native_locator/src/pyenv.rs

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
use regex::Regex;
45
use std::fs;
56
use std::path::PathBuf;
67

@@ -66,6 +67,38 @@ fn get_pyenv_binary(environment: &impl known::Environment) -> Option<String> {
6667
}
6768
}
6869

70+
fn get_pyenv_version(folder_name: String) -> Option<String> {
71+
// Stable Versions = like 3.10.10
72+
let python_regex = Regex::new(r"^(\d+\.\d+\.\d+)$").unwrap();
73+
match python_regex.captures(&folder_name) {
74+
Some(captures) => match captures.get(1) {
75+
Some(version) => Some(version.as_str().to_string()),
76+
None => None,
77+
},
78+
None => {
79+
// Dev Versions = like 3.10-dev
80+
let python_regex = Regex::new(r"^(\d+\.\d+-dev)$").unwrap();
81+
match python_regex.captures(&folder_name) {
82+
Some(captures) => match captures.get(1) {
83+
Some(version) => Some(version.as_str().to_string()),
84+
None => None,
85+
},
86+
None => {
87+
// Alpha Versions = like 3.10.0a3
88+
let python_regex = Regex::new(r"^(\d+\.\d+.\d+a\d+)").unwrap();
89+
match python_regex.captures(&folder_name) {
90+
Some(captures) => match captures.get(1) {
91+
Some(version) => Some(version.as_str().to_string()),
92+
None => None,
93+
},
94+
None => None,
95+
}
96+
}
97+
}
98+
}
99+
}
100+
}
101+
69102
pub fn find_and_report(
70103
dispatcher: &mut impl messaging::MessageDispatcher,
71104
environment: &impl known::Environment,
@@ -91,18 +124,29 @@ pub fn find_and_report(
91124
let path = path.path();
92125
if path.is_dir() {
93126
if let Some(executable) = find_python_binary_path(&path) {
94-
let version = path.file_name().unwrap().to_string_lossy().to_string();
127+
let version =
128+
get_pyenv_version(path.file_name().unwrap().to_string_lossy().to_string());
129+
130+
// If we cannot extract version, this isn't a valid pyenv environment.
131+
// Or its one that we're not interested in.
132+
if version.is_none() {
133+
continue;
134+
}
95135
let env_path = path.to_string_lossy().to_string();
136+
let activated_run = match version.clone() {
137+
Some(version) => Some(vec![
138+
pyenv_binary_for_activation.clone(),
139+
"local".to_string(),
140+
version.clone(),
141+
]),
142+
None => None,
143+
};
96144
dispatcher.report_environment(messaging::PythonEnvironment::new(
97145
"Python".to_string(),
98146
vec![executable.into_os_string().into_string().unwrap()],
99147
messaging::PythonEnvironmentCategory::Pyenv,
100-
Some(version.clone()),
101-
Some(vec![
102-
pyenv_binary_for_activation.clone(),
103-
"shell".to_string(),
104-
version,
105-
]),
148+
version,
149+
activated_run,
106150
Some(env_path.clone()),
107151
Some(env_path),
108152
));

native_locator/tests/common.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub fn test_file_path(paths: &[&str]) -> String {
1616
root.to_string_lossy().to_string()
1717
}
1818

19+
1920
#[allow(dead_code)]
2021
pub fn join_test_paths(paths: &[&str]) -> String {
2122
let path: PathBuf = paths.iter().map(|p| p.to_string()).collect();

native_locator/tests/pyenv_test.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
mod common;
5+
6+
#[test]
7+
#[cfg(unix)]
8+
fn does_not_find_any_pyenv_envs() {
9+
use crate::common::{create_test_dispatcher, create_test_environment};
10+
use python_finder::pyenv;
11+
use std::collections::HashMap;
12+
13+
let mut dispatcher = create_test_dispatcher();
14+
let known = create_test_environment(
15+
HashMap::new(),
16+
Some("SOME_BOGUS_HOME_DIR".to_string()),
17+
Vec::new(),
18+
);
19+
20+
pyenv::find_and_report(&mut dispatcher, &known);
21+
22+
assert_eq!(dispatcher.messages.len(), 0);
23+
}
24+
25+
#[test]
26+
#[cfg(unix)]
27+
fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() {
28+
use crate::common::{
29+
assert_messages, create_test_dispatcher, create_test_environment, join_test_paths,
30+
test_file_path,
31+
};
32+
use python_finder::pyenv;
33+
use serde_json::json;
34+
use std::{collections::HashMap, path::PathBuf};
35+
36+
let mut dispatcher = create_test_dispatcher();
37+
let home = test_file_path(&["tests", "unix", "pyenv_without_envs"]);
38+
let homebrew_bin = join_test_paths(&[home.as_str(), "opt", "homebrew", "bin"]);
39+
let pyenv_exe = join_test_paths(&[homebrew_bin.as_str(), "pyenv"]);
40+
let known = create_test_environment(
41+
HashMap::new(),
42+
Some(home.clone()),
43+
vec![PathBuf::from(homebrew_bin)],
44+
);
45+
46+
pyenv::find_and_report(&mut dispatcher, &known);
47+
48+
assert_eq!(dispatcher.messages.len(), 1);
49+
let expected_json = json!({"executablePath":[pyenv_exe],"version":null});
50+
assert_messages(&[expected_json], &dispatcher)
51+
}
52+
53+
#[test]
54+
#[cfg(unix)]
55+
fn find_pyenv_envs() {
56+
use crate::common::{
57+
assert_messages, create_test_dispatcher, create_test_environment, join_test_paths,
58+
test_file_path,
59+
};
60+
use python_finder::pyenv;
61+
use serde_json::json;
62+
use std::{collections::HashMap, path::PathBuf};
63+
64+
let mut dispatcher = create_test_dispatcher();
65+
let home = test_file_path(&["tests", "unix", "pyenv"]);
66+
let homebrew_bin = join_test_paths(&[home.as_str(), "opt", "homebrew", "bin"]);
67+
let pyenv_exe = join_test_paths(&[homebrew_bin.as_str(), "pyenv"]);
68+
let known = create_test_environment(
69+
HashMap::new(),
70+
Some(home.clone()),
71+
vec![PathBuf::from(homebrew_bin)],
72+
);
73+
74+
pyenv::find_and_report(&mut dispatcher, &known);
75+
76+
assert_eq!(dispatcher.messages.len(), 5);
77+
let expected_manager = json!({ "executablePath": [pyenv_exe.clone()], "version": null });
78+
let expected_3_9_9 = json!({"name": "Python","pythonExecutablePath": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9/bin/python"])],"category": "pyenv","version": "3.9.9","activatedRun": [pyenv_exe.clone(), "local", "3.9.9"],"envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.9.9/bin/python"])});
79+
let expected_3_12_1 = json!({"name": "Python","pythonExecutablePath": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1/bin/python"])],"category": "pyenv","version": "3.12.1","activatedRun": [pyenv_exe.clone(), "local", "3.12.1"],"envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1/bin/python"])});
80+
let expected_3_13_dev = json!({"name": "Python","pythonExecutablePath": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev/bin/python"])],"category": "pyenv","version": "3.13-dev","activatedRun": [pyenv_exe.clone(), "local", "3.13-dev"],"envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.13-dev/bin/python"])});
81+
let expected_3_12_1a3 = json!({"name": "Python","pythonExecutablePath": [join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3/bin/python"])],"category": "pyenv","version": "3.12.1a3","activatedRun": [pyenv_exe.clone(), "local", "3.12.1a3"],"envPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3"]),"sysPrefixPath": join_test_paths(&[home.as_str(), ".pyenv/versions/3.12.1a3/bin/python"])});
82+
assert_messages(
83+
&[
84+
expected_manager,
85+
expected_3_9_9,
86+
expected_3_12_1,
87+
expected_3_13_dev,
88+
expected_3_12_1a3,
89+
],
90+
&dispatcher,
91+
)
92+
}

native_locator/tests/unix/pyenv/.pyenv/versions/3.12.1/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/3.12.1a3/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/3.13-dev/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/3.9.9/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/miniconda3-3.10-22.11.1-1/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/miniforge3-4.11.0-1/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniconda3-3.10-22.11.1-1/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniforge3-4.11.0-1/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniconda3-3.10-22.11.1-1/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniforge3-4.11.0-1/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/miniconda-latest/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/miniconda-latest/miniforge3-4.11.0-1/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10.1/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10.1/miniforge3-4.11.0-1/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-4.0.5/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-4.0.5/miniforge3-4.11.0-1/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/miniforge3-4.11.0-1/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/nogil-3.9.10/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/pypy3.10-7.3.14/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/pyston-2.3.5/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/.pyenv/versions/stacklets-3.7.5/bin/python

Whitespace-only changes.

native_locator/tests/unix/pyenv/opt/homebrew/bin/pyenv

Whitespace-only changes.

native_locator/tests/unix/pyenv_without_envs/opt/homebrew/bin/pyenv

Whitespace-only changes.

0 commit comments

Comments
 (0)