diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 19556a38e30c..ed9b10e29e2b 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -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: diff --git a/native_locator/src/lib.rs b/native_locator/src/lib.rs index d95a4300d253..17ce17253f77 100644 --- a/native_locator/src/lib.rs +++ b/native_locator/src/lib.rs @@ -7,3 +7,4 @@ pub mod common_python; pub mod logging; pub mod conda; pub mod known; +pub mod pyenv; diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs index 6d66b906c82c..8e7a734a297d 100644 --- a/native_locator/src/pyenv.rs +++ b/native_locator/src/pyenv.rs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use regex::Regex; use std::fs; use std::path::PathBuf; @@ -66,6 +67,38 @@ fn get_pyenv_binary(environment: &impl known::Environment) -> Option { } } +fn get_pyenv_version(folder_name: String) -> Option { + // 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, @@ -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), )); diff --git a/native_locator/tests/common.rs b/native_locator/tests/common.rs index 107e649e3ae5..e75352691b6b 100644 --- a/native_locator/tests/common.rs +++ b/native_locator/tests/common.rs @@ -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(); diff --git a/native_locator/tests/pyenv_test.rs b/native_locator/tests/pyenv_test.rs new file mode 100644 index 000000000000..896fbe62a035 --- /dev/null +++ b/native_locator/tests/pyenv_test.rs @@ -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, + ) +} diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/3.12.1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/3.12.1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/3.12.1a3/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/3.12.1a3/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/3.13-dev/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/3.13-dev/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/3.9.9/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/3.9.9/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/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 b/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 new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/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 b/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 new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/miniconda3-3.10-22.11.1-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/miniconda3-3.10-22.11.1-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/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 b/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 new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/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 b/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 new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniconda3-3.10-22.11.1-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniconda3-3.10-22.11.1-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/miniconda3-3.10-22.11.1-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/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 b/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 new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniconda3-3.10-22.11.1-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniconda3-3.10-22.11.1-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda-latest/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda-latest/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda-latest/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda-latest/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10-22.11.1-1/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10.1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10.1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10.1/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10.1/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-4.0.5/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-4.0.5/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-4.0.5/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-4.0.5/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/nogil-3.9.10/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/nogil-3.9.10/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/pypy3.10-7.3.14/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/pypy3.10-7.3.14/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/pyston-2.3.5/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/pyston-2.3.5/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/stacklets-3.7.5/bin/python b/native_locator/tests/unix/pyenv/.pyenv/versions/stacklets-3.7.5/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/opt/homebrew/bin/pyenv b/native_locator/tests/unix/pyenv/opt/homebrew/bin/pyenv new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv_without_envs/opt/homebrew/bin/pyenv b/native_locator/tests/unix/pyenv_without_envs/opt/homebrew/bin/pyenv new file mode 100644 index 000000000000..e69de29bb2d1