Skip to content

Commit 3e7bf50

Browse files
committed
Remove duplicates from reported envs & refactor locators (#23407)
1 parent 424ca3f commit 3e7bf50

17 files changed

+495
-546
lines changed

native_locator/src/common_python.rs

Lines changed: 24 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@
22
// Licensed under the MIT License.
33

44
use crate::known::Environment;
5-
use crate::locator::Locator;
6-
use crate::messaging::MessageDispatcher;
5+
use crate::locator::{Locator, LocatorResult};
76
use crate::messaging::PythonEnvironment;
87
use crate::utils::{self, PythonEnv};
9-
use std::collections::HashMap;
108
use std::env;
119
use std::path::PathBuf;
1210

@@ -20,73 +18,61 @@ fn get_env_path(python_executable_path: &PathBuf) -> Option<PathBuf> {
2018
}
2119

2220
pub struct PythonOnPath<'a> {
23-
pub environments: HashMap<String, PythonEnvironment>,
2421
pub environment: &'a dyn Environment,
2522
}
2623

2724
impl PythonOnPath<'_> {
2825
pub fn with<'a>(environment: &'a impl Environment) -> PythonOnPath {
29-
PythonOnPath {
30-
environments: HashMap::new(),
31-
environment,
32-
}
26+
PythonOnPath { environment }
3327
}
3428
}
3529

3630
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 {
31+
fn resolve(&self, env: &PythonEnv) -> Option<PythonEnvironment> {
4332
let bin = if cfg!(windows) {
4433
"python.exe"
4534
} else {
4635
"python"
4736
};
4837
if env.executable.file_name().unwrap().to_ascii_lowercase() != bin {
49-
return false;
38+
return None;
5039
}
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
40+
Some(PythonEnvironment {
41+
name: None,
42+
python_executable_path: Some(env.executable.clone()),
43+
version: env.version.clone(),
44+
category: crate::messaging::PythonEnvironmentCategory::System,
45+
sys_prefix_path: None,
46+
env_path: env.path.clone(),
47+
env_manager: None,
48+
project_path: None,
49+
python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]),
50+
})
6651
}
6752

68-
fn gather(&mut self) -> Option<()> {
53+
fn find(&self) -> Option<LocatorResult> {
6954
let paths = self.environment.get_env_var("PATH".to_string())?;
7055
let bin = if cfg!(windows) {
7156
"python.exe"
7257
} else {
7358
"python"
7459
};
60+
let mut environments: Vec<PythonEnvironment> = vec![];
7561
env::split_paths(&paths)
7662
.map(|p| p.join(bin))
7763
.filter(|p| p.exists())
7864
.for_each(|full_path| {
7965
let version = utils::get_version(&full_path);
8066
let env_path = get_env_path(&full_path);
81-
self.track_if_compatible(&PythonEnv::new(full_path, env_path, version));
67+
if let Some(env) = self.resolve(&PythonEnv::new(full_path, env_path, version)) {
68+
environments.push(env);
69+
}
8270
});
8371

84-
Some(())
85-
}
86-
87-
fn report(&self, reporter: &mut dyn MessageDispatcher) {
88-
for env in self.environments.values() {
89-
reporter.report_environment(env.clone());
72+
if environments.is_empty() {
73+
None
74+
} else {
75+
Some(LocatorResult::Environments(environments))
9076
}
9177
}
9278
}

native_locator/src/conda.rs

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@
44
use crate::known;
55
use crate::known::Environment;
66
use crate::locator::Locator;
7+
use crate::locator::LocatorResult;
78
use crate::messaging;
89
use crate::messaging::EnvManager;
910
use crate::messaging::EnvManagerType;
10-
use crate::messaging::MessageDispatcher;
1111
use crate::messaging::PythonEnvironment;
1212
use crate::utils::find_python_binary_path;
1313
use crate::utils::PythonEnv;
1414
use regex::Regex;
15-
use std::collections::HashMap;
1615
use std::env;
1716
use std::path::{Path, PathBuf};
1817

@@ -385,42 +384,35 @@ fn get_distinct_conda_envs(
385384
}
386385

387386
pub struct Conda<'a> {
388-
pub environments: HashMap<String, PythonEnvironment>,
389387
pub manager: Option<EnvManager>,
390388
pub environment: &'a dyn Environment,
391389
}
392390

393391
impl Conda<'_> {
394392
pub fn with<'a>(environment: &'a impl Environment) -> Conda {
395393
Conda {
396-
environments: HashMap::new(),
397394
environment,
398395
manager: None,
399396
}
400397
}
401398
}
402399

403400
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 {
401+
fn resolve(&self, _env: &PythonEnv) -> Option<PythonEnvironment> {
410402
// We will find everything in gather
411-
false
403+
None
412404
}
413405

414-
fn gather(&mut self) -> Option<()> {
406+
fn find(&self) -> Option<LocatorResult> {
415407
let conda_binary = find_conda_binary(self.environment)?;
416408
let manager = EnvManager::new(
417409
conda_binary.clone(),
418410
get_conda_version(&conda_binary),
419411
EnvManagerType::Conda,
420412
);
421-
self.manager = Some(manager.clone());
422413

423414
let envs = get_distinct_conda_envs(&conda_binary, self.environment);
415+
let mut environments: Vec<PythonEnvironment> = vec![];
424416
for env in envs {
425417
let executable = find_python_binary_path(Path::new(&env.path));
426418
let env = messaging::PythonEnvironment::new(
@@ -450,25 +442,13 @@ impl Locator for Conda<'_> {
450442
},
451443
);
452444

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);
459-
}
460-
}
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());
445+
environments.push(env)
468446
}
469447

