Skip to content

Commit 47ad791

Browse files
authored
Parallelize locating of global python environments (#23490)
1 parent b402f12 commit 47ad791

File tree

5 files changed

+206
-104
lines changed

5 files changed

+206
-104
lines changed

native_locator/src/conda.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ fn get_conda_package_json_path(path: &Path, package: &str) -> Option<CondaPackag
117117
})
118118
}
119119

120-
fn get_conda_executable(path: &PathBuf) -> Option<PathBuf> {
120+
fn get_conda_executable(path: &Path) -> Option<PathBuf> {
121121
for relative_path in get_relative_paths_to_conda_executable() {
122122
let exe = path.join(&relative_path);
123123
if exe.exists() {
@@ -226,7 +226,7 @@ pub fn find_conda_binary(environment: &dyn known::Environment) -> Option<PathBuf
226226
}
227227
}
228228

229-
fn get_conda_manager(path: &PathBuf) -> Option<EnvManager> {
229+
fn get_conda_manager(path: &Path) -> Option<EnvManager> {
230230
let conda_exe = get_conda_executable(path)?;
231231
let conda_pkg = get_conda_package_json_path(path, "conda")?;
232232

@@ -529,7 +529,7 @@ fn get_conda_conda_rc(environment: &dyn known::Environment) -> Option<Condarc> {
529529
*/
530530
fn was_conda_environment_created_by_specific_conda(
531531
env: &CondaEnvironment,
532-
root_conda_path: &PathBuf,
532+
root_conda_path: &Path,
533533
) -> bool {
534534
if let Some(cmd_line) = env.conda_install_folder.clone() {
535535
if cmd_line
@@ -661,7 +661,7 @@ fn get_activation_command(env: &CondaEnvironment, manager: &EnvManager) -> Optio
661661
}
662662
}
663663

664-
fn get_root_python_environment(path: &PathBuf, manager: &EnvManager) -> Option<PythonEnvironment> {
664+
fn get_root_python_environment(path: &Path, manager: &EnvManager) -> Option<PythonEnvironment> {
665665
let python_exe = path.join(get_relative_paths_to_main_python_executable());
666666
if !python_exe.exists() {
667667
return None;
@@ -680,7 +680,7 @@ fn get_root_python_environment(path: &PathBuf, manager: &EnvManager) -> Option<P
680680
python_executable_path: Some(python_exe),
681681
version: Some(package_info.version),
682682
arch: package_info.arch,
683-
env_path: Some(path.clone()),
683+
env_path: Some(path.to_path_buf().clone()),
684684
env_manager: Some(manager.clone()),
685685
python_run_command: Some(vec![
686686
conda_exe,
@@ -696,7 +696,7 @@ fn get_root_python_environment(path: &PathBuf, manager: &EnvManager) -> Option<P
696696
}
697697

698698
fn get_conda_environments_in_specified_install_path(
699-
conda_install_folder: &PathBuf,
699+
conda_install_folder: &Path,
700700
possible_conda_envs: &mut HashMap<PathBuf, CondaEnvironment>,
701701
) -> Option<LocatorResult> {
702702
let mut managers: Vec<EnvManager> = vec![];
@@ -842,7 +842,7 @@ fn find_conda_environments_from_known_conda_install_locations(
842842
})
843843
}
844844

845-
fn is_conda_install_location(path: &PathBuf) -> bool {
845+
fn is_conda_install_location(path: &Path) -> bool {
846846
let envs_path = path.join("envs");
847847
return envs_path.exists() && envs_path.is_dir();
848848
}
@@ -963,7 +963,7 @@ pub struct Conda<'a> {
963963
}
964964

965965
pub trait CondaLocator {
966-
fn find_in(&mut self, possible_conda_folder: &PathBuf) -> Option<LocatorResult>;
966+
fn find_in(&mut self, possible_conda_folder: &Path) -> Option<LocatorResult>;
967967
}
968968

969969
impl Conda<'_> {
@@ -1019,7 +1019,7 @@ impl Conda<'_> {
10191019
}
10201020

10211021
impl CondaLocator for Conda<'_> {
1022-
fn find_in(&mut self, possible_conda_folder: &PathBuf) -> Option<LocatorResult> {
1022+
fn find_in(&mut self, possible_conda_folder: &Path) -> Option<LocatorResult> {
10231023
if !is_conda_install_location(possible_conda_folder) {
10241024
return None;
10251025
}

native_locator/src/global_virtualenvs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::{
77
};
88
use std::{fs, path::PathBuf};
99

10-
pub fn get_global_virtualenv_dirs(environment: &impl known::Environment) -> Vec<PathBuf> {
10+
fn get_global_virtualenv_dirs(environment: &impl known::Environment) -> Vec<PathBuf> {
1111
let mut venv_dirs: Vec<PathBuf> = vec![];
1212

1313
if let Some(work_on_home) = environment.get_env_var("WORKON_HOME".to_string()) {

native_locator/src/known.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ pub trait Environment {
1414
}
1515

1616
pub struct EnvironmentApi {}
17+
impl EnvironmentApi {
18+
pub fn new() -> Self {
19+
EnvironmentApi {}
20+
}
21+
}
1722

1823
#[cfg(windows)]
1924
impl Environment for EnvironmentApi {

native_locator/src/lib.rs

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

4-
pub mod messaging;
5-
pub mod utils;
4+
use global_virtualenvs::list_global_virtual_envs;
5+
use known::EnvironmentApi;
6+
use locator::{Locator, LocatorResult};
7+
use messaging::{create_dispatcher, JsonRpcDispatcher, MessageDispatcher};
8+
use std::thread::{self, JoinHandle};
9+
use utils::PythonEnv;
10+
611
pub mod common_python;
7-
pub mod logging;
812
pub mod conda;
9-
pub mod known;
10-
pub mod pyenv;
1113
pub mod global_virtualenvs;
12-
pub mod virtualenvwrapper;
14+
pub mod homebrew;
15+
pub mod known;
16+
pub mod locator;
17+
pub mod logging;
18+
pub mod messaging;
1319
pub mod pipenv;
14-
pub mod virtualenv;
20+
pub mod pyenv;
21+
pub mod utils;
1522
pub mod venv;
16-
pub mod locator;
23+
pub mod virtualenv;
24+
pub mod virtualenvwrapper;
1725
pub mod windows_registry;
1826
pub mod windows_store;
27+
28+
pub fn find_and_report_envs() {
29+
let mut dispatcher: JsonRpcDispatcher = create_dispatcher();
30+
31+
// 1. Find using known global locators.
32+
find_using_global_finders(&mut dispatcher);
33+
34+
// Step 2: Search in some global locations for virtual envs.
35+
find_in_global_virtual_env_dirs(&mut dispatcher);
36+
37+
// Step 3: Finally find in the current PATH variable
38+
let environment = EnvironmentApi::new();
39+
let mut path_locator = common_python::PythonOnPath::with(&environment);
40+
report_result(path_locator.find(), &mut dispatcher)
41+
}
42+
43+
fn find_using_global_finders(dispatcher: &mut JsonRpcDispatcher) {
44+
// Step 1: These environments take precedence over all others.
45+
// As they are very specific and guaranteed to be specific type.
46+
#[cfg(windows)]
47+
fn find() -> Vec<JoinHandle<std::option::Option<LocatorResult>>> {
48+
// The order matters,
49+
// Windows store can sometimes get detected via registry locator (but we want to avoid that),
50+
// difficult to repro, but we have see this on Karthiks machine
51+
// Windows registry can contain conda envs (e.g. installing Ananconda will result in registry entries).
52+
// Conda is best done last, as Windows Registry and Pyenv can also contain conda envs,
53+
// Thus lets leave the generic conda locator to last to find all remaining conda envs.
54+
// pyenv can be treated as a virtualenvwrapper environment, hence virtualenvwrapper needs to be detected first
55+
vec![
56+
// 1. windows store
57+
thread::spawn(|| {
58+
let environment = EnvironmentApi::new();
59+
let mut windows_store = windows_store::WindowsStore::with(&environment);
60+
windows_store.find()
61+
}),
62+
// 2. windows registry
63+
thread::spawn(|| {
64+
let environment = EnvironmentApi::new();
65+
let mut conda_locator = conda::Conda::with(&environment);
66+
windows_registry::WindowsRegistry::with(&mut conda_locator).find()
67+
}),
68+
// 3. virtualenvwrapper
69+
thread::spawn(|| {
70+
let environment = EnvironmentApi::new();
71+
virtualenvwrapper::VirtualEnvWrapper::with(&environment).find()
72+
}),
73+
// 4. pyenv
74+
thread::spawn(|| {
75+
let environment = EnvironmentApi::new();
76+
let mut conda_locator = conda::Conda::with(&environment);
77+
pyenv::PyEnv::with(&environment, &mut conda_locator).find()
78+
}),
79+
// 5. conda
80+
thread::spawn(|| {
81+
let environment = EnvironmentApi::new();
82+
conda::Conda::with(&environment).find()
83+
}),
84+
]
85+
}
86+
87+
#[cfg(unix)]
88+
fn find() -> Vec<JoinHandle<std::option::Option<LocatorResult>>> {
89+
// The order matters,
90+
// pyenv can be treated as a virtualenvwrapper environment, hence virtualenvwrapper needs to be detected first
91+
// Homebrew can happen anytime
92+
// Conda is best done last, as pyenv can also contain conda envs,
93+
// Thus lets leave the generic conda locator to last to find all remaining conda envs.
94+
95+
vec![
96+
// 1. virtualenvwrapper
97+
thread::spawn(|| {
98+
let environment = EnvironmentApi::new();
99+
virtualenvwrapper::VirtualEnvWrapper::with(&environment).find()
100+
}),
101+
// 2. pyenv
102+
thread::spawn(|| {
103+
let environment = EnvironmentApi::new();
104+
let mut conda_locator = conda::Conda::with(&environment);
105+
pyenv::PyEnv::with(&environment, &mut conda_locator).find()
106+
}),
107+
// 3. homebrew
108+
thread::spawn(|| {
109+
let environment = EnvironmentApi::new();
110+
homebrew::Homebrew::with(&environment).find()
111+
}),
112+
// 4. conda
113+
thread::spawn(|| {
114+
let environment = EnvironmentApi::new();
115+
conda::Conda::with(&environment).find()
116+
}),
117+
]
118+
}
119+
120+
for handle in find() {
121+
if let Ok(result) = handle.join() {
122+
report_result(result, dispatcher);
123+
} else {
124+
log::error!("Error getting result from thread.");
125+
}
126+
}
127+
}
128+
129+
fn find_in_global_virtual_env_dirs(dispatcher: &mut JsonRpcDispatcher) -> Option<LocatorResult> {
130+
// Step 1: These environments take precedence over all others.
131+
// As they are very specific and guaranteed to be specific type.
132+
133+
let environment = EnvironmentApi::new();
134+
let virtualenv_locator = virtualenv::VirtualEnv::new();
135+
let venv_locator = venv::Venv::new();
136+
let virtualenvwrapper = virtualenvwrapper::VirtualEnvWrapper::with(&environment);
137+
let pipenv_locator = pipenv::PipEnv::new();
138+
#[cfg(unix)]
139+
let homebrew_locator = homebrew::Homebrew::with(&environment);
140+
141+
let venv_type_locators = vec![
142+
Box::new(pipenv_locator) as Box<dyn Locator>,
143+
Box::new(virtualenvwrapper) as Box<dyn Locator>,
144+
Box::new(venv_locator) as Box<dyn Locator>,
145+
Box::new(virtualenv_locator) as Box<dyn Locator>,
146+
];
147+
148+
// Step 2: Search in some global locations for virtual envs.
149+
for env in list_global_virtual_envs(&environment) {
150+
if dispatcher.was_environment_reported(&env) {
151+
continue;
152+
}
153+
154+
// 1. First must be homebrew, as it is the most specific and supports symlinks
155+
#[cfg(unix)]
156+
if resolve_and_report_environment(&homebrew_locator, &env, dispatcher) {
157+
continue;
158+
}
159+
160+
// 3. Finally Check if these are some kind of virtual env or pipenv.
161+
// Pipeenv before virtualenvwrapper as it is more specific.
162+
// Because pipenv environments are also virtualenvwrapper environments.
163+
// Before venv, as all venvs are also virtualenvwrapper environments.
164+
// Before virtualenv as this is more specific.
165+
// All venvs are also virtualenvs environments.
166+
for locator in &venv_type_locators {
167+
if resolve_and_report_environment(locator.as_ref(), &env, dispatcher) {
168+
break;
169+
}
170+
}
171+
}
172+
None
173+
}
174+
175+
fn resolve_and_report_environment(
176+
locator: &dyn Locator,
177+
env: &PythonEnv,
178+
dispatcher: &mut JsonRpcDispatcher,
179+
) -> bool {
180+
if let Some(env) = locator.resolve(env) {
181+
dispatcher.report_environment(env);
182+
return true;
183+
}
184+
false
185+
}
186+
187+
fn report_result(result: Option<LocatorResult>, dispatcher: &mut JsonRpcDispatcher) {
188+
if let Some(result) = result {
189+
result
190+
.environments
191+
.iter()
192+
.for_each(|e| dispatcher.report_environment(e.clone()));
193+
result
194+
.managers
195+
.iter()
196+
.for_each(|m| dispatcher.report_environment_manager(m.clone()));
197+
}
198+
}

0 commit comments

Comments
 (0)