Skip to content

Commit 49945b0

Browse files
authored
Support venv, virtualEnv in global folders (#23395)
1 parent 8993be7 commit 49945b0

File tree

33 files changed

+128
-36
lines changed

33 files changed

+128
-36
lines changed

native_locator/src/global_virtualenvs.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Licensed under the MIT License.
33

44
use crate::pipenv;
5+
use crate::venv;
6+
use crate::virtualenv;
57
use crate::virtualenvwrapper;
68
use crate::{
79
known,
@@ -82,6 +84,12 @@ pub fn find_and_report(
8284
if virtualenvwrapper::find_and_report(&env, dispatcher, environment).is_some() {
8385
continue;
8486
}
87+
if venv::find_and_report(&env, dispatcher).is_some() {
88+
continue;
89+
}
90+
if virtualenv::find_and_report(&env, dispatcher).is_some() {
91+
continue;
92+
}
8593
}
8694

8795
None

native_locator/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ pub mod global_virtualenvs;
1212
pub mod virtualenvwrapper;
1313
pub mod pipenv;
1414
pub mod virtualenv;
15+
pub mod venv;

native_locator/src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ mod utils;
1919
mod virtualenv;
2020
mod virtualenvwrapper;
2121
mod windows_python;
22+
mod venv;
2223

2324
fn main() {
2425
let mut dispatcher = create_dispatcher();
@@ -55,6 +56,6 @@ fn main() {
5556
dispatcher.log_error(&format!("Error getting elapsed time: {:?}", e));
5657
}
5758
}
58-
59+
5960
dispatcher.exit();
6061
}

native_locator/src/messaging.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ pub enum PythonEnvironmentCategory {
8080
WindowsStore,
8181
Pipenv,
8282
VirtualEnvWrapper,
83+
Venv,
84+
VirtualEnv,
8385
}
8486

8587
#[derive(Serialize, Deserialize)]

native_locator/src/pyenv.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ use crate::known;
99
use crate::messaging;
1010
use crate::messaging::EnvManager;
1111
use crate::messaging::EnvManagerType;
12+
use crate::utils::find_and_parse_pyvenv_cfg;
1213
use crate::utils::find_python_binary_path;
13-
use crate::utils::parse_pyenv_cfg;
1414

1515
#[cfg(windows)]
1616
fn get_home_pyenv_dir(environment: &impl known::Environment) -> Option<PathBuf> {
@@ -124,7 +124,7 @@ fn report_if_virtual_env_environment(
124124
manager: Option<EnvManager>,
125125
dispatcher: &mut impl messaging::MessageDispatcher,
126126
) -> Option<()> {
127-
let pyenv_cfg = parse_pyenv_cfg(path)?;
127+
let pyenv_cfg = find_and_parse_pyvenv_cfg(executable)?;
128128
let folder_name = path.file_name().unwrap().to_string_lossy().to_string();
129129
dispatcher.report_environment(messaging::PythonEnvironment::new(
130130
Some(folder_name),
@@ -182,7 +182,6 @@ pub fn find_and_report(
182182
{
183183
continue;
184184
}
185-
186185
report_if_virtual_env_environment(&executable, &path, manager.clone(), dispatcher);
187186
}
188187
}

native_locator/src/utils.rs

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,41 @@ pub struct PyEnvCfg {
2020
pub version: String,
2121
}
2222

23-
pub fn parse_pyenv_cfg(path: &PathBuf) -> Option<PyEnvCfg> {
24-
let cfg = path.join("pyvenv.cfg");
23+
const PYVENV_CONFIG_FILE: &str = "pyvenv.cfg";
24+
25+
pub fn find_pyvenv_config_path(python_executable: &PathBuf) -> Option<PathBuf> {
26+
// Check if the pyvenv.cfg file is in the parent directory relative to the interpreter.
27+
// env
28+
// |__ pyvenv.cfg <--- check if this file exists
29+
// |__ bin or Scripts
30+
// |__ python <--- interpreterPath
31+
let cfg = python_executable.parent()?.join(PYVENV_CONFIG_FILE);
32+
if fs::metadata(&cfg).is_ok() {
33+
return Some(cfg);
34+
}
35+
36+
// Check if the pyvenv.cfg file is in the directory as the interpreter.
37+
// env
38+
// |__ pyvenv.cfg <--- check if this file exists
39+
// |__ python <--- interpreterPath
40+
let cfg = python_executable
41+
.parent()?
42+
.parent()?
43+
.join(PYVENV_CONFIG_FILE);
44+
if fs::metadata(&cfg).is_ok() {
45+
return Some(cfg);
46+
}
47+
48+
None
49+
}
50+
51+
pub fn find_and_parse_pyvenv_cfg(python_executable: &PathBuf) -> Option<PyEnvCfg> {
52+
let cfg = find_pyvenv_config_path(&PathBuf::from(python_executable))?;
2553
if !fs::metadata(&cfg).is_ok() {
2654
return None;
2755
}
2856

29-
let contents = fs::read_to_string(cfg).ok()?;
57+
let contents = fs::read_to_string(&cfg).ok()?;
3058
let version_regex = Regex::new(r"^version\s*=\s*(\d+\.\d+\.\d+)$").unwrap();
3159
let version_info_regex = Regex::new(r"^version_info\s*=\s*(\d+\.\d+\.\d+.*)$").unwrap();
3260
for line in contents.lines() {
@@ -51,18 +79,14 @@ pub fn parse_pyenv_cfg(path: &PathBuf) -> Option<PyEnvCfg> {
5179
None
5280
}
5381

54-
pub fn get_version(path: &str) -> Option<String> {
55-
if let Some(parent_folder) = PathBuf::from(path).parent() {
56-
if let Some(pyenv_cfg) = parse_pyenv_cfg(&parent_folder.to_path_buf()) {
82+
pub fn get_version(python_executable: &str) -> Option<String> {
83+
if let Some(parent_folder) = PathBuf::from(python_executable).parent() {
84+
if let Some(pyenv_cfg) = find_and_parse_pyvenv_cfg(&parent_folder.to_path_buf()) {
5785
return Some(pyenv_cfg.version);
5886
}
59-
if let Some(parent_folder) = parent_folder.parent() {
60-
if let Some(pyenv_cfg) = parse_pyenv_cfg(&parent_folder.to_path_buf()) {
61-
return Some(pyenv_cfg.version);
62-
}
63-
}
6487
}
65-
let output = Command::new(path)
88+
89+
let output = Command::new(python_executable)
6690
.arg("-c")
6791
.arg("import sys; print(sys.version)")
6892
.output()

native_locator/src/venv.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use crate::{
5+
messaging::{MessageDispatcher, PythonEnvironment, PythonEnvironmentCategory},
6+
utils::{self, PythonEnv},
7+
};
8+
9+
pub fn is_venv(env: &PythonEnv) -> bool {
10+
return utils::find_pyvenv_config_path(&env.executable).is_some();
11+
}
12+
13+
pub fn find_and_report(env: &PythonEnv, dispatcher: &mut impl MessageDispatcher) -> Option<()> {
14+
if is_venv(env) {
15+
let env = PythonEnvironment {
16+
name: match env.path.file_name().to_owned() {
17+
Some(name) => Some(name.to_string_lossy().to_owned().to_string()),
18+
None => None,
19+
},
20+
python_executable_path: Some(env.executable.clone()),
21+
category: PythonEnvironmentCategory::Venv,
22+
version: env.version.clone(),
23+
env_path: Some(env.path.clone()),
24+
sys_prefix_path: Some(env.path.clone()),
25+
env_manager: None,
26+
python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]),
27+
project_path: None,
28+
};
29+
30+
dispatcher.report_environment(env);
31+
32+
return Some(());
33+
}
34+
None
35+
}

native_locator/src/virtualenv.rs

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

4-
use std::path::PathBuf;
5-
4+
use crate::messaging::{MessageDispatcher, PythonEnvironment, PythonEnvironmentCategory};
65
use crate::utils::PythonEnv;
6+
use std::path::PathBuf;
77

88
pub fn is_virtualenv(env: &PythonEnv) -> bool {
99
if let Some(file_path) = PathBuf::from(env.executable.clone()).parent() {
@@ -41,3 +41,27 @@ pub fn is_virtualenv(env: &PythonEnv) -> bool {
4141

4242
false
4343
}
44+
45+
pub fn find_and_report(env: &PythonEnv, dispatcher: &mut impl MessageDispatcher) -> Option<()> {
46+
if is_virtualenv(env) {
47+
let env = PythonEnvironment {
48+
name: match env.path.file_name().to_owned() {
49+
Some(name) => Some(name.to_string_lossy().to_owned().to_string()),
50+
None => None,
51+
},
52+
python_executable_path: Some(env.executable.clone()),
53+
category: PythonEnvironmentCategory::VirtualEnv,
54+
version: env.version.clone(),
55+
env_path: Some(env.path.clone()),
56+
sys_prefix_path: Some(env.path.clone()),
57+
env_manager: None,
58+
python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]),
59+
project_path: None,
60+
};
61+
62+
dispatcher.report_environment(env);
63+
64+
return Some(());
65+
}
66+
None
67+
}

native_locator/src/virtualenvwrapper.rs

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -48,23 +48,6 @@ fn get_work_on_home_path(environment: &impl Environment) -> Option<PathBuf> {
4848
get_default_virtualenvwrapper_path(environment)
4949
}
5050

51-
fn create_virtualenvwrapper_env(env: &PythonEnv) -> PythonEnvironment {
52-
PythonEnvironment {
53-
name: match env.path.file_name().to_owned() {
54-
Some(name) => Some(name.to_string_lossy().to_owned().to_string()),
55-
None => None,
56-
},
57-
python_executable_path: Some(env.executable.clone()),
58-
category: PythonEnvironmentCategory::VirtualEnvWrapper,
59-
version: env.version.clone(),
60-
env_path: Some(env.path.clone()),
61-
sys_prefix_path: Some(env.path.clone()),
62-
env_manager: None,
63-
python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]),
64-
project_path: None,
65-
}
66-
}
67-
6851
pub fn is_virtualenvwrapper(env: &PythonEnv, environment: &impl Environment) -> bool {
6952
// For environment to be a virtualenvwrapper based it has to follow these two rules:
7053
// 1. It should be in a sub-directory under the WORKON_HOME
@@ -84,7 +67,22 @@ pub fn find_and_report(
8467
environment: &impl Environment,
8568
) -> Option<()> {
8669
if is_virtualenvwrapper(env, environment) {
87-
dispatcher.report_environment(create_virtualenvwrapper_env(env));
70+
let env = PythonEnvironment {
71+
name: match env.path.file_name().to_owned() {
72+
Some(name) => Some(name.to_string_lossy().to_owned().to_string()),
73+
None => None,
74+
},
75+
python_executable_path: Some(env.executable.clone()),
76+
category: PythonEnvironmentCategory::VirtualEnvWrapper,
77+
version: env.version.clone(),
78+
env_path: Some(env.path.clone()),
79+
sys_prefix_path: Some(env.path.clone()),
80+
env_manager: None,
81+
python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]),
82+
project_path: None,
83+
};
84+
85+
dispatcher.report_environment(env);
8886
return Some(());
8987
}
9088
None

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/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/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/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/miniforge3-4.11.0-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/miniforge3-4.11.0-1/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.

0 commit comments

Comments
 (0)