diff --git a/native_locator/src/common_python.rs b/native_locator/src/common_python.rs index dc796c164070..ddb3d3bbe1ab 100644 --- a/native_locator/src/common_python.rs +++ b/native_locator/src/common_python.rs @@ -2,11 +2,9 @@ // Licensed under the MIT License. use crate::known::Environment; -use crate::locator::Locator; -use crate::messaging::MessageDispatcher; +use crate::locator::{Locator, LocatorResult}; use crate::messaging::PythonEnvironment; use crate::utils::{self, PythonEnv}; -use std::collections::HashMap; use std::env; use std::path::PathBuf; @@ -20,73 +18,61 @@ fn get_env_path(python_executable_path: &PathBuf) -> Option { } pub struct PythonOnPath<'a> { - pub environments: HashMap, pub environment: &'a dyn Environment, } impl PythonOnPath<'_> { pub fn with<'a>(environment: &'a impl Environment) -> PythonOnPath { - PythonOnPath { - environments: HashMap::new(), - environment, - } + PythonOnPath { environment } } } impl Locator for PythonOnPath<'_> { - fn is_known(&self, python_executable: &PathBuf) -> bool { - self.environments - .contains_key(python_executable.to_str().unwrap_or_default()) - } - - fn track_if_compatible(&mut self, env: &PythonEnv) -> bool { + fn resolve(&self, env: &PythonEnv) -> Option { let bin = if cfg!(windows) { "python.exe" } else { "python" }; if env.executable.file_name().unwrap().to_ascii_lowercase() != bin { - return false; + return None; } - self.environments.insert( - env.executable.to_str().unwrap().to_string(), - PythonEnvironment { - name: None, - python_executable_path: Some(env.executable.clone()), - version: env.version.clone(), - category: crate::messaging::PythonEnvironmentCategory::System, - sys_prefix_path: None, - env_path: env.path.clone(), - env_manager: None, - project_path: None, - python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), - }, - ); - true + Some(PythonEnvironment { + name: None, + python_executable_path: Some(env.executable.clone()), + version: env.version.clone(), + category: crate::messaging::PythonEnvironmentCategory::System, + sys_prefix_path: None, + env_path: env.path.clone(), + env_manager: None, + project_path: None, + python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), + }) } - fn gather(&mut self) -> Option<()> { + fn find(&self) -> Option { let paths = self.environment.get_env_var("PATH".to_string())?; let bin = if cfg!(windows) { "python.exe" } else { "python" }; + let mut environments: Vec = vec![]; env::split_paths(&paths) .map(|p| p.join(bin)) .filter(|p| p.exists()) .for_each(|full_path| { let version = utils::get_version(&full_path); let env_path = get_env_path(&full_path); - self.track_if_compatible(&PythonEnv::new(full_path, env_path, version)); + if let Some(env) = self.resolve(&PythonEnv::new(full_path, env_path, version)) { + environments.push(env); + } }); - Some(()) - } - - fn report(&self, reporter: &mut dyn MessageDispatcher) { - for env in self.environments.values() { - reporter.report_environment(env.clone()); + if environments.is_empty() { + None + } else { + Some(LocatorResult::Environments(environments)) } } } diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 23f86f811ba0..a56c655c06ee 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -4,15 +4,14 @@ use crate::known; use crate::known::Environment; use crate::locator::Locator; +use crate::locator::LocatorResult; use crate::messaging; use crate::messaging::EnvManager; use crate::messaging::EnvManagerType; -use crate::messaging::MessageDispatcher; use crate::messaging::PythonEnvironment; use crate::utils::find_python_binary_path; use crate::utils::PythonEnv; use regex::Regex; -use std::collections::HashMap; use std::env; use std::path::{Path, PathBuf}; @@ -385,7 +384,6 @@ fn get_distinct_conda_envs( } pub struct Conda<'a> { - pub environments: HashMap, pub manager: Option, pub environment: &'a dyn Environment, } @@ -393,7 +391,6 @@ pub struct Conda<'a> { impl Conda<'_> { pub fn with<'a>(environment: &'a impl Environment) -> Conda { Conda { - environments: HashMap::new(), environment, manager: None, } @@ -401,26 +398,21 @@ impl Conda<'_> { } impl Locator for Conda<'_> { - fn is_known(&self, python_executable: &PathBuf) -> bool { - self.environments - .contains_key(python_executable.to_str().unwrap_or_default()) - } - - fn track_if_compatible(&mut self, _env: &PythonEnv) -> bool { + fn resolve(&self, _env: &PythonEnv) -> Option { // We will find everything in gather - false + None } - fn gather(&mut self) -> Option<()> { + fn find(&self) -> Option { let conda_binary = find_conda_binary(self.environment)?; let manager = EnvManager::new( conda_binary.clone(), get_conda_version(&conda_binary), EnvManagerType::Conda, ); - self.manager = Some(manager.clone()); let envs = get_distinct_conda_envs(&conda_binary, self.environment); + let mut environments: Vec = vec![]; for env in envs { let executable = find_python_binary_path(Path::new(&env.path)); let env = messaging::PythonEnvironment::new( @@ -450,25 +442,13 @@ impl Locator for Conda<'_> { }, ); - if let Some(exe) = executable { - self.environments - .insert(exe.to_str().unwrap_or_default().to_string(), env); - } else if let Some(env_path) = env.env_path.clone() { - self.environments - .insert(env_path.to_str().unwrap().to_string(), env); - } - } - - Some(()) - } - - fn report(&self, reporter: &mut dyn MessageDispatcher) { - if let Some(manager) = &self.manager { - reporter.report_environment_manager(manager.clone()); + environments.push(env) } - for env in self.environments.values() { - reporter.report_environment(env.clone()); + if environments.is_empty() { + Some(LocatorResult::Managers(vec![manager])) + } else { + Some(LocatorResult::Environments(environments)) } } } diff --git a/native_locator/src/homebrew.rs b/native_locator/src/homebrew.rs index b565dbaf27ba..0830c4f7d532 100644 --- a/native_locator/src/homebrew.rs +++ b/native_locator/src/homebrew.rs @@ -1,20 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use std::{ - collections::{HashMap, HashSet}, - fs::DirEntry, - io::Error, - path::PathBuf, -}; - use crate::{ known::Environment, - locator::Locator, - messaging::{MessageDispatcher, PythonEnvironment}, + locator::{Locator, LocatorResult}, + messaging::PythonEnvironment, utils::PythonEnv, }; use regex::Regex; +use std::{collections::HashSet, fs::DirEntry, io::Error, path::PathBuf}; fn is_symlinked_python_executable(path: Result) -> Option { let path = path.ok()?.path(); @@ -30,37 +24,28 @@ fn is_symlinked_python_executable(path: Result) -> Option { - pub environments: HashMap, pub environment: &'a dyn Environment, } impl Homebrew<'_> { pub fn with<'a>(environment: &'a impl Environment) -> Homebrew { - Homebrew { - environments: HashMap::new(), - environment, - } + Homebrew { environment } } } impl Locator for Homebrew<'_> { - fn is_known(&self, python_executable: &PathBuf) -> bool { - self.environments - .contains_key(python_executable.to_str().unwrap_or_default()) + fn resolve(&self, _env: &PythonEnv) -> Option { + None } - fn track_if_compatible(&mut self, _env: &PythonEnv) -> bool { - // We will find everything in gather - false - } - - fn gather(&mut self) -> Option<()> { + fn find(&self) -> Option { let homebrew_prefix = self .environment .get_env_var("HOMEBREW_PREFIX".to_string())?; let homebrew_prefix_bin = PathBuf::from(homebrew_prefix).join("bin"); let mut reported: HashSet = HashSet::new(); let python_regex = Regex::new(r"/(\d+\.\d+\.\d+)/").unwrap(); + let mut environments: Vec = vec![]; for file in std::fs::read_dir(homebrew_prefix_bin).ok()? { if let Some(exe) = is_symlinked_python_executable(file) { let python_version = exe.to_string_lossy().to_string(); @@ -85,16 +70,13 @@ impl Locator for Homebrew<'_> { None, Some(vec![exe.to_string_lossy().to_string()]), ); - self.environments - .insert(exe.to_string_lossy().to_string(), env); + environments.push(env); } } - Some(()) - } - - fn report(&self, reporter: &mut dyn MessageDispatcher) { - for env in self.environments.values() { - reporter.report_environment(env.clone()); + if environments.is_empty() { + None + } else { + Some(LocatorResult::Environments(environments)) } } } diff --git a/native_locator/src/locator.rs b/native_locator/src/locator.rs index 9354207fa14a..aa812c11bd37 100644 --- a/native_locator/src/locator.rs +++ b/native_locator/src/locator.rs @@ -1,26 +1,27 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::{messaging::MessageDispatcher, utils::PythonEnv}; -use std::path::PathBuf; +use crate::{ + messaging::{EnvManager, PythonEnvironment}, + utils::PythonEnv, +}; + +#[derive(Debug)] +pub enum LocatorResult { + Managers(Vec), + Environments(Vec), +} pub trait Locator { /** - * Whether the given Python executable is known to this locator. - */ - fn is_known(&self, python_executable: &PathBuf) -> bool; - /** - * Track the given Python executable if it is compatible with the environments supported by this locator. - * This way, when report is called, the environment passed here will be reported as a known environment by this locator. - * Returns true if the environment was tracked, false otherwise. - */ - fn track_if_compatible(&mut self, env: &PythonEnv) -> bool; - /** - * Finds all environments managed by this locator. + * Given a Python environment, this will convert it to a PythonEnvironment that can be supported by this locator. + * If an environment is not supported by this locator, this will return None. + * + * I.e. use this to test whether an environment is of a specific type. */ - fn gather(&mut self) -> Option<()>; + fn resolve(&self, env: &PythonEnv) -> Option; /** - * Report all of the tracked environments and managers. + * Finds all environments specific to this locator. */ - fn report(&self, reporter: &mut dyn MessageDispatcher); + fn find(&self) -> Option; } diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index faa1108f8b05..9a3ffc27d6df 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -1,14 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +use crate::messaging::initialize_logger; use global_virtualenvs::list_global_virtual_envs; use known::EnvironmentApi; use locator::Locator; use log::LevelFilter; -use messaging::{create_dispatcher, MessageDispatcher}; +use messaging::{create_dispatcher, JsonRpcDispatcher, MessageDispatcher}; use std::time::SystemTime; - -use crate::messaging::initialize_logger; +use utils::PythonEnv; mod common_python; mod conda; @@ -27,86 +27,49 @@ mod virtualenvwrapper; mod windows_python; fn main() { - let mut dispatcher = create_dispatcher(); let environment = EnvironmentApi {}; initialize_logger(LevelFilter::Debug); log::info!("Starting Native Locator"); let now = SystemTime::now(); + let mut dispatcher = create_dispatcher(); + + let virtualenv_locator = virtualenv::VirtualEnv::new(); + let venv_locator = venv::Venv::new(); + let virtualenvwrapper_locator = virtualenvwrapper::VirtualEnvWrapper::with(&environment); + let pipenv_locator = pipenv::PipEnv::new(); + let path_locator = common_python::PythonOnPath::with(&environment); + let pyenv_locator = pyenv::PyEnv::with(&environment); - let mut virtualenv_locator = virtualenv::VirtualEnv::new(); - let mut venv_locator = venv::Venv::new(); - let mut virtualenvwrapper_locator = virtualenvwrapper::VirtualEnvWrapper::with(&environment); - let mut pipenv_locator = pipenv::PipEnv::new(); - let mut path_locator = common_python::PythonOnPath::with(&environment); - let mut pyenv_locator = pyenv::PyEnv::with(&environment); #[cfg(unix)] - let mut homebrew_locator = homebrew::Homebrew::with(&environment); + let homebrew_locator = homebrew::Homebrew::with(&environment); #[cfg(windows)] - let mut windows_locator = windows_python::WindowsPython::with(&environment); - let mut conda_locator = conda::Conda::with(&environment); + let windows_locator = windows_python::WindowsPython::with(&environment); + let conda_locator = conda::Conda::with(&environment); - // These environments take predence over all others. + // Step 1: These environments take precedence over all others. // As they are very specific and guaranteed to be specific type. - pyenv_locator.gather(); + find_environments(&pyenv_locator, &mut dispatcher); #[cfg(unix)] - homebrew_locator.gather(); - conda_locator.gather(); - - // Finds Windows Store, Known Path, and Registry pythons + find_environments(&homebrew_locator, &mut dispatcher); + find_environments(&conda_locator, &mut dispatcher); #[cfg(windows)] - windows_locator.gather(); + find_environments(&windows_locator, &mut dispatcher); + // Step 2: Search in some global locations. for env in list_global_virtual_envs(&environment).iter() { - if pyenv_locator.is_known(&env.executable) { - continue; - } - #[cfg(windows)] - if windows_locator.is_known(&env.executable) { - continue; - } - if conda_locator.is_known(&env.executable) { - continue; - } - #[cfg(unix)] - if homebrew_locator.is_known(&env.executable) { + if dispatcher.was_environment_reported(&env) { continue; } - if pipenv_locator.track_if_compatible(&env) { - continue; - } - if virtualenvwrapper_locator.track_if_compatible(&env) { - continue; - } - if venv_locator.track_if_compatible(&env) { - continue; - } - if virtualenv_locator.track_if_compatible(&env) { - continue; - } + let _ = resolve_environment(&pipenv_locator, env, &mut dispatcher) + || resolve_environment(&virtualenvwrapper_locator, env, &mut dispatcher) + || resolve_environment(&venv_locator, env, &mut dispatcher) + || resolve_environment(&virtualenv_locator, env, &mut dispatcher); } - // Finds python on PATH - // This is the last place to look for unknown python environments. - path_locator.gather(); - - let all_locators: [&dyn Locator; 8] = [ - &virtualenv_locator, - &venv_locator, - &virtualenvwrapper_locator, - &pipenv_locator, - &path_locator, - &pyenv_locator, - #[cfg(unix)] - &homebrew_locator, - #[cfg(windows)] - &windows_locator, - &conda_locator, - ]; - all_locators - .iter() - .for_each(|locator| locator.report(&mut dispatcher)); + // Step 3: Finally find in the current PATH variable + find_environments(&path_locator, &mut dispatcher); match now.elapsed() { Ok(elapsed) => { @@ -119,3 +82,27 @@ fn main() { dispatcher.exit(); } + +fn resolve_environment( + locator: &dyn Locator, + env: &PythonEnv, + dispatcher: &mut JsonRpcDispatcher, +) -> bool { + if let Some(env) = locator.resolve(env) { + dispatcher.report_environment(env); + return true; + } + false +} + +fn find_environments(locator: &dyn Locator, dispatcher: &mut JsonRpcDispatcher) -> Option<()> { + match locator.find()? { + locator::LocatorResult::Environments(envs) => envs + .iter() + .for_each(|e| dispatcher.report_environment(e.clone())), + locator::LocatorResult::Managers(items) => items + .iter() + .for_each(|m| dispatcher.report_environment_manager(m.clone())), + } + Some(()) +} diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index 7b782bca1b11..959ec8d36f31 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -1,13 +1,17 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -use crate::logging::{LogLevel, LogMessage}; +use crate::{ + logging::{LogLevel, LogMessage}, + utils::PythonEnv, +}; use env_logger::Builder; use log::LevelFilter; use serde::{Deserialize, Serialize}; -use std::path::PathBuf; +use std::{collections::HashSet, path::PathBuf}; pub trait MessageDispatcher { + fn was_environment_reported(&self, env: &PythonEnv) -> bool; fn report_environment_manager(&mut self, env: EnvManager) -> (); fn report_environment(&mut self, env: PythonEnvironment) -> (); fn exit(&mut self) -> (); @@ -15,6 +19,7 @@ pub trait MessageDispatcher { #[derive(Serialize, Deserialize, Copy, Clone)] #[serde(rename_all = "camelCase")] +#[derive(Debug)] pub enum EnvManagerType { Conda, Pyenv, @@ -22,6 +27,7 @@ pub enum EnvManagerType { #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] +#[derive(Debug)] pub struct EnvManager { pub executable_path: PathBuf, pub version: Option, @@ -50,6 +56,7 @@ impl Clone for EnvManager { #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] +#[derive(Debug)] pub struct EnvManagerMessage { pub jsonrpc: String, pub method: String, @@ -68,6 +75,7 @@ impl EnvManagerMessage { #[derive(Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] +#[derive(Debug)] pub enum PythonEnvironmentCategory { System, Homebrew, @@ -83,6 +91,7 @@ pub enum PythonEnvironmentCategory { #[derive(Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] +#[derive(Debug)] pub struct PythonEnvironment { pub name: Option, pub python_executable_path: Option, @@ -148,6 +157,7 @@ impl PythonEnvironment { #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] +#[derive(Debug)] pub struct PythonEnvironmentMessage { pub jsonrpc: String, pub method: String, @@ -166,6 +176,7 @@ impl PythonEnvironmentMessage { #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] +#[derive(Debug)] pub struct ExitMessage { pub jsonrpc: String, pub method: String, @@ -182,7 +193,10 @@ impl ExitMessage { } } -pub struct JsonRpcDispatcher {} +pub struct JsonRpcDispatcher { + pub reported_managers: HashSet, + pub reported_environments: HashSet, +} pub fn send_message(message: T) -> () { let message = serde_json::to_string(&message).unwrap(); print!( @@ -211,12 +225,34 @@ pub fn initialize_logger(log_level: LevelFilter) { .filter(None, log_level) .init(); } + +impl JsonRpcDispatcher {} impl MessageDispatcher for JsonRpcDispatcher { + fn was_environment_reported(&self, env: &PythonEnv) -> bool { + if let Some(key) = env.executable.as_os_str().to_str() { + return self.reported_environments.contains(key); + } + false + } + fn report_environment_manager(&mut self, env: EnvManager) -> () { - send_message(EnvManagerMessage::new(env)); + if let Some(key) = get_manager_key(&env) { + if !self.reported_managers.contains(&key) { + self.reported_managers.insert(key); + send_message(EnvManagerMessage::new(env)); + } + } } fn report_environment(&mut self, env: PythonEnvironment) -> () { - send_message(PythonEnvironmentMessage::new(env)); + if let Some(key) = get_environment_key(&env) { + if !self.reported_environments.contains(&key) { + self.reported_environments.insert(key); + send_message(PythonEnvironmentMessage::new(env.clone())); + } + if let Some(manager) = env.env_manager { + self.report_environment_manager(manager); + } + } } fn exit(&mut self) -> () { send_message(ExitMessage::new()); @@ -224,5 +260,22 @@ impl MessageDispatcher for JsonRpcDispatcher { } pub fn create_dispatcher() -> JsonRpcDispatcher { - JsonRpcDispatcher {} + JsonRpcDispatcher { + reported_managers: HashSet::new(), + reported_environments: HashSet::new(), + } +} + +fn get_environment_key(env: &PythonEnvironment) -> Option { + match env.python_executable_path.clone() { + Some(key) => Some(key.as_os_str().to_str()?.to_string()), + None => match env.env_path.clone() { + Some(key) => Some(key.as_os_str().to_str().unwrap().to_string()), + None => None, + }, + } +} + +fn get_manager_key(manager: &EnvManager) -> Option { + Some(manager.executable_path.to_str()?.to_string()) } diff --git a/native_locator/src/pipenv.rs b/native_locator/src/pipenv.rs index 1e266732536c..3e9de48ff13a 100644 --- a/native_locator/src/pipenv.rs +++ b/native_locator/src/pipenv.rs @@ -1,10 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::locator::Locator; -use crate::messaging::{MessageDispatcher, PythonEnvironment}; +use crate::locator::{Locator, LocatorResult}; +use crate::messaging::PythonEnvironment; use crate::utils::PythonEnv; -use std::collections::HashMap; use std::fs; use std::path::PathBuf; @@ -22,56 +21,28 @@ fn get_pipenv_project(env: &PythonEnv) -> Option { None } -pub struct PipEnv { - pub environments: HashMap, -} +pub struct PipEnv {} impl PipEnv { pub fn new() -> PipEnv { - PipEnv { - environments: HashMap::new(), - } + PipEnv {} } } impl Locator for PipEnv { - fn is_known(&self, python_executable: &PathBuf) -> bool { - self.environments - .contains_key(python_executable.to_str().unwrap_or_default()) + fn resolve(&self, env: &PythonEnv) -> Option { + let project_path = get_pipenv_project(env)?; + Some(PythonEnvironment::new_pipenv( + Some(env.executable.clone()), + env.version.clone(), + env.path.clone(), + env.path.clone(), + None, + project_path, + )) } - fn track_if_compatible(&mut self, env: &PythonEnv) -> bool { - if let Some(project_path) = get_pipenv_project(env) { - let env = PythonEnvironment::new_pipenv( - Some(env.executable.clone()), - env.version.clone(), - env.path.clone(), - env.path.clone(), - None, - project_path, - ); - - self.environments.insert( - env.python_executable_path - .clone() - .unwrap() - .to_str() - .unwrap() - .to_string(), - env, - ); - return true; - } - false - } - - fn gather(&mut self) -> Option<()> { + fn find(&self) -> Option { None } - - fn report(&self, reporter: &mut dyn MessageDispatcher) { - for env in self.environments.values() { - reporter.report_environment(env.clone()); - } - } } diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs index a5ead7d44ad2..1d1d73b94e64 100644 --- a/native_locator/src/pyenv.rs +++ b/native_locator/src/pyenv.rs @@ -1,22 +1,20 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use regex::Regex; -use std::collections::HashMap; -use std::fs; -use std::path::PathBuf; - use crate::known; use crate::known::Environment; use crate::locator::Locator; +use crate::locator::LocatorResult; use crate::messaging; use crate::messaging::EnvManager; use crate::messaging::EnvManagerType; -use crate::messaging::MessageDispatcher; use crate::messaging::PythonEnvironment; use crate::utils::find_and_parse_pyvenv_cfg; use crate::utils::find_python_binary_path; use crate::utils::PythonEnv; +use regex::Regex; +use std::fs; +use std::path::PathBuf; #[cfg(windows)] fn get_home_pyenv_dir(environment: &dyn known::Environment) -> Option { @@ -178,63 +176,34 @@ pub fn list_pyenv_environments( } pub struct PyEnv<'a> { - pub environments: HashMap, pub environment: &'a dyn Environment, - pub manager: Option, } impl PyEnv<'_> { pub fn with<'a>(environment: &'a impl Environment) -> PyEnv { - PyEnv { - environments: HashMap::new(), - environment, - manager: None, - } + PyEnv { environment } } } impl Locator for PyEnv<'_> { - fn is_known(&self, python_executable: &PathBuf) -> bool { - self.environments - .contains_key(python_executable.to_str().unwrap_or_default()) - } - - fn track_if_compatible(&mut self, _env: &PythonEnv) -> bool { + fn resolve(&self, _env: &PythonEnv) -> Option { // We will find everything in gather - false + None } - fn gather(&mut self) -> Option<()> { - let manager = match get_pyenv_binary(self.environment) { - Some(pyenv_binary) => Some(messaging::EnvManager::new( - pyenv_binary, - None, - EnvManagerType::Pyenv, - )), - None => None, - }; - self.manager = manager.clone(); - - for env in list_pyenv_environments(&manager, self.environment)? { - self.environments.insert( - env.python_executable_path - .as_ref() - .unwrap() - .to_str() - .unwrap() - .to_string(), - env, - ); - } - Some(()) - } - - fn report(&self, reporter: &mut dyn MessageDispatcher) { - if let Some(manager) = &self.manager { - reporter.report_environment_manager(manager.clone()); + fn find(&self) -> Option { + let pyenv_binary = get_pyenv_binary(self.environment)?; + let manager = messaging::EnvManager::new(pyenv_binary, None, EnvManagerType::Pyenv); + let mut environments: Vec = vec![]; + if let Some(envs) = list_pyenv_environments(&Some(manager.clone()), self.environment) { + for env in envs { + environments.push(env); + } } - for env in self.environments.values() { - reporter.report_environment(env.clone()); + if environments.is_empty() { + Some(LocatorResult::Managers(vec![manager])) + } else { + Some(LocatorResult::Environments(environments)) } } } diff --git a/native_locator/src/venv.rs b/native_locator/src/venv.rs index 2b76c7dcf751..24c490acafad 100644 --- a/native_locator/src/venv.rs +++ b/native_locator/src/venv.rs @@ -1,11 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use std::{collections::HashMap, path::PathBuf}; - use crate::{ - locator::Locator, - messaging::{MessageDispatcher, PythonEnvironment}, + locator::{Locator, LocatorResult}, + messaging::PythonEnvironment, utils::{self, PythonEnv}, }; @@ -16,62 +14,43 @@ pub fn is_venv(env: &PythonEnv) -> bool { } return utils::find_pyvenv_config_path(&env.executable).is_some(); } -pub struct Venv { - pub environments: HashMap, -} +pub struct Venv {} impl Venv { pub fn new() -> Venv { - Venv { - environments: HashMap::new(), - } + Venv {} } } impl Locator for Venv { - fn is_known(&self, python_executable: &PathBuf) -> bool { - self.environments - .contains_key(python_executable.to_str().unwrap_or_default()) - } - - fn track_if_compatible(&mut self, env: &PythonEnv) -> bool { + fn resolve(&self, env: &PythonEnv) -> Option { if is_venv(&env) { - self.environments.insert( - env.executable.to_str().unwrap().to_string(), - PythonEnvironment { - name: Some( - env.path - .clone() - .expect("env.path can never be empty for venvs") - .file_name() - .unwrap() - .to_string_lossy() - .to_string(), - ), - python_executable_path: Some(env.executable.clone()), - version: env.version.clone(), - category: crate::messaging::PythonEnvironmentCategory::Venv, - sys_prefix_path: env.path.clone(), - env_path: env.path.clone(), - env_manager: None, - project_path: None, - python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), - }, - ); - return true; + return Some(PythonEnvironment { + name: Some( + env.path + .clone() + .expect("env.path can never be empty for venvs") + .file_name() + .unwrap() + .to_string_lossy() + .to_string(), + ), + python_executable_path: Some(env.executable.clone()), + version: env.version.clone(), + category: crate::messaging::PythonEnvironmentCategory::Venv, + sys_prefix_path: env.path.clone(), + env_path: env.path.clone(), + env_manager: None, + project_path: None, + python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), + }); } - false + None } - fn gather(&mut self) -> Option<()> { + fn find(&self) -> Option { // There are no common global locations for virtual environments. // We expect the user of this class to call `is_compatible` None } - - fn report(&self, reporter: &mut dyn MessageDispatcher) { - for env in self.environments.values() { - reporter.report_environment(env.clone()); - } - } } diff --git a/native_locator/src/virtualenv.rs b/native_locator/src/virtualenv.rs index 40972b80a14d..49926f14c0ad 100644 --- a/native_locator/src/virtualenv.rs +++ b/native_locator/src/virtualenv.rs @@ -1,11 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::locator::Locator; -use crate::messaging::{MessageDispatcher, PythonEnvironment}; +use crate::locator::{Locator, LocatorResult}; +use crate::messaging::PythonEnvironment; use crate::utils::PythonEnv; -use std::collections::HashMap; -use std::path::PathBuf; pub fn is_virtualenv(env: &PythonEnv) -> bool { if env.path.is_none() { @@ -47,62 +45,43 @@ pub fn is_virtualenv(env: &PythonEnv) -> bool { false } -pub struct VirtualEnv { - pub environments: HashMap, -} +pub struct VirtualEnv {} impl VirtualEnv { pub fn new() -> VirtualEnv { - VirtualEnv { - environments: HashMap::new(), - } + VirtualEnv {} } } impl Locator for VirtualEnv { - fn is_known(&self, python_executable: &PathBuf) -> bool { - self.environments - .contains_key(python_executable.to_str().unwrap_or_default()) - } - - fn track_if_compatible(&mut self, env: &PythonEnv) -> bool { + fn resolve(&self, env: &PythonEnv) -> Option { if is_virtualenv(env) { - self.environments.insert( - env.executable.to_str().unwrap().to_string(), - PythonEnvironment { - name: Some( - env.path - .clone() - .expect("env.path can never be empty for virtualenvs") - .file_name() - .unwrap() - .to_string_lossy() - .to_string(), - ), - python_executable_path: Some(env.executable.clone()), - version: env.version.clone(), - category: crate::messaging::PythonEnvironmentCategory::VirtualEnv, - sys_prefix_path: env.path.clone(), - env_path: env.path.clone(), - env_manager: None, - project_path: None, - python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), - }, - ); - return true; + return Some(PythonEnvironment { + name: Some( + env.path + .clone() + .expect("env.path can never be empty for virtualenvs") + .file_name() + .unwrap() + .to_string_lossy() + .to_string(), + ), + python_executable_path: Some(env.executable.clone()), + version: env.version.clone(), + category: crate::messaging::PythonEnvironmentCategory::VirtualEnv, + sys_prefix_path: env.path.clone(), + env_path: env.path.clone(), + env_manager: None, + project_path: None, + python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), + }); } - false + None } - fn gather(&mut self) -> Option<()> { + fn find(&self) -> Option { // There are no common global locations for virtual environments. // We expect the user of this class to call `is_compatible` None } - - fn report(&self, reporter: &mut dyn MessageDispatcher) { - for env in self.environments.values() { - reporter.report_environment(env.clone()); - } - } } diff --git a/native_locator/src/virtualenvwrapper.rs b/native_locator/src/virtualenvwrapper.rs index e70cfc82af75..65b5b3da0003 100644 --- a/native_locator/src/virtualenvwrapper.rs +++ b/native_locator/src/virtualenvwrapper.rs @@ -1,12 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::locator::Locator; +use crate::locator::{Locator, LocatorResult}; use crate::messaging::PythonEnvironment; use crate::utils::list_python_environments; use crate::virtualenv; -use crate::{known::Environment, messaging::MessageDispatcher, utils::PythonEnv}; -use std::collections::HashMap; +use crate::{known::Environment, utils::PythonEnv}; use std::path::PathBuf; #[cfg(windows)] @@ -68,67 +67,55 @@ pub fn is_virtualenvwrapper(env: &PythonEnv, environment: &dyn Environment) -> b } pub struct VirtualEnvWrapper<'a> { - pub environments: HashMap, pub environment: &'a dyn Environment, } impl VirtualEnvWrapper<'_> { pub fn with<'a>(environment: &'a impl Environment) -> VirtualEnvWrapper { - VirtualEnvWrapper { - environments: HashMap::new(), - environment, - } + VirtualEnvWrapper { environment } } } impl Locator for VirtualEnvWrapper<'_> { - fn is_known(&self, python_executable: &PathBuf) -> bool { - self.environments - .contains_key(python_executable.to_str().unwrap_or_default()) - } - - fn track_if_compatible(&mut self, env: &PythonEnv) -> bool { + fn resolve(&self, env: &PythonEnv) -> Option { if is_virtualenvwrapper(env, self.environment) { - self.environments.insert( - env.executable.to_str().unwrap().to_string(), - PythonEnvironment { - name: Some( - env.path - .clone() - .expect("env.path cannot be empty for virtualenv rapper") - .file_name() - .unwrap() - .to_string_lossy() - .to_string(), - ), - python_executable_path: Some(env.executable.clone()), - version: env.version.clone(), - category: crate::messaging::PythonEnvironmentCategory::Venv, - sys_prefix_path: env.path.clone(), - env_path: env.path.clone(), - env_manager: None, - project_path: None, - python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), - }, - ); - return true; + return Some(PythonEnvironment { + name: Some( + env.path + .clone() + .expect("env.path cannot be empty for virtualenv rapper") + .file_name() + .unwrap() + .to_string_lossy() + .to_string(), + ), + python_executable_path: Some(env.executable.clone()), + version: env.version.clone(), + category: crate::messaging::PythonEnvironmentCategory::Venv, + sys_prefix_path: env.path.clone(), + env_path: env.path.clone(), + env_manager: None, + project_path: None, + python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), + }); } - false + None } - fn gather(&mut self) -> Option<()> { + fn find(&self) -> Option { let work_on_home = get_work_on_home_path(self.environment)?; let envs = list_python_environments(&work_on_home)?; + let mut environments: Vec = vec![]; envs.iter().for_each(|env| { - self.track_if_compatible(env); + if let Some(env) = self.resolve(env) { + environments.push(env); + } }); - Some(()) - } - - fn report(&self, reporter: &mut dyn MessageDispatcher) { - for env in self.environments.values() { - reporter.report_environment(env.clone()); + if environments.is_empty() { + None + } else { + Some(LocatorResult::Environments(environments)) } } } diff --git a/native_locator/src/windows_python.rs b/native_locator/src/windows_python.rs index 5e34ba127947..cb1d384869a7 100644 --- a/native_locator/src/windows_python.rs +++ b/native_locator/src/windows_python.rs @@ -3,11 +3,9 @@ use crate::known; use crate::known::Environment; -use crate::locator::Locator; -use crate::messaging::MessageDispatcher; +use crate::locator::{Locator, LocatorResult}; use crate::messaging::PythonEnvironment; use crate::utils::PythonEnv; -use std::collections::HashMap; use std::path::Path; use std::path::PathBuf; @@ -45,64 +43,55 @@ fn list_registry_pythons() -> Option> { } pub struct WindowsPython<'a> { - pub environments: HashMap, pub environment: &'a dyn Environment, } impl WindowsPython<'_> { #[allow(dead_code)] pub fn with<'a>(environment: &'a impl Environment) -> WindowsPython { - WindowsPython { - environments: HashMap::new(), - environment, - } + WindowsPython { environment } } } impl Locator for WindowsPython<'_> { - fn is_known(&self, python_executable: &PathBuf) -> bool { - self.environments - .contains_key(python_executable.to_str().unwrap_or_default()) - } - - fn track_if_compatible(&mut self, env: &PythonEnv) -> bool { + fn resolve(&self, env: &PythonEnv) -> Option { if is_windows_python_executable(&env.executable) { - self.environments.insert( - env.executable.to_str().unwrap().to_string(), - PythonEnvironment { - name: None, - python_executable_path: Some(env.executable.clone()), - version: None, - category: crate::messaging::PythonEnvironmentCategory::WindowsStore, - sys_prefix_path: None, - env_path: None, - env_manager: None, - project_path: None, - python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), - }, - ); - return true; + return Some(PythonEnvironment { + name: None, + python_executable_path: Some(env.executable.clone()), + version: None, + category: crate::messaging::PythonEnvironmentCategory::WindowsStore, + sys_prefix_path: None, + env_path: None, + env_manager: None, + project_path: None, + python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), + }); } - false + None } - fn gather(&mut self) -> Option<()> { + fn find(&self) -> Option { + let mut environments: Vec = vec![]; if let Some(envs) = list_windows_store_python_executables(self.environment) { envs.iter().for_each(|env| { - self.track_if_compatible(&&PythonEnv::from(env.clone())); + if let Some(env) = self.resolve(&&PythonEnv::from(env.clone())) { + environments.push(env); + } }); } if let Some(envs) = list_registry_pythons() { envs.iter().for_each(|env| { - self.track_if_compatible(&&PythonEnv::from(env.clone())); + if let Some(env) = self.resolve(&&PythonEnv::from(env.clone())) { + environments.push(env); + } }); } - Some(()) - } - fn report(&self, reporter: &mut dyn MessageDispatcher) { - for env in self.environments.values() { - reporter.report_environment(env.clone()); + if environments.is_empty() { + None + } else { + Some(LocatorResult::Environments(environments)) } } } diff --git a/native_locator/src/windows_registry.rs b/native_locator/src/windows_registry.rs new file mode 100644 index 000000000000..beb503a4a541 --- /dev/null +++ b/native_locator/src/windows_registry.rs @@ -0,0 +1,59 @@ +// // Copyright (c) Microsoft Corporation. All rights reserved. +// // Licensed under the MIT License. +// use crate::messaging; +// use std::path::PathBuf; +// use winreg::RegKey; + +// fn get_registry_pythons_from_key( +// dispatcher: &mut impl messaging::MessageDispatcher, +// hk: &RegKey, +// company: &str, +// ) -> Option> { +// let python_key = hk.open_subkey("Software\\Python").ok()?; +// let company_key = python_key.open_subkey(company).ok()?; + +// let mut pythons = vec![]; +// for key in company_key.enum_keys().filter_map(Result::ok) { +// let version_key = company_key.open_subkey(key).ok()?; +// let install_path_key = version_key.open_subkey("InstallPath").ok()?; +// let executable: String = install_path_key.get_value("ExecutablePath").ok()?; +// let version = version_key.get_value("Version").ok()?; + +// dispatcher.report_environment(messaging::PythonEnvironment::new( +// None, +// Some(PathBuf::from(executable.clone())), +// messaging::PythonEnvironmentCategory::WindowsRegistry, +// Some(version), +// None, +// None, +// None, +// None, +// )); + +// pythons.push(PathBuf::from(executable)); +// } + +// Some(pythons) +// } + +// #[cfg(windows)] +// pub fn report_and_get_registry_pythons( +// dispatcher: &mut impl messaging::MessageDispatcher, +// company: &str, +// ) -> Option> { +// let hklm = winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE); +// let hkcu = winreg::RegKey::predef(winreg::enums::HKEY_CURRENT_USER); + +// let mut pythons = vec![]; +// if let Some(hklm_pythons) = get_registry_pythons_from_key(dispatcher, &hklm, company) { +// pythons.extend(hklm_pythons); +// } +// if let Some(hkcu_pythons) = get_registry_pythons_from_key(dispatcher, &hkcu, company) { +// pythons.extend(hkcu_pythons); +// } + +// Some(pythons) +// } + +// // PythonCore +// // ContinuumAnalytics diff --git a/native_locator/tests/common.rs b/native_locator/tests/common.rs index 7752bc4eb468..a26f44cafc15 100644 --- a/native_locator/tests/common.rs +++ b/native_locator/tests/common.rs @@ -3,7 +3,8 @@ use python_finder::{ known::Environment, - messaging::{EnvManager, MessageDispatcher, PythonEnvironment}, + locator::LocatorResult, + messaging::{EnvManager, PythonEnvironment}, }; use serde_json::Value; use std::{collections::HashMap, path::PathBuf}; @@ -24,35 +25,10 @@ pub fn join_test_paths(paths: &[&str]) -> PathBuf { path } -pub struct TestDispatcher { - pub messages: Vec, -} pub trait TestMessages { fn get_messages(&self) -> Vec; } -#[allow(dead_code)] -pub fn create_test_dispatcher() -> TestDispatcher { - impl MessageDispatcher for TestDispatcher { - fn report_environment_manager(&mut self, env: EnvManager) -> () { - self.messages.push(serde_json::to_string(&env).unwrap()); - } - fn report_environment(&mut self, env: PythonEnvironment) -> () { - self.messages.push(serde_json::to_string(&env).unwrap()); - } - fn exit(&mut self) -> () { - // - } - } - impl TestMessages for TestDispatcher { - fn get_messages(&self) -> Vec { - self.messages.clone() - } - } - TestDispatcher { - messages: Vec::new(), - } -} pub struct TestEnvironment { vars: HashMap, home: Option, @@ -106,8 +82,6 @@ fn compare_json(expected: &Value, actual: &Value) -> bool { if !actual.contains_key(key) { return false; } - println!("\nCompare Key {:?}", key); - println!("\nCompare Key {:?}", actual.get(key).is_none()); if !compare_json(value, actual.get(key).unwrap()) { return false; } @@ -135,11 +109,11 @@ fn compare_json(expected: &Value, actual: &Value) -> bool { } #[allow(dead_code)] -pub fn assert_messages(expected_json: &[Value], dispatcher: &TestDispatcher) { +pub fn assert_messages(expected_json: &[Value], actual_json: &[Value]) { let mut expected_json = expected_json.to_vec(); assert_eq!( expected_json.len(), - dispatcher.messages.len(), + actual_json.len(), "Incorrect number of messages" ); @@ -148,9 +122,7 @@ pub fn assert_messages(expected_json: &[Value], dispatcher: &TestDispatcher) { } // Ignore the order of the json items when comparing. - for actual in dispatcher.messages.iter() { - let actual: serde_json::Value = serde_json::from_str(actual.as_str()).unwrap(); - + for actual in actual_json.iter() { let mut valid_index: Option = None; for (i, expected) in expected_json.iter().enumerate() { if !compare_json(expected, &actual) { @@ -159,14 +131,48 @@ pub fn assert_messages(expected_json: &[Value], dispatcher: &TestDispatcher) { // Ensure we verify using standard assert_eq!, just in case the code is faulty.. valid_index = Some(i); - assert_eq!(expected, &actual); + assert_eq!(expected, actual); } if let Some(index) = valid_index { // This is to ensure we don't compare the same item twice. expected_json.remove(index); } else { // Use traditional assert so we can see the fully output in the test results. - assert_eq!(expected_json[0], actual); + assert_eq!(&expected_json[0], actual); } } } + +#[allow(dead_code)] +pub fn get_environments_from_result(result: &Option) -> Vec { + match result { + Some(environments) => match environments { + python_finder::locator::LocatorResult::Environments(envs) => envs.clone(), + _ => vec![], + }, + None => vec![], + } +} + +#[allow(dead_code)] +pub fn get_managers_from_result(result: &Option) -> Vec { + match result { + Some(environments) => match environments { + python_finder::locator::LocatorResult::Managers(managers) => managers.clone(), + python_finder::locator::LocatorResult::Environments(envs) => { + let mut managers: HashMap = HashMap::new(); + envs.iter().for_each(|env| { + if let Some(manager) = env.env_manager.clone() { + let key = manager.executable_path.to_str().unwrap().to_string(); + managers.insert(key, manager); + } + }); + managers + .values() + .map(|m| m.clone()) + .collect::>() + } + }, + None => vec![], + } +} diff --git a/native_locator/tests/common_python_test.rs b/native_locator/tests/common_python_test.rs index 00050fec96f4..01e765d23c0d 100644 --- a/native_locator/tests/common_python_test.rs +++ b/native_locator/tests/common_python_test.rs @@ -7,7 +7,7 @@ mod common; #[cfg(unix)] fn find_python_in_path_this() { use crate::common::{ - assert_messages, create_test_dispatcher, create_test_environment, join_test_paths, + assert_messages, create_test_environment, get_environments_from_result, join_test_paths, test_file_path, }; use python_finder::{common_python, locator::Locator, messaging::PythonEnvironment}; @@ -17,7 +17,6 @@ fn find_python_in_path_this() { let unix_python = test_file_path(&["tests/unix/known"]); let unix_python_exe = join_test_paths(&[unix_python.clone().to_str().unwrap(), "python"]); - let mut dispatcher = create_test_dispatcher(); let known = create_test_environment( HashMap::from([( "PATH".to_string(), @@ -27,11 +26,13 @@ fn find_python_in_path_this() { Vec::new(), ); - let mut locator = common_python::PythonOnPath::with(&known); - locator.gather(); - locator.report(&mut dispatcher); + let locator = common_python::PythonOnPath::with(&known); + locator.find(); + let result = locator.find(); + + let environments = get_environments_from_result(&result); + assert_eq!(environments.len(), 1); - assert_eq!(dispatcher.messages.len(), 1); let env = PythonEnvironment { env_manager: None, project_path: None, @@ -43,5 +44,8 @@ fn find_python_in_path_this() { env_path: Some(unix_python.clone()), sys_prefix_path: None, }; - assert_messages(&[json!(env)], &dispatcher); + assert_messages( + &[json!(env)], + &environments.iter().map(|e| json!(e)).collect::>(), + ); } diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs index 80798fdbfe91..3d3a296e52b4 100644 --- a/native_locator/tests/conda_test.rs +++ b/native_locator/tests/conda_test.rs @@ -6,29 +6,28 @@ mod common; #[test] #[cfg(unix)] fn does_not_find_any_conda_envs() { - use crate::common::{create_test_dispatcher, create_test_environment}; + use crate::common::{create_test_environment, get_environments_from_result}; use python_finder::{conda, locator::Locator}; use std::{collections::HashMap, path::PathBuf}; - let mut dispatcher = create_test_dispatcher(); let known = create_test_environment( HashMap::from([("PATH".to_string(), "".to_string())]), Some(PathBuf::from("SOME_BOGUS_HOME_DIR")), Vec::new(), ); - let mut locator = conda::Conda::with(&known); - locator.gather(); - locator.report(&mut dispatcher); + let locator = conda::Conda::with(&known); + let result = locator.find(); - assert_eq!(dispatcher.messages.len(), 0); + let environments = get_environments_from_result(&result); + assert_eq!(environments.len(), 0); } #[test] #[cfg(unix)] fn find_conda_exe_and_empty_envs() { use crate::common::{ - assert_messages, create_test_dispatcher, create_test_environment, join_test_paths, + assert_messages, create_test_environment, get_managers_from_result, join_test_paths, test_file_path, }; use python_finder::messaging::{EnvManager, EnvManagerType}; @@ -37,7 +36,6 @@ fn find_conda_exe_and_empty_envs() { use std::{collections::HashMap, path::PathBuf}; let conda_dir = test_file_path(&["tests/unix/conda_without_envs"]); - let mut dispatcher = create_test_dispatcher(); let known = create_test_environment( HashMap::from([( "PATH".to_string(), @@ -47,9 +45,11 @@ fn find_conda_exe_and_empty_envs() { Vec::new(), ); - let mut locator = conda::Conda::with(&known); - locator.gather(); - locator.report(&mut dispatcher); + let locator = conda::Conda::with(&known); + let result = locator.find(); + + let managers = get_managers_from_result(&result); + assert_eq!(managers.len(), 1); let conda_exe = join_test_paths(&[conda_dir.clone().to_str().unwrap(), "conda"]); let expected_conda_manager = EnvManager { @@ -57,14 +57,17 @@ fn find_conda_exe_and_empty_envs() { version: None, tool: EnvManagerType::Conda, }; - assert_messages(&[json!(expected_conda_manager)], &dispatcher) + assert_messages( + &[json!(expected_conda_manager)], + &managers.iter().map(|e| json!(e)).collect::>(), + ) } #[test] #[cfg(unix)] fn finds_two_conda_envs_from_txt() { use crate::common::{ - assert_messages, create_test_dispatcher, create_test_environment, join_test_paths, - test_file_path, + assert_messages, create_test_environment, get_environments_from_result, + get_managers_from_result, join_test_paths, test_file_path, }; use python_finder::messaging::{EnvManager, EnvManagerType, PythonEnvironment}; use python_finder::{conda, locator::Locator}; @@ -84,7 +87,6 @@ fn finds_two_conda_envs_from_txt() { ), ); - let mut dispatcher = create_test_dispatcher(); let known = create_test_environment( HashMap::from([( "PATH".to_string(), @@ -94,9 +96,12 @@ fn finds_two_conda_envs_from_txt() { Vec::new(), ); - let mut locator = conda::Conda::with(&known); - locator.gather(); - locator.report(&mut dispatcher); + let locator = conda::Conda::with(&known); + let result = locator.find(); + + let managers = get_managers_from_result(&result); + let environments = get_environments_from_result(&result); + assert_eq!(managers.len(), 1); let conda_exe = join_test_paths(&[conda_dir.clone().to_str().unwrap(), "conda"]); let conda_1_exe = join_test_paths(&[conda_1.clone().to_str().unwrap(), "python"]); @@ -142,11 +147,11 @@ fn finds_two_conda_envs_from_txt() { ]), }; assert_messages( - &[ - json!(expected_conda_1), - json!(expected_conda_manager), - json!(expected_conda_2), - ], - &dispatcher, - ) + &[json!(expected_conda_manager)], + &managers.iter().map(|e| json!(e)).collect::>(), + ); + assert_messages( + &[json!(expected_conda_1), json!(expected_conda_2)], + &environments.iter().map(|e| json!(e)).collect::>(), + ); } diff --git a/native_locator/tests/pyenv_test.rs b/native_locator/tests/pyenv_test.rs index 6702d258d958..a63615a68ef1 100644 --- a/native_locator/tests/pyenv_test.rs +++ b/native_locator/tests/pyenv_test.rs @@ -6,29 +6,31 @@ mod common; #[test] #[cfg(unix)] fn does_not_find_any_pyenv_envs() { - use crate::common::{create_test_dispatcher, create_test_environment}; + use crate::common::{ + create_test_environment, get_environments_from_result, get_managers_from_result, + }; use python_finder::{locator::Locator, pyenv}; use std::{collections::HashMap, path::PathBuf}; - let mut dispatcher = create_test_dispatcher(); let known = create_test_environment( HashMap::new(), Some(PathBuf::from("SOME_BOGUS_HOME_DIR")), Vec::new(), ); - let mut locator = pyenv::PyEnv::with(&known); - locator.gather(); - locator.report(&mut dispatcher); + let locator = pyenv::PyEnv::with(&known); + locator.find(); + let result = locator.find(); - assert_eq!(dispatcher.messages.len(), 0); + assert_eq!(get_managers_from_result(&result).len(), 0); + assert_eq!(get_environments_from_result(&result).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, + assert_messages, create_test_environment, get_managers_from_result, join_test_paths, test_file_path, }; use python_finder::locator::Locator; @@ -36,7 +38,6 @@ fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() { 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.to_str().unwrap(), "opt", "homebrew", "bin"]); let pyenv_exe = join_test_paths(&[homebrew_bin.to_str().unwrap(), "pyenv"]); @@ -46,21 +47,25 @@ fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() { vec![PathBuf::from(homebrew_bin)], ); - let mut locator = pyenv::PyEnv::with(&known); - locator.gather(); - locator.report(&mut dispatcher); + let locator = pyenv::PyEnv::with(&known); + let result = locator.find(); + + let managers = get_managers_from_result(&result); + assert_eq!(managers.len(), 1); - assert_eq!(dispatcher.messages.len(), 1); let expected_json = json!({"executablePath":pyenv_exe,"version":null, "tool": "pyenv"}); - assert_messages(&[expected_json], &dispatcher) + assert_messages( + &[expected_json], + &managers.iter().map(|e| json!(e)).collect::>(), + ) } #[test] #[cfg(unix)] fn find_pyenv_envs() { use crate::common::{ - assert_messages, create_test_dispatcher, create_test_environment, join_test_paths, - test_file_path, + assert_messages, create_test_environment, get_environments_from_result, + get_managers_from_result, join_test_paths, test_file_path, }; use python_finder::locator::Locator; use python_finder::{ @@ -70,7 +75,6 @@ fn find_pyenv_envs() { 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.to_str().unwrap(), "opt", "homebrew", "bin"]); let pyenv_exe = join_test_paths(&[homebrew_bin.to_str().unwrap(), "pyenv"]); @@ -80,16 +84,23 @@ fn find_pyenv_envs() { vec![PathBuf::from(homebrew_bin)], ); - let mut locator = pyenv::PyEnv::with(&known); - locator.gather(); - locator.report(&mut dispatcher); + let locator = pyenv::PyEnv::with(&known); + let result = locator.find(); + + let managers = get_managers_from_result(&result); + assert_eq!(managers.len(), 1); - assert_eq!(dispatcher.messages.len(), 6); let expected_manager = EnvManager { executable_path: pyenv_exe.clone(), version: None, tool: EnvManagerType::Pyenv, }; + + assert_messages( + &[json!(expected_manager)], + &managers.iter().map(|e| json!(e)).collect::>(), + ); + let expected_3_9_9 = json!(PythonEnvironment { project_path: None, name: None, @@ -220,15 +231,16 @@ fn find_pyenv_envs() { ])), env_manager: Some(expected_manager.clone()), }; + let environments = get_environments_from_result(&result); + assert_messages( &[ - json!(expected_manager), json!(expected_3_9_9), json!(expected_virtual_env), json!(expected_3_12_1), json!(expected_3_13_dev), json!(expected_3_12_1a3), ], - &dispatcher, + &environments.iter().map(|e| json!(e)).collect::>(), ) }