Skip to content

Better py env version extraction #23368

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

Merged
merged 9 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pr-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ jobs:
path: ${{ env.special-working-directory-relative }}

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

smoke-tests:
Expand Down
1 change: 1 addition & 0 deletions native_locator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ pub mod common_python;
pub mod logging;
pub mod conda;
pub mod known;
pub mod pyenv;
58 changes: 51 additions & 7 deletions native_locator/src/pyenv.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use regex::Regex;
use std::fs;
use std::path::PathBuf;

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

fn get_pyenv_version(folder_name: String) -> Option<String> {
// Stable Versions = like 3.10.10
let python_regex = Regex::new(r"^(\d+\.\d+\.\d+)$").unwrap();
match python_regex.captures(&folder_name) {
Some(captures) => match captures.get(1) {
Some(version) => Some(version.as_str().to_string()),
None => None,
},
None => {
// Dev Versions = like 3.10-dev
let python_regex = Regex::new(r"^(\d+\.\d+-dev)$").unwrap();
match python_regex.captures(&folder_name) {
Some(captures) => match captures.get(1) {
Some(version) => Some(version.as_str().to_string()),
None => None,
},
None => {
// Alpha Versions = like 3.10.0a3
let python_regex = Regex::new(r"^(\d+\.\d+.\d+a\d+)").unwrap();
match python_regex.captures(&folder_name) {
Some(captures) => match captures.get(1) {
Some(version) => Some(version.as_str().to_string()),
None => None,
},
None => None,
}
}
}
}
}
}

pub fn find_and_report(
dispatcher: &mut impl messaging::MessageDispatcher,
environment: &impl known::Environment,
Expand All @@ -91,18 +124,29 @@ pub fn find_and_report(
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();
let version =
get_pyenv_version(path.file_name().unwrap().to_string_lossy().to_string());

// If we cannot extract version, this isn't a valid pyenv environment.
// Or its one that we're not interested in.
if version.is_none() {
continue;
}
let env_path = path.to_string_lossy().to_string();
let activated_run = match version.clone() {
Some(version) => Some(vec![
pyenv_binary_for_activation.clone(),
"local".to_string(),
version.clone(),
]),
None => None,
};
dispatcher.report_environment(messaging::PythonEnvironment::new(
"Python".to_string(),
vec![executable.into_os_string().into_string().unwrap()],
messaging::PythonEnvironmentCategory::Pyenv,
Some(version.clone()),
Some(vec![
pyenv_binary_for_activation.clone(),
"shell".to_string(),
version,
]),
version,
activated_run,
Some(env_path.clone()),
Some(env_path),
));
Expand Down
1 change: 1 addition & 0 deletions native_locator/tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub fn test_file_path(paths: &[&str]) -> String {
root.to_string_lossy().to_string()
}


#[allow(dead_code)]
pub fn join_test_paths(paths: &[&str]) -> String {
let path: PathBuf = paths.iter().map(|p| p.to_string()).collect();
Expand Down
92 changes: 92 additions & 0 deletions native_locator/tests/pyenv_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

mod common;

#[test]
#[cfg(unix)]
fn does_not_find_any_pyenv_envs() {
use crate::common::{create_test_dispatcher, create_test_environment};
use python_finder::pyenv;
use std::collections::HashMap;

let mut dispatcher = create_test_dispatcher();
let known = create_test_environment(
HashMap::new(),
Some("SOME_BOGUS_HOME_DIR".to_string()),
Vec::new(),
);

pyenv::find_and_report(&mut dispatcher, &known);

assert_eq!(dispatcher.messages.len(), 0);
}

#[test]
#[cfg(unix)]
fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() {
use crate::common::{
assert_messages, create_test_dispatcher, create_test_environment, join_test_paths,
test_file_path,
};
use python_finder::pyenv;
use serde_json::json;
use std::{collections::HashMap, path::PathBuf};

let mut dispatcher = create_test_dispatcher();
let home = test_file_path(&["tests", "unix", "pyenv_without_envs"]);
let homebrew_bin = join_test_paths(&[home.as_str(), "opt", "homebrew", "bin"]);
let pyenv_exe = join_test_paths(&[homebrew_bin.as_str(), "pyenv"]);
let known = create_test_environment(
HashMap::new(),
Some(home.clone()),
vec![PathBuf::from(homebrew_bin)],
);

pyenv::find_and_report(&mut dispatcher, &known);

assert_eq!(dispatcher.messages.len(), 1);
let expected_json = json!({"executablePath":[pyenv_exe],"version":null});
assert_messages(&[expected_json], &dispatcher)
}

#[test]
#[cfg(unix)]
fn find_pyenv_envs() {
use crate::common::{
assert_messages, create_test_dispatcher, create_test_environment, join_test_paths,
test_file_path,
};
use python_finder::pyenv;
use serde_json::json;
use std::{collections::HashMap, path::PathBuf};

let mut dispatcher = create_test_dispatcher();
let home = test_file_path(&["tests", "unix", "pyenv"]);
let homebrew_bin = join_test_paths(&[home.as_str(), "opt", "homebrew", "bin"]);
let pyenv_exe = join_test_paths(&[homebrew_bin.as_str(), "pyenv"]);
let known = create_test_environment(
HashMap::new(),
Some(home.clone()),
vec![PathBuf::from(homebrew_bin)],
);

pyenv::find_and_report(&mut dispatcher, &known);

assert_eq!(dispatcher.messages.len(), 5);
let expected_manager = json!({ "executablePath": [pyenv_exe.clone()], "version": null });
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"])});
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"])});
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"])});
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"])});
assert_messages(
&[
expected_manager,
expected_3_9_9,
expected_3_12_1,
expected_3_13_dev,
expected_3_12_1a3,
],
&dispatcher,
)
}
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Loading