Skip to content

Commit dbb813a

Browse files
committed
Refactor locators to implement a specific trait (#23404)
1 parent 3119849 commit dbb813a

18 files changed

+866
-393
lines changed

native_locator/src/common_python.rs

Lines changed: 70 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,72 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4-
use crate::known;
5-
use crate::messaging;
6-
use crate::utils;
4+
use crate::known::Environment;
5+
use crate::locator::Locator;
6+
use crate::messaging::MessageDispatcher;
7+
use crate::messaging::PythonEnvironment;
8+
use crate::utils::{self, PythonEnv};
9+
use std::collections::HashMap;
710
use std::env;
8-
use std::path::Path;
911
use std::path::PathBuf;
1012

11-
fn get_env_path(path: &str) -> Option<PathBuf> {
12-
let path = Path::new(path);
13-
match path.parent() {
14-
Some(parent) => {
15-
if parent.file_name()? == "Scripts" {
16-
return Some(parent.parent()?.to_path_buf());
17-
} else {
18-
return Some(parent.to_path_buf());
19-
}
20-
}
21-
None => None,
13+
fn get_env_path(python_executable_path: &PathBuf) -> Option<PathBuf> {
14+
let parent = python_executable_path.parent()?;
15+
if parent.file_name()? == "Scripts" {
16+
return Some(parent.parent()?.to_path_buf());
17+
} else {
18+
return Some(parent.to_path_buf());
2219
}
2320
}
2421

25-
fn report_path_python(dispatcher: &mut impl messaging::MessageDispatcher, path: &str) {
26-
let version = utils::get_version(path);
27-
let env_path = get_env_path(path);
28-
dispatcher.report_environment(messaging::PythonEnvironment::new(
29-
None,
30-
Some(PathBuf::from(path)),
31-
messaging::PythonEnvironmentCategory::System,
32-
version,
33-
env_path.clone(),
34-
env_path,
35-
None,
36-
Some(vec![path.to_string()]),
37-
));
22+
pub struct PythonOnPath<'a> {
23+
pub environments: HashMap<String, PythonEnvironment>,
24+
pub environment: &'a dyn Environment,
25+
}
26+
27+
impl PythonOnPath<'_> {
28+
pub fn with<'a>(environment: &'a impl Environment) -> PythonOnPath {
29+
PythonOnPath {
30+
environments: HashMap::new(),
31+
environment,
32+
}
33+
}
3834
}
3935

40-
fn report_python_on_path(
41-
dispatcher: &mut impl messaging::MessageDispatcher,
42-
environment: &impl known::Environment,
43-
) {
44-
if let Some(paths) = environment.get_env_var("PATH".to_string()) {
36+
impl Locator for PythonOnPath<'_> {
37+
fn is_known(&self, python_executable: &PathBuf) -> bool {
38+
self.environments
39+
.contains_key(python_executable.to_str().unwrap_or_default())
40+
}
41+
42+
fn track_if_compatible(&mut self, env: &PythonEnv) -> bool {
43+
let bin = if cfg!(windows) {
44+
"python.exe"
45+
} else {
46+
"python"
47+
};
48+
if env.executable.file_name().unwrap().to_ascii_lowercase() != bin {
49+
return false;
50+
}
51+
self.environments.insert(
52+
env.executable.to_str().unwrap().to_string(),
53+
PythonEnvironment {
54+
name: None,
55+
python_executable_path: Some(env.executable.clone()),
56+
version: env.version.clone(),
57+
category: crate::messaging::PythonEnvironmentCategory::System,
58+
sys_prefix_path: None,
59+
env_path: env.path.clone(),
60+
env_manager: None,
61+
project_path: None,
62+
python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]),
63+
},
64+
);
65+
true
66+
}
67+
68+
fn gather(&mut self) -> Option<()> {
69+
let paths = self.environment.get_env_var("PATH".to_string())?;
4570
let bin = if cfg!(windows) {
4671
"python.exe"
4772
} else {
@@ -50,13 +75,18 @@ fn report_python_on_path(
5075
env::split_paths(&paths)
5176
.map(|p| p.join(bin))
5277
.filter(|p| p.exists())
53-
.for_each(|full_path| report_path_python(dispatcher, full_path.to_str().unwrap()));
78+
.for_each(|full_path| {
79+
let version = utils::get_version(&full_path);
80+
let env_path = get_env_path(&full_path);
81+
self.track_if_compatible(&PythonEnv::new(full_path, env_path, version));
82+
});
83+
84+
Some(())
5485
}
55-
}
5686

57-
pub fn find_and_report(
58-
dispatcher: &mut impl messaging::MessageDispatcher,
59-
environment: &impl known::Environment,
60-
) {
61-
report_python_on_path(dispatcher, environment);
87+
fn report(&self, reporter: &mut dyn MessageDispatcher) {
88+
for env in self.environments.values() {
89+
reporter.report_environment(env.clone());
90+
}
91+
}
6292
}

native_locator/src/conda.rs

Lines changed: 99 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,17 @@
22
// Licensed under the MIT License.
33

44
use crate::known;
5+
use crate::known::Environment;
6+
use crate::locator::Locator;
57
use crate::messaging;
8+
use crate::messaging::EnvManager;
69
use crate::messaging::EnvManagerType;
10+
use crate::messaging::MessageDispatcher;
11+
use crate::messaging::PythonEnvironment;
712
use crate::utils::find_python_binary_path;
13+
use crate::utils::PythonEnv;
814
use regex::Regex;
15+
use std::collections::HashMap;
916
use std::env;
1017
use std::path::{Path, PathBuf};
1118

@@ -129,7 +136,7 @@ fn get_conda_bin_names() -> Vec<&'static str> {
129136
}
130137

131138
/// Find the conda binary on the PATH environment variable
132-
fn find_conda_binary_on_path(environment: &impl known::Environment) -> Option<PathBuf> {
139+
fn find_conda_binary_on_path(environment: &dyn known::Environment) -> Option<PathBuf> {
133140
let paths = environment.get_env_var("PATH".to_string())?;
134141
for path in env::split_paths(&paths) {
135142
for bin in get_conda_bin_names() {
@@ -148,7 +155,7 @@ fn find_conda_binary_on_path(environment: &impl known::Environment) -> Option<Pa
148155
}
149156

150157
#[cfg(windows)]
151-
fn get_known_conda_locations(environment: &impl known::Environment) -> Vec<PathBuf> {
158+
fn get_known_conda_locations(environment: &dyn known::Environment) -> Vec<PathBuf> {
152159
let user_profile = environment.get_env_var("USERPROFILE".to_string()).unwrap();
153160
let program_data = environment.get_env_var("PROGRAMDATA".to_string()).unwrap();
154161
let all_user_profile = environment
@@ -170,7 +177,7 @@ fn get_known_conda_locations(environment: &impl known::Environment) -> Vec<PathB
170177
}
171178

172179
#[cfg(unix)]
173-
fn get_known_conda_locations(environment: &impl known::Environment) -> Vec<PathBuf> {
180+
fn get_known_conda_locations(environment: &dyn known::Environment) -> Vec<PathBuf> {
174181
let mut known_paths = vec![
175182
PathBuf::from("/opt/anaconda3/bin"),
176183
PathBuf::from("/opt/miniconda3/bin"),
@@ -192,7 +199,7 @@ fn get_known_conda_locations(environment: &impl known::Environment) -> Vec<PathB
192199
}
193200

194201
/// Find conda binary in known locations
195-
fn find_conda_binary_in_known_locations(environment: &impl known::Environment) -> Option<PathBuf> {
202+
fn find_conda_binary_in_known_locations(environment: &dyn known::Environment) -> Option<PathBuf> {
196203
let conda_bin_names = get_conda_bin_names();
197204
let known_locations = get_known_conda_locations(environment);
198205
for location in known_locations {
@@ -209,7 +216,7 @@ fn find_conda_binary_in_known_locations(environment: &impl known::Environment) -
209216
}
210217

211218
/// Find the conda binary on the system
212-
pub fn find_conda_binary(environment: &impl known::Environment) -> Option<PathBuf> {
219+
pub fn find_conda_binary(environment: &dyn known::Environment) -> Option<PathBuf> {
213220
let conda_binary_on_path = find_conda_binary_on_path(environment);
214221
match conda_binary_on_path {
215222
Some(conda_binary_on_path) => Some(conda_binary_on_path),
@@ -232,7 +239,7 @@ pub fn get_conda_version(conda_binary: &PathBuf) -> Option<String> {
232239
get_version_from_meta_json(&conda_python_json_path)
233240
}
234241

235-
fn get_conda_envs_from_environment_txt(environment: &impl known::Environment) -> Vec<String> {
242+
fn get_conda_envs_from_environment_txt(environment: &dyn known::Environment) -> Vec<String> {
236243
let mut envs = vec![];
237244
let home = environment.get_user_home();
238245
match home {
@@ -255,7 +262,7 @@ fn get_conda_envs_from_environment_txt(environment: &impl known::Environment) ->
255262

256263
fn get_known_env_locations(
257264
conda_bin: &PathBuf,
258-
environment: &impl known::Environment,
265+
environment: &dyn known::Environment,
259266
) -> Vec<String> {
260267
let mut paths = vec![];
261268
let home = environment.get_user_home();
@@ -290,7 +297,7 @@ fn get_known_env_locations(
290297

291298
fn get_conda_envs_from_known_env_locations(
292299
conda_bin: &PathBuf,
293-
environment: &impl known::Environment,
300+
environment: &dyn known::Environment,
294301
) -> Vec<String> {
295302
let mut envs = vec![];
296303
for location in get_known_env_locations(conda_bin, environment) {
@@ -333,7 +340,7 @@ struct CondaEnv {
333340

334341
fn get_distinct_conda_envs(
335342
conda_bin: &PathBuf,
336-
environment: &impl known::Environment,
343+
environment: &dyn known::Environment,
337344
) -> Vec<CondaEnv> {
338345
let mut envs = get_conda_envs_from_environment_txt(environment);
339346
let mut known_envs = get_conda_envs_from_known_env_locations(conda_bin, environment);
@@ -377,52 +384,91 @@ fn get_distinct_conda_envs(
377384
conda_envs
378385
}
379386

380-
pub fn find_and_report(
381-
dispatcher: &mut impl messaging::MessageDispatcher,
382-
environment: &impl known::Environment,
383-
) {
384-
let conda_binary = find_conda_binary(environment);
385-
match conda_binary {
386-
Some(conda_binary) => {
387-
let env_manager = messaging::EnvManager::new(
388-
conda_binary.clone(),
389-
get_conda_version(&conda_binary),
390-
EnvManagerType::Conda,
387+
pub struct Conda<'a> {
388+
pub environments: HashMap<String, PythonEnvironment>,
389+
pub manager: Option<EnvManager>,
390+
pub environment: &'a dyn Environment,
391+
}
392+
393+
impl Conda<'_> {
394+
pub fn with<'a>(environment: &'a impl Environment) -> Conda {
395+
Conda {
396+
environments: HashMap::new(),
397+
environment,
398+
manager: None,
399+
}
400+
}
401+
}
402+
403+
impl Locator for Conda<'_> {
404+
fn is_known(&self, python_executable: &PathBuf) -> bool {
405+
self.environments
406+
.contains_key(python_executable.to_str().unwrap_or_default())
407+
}
408+
409+
fn track_if_compatible(&mut self, _env: &PythonEnv) -> bool {
410+
// We will find everything in gather
411+
false
412+
}
413+
414+
fn gather(&mut self) -> Option<()> {
415+
let conda_binary = find_conda_binary(self.environment)?;
416+
let manager = EnvManager::new(
417+
conda_binary.clone(),
418+
get_conda_version(&conda_binary),
419+
EnvManagerType::Conda,
420+
);
421+
self.manager = Some(manager.clone());
422+
423+
let envs = get_distinct_conda_envs(&conda_binary, self.environment);
424+
for env in envs {
425+
let executable = find_python_binary_path(Path::new(&env.path));
426+
let env = messaging::PythonEnvironment::new(
427+
Some(env.name.to_string()),
428+
executable.clone(),
429+
messaging::PythonEnvironmentCategory::Conda,
430+
get_conda_python_version(&env.path),
431+
Some(env.path.clone()),
432+
Some(env.path.clone()),
433+
Some(manager.clone()),
434+
if env.named {
435+
Some(vec![
436+
conda_binary.to_string_lossy().to_string(),
437+
"run".to_string(),
438+
"-n".to_string(),
439+
env.name.to_string(),
440+
"python".to_string(),
441+
])
442+
} else {
443+
Some(vec![
444+
conda_binary.to_string_lossy().to_string(),
445+
"run".to_string(),
446+
"-p".to_string(),
447+
env.path.to_string_lossy().to_string(),
448+
"python".to_string(),
449+
])
450+
},
391451
);
392-
dispatcher.report_environment_manager(env_manager.clone());
393-
394-
let envs = get_distinct_conda_envs(&conda_binary, environment);
395-
for env in envs {
396-
let executable = find_python_binary_path(Path::new(&env.path));
397-
let params = messaging::PythonEnvironment::new(
398-
Some(env.name.to_string()),
399-
executable,
400-
messaging::PythonEnvironmentCategory::Conda,
401-
get_conda_python_version(&env.path),
402-
Some(env.path.clone()),
403-
Some(env.path.clone()),
404-
Some(env_manager.clone()),
405-
if env.named {
406-
Some(vec![
407-
conda_binary.to_string_lossy().to_string(),
408-
"run".to_string(),
409-
"-n".to_string(),
410-
env.name.to_string(),
411-
"python".to_string(),
412-
])
413-
} else {
414-
Some(vec![
415-
conda_binary.to_string_lossy().to_string(),
416-
"run".to_string(),
417-
"-p".to_string(),
418-
env.path.to_string_lossy().to_string(),
419-
"python".to_string(),
420-
])
421-
},
422-
);
423-
dispatcher.report_environment(params);
452+
453+
if let Some(exe) = executable {
454+
self.environments
455+
.insert(exe.to_str().unwrap_or_default().to_string(), env);
456+
} else if let Some(env_path) = env.env_path.clone() {
457+
self.environments
458+
.insert(env_path.to_str().unwrap().to_string(), env);
424459
}
425460
}
426-
None => (),
461+
462+
Some(())
463+
}
464+
465+
fn report(&self, reporter: &mut dyn MessageDispatcher) {
466+
if let Some(manager) = &self.manager {
467+
reporter.report_environment_manager(manager.clone());
468+
}
469+
470+
for env in self.environments.values() {
471+
reporter.report_environment(env.clone());
472+
}
427473
}
428474
}

0 commit comments

Comments
 (0)