470-
for env in self.environments.values() {
471-
reporter.report_environment(env.clone());
448+
if environments.is_empty() {
449+
Some(LocatorResult::Managers(vec![manager]))
450+
} else {
451+
Some(LocatorResult::Environments(environments))
472452
}
473453
}
474454
}

native_locator/src/homebrew.rs

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

4-
use std::{
5-
collections::{HashMap, HashSet},
6-
fs::DirEntry,
7-
io::Error,
8-
path::PathBuf,
9-
};
10-
114
use crate::{
125
known::Environment,
13-
locator::Locator,
14-
messaging::{MessageDispatcher, PythonEnvironment},
6+
locator::{Locator, LocatorResult},
7+
messaging::PythonEnvironment,
158
utils::PythonEnv,
169
};
1710
use regex::Regex;
11+
use std::{collections::HashSet, fs::DirEntry, io::Error, path::PathBuf};
1812

1913
fn is_symlinked_python_executable(path: Result<DirEntry, Error>) -> Option<PathBuf> {
2014
let path = path.ok()?.path();
@@ -30,37 +24,28 @@ fn is_symlinked_python_executable(path: Result<DirEntry, Error>) -> Option<PathB
3024
}
3125

3226
pub struct Homebrew<'a> {
33-
pub environments: HashMap<String, PythonEnvironment>,
3427
pub environment: &'a dyn Environment,
3528
}
3629

3730
impl Homebrew<'_> {
3831
pub fn with<'a>(environment: &'a impl Environment) -> Homebrew {
39-
Homebrew {
40-
environments: HashMap::new(),
41-
environment,
42-
}
32+
Homebrew { environment }
4333
}
4434
}
4535

4636
impl Locator for Homebrew<'_> {
47-
fn is_known(&self, python_executable: &PathBuf) -> bool {
48-
self.environments
49-
.contains_key(python_executable.to_str().unwrap_or_default())
37+
fn resolve(&self, _env: &PythonEnv) -> Option<PythonEnvironment> {
38+
None
5039
}
5140

52-
fn track_if_compatible(&mut self, _env: &PythonEnv) -> bool {
53-
// We will find everything in gather
54-
false
55-
}
56-
57-
fn gather(&mut self) -> Option<()> {
41+
fn find(&self) -> Option<LocatorResult> {
5842
let homebrew_prefix = self
5943
.environment
6044
.get_env_var("HOMEBREW_PREFIX".to_string())?;
6145
let homebrew_prefix_bin = PathBuf::from(homebrew_prefix).join("bin");
6246
let mut reported: HashSet<String> = HashSet::new();
6347
let python_regex = Regex::new(r"/(\d+\.\d+\.\d+)/").unwrap();
48+
let mut environments: Vec<PythonEnvironment> = vec![];
6449
for file in std::fs::read_dir(homebrew_prefix_bin).ok()? {
6550
if let Some(exe) = is_symlinked_python_executable(file) {
6651
let python_version = exe.to_string_lossy().to_string();
@@ -85,16 +70,13 @@ impl Locator for Homebrew<'_> {
8570
None,
8671
Some(vec![exe.to_string_lossy().to_string()]),
8772
);
88-
self.environments
89-
.insert(exe.to_string_lossy().to_string(), env);
73+
environments.push(env);
9074
}
9175
}
92-
Some(())
93-
}
94-
95-
fn report(&self, reporter: &mut dyn MessageDispatcher) {
96-
for env in self.environments.values() {
97-
reporter.report_environment(env.clone());
76+
if environments.is_empty() {
77+
None
78+
} else {
79+
Some(LocatorResult::Environments(environments))
9880
}
9981
}
10082
}

native_locator/src/locator.rs

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

4-
use crate::{messaging::MessageDispatcher, utils::PythonEnv};
5-
use std::path::PathBuf;
4+
use crate::{
5+
messaging::{EnvManager, PythonEnvironment},
6+
utils::PythonEnv,
7+
};
8+
9+
#[derive(Debug)]
10+
pub enum LocatorResult {
11+
Managers(Vec<EnvManager>),
12+
Environments(Vec<PythonEnvironment>),
13+
}
614

715
pub trait Locator {
816
/**
9-
* Whether the given Python executable is known to this locator.
10-
*/
11-
fn is_known(&self, python_executable: &PathBuf) -> bool;
12-
/**
13-
* Track the given Python executable if it is compatible with the environments supported by this locator.
14-
* This way, when report is called, the environment passed here will be reported as a known environment by this locator.
15-
* Returns true if the environment was tracked, false otherwise.
16-
*/
17-
fn track_if_compatible(&mut self, env: &PythonEnv) -> bool;
18-
/**
19-
* Finds all environments managed by this locator.
17+
* Given a Python environment, this will convert it to a PythonEnvironment that can be supported by this locator.
18+
* If an environment is not supported by this locator, this will return None.
19+
*
20+
* I.e. use this to test whether an environment is of a specific type.
2021
*/
21-
fn gather(&mut self) -> Option<()>;
22+
fn resolve(&self, env: &PythonEnv) -> Option<PythonEnvironment>;
2223
/**
23-
* Report all of the tracked environments and managers.
24+
* Finds all environments specific to this locator.
2425
*/
25-
fn report(&self, reporter: &mut dyn MessageDispatcher);
26+
fn find(&self) -> Option<LocatorResult>;
2627
}

0 commit comments

Comments
 (0)