From 4a56747b4e53cf9ef71299b96134fa28c473c33f Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 10 May 2024 11:27:09 +1000 Subject: [PATCH 1/4] Refactor native locators to impl specific trait --- native_locator/src/lib.rs | 1 + native_locator/src/main.rs | 1 + native_locator/src/messaging.rs | 3 +- native_locator/src/virtualenv.rs | 101 ++++++++++++++++++++++++++++++- 4 files changed, 103 insertions(+), 3 deletions(-) diff --git a/native_locator/src/lib.rs b/native_locator/src/lib.rs index b0cccef843c5..251e977af00c 100644 --- a/native_locator/src/lib.rs +++ b/native_locator/src/lib.rs @@ -13,3 +13,4 @@ pub mod virtualenvwrapper; pub mod pipenv; pub mod virtualenv; pub mod venv; +pub mod locator; diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index 0332bbaf2f63..42f056fea59a 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -20,6 +20,7 @@ mod virtualenv; mod virtualenvwrapper; mod windows_python; mod venv; +mod locator; fn main() { let mut dispatcher = create_dispatcher(); diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index 01f13c34984f..eb393684b31f 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -1,10 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -use std::path::PathBuf; - use crate::logging::{LogLevel, LogMessage}; use serde::{Deserialize, Serialize}; +use std::path::PathBuf; pub trait MessageDispatcher { fn report_environment_manager(&mut self, env: EnvManager) -> (); diff --git a/native_locator/src/virtualenv.rs b/native_locator/src/virtualenv.rs index 65da331abaa2..d2701d3e7fb0 100644 --- a/native_locator/src/virtualenv.rs +++ b/native_locator/src/virtualenv.rs @@ -4,9 +4,15 @@ use crate::messaging::{MessageDispatcher, PythonEnvironment, PythonEnvironmentCategory}; use crate::utils::PythonEnv; use std::path::PathBuf; +use std::{collections::HashMap, path::PathBuf}; +use crate::{ + locator::{self, Locator}, + messaging::{MessageDispatcher, PythonEnvironment}, + utils::PythonEnv, +}; pub fn is_virtualenv(env: &PythonEnv) -> bool { - if let Some(file_path) = PathBuf::from(env.executable.clone()).parent() { + if let Some(file_path) = env.executable.parent() { // Check if there are any activate.* files in the same directory as the interpreter. // // env @@ -65,3 +71,96 @@ pub fn find_and_report(env: &PythonEnv, dispatcher: &mut impl MessageDispatcher) } None } + +pub fn is_exe_virtualenv(python_executable: &PathBuf) -> bool { + if let Some(file_path) = python_executable.parent() { + // Check if there are any activate.* files in the same directory as the interpreter. + // + // env + // |__ activate, activate.* <--- check if any of these files exist + // |__ python <--- interpreterPath + + // if let Some(parent_path) = PathBuf::from(env.) + // const directory = path.dirname(interpreterPath); + // const files = await fsapi.readdir(directory); + // const regex = /^activate(\.([A-z]|\d)+)?$/i; + if file_path.join("activate").exists() || file_path.join("activate.bat").exists() { + return true; + } + + // Support for activate.ps, etc. + match std::fs::read_dir(file_path) { + Ok(files) => { + for file in files { + if let Ok(file) = file { + if let Some(file_name) = file.file_name().to_str() { + if file_name.starts_with("activate") { + return true; + } + } + } + } + return false; + } + Err(_) => return false, + }; + } + + false +} + +pub struct VirtualEnv { + pub environments: HashMap, +} + +impl VirtualEnv { + pub fn new() -> VirtualEnv { + VirtualEnv { + environments: HashMap::new(), + } + } +} + +impl Locator for VirtualEnv { + fn get_type(&mut self) -> String { + "virtualenv".to_string() + } + + fn is_known(&mut self, python_executable: &PathBuf) -> bool { + self.environments + .contains_key(python_executable.to_str().unwrap_or_default()) + } + + fn is_compatible(&mut self, python_executable: &PathBuf) -> bool { + is_exe_virtualenv(python_executable) + } + + fn track_if_compatible(&mut self, env: &locator::PythonEnv) -> () { + if is_exe_virtualenv(&env.executable) { + let executable = env.executable.to_str().unwrap().to_string(); + self.environments.insert( + executable, + PythonEnvironment { + python_executable_path: Some(executable), + version: Some(env.version.clone()), + category: crate::messaging::PythonEnvironmentCategory:: + sys_path: Some(env.sys_path.clone()), + display_name: env.display_name.clone(), + architecture: env.architecture.clone(), + path: env.path.clone(), + kind: "virtualenv".to_string(), + }, + ); + } + } + + fn find(&mut self) -> () { + // There are no common global locations for virtual environments. + // We expect the user of this class to call `is_compatible` + () + } + + fn report(&mut self, reporter: &dyn MessageDispatcher) -> () { + todo!() + } +} From 2b7b66a21165279439ea8cb31f1f66c188922f30 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 10 May 2024 11:28:53 +1000 Subject: [PATCH 2/4] Refactor locators to implement a trait --- native_locator/src/common_python.rs | 151 +++++++++++++++++------ native_locator/src/conda.rs | 107 ++++++++++++++-- native_locator/src/global_virtualenvs.rs | 59 ++++----- native_locator/src/homebrew.rs | 83 ++++++++++++- native_locator/src/locator.rs | 33 +++++ native_locator/src/main.rs | 88 ++++++++++--- native_locator/src/messaging.rs | 4 +- native_locator/src/pipenv.rs | 63 +++++++++- native_locator/src/pyenv.rs | 140 ++++++++++++++------- native_locator/src/utils.rs | 44 ++++++- native_locator/src/venv.rs | 79 +++++++++++- native_locator/src/virtualenv.rs | 120 ++++++++---------- native_locator/src/virtualenvwrapper.rs | 116 +++++++++++++++-- native_locator/src/windows_python.rs | 149 +++++++++++++--------- 14 files changed, 947 insertions(+), 289 deletions(-) create mode 100644 native_locator/src/locator.rs diff --git a/native_locator/src/common_python.rs b/native_locator/src/common_python.rs index 0237d5f22700..5c9f6a29e42a 100644 --- a/native_locator/src/common_python.rs +++ b/native_locator/src/common_python.rs @@ -2,46 +2,114 @@ // Licensed under the MIT License. use crate::known; -use crate::messaging; -use crate::utils; +use crate::known::Environment; +use crate::locator::Locator; +use crate::messaging::PythonEnvironment; +use crate::messaging::{self, MessageDispatcher}; +use crate::utils::{self, PythonEnv}; +use std::collections::HashMap; use std::env; -use std::path::Path; use std::path::PathBuf; -fn get_env_path(path: &str) -> Option { - let path = Path::new(path); - match path.parent() { - Some(parent) => { - if parent.file_name()? == "Scripts" { - return Some(parent.parent()?.to_path_buf()); - } else { - return Some(parent.to_path_buf()); - } - } - None => None, +fn get_env_path(python_executable_path: &PathBuf) -> Option { + let parent = python_executable_path.parent()?; + if parent.file_name()? == "Scripts" { + return Some(parent.parent()?.to_path_buf()); + } else { + return Some(parent.to_path_buf()); } } -fn report_path_python(dispatcher: &mut impl messaging::MessageDispatcher, path: &str) { - let version = utils::get_version(path); - let env_path = get_env_path(path); - dispatcher.report_environment(messaging::PythonEnvironment::new( - None, - Some(PathBuf::from(path)), - messaging::PythonEnvironmentCategory::System, - version, - env_path.clone(), - env_path, - None, - Some(vec![path.to_string()]), - )); +// fn report_path_python( +// dispatcher: &mut impl messaging::MessageDispatcher, +// python_executable_path: &PathBuf, +// ) { +// let version = utils::get_version(python_executable_path); +// let env_path = get_env_path(python_executable_path); +// dispatcher.report_environment(messaging::PythonEnvironment::new( +// None, +// Some(PathBuf::from(python_executable_path)), +// messaging::PythonEnvironmentCategory::System, +// version, +// env_path.clone(), +// env_path, +// None, +// Some(vec![python_executable_path.to_str().unwrap().to_string()]), +// )); +// } + +// fn report_python_on_path( +// dispatcher: &mut impl messaging::MessageDispatcher, +// environment: &impl known::Environment, +// ) { +// if let Some(paths) = environment.get_env_var("PATH".to_string()) { +// let bin = if cfg!(windows) { +// "python.exe" +// } else { +// "python" +// }; +// env::split_paths(&paths) +// .map(|p| p.join(bin)) +// .filter(|p| p.exists()) +// .for_each(|full_path| report_path_python(dispatcher, &full_path)); +// } +// } + +// pub fn find_and_report( +// dispatcher: &mut impl messaging::MessageDispatcher, +// environment: &impl known::Environment, +// ) { +// report_python_on_path(dispatcher, environment); +// } + +pub struct PythonOnPath<'a> { + pub environments: HashMap, + pub environment: &'a dyn Environment, } -fn report_python_on_path( - dispatcher: &mut impl messaging::MessageDispatcher, - environment: &impl known::Environment, -) { - if let Some(paths) = environment.get_env_var("PATH".to_string()) { +impl PythonOnPath<'_> { + pub fn with<'a>(environment: &'a impl Environment) -> PythonOnPath { + PythonOnPath { + environments: HashMap::new(), + 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 { + let bin = if cfg!(windows) { + "python.exe" + } else { + "python" + }; + if env.executable.file_name().unwrap().to_ascii_lowercase() != bin { + return false; + } + 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 + } + + fn gather(&mut self) -> Option<()> { + let paths = self.environment.get_env_var("PATH".to_string())?; let bin = if cfg!(windows) { "python.exe" } else { @@ -50,13 +118,18 @@ fn report_python_on_path( env::split_paths(&paths) .map(|p| p.join(bin)) .filter(|p| p.exists()) - .for_each(|full_path| report_path_python(dispatcher, full_path.to_str().unwrap())); + .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)); + }); + + Some(()) } -} -pub fn find_and_report( - dispatcher: &mut impl messaging::MessageDispatcher, - environment: &impl known::Environment, -) { - report_python_on_path(dispatcher, environment); + fn report(&self, reporter: &mut dyn MessageDispatcher) { + for env in self.environments.values() { + reporter.report_environment(env.clone()); + } + } } diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 68557dbd46ce..bbf9cb3b25d7 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -2,10 +2,17 @@ // Licensed under the MIT License. use crate::known; +use crate::known::Environment; +use crate::locator::Locator; 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}; @@ -129,7 +136,7 @@ fn get_conda_bin_names() -> Vec<&'static str> { } /// Find the conda binary on the PATH environment variable -fn find_conda_binary_on_path(environment: &impl known::Environment) -> Option { +fn find_conda_binary_on_path(environment: &dyn known::Environment) -> Option { let paths = environment.get_env_var("PATH".to_string())?; for path in env::split_paths(&paths) { for bin in get_conda_bin_names() { @@ -148,7 +155,7 @@ fn find_conda_binary_on_path(environment: &impl known::Environment) -> Option Vec { +fn get_known_conda_locations(environment: &dyn known::Environment) -> Vec { let user_profile = environment.get_env_var("USERPROFILE".to_string()).unwrap(); let program_data = environment.get_env_var("PROGRAMDATA".to_string()).unwrap(); let all_user_profile = environment @@ -170,7 +177,7 @@ fn get_known_conda_locations(environment: &impl known::Environment) -> Vec Vec { +fn get_known_conda_locations(environment: &dyn known::Environment) -> Vec { let mut known_paths = vec![ PathBuf::from("/opt/anaconda3/bin"), PathBuf::from("/opt/miniconda3/bin"), @@ -192,7 +199,7 @@ fn get_known_conda_locations(environment: &impl known::Environment) -> Vec Option { +fn find_conda_binary_in_known_locations(environment: &dyn known::Environment) -> Option { let conda_bin_names = get_conda_bin_names(); let known_locations = get_known_conda_locations(environment); for location in known_locations { @@ -209,7 +216,7 @@ fn find_conda_binary_in_known_locations(environment: &impl known::Environment) - } /// Find the conda binary on the system -pub fn find_conda_binary(environment: &impl known::Environment) -> Option { +pub fn find_conda_binary(environment: &dyn known::Environment) -> Option { let conda_binary_on_path = find_conda_binary_on_path(environment); match conda_binary_on_path { Some(conda_binary_on_path) => Some(conda_binary_on_path), @@ -232,7 +239,7 @@ pub fn get_conda_version(conda_binary: &PathBuf) -> Option { get_version_from_meta_json(&conda_python_json_path) } -fn get_conda_envs_from_environment_txt(environment: &impl known::Environment) -> Vec { +fn get_conda_envs_from_environment_txt(environment: &dyn known::Environment) -> Vec { let mut envs = vec![]; let home = environment.get_user_home(); match home { @@ -255,7 +262,7 @@ fn get_conda_envs_from_environment_txt(environment: &impl known::Environment) -> fn get_known_env_locations( conda_bin: &PathBuf, - environment: &impl known::Environment, + environment: &dyn known::Environment, ) -> Vec { let mut paths = vec![]; let home = environment.get_user_home(); @@ -290,7 +297,7 @@ fn get_known_env_locations( fn get_conda_envs_from_known_env_locations( conda_bin: &PathBuf, - environment: &impl known::Environment, + environment: &dyn known::Environment, ) -> Vec { let mut envs = vec![]; for location in get_known_env_locations(conda_bin, environment) { @@ -333,7 +340,7 @@ struct CondaEnv { fn get_distinct_conda_envs( conda_bin: &PathBuf, - environment: &impl known::Environment, + environment: &dyn known::Environment, ) -> Vec { let mut envs = get_conda_envs_from_environment_txt(environment); let mut known_envs = get_conda_envs_from_known_env_locations(conda_bin, environment); @@ -426,3 +433,85 @@ pub fn find_and_report( None => (), } } + +pub struct Conda<'a> { + pub environments: HashMap, + pub manager: Option, + pub environment: &'a dyn Environment, +} + +impl Conda<'_> { + pub fn with<'a>(environment: &'a impl Environment) -> Conda { + Conda { + environments: HashMap::new(), + environment, + manager: None, + } + } +} + +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 { + // We will find everything in gather + false + } + + fn gather(&mut 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); + for env in envs { + let executable = find_python_binary_path(Path::new(&env.path)); + let env = messaging::PythonEnvironment::new( + Some(env.name.to_string()), + executable.clone(), + messaging::PythonEnvironmentCategory::Conda, + get_conda_python_version(&env.path), + Some(env.path.clone()), + Some(env.path.clone()), + Some(manager.clone()), + if env.named { + Some(vec![ + conda_binary.to_string_lossy().to_string(), + "run".to_string(), + "-n".to_string(), + env.name.to_string(), + "python".to_string(), + ]) + } else { + Some(vec![ + conda_binary.to_string_lossy().to_string(), + "run".to_string(), + "-p".to_string(), + env.path.to_string_lossy().to_string(), + "python".to_string(), + ]) + }, + ); + + if let Some(exe) = executable { + self.environments + .insert(exe.to_str().unwrap_or_default().to_string(), env); + } + } + + Some(()) + } + + fn report(&self, reporter: &mut dyn MessageDispatcher) { + for env in self.environments.values() { + reporter.report_environment(env.clone()); + } + } +} diff --git a/native_locator/src/global_virtualenvs.rs b/native_locator/src/global_virtualenvs.rs index 67b8cc2c6ea8..0b508209e570 100644 --- a/native_locator/src/global_virtualenvs.rs +++ b/native_locator/src/global_virtualenvs.rs @@ -1,18 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::pipenv; -use crate::venv; -use crate::virtualenv; -use crate::virtualenvwrapper; use crate::{ known, - messaging::MessageDispatcher, utils::{find_python_binary_path, get_version, PythonEnv}, }; use std::{fs, path::PathBuf}; -fn get_global_virtualenv_dirs(environment: &impl known::Environment) -> Vec { +pub fn get_global_virtualenv_dirs(environment: &impl known::Environment) -> Vec { let mut venv_dirs: Vec = vec![]; if let Some(work_on_home) = environment.get_env_var("WORKON_HOME".to_string()) { @@ -48,7 +43,7 @@ fn get_global_virtualenv_dirs(environment: &impl known::Environment) -> Vec Vec { +pub fn list_global_virtual_envs(environment: &impl known::Environment) -> Vec { let mut python_envs: Vec = vec![]; for root_dir in get_global_virtualenv_dirs(environment).iter() { if let Ok(dirs) = fs::read_dir(root_dir) { @@ -59,11 +54,11 @@ pub fn list_global_virtualenvs(environment: &impl known::Environment) -> Vec Vec Option<()> { - for env in list_global_virtualenvs(environment).iter() { - if pipenv::find_and_report(&env, dispatcher).is_some() { - continue; - } - if virtualenvwrapper::find_and_report(&env, dispatcher, environment).is_some() { - continue; - } - if venv::find_and_report(&env, dispatcher).is_some() { - continue; - } - if virtualenv::find_and_report(&env, dispatcher).is_some() { - continue; - } - } +// pub fn find_and_report( +// dispatcher: &mut impl MessageDispatcher, +// environment: &impl known::Environment, +// ) -> Option<()> { +// for env in list_global_virtualenvs(environment).iter() { +// if pipenv::find_and_report(&env, dispatcher).is_some() { +// continue; +// } +// if virtualenvwrapper::find_and_report(&env, dispatcher, environment).is_some() { +// continue; +// } +// if venv::find_and_report(&env, dispatcher).is_some() { +// continue; +// } +// if virtualenv::find_and_report(&env, dispatcher).is_some() { +// continue; +// } +// } - None -} +// None +// } diff --git a/native_locator/src/homebrew.rs b/native_locator/src/homebrew.rs index f70c68fb6411..ad0ad1aa9965 100644 --- a/native_locator/src/homebrew.rs +++ b/native_locator/src/homebrew.rs @@ -1,9 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use std::{collections::HashSet, fs::DirEntry, io::Error, path::PathBuf}; +use std::{ + collections::{HashMap, HashSet}, + fs::DirEntry, + io::Error, + path::PathBuf, +}; -use crate::{known::Environment, messaging::MessageDispatcher}; +use crate::{ + known::Environment, + locator::Locator, + messaging::{MessageDispatcher, PythonEnvironment}, utils::PythonEnv, +}; use regex::Regex; fn is_symlinked_python_executable(path: Result) -> Option { @@ -61,3 +70,73 @@ pub fn find_and_report( None } + +pub struct Homebrew<'a> { + pub environments: HashMap, + pub environment: &'a dyn Environment, +} + +impl Homebrew<'_> { + pub fn with<'a>(environment: &'a impl Environment) -> Homebrew { + Homebrew { + environments: HashMap::new(), + 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 track_if_compatible(&mut self, _env: &PythonEnv) -> bool { + // We will find everything in gather + false + } + + fn gather(&mut 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(); + 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(); + let version = match python_regex.captures(&python_version) { + Some(captures) => match captures.get(1) { + Some(version) => Some(version.as_str().to_string()), + None => None, + }, + None => None, + }; + if reported.contains(&exe.to_string_lossy().to_string()) { + continue; + } + reported.insert(exe.to_string_lossy().to_string()); + let env = crate::messaging::PythonEnvironment::new( + None, + Some(exe.clone()), + crate::messaging::PythonEnvironmentCategory::Homebrew, + version, + None, + None, + None, + Some(vec![exe.to_string_lossy().to_string()]), + ); + self.environments + .insert(exe.to_string_lossy().to_string(), env); + } + } + Some(()) + } + + fn report(&self, reporter: &mut dyn MessageDispatcher) { + for env in self.environments.values() { + reporter.report_environment(env.clone()); + } + } +} diff --git a/native_locator/src/locator.rs b/native_locator/src/locator.rs new file mode 100644 index 000000000000..2d6454c9ab99 --- /dev/null +++ b/native_locator/src/locator.rs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::{messaging::MessageDispatcher, utils::PythonEnv}; +use std::path::PathBuf; + +pub trait Locator { + // fn get_type(&mut self) -> String; + /** + * Whether the given Python executable is known to this locator. + */ + fn is_known(&self, python_executable: &PathBuf) -> bool; + /** + * Whether the given Python executable belongs to an environment that is + * compatible with the environments supported by this locator. + */ + // fn is_compatible(&mut 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. + */ + fn gather(&mut self) -> Option<()>; + // /** + // * Finds all environments managed by this locator. + // */ + // fn find_in_location(path: &PathBuf) -> (); + fn report(&self, reporter: &mut dyn MessageDispatcher); +} diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index 42f056fea59a..3ca557ac518b 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -1,26 +1,29 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -use std::time::SystemTime; - +use global_virtualenvs::{get_global_virtualenv_dirs, list_global_virtual_envs}; use known::EnvironmentApi; +use locator::Locator; use messaging::{create_dispatcher, MessageDispatcher}; +use std::slice::Windows; +use std::time::SystemTime; +use std::{fs, path::PathBuf}; mod common_python; mod conda; mod global_virtualenvs; mod homebrew; mod known; +mod locator; mod logging; mod messaging; mod pipenv; mod pyenv; mod utils; +mod venv; mod virtualenv; mod virtualenvwrapper; mod windows_python; -mod venv; -mod locator; fn main() { let mut dispatcher = create_dispatcher(); @@ -29,22 +32,79 @@ fn main() { dispatcher.log_info("Starting Native Locator"); let now = SystemTime::now(); - global_virtualenvs::find_and_report(&mut dispatcher, &environment); - - // Finds python on PATH - common_python::find_and_report(&mut dispatcher, &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); + #[cfg(windows)] + let mut windows_locator = windows_python::WindowsPython::with(&environment); + let mut conda_locator = conda::Conda::with(&environment); - // Finds conda binary and conda environments - conda::find_and_report(&mut dispatcher, &environment); + // These environments take predence over all others. + // As they are very specific and guaranteed to be specific type. + pyenv_locator.gather(); + #[cfg(unix)] + homebrew_locator.gather(); + conda_locator.gather(); // Finds Windows Store, Known Path, and Registry pythons #[cfg(windows)] - windows_python::find_and_report(&mut dispatcher, &environment); + windows_locator.gather(); - pyenv::find_and_report(&mut dispatcher, &environment); + 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) { + continue; + } - #[cfg(unix)] - homebrew::find_and_report(&mut dispatcher, &environment); + 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; + } + } + + // 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)); match now.elapsed() { Ok(elapsed) => { diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index eb393684b31f..044c1af82b5c 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -68,7 +68,7 @@ impl EnvManagerMessage { } } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub enum PythonEnvironmentCategory { System, @@ -83,7 +83,7 @@ pub enum PythonEnvironmentCategory { VirtualEnv, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub struct PythonEnvironment { pub name: Option, diff --git a/native_locator/src/pipenv.rs b/native_locator/src/pipenv.rs index 737c7c776c18..adbc2abf5b96 100644 --- a/native_locator/src/pipenv.rs +++ b/native_locator/src/pipenv.rs @@ -1,13 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use crate::known::Environment; +use crate::locator::Locator; use crate::messaging::{MessageDispatcher, PythonEnvironment}; use crate::utils::PythonEnv; +use std::collections::HashMap; use std::fs; use std::path::PathBuf; fn get_pipenv_project(env: &PythonEnv) -> Option { - let project_file = env.path.join(".project"); + let project_file = env.path.clone()?.join(".project"); if project_file.exists() { if let Ok(contents) = fs::read_to_string(project_file) { let project_folder = PathBuf::from(contents.trim().to_string()); @@ -25,8 +28,8 @@ pub fn find_and_report(env: &PythonEnv, dispatcher: &mut impl MessageDispatcher) let env = PythonEnvironment::new_pipenv( Some(env.executable.clone()), env.version.clone(), - Some(env.path.clone()), - Some(env.path.clone()), + Some(env.path.clone()?), + env.path.clone(), None, project_path, ); @@ -37,3 +40,57 @@ pub fn find_and_report(env: &PythonEnv, dispatcher: &mut impl MessageDispatcher) None } + +pub struct PipEnv { + pub environments: HashMap, +} + +impl PipEnv { + pub fn new() -> PipEnv { + PipEnv { + environments: HashMap::new(), + } + } +} + +impl Locator for PipEnv { + 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 { + 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<()> { + 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 814be30af441..4f1dbf11f961 100644 --- a/native_locator/src/pyenv.rs +++ b/native_locator/src/pyenv.rs @@ -2,29 +2,35 @@ // 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::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; #[cfg(windows)] -fn get_home_pyenv_dir(environment: &impl known::Environment) -> Option { +fn get_home_pyenv_dir(environment: &dyn known::Environment) -> Option { let home = environment.get_user_home()?; Some(PathBuf::from(home).join(".pyenv").join("pyenv-win")) } #[cfg(unix)] -fn get_home_pyenv_dir(environment: &impl known::Environment) -> Option { +fn get_home_pyenv_dir(environment: &dyn known::Environment) -> Option { let home = environment.get_user_home()?; Some(PathBuf::from(home).join(".pyenv")) } -fn get_binary_from_known_paths(environment: &impl known::Environment) -> Option { +fn get_binary_from_known_paths(environment: &dyn known::Environment) -> Option { for known_path in environment.get_know_global_search_locations() { let bin = known_path.join("pyenv"); if bin.exists() { @@ -34,7 +40,7 @@ fn get_binary_from_known_paths(environment: &impl known::Environment) -> Option< None } -fn get_pyenv_dir(environment: &impl known::Environment) -> Option { +fn get_pyenv_dir(environment: &dyn known::Environment) -> Option { // Check if the pyenv environment variables exist: PYENV on Windows, PYENV_ROOT on Unix. // They contain the path to pyenv's installation folder. // If they don't exist, use the default path: ~/.pyenv/pyenv-win on Windows, ~/.pyenv on Unix. @@ -51,7 +57,7 @@ fn get_pyenv_dir(environment: &impl known::Environment) -> Option { } } -fn get_pyenv_binary(environment: &impl known::Environment) -> Option { +fn get_pyenv_binary(environment: &dyn known::Environment) -> Option { let dir = get_pyenv_dir(environment)?; let exe = PathBuf::from(dir).join("bin").join("pyenv"); if fs::metadata(&exe).is_ok() { @@ -93,72 +99,57 @@ fn get_pyenv_version(folder_name: &String) -> Option { } } -fn report_if_pure_python_environment( +fn get_pure_python_environment( executable: &PathBuf, path: &PathBuf, - manager: Option, - dispatcher: &mut impl messaging::MessageDispatcher, -) -> Option<()> { + manager: &Option, +) -> Option { let version = get_pyenv_version(&path.file_name().unwrap().to_string_lossy().to_string())?; - dispatcher.report_environment(messaging::PythonEnvironment::new( + Some(messaging::PythonEnvironment::new( None, Some(executable.clone()), messaging::PythonEnvironmentCategory::Pyenv, Some(version), Some(path.clone()), Some(path.clone()), - manager, + manager.clone(), Some(vec![executable .clone() .into_os_string() .into_string() .unwrap()]), - )); - - Some(()) + )) } -fn report_if_virtual_env_environment( +fn get_virtual_env_environment( executable: &PathBuf, path: &PathBuf, - manager: Option, - dispatcher: &mut impl messaging::MessageDispatcher, -) -> Option<()> { + manager: &Option, +) -> Option { let pyenv_cfg = find_and_parse_pyvenv_cfg(executable)?; let folder_name = path.file_name().unwrap().to_string_lossy().to_string(); - dispatcher.report_environment(messaging::PythonEnvironment::new( + Some(messaging::PythonEnvironment::new( Some(folder_name), Some(executable.clone()), messaging::PythonEnvironmentCategory::PyenvVirtualEnv, Some(pyenv_cfg.version), Some(path.clone()), Some(path.clone()), - manager, + manager.clone(), Some(vec![executable .clone() .into_os_string() .into_string() .unwrap()]), - )); - - Some(()) + )) } -pub fn find_and_report( - dispatcher: &mut impl messaging::MessageDispatcher, - environment: &impl known::Environment, -) -> Option<()> { +pub fn list_pyenv_environments( + manager: &Option, + environment: &dyn known::Environment, +) -> Option> { let pyenv_dir = get_pyenv_dir(environment)?; - - let manager = match get_pyenv_binary(environment) { - Some(pyenv_binary) => { - let manager = messaging::EnvManager::new(pyenv_binary, None, EnvManagerType::Pyenv); - dispatcher.report_environment_manager(manager.clone()); - Some(manager) - } - None => None, - }; - + let mut envs: Vec = vec![]; let versions_dir = PathBuf::from(&pyenv_dir) .join("versions") .into_os_string() @@ -172,20 +163,73 @@ pub fn find_and_report( continue; } if let Some(executable) = find_python_binary_path(&path) { - if report_if_pure_python_environment( - &executable, - &path, - manager.clone(), - dispatcher, - ) - .is_some() - { - continue; + match get_pure_python_environment(&executable, &path, manager) { + Some(env) => envs.push(env), + None => match get_virtual_env_environment(&executable, &path, manager) { + Some(env) => envs.push(env), + None => (), + }, } - report_if_virtual_env_environment(&executable, &path, manager.clone(), dispatcher); } } } - None + Some(envs) +} + +pub struct PyEnv<'a> { + pub environments: HashMap, + pub environment: &'a dyn Environment, +} + +impl PyEnv<'_> { + pub fn with<'a>(environment: &'a impl Environment) -> PyEnv { + PyEnv { + environments: HashMap::new(), + 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 { + // We will find everything in gather + false + } + + fn gather(&mut self) -> Option<()> { + let pyenv_dir = get_pyenv_dir(self.environment)?; + let manager = match get_pyenv_binary(self.environment) { + Some(pyenv_binary) => Some(messaging::EnvManager::new( + pyenv_binary, + None, + EnvManagerType::Pyenv, + )), + None => None, + }; + + 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) { + for env in self.environments.values() { + reporter.report_environment(env.clone()); + } + } } diff --git a/native_locator/src/utils.rs b/native_locator/src/utils.rs index 88122085a9d8..26aa68566019 100644 --- a/native_locator/src/utils.rs +++ b/native_locator/src/utils.rs @@ -10,11 +10,28 @@ use std::{ #[derive(Debug)] pub struct PythonEnv { - pub path: PathBuf, pub executable: PathBuf, + pub path: Option, pub version: Option, } +impl PythonEnv { + pub fn new(executable: PathBuf, path: Option, version: Option) -> Self { + Self { + executable, + path, + version, + } + } + pub fn from(executable: PathBuf) -> Self { + Self { + executable, + path: None, + version: None, + } + } +} + #[derive(Debug)] pub struct PyEnvCfg { pub version: String, @@ -79,8 +96,8 @@ pub fn find_and_parse_pyvenv_cfg(python_executable: &PathBuf) -> Option Option { - if let Some(parent_folder) = PathBuf::from(python_executable).parent() { +pub fn get_version(python_executable: &PathBuf) -> Option { + if let Some(parent_folder) = python_executable.parent() { if let Some(pyenv_cfg) = find_and_parse_pyvenv_cfg(&parent_folder.to_path_buf()) { return Some(pyenv_cfg.version); } @@ -109,3 +126,24 @@ pub fn find_python_binary_path(env_path: &Path) -> Option { let paths = vec![path_1, path_2, path_3]; paths.into_iter().find(|path| path.exists()) } + +pub fn list_python_environments(path: &PathBuf) -> Option> { + let mut python_envs: Vec = vec![]; + for venv_dir in fs::read_dir(path).ok()? { + if let Ok(venv_dir) = venv_dir { + let venv_dir = venv_dir.path(); + if !venv_dir.is_dir() { + continue; + } + if let Some(executable) = find_python_binary_path(&venv_dir) { + python_envs.push(PythonEnv::new( + executable.clone(), + Some(venv_dir), + get_version(&executable), + )); + } + } + } + + Some(python_envs) +} diff --git a/native_locator/src/venv.rs b/native_locator/src/venv.rs index 9fa16031af8a..aeffe1f35da6 100644 --- a/native_locator/src/venv.rs +++ b/native_locator/src/venv.rs @@ -1,27 +1,40 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use std::{collections::HashMap, path::PathBuf}; + use crate::{ + locator::{self, Locator}, messaging::{MessageDispatcher, PythonEnvironment, PythonEnvironmentCategory}, utils::{self, PythonEnv}, }; pub fn is_venv(env: &PythonEnv) -> bool { + // env path cannot be empty. + if env.path.is_none() { + return false; + } return utils::find_pyvenv_config_path(&env.executable).is_some(); } pub fn find_and_report(env: &PythonEnv, dispatcher: &mut impl MessageDispatcher) -> Option<()> { if is_venv(env) { let env = PythonEnvironment { - name: match env.path.file_name().to_owned() { + name: match env + .path + .clone() + .expect("env.path can never be empty for venvs") + .file_name() + .to_owned() + { Some(name) => Some(name.to_string_lossy().to_owned().to_string()), None => None, }, python_executable_path: Some(env.executable.clone()), category: PythonEnvironmentCategory::Venv, version: env.version.clone(), - env_path: Some(env.path.clone()), - sys_prefix_path: Some(env.path.clone()), + env_path: env.path.clone(), + sys_prefix_path: env.path.clone(), env_manager: None, python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), project_path: None, @@ -33,3 +46,63 @@ pub fn find_and_report(env: &PythonEnv, dispatcher: &mut impl MessageDispatcher) } None } + +pub struct Venv { + pub environments: HashMap, +} + +impl Venv { + pub fn new() -> Venv { + Venv { + environments: HashMap::new(), + } + } +} + +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 { + 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; + } + false + } + + fn gather(&mut 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 d2701d3e7fb0..b1fd8adafa0a 100644 --- a/native_locator/src/virtualenv.rs +++ b/native_locator/src/virtualenv.rs @@ -1,17 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use crate::locator::Locator; use crate::messaging::{MessageDispatcher, PythonEnvironment, PythonEnvironmentCategory}; use crate::utils::PythonEnv; +use std::collections::HashMap; use std::path::PathBuf; -use std::{collections::HashMap, path::PathBuf}; -use crate::{ - locator::{self, Locator}, - messaging::{MessageDispatcher, PythonEnvironment}, - utils::PythonEnv, -}; pub fn is_virtualenv(env: &PythonEnv) -> bool { + if env.path.is_none() { + return false; + } if let Some(file_path) = env.executable.parent() { // Check if there are any activate.* files in the same directory as the interpreter. // @@ -51,15 +50,21 @@ pub fn is_virtualenv(env: &PythonEnv) -> bool { pub fn find_and_report(env: &PythonEnv, dispatcher: &mut impl MessageDispatcher) -> Option<()> { if is_virtualenv(env) { let env = PythonEnvironment { - name: match env.path.file_name().to_owned() { + name: match env + .path + .clone() + .expect("env.path can never be empty for virtualenvs") + .file_name() + .to_owned() + { Some(name) => Some(name.to_string_lossy().to_owned().to_string()), None => None, }, python_executable_path: Some(env.executable.clone()), category: PythonEnvironmentCategory::VirtualEnv, version: env.version.clone(), - env_path: Some(env.path.clone()), - sys_prefix_path: Some(env.path.clone()), + env_path: env.path.clone(), + sys_prefix_path: env.path.clone(), env_manager: None, python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), project_path: None, @@ -72,43 +77,6 @@ pub fn find_and_report(env: &PythonEnv, dispatcher: &mut impl MessageDispatcher) None } -pub fn is_exe_virtualenv(python_executable: &PathBuf) -> bool { - if let Some(file_path) = python_executable.parent() { - // Check if there are any activate.* files in the same directory as the interpreter. - // - // env - // |__ activate, activate.* <--- check if any of these files exist - // |__ python <--- interpreterPath - - // if let Some(parent_path) = PathBuf::from(env.) - // const directory = path.dirname(interpreterPath); - // const files = await fsapi.readdir(directory); - // const regex = /^activate(\.([A-z]|\d)+)?$/i; - if file_path.join("activate").exists() || file_path.join("activate.bat").exists() { - return true; - } - - // Support for activate.ps, etc. - match std::fs::read_dir(file_path) { - Ok(files) => { - for file in files { - if let Ok(file) = file { - if let Some(file_name) = file.file_name().to_str() { - if file_name.starts_with("activate") { - return true; - } - } - } - } - return false; - } - Err(_) => return false, - }; - } - - false -} - pub struct VirtualEnv { pub environments: HashMap, } @@ -122,45 +90,57 @@ impl VirtualEnv { } impl Locator for VirtualEnv { - fn get_type(&mut self) -> String { - "virtualenv".to_string() - } - - fn is_known(&mut self, python_executable: &PathBuf) -> bool { + fn is_known(&self, python_executable: &PathBuf) -> bool { self.environments .contains_key(python_executable.to_str().unwrap_or_default()) } - fn is_compatible(&mut self, python_executable: &PathBuf) -> bool { - is_exe_virtualenv(python_executable) - } + // fn is_compatible(&mut self, python_executable: &PathBuf) -> bool { + // is_virtualenv(&PythonEnv { + // executable: python_executable.clone(), + // path: python_executable.parent().unwrap().to_path_buf(), + // version: None, + // }) + // } - fn track_if_compatible(&mut self, env: &locator::PythonEnv) -> () { - if is_exe_virtualenv(&env.executable) { - let executable = env.executable.to_str().unwrap().to_string(); + fn track_if_compatible(&mut self, env: &PythonEnv) -> bool { + if is_virtualenv(env) { self.environments.insert( - executable, + env.executable.to_str().unwrap().to_string(), PythonEnvironment { - python_executable_path: Some(executable), - version: Some(env.version.clone()), - category: crate::messaging::PythonEnvironmentCategory:: - sys_path: Some(env.sys_path.clone()), - display_name: env.display_name.clone(), - architecture: env.architecture.clone(), - path: env.path.clone(), - kind: "virtualenv".to_string(), + 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; } + false } - fn find(&mut self) -> () { + fn gather(&mut 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(&mut self, reporter: &dyn MessageDispatcher) -> () { - todo!() + 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 d0cc5d4551e1..29d17c9ae776 100644 --- a/native_locator/src/virtualenvwrapper.rs +++ b/native_locator/src/virtualenvwrapper.rs @@ -1,13 +1,17 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use crate::locator::Locator; use crate::messaging::{PythonEnvironment, PythonEnvironmentCategory}; +use crate::utils::{find_python_binary_path, get_version, list_python_environments}; use crate::virtualenv; use crate::{known::Environment, messaging::MessageDispatcher, utils::PythonEnv}; +use std::collections::HashMap; +use std::fs; use std::path::PathBuf; #[cfg(windows)] -fn get_default_virtualenvwrapper_path(environment: &impl Environment) -> Option { +fn get_default_virtualenvwrapper_path(environment: &dyn Environment) -> Option { // In Windows, the default path for WORKON_HOME is %USERPROFILE%\Envs. // If 'Envs' is not available we should default to '.virtualenvs'. Since that // is also valid for windows. @@ -25,7 +29,7 @@ fn get_default_virtualenvwrapper_path(environment: &impl Environment) -> Option< } #[cfg(unix)] -fn get_default_virtualenvwrapper_path(environment: &impl Environment) -> Option { +fn get_default_virtualenvwrapper_path(environment: &dyn Environment) -> Option { if let Some(home) = environment.get_user_home() { let home = PathBuf::from(home).join("virtualenvs"); if home.exists() { @@ -35,7 +39,7 @@ fn get_default_virtualenvwrapper_path(environment: &impl Environment) -> Option< None } -fn get_work_on_home_path(environment: &impl Environment) -> Option { +fn get_work_on_home_path(environment: &dyn Environment) -> Option { // The WORKON_HOME variable contains the path to the root directory of all virtualenvwrapper environments. // If the interpreter path belongs to one of them then it is a virtualenvwrapper type of environment. if let Some(work_on_home) = environment.get_env_var("WORKON_HOME".to_string()) { @@ -48,7 +52,10 @@ fn get_work_on_home_path(environment: &impl Environment) -> Option { get_default_virtualenvwrapper_path(environment) } -pub fn is_virtualenvwrapper(env: &PythonEnv, environment: &impl Environment) -> bool { +pub fn is_virtualenvwrapper(env: &PythonEnv, environment: &dyn Environment) -> bool { + if env.path.is_none() { + return false; + } // For environment to be a virtualenvwrapper based it has to follow these two rules: // 1. It should be in a sub-directory under the WORKON_HOME // 2. It should be a valid virtualenv environment @@ -68,15 +75,21 @@ pub fn find_and_report( ) -> Option<()> { if is_virtualenvwrapper(env, environment) { let env = PythonEnvironment { - name: match env.path.file_name().to_owned() { + name: match env + .path + .clone() + .expect("env.path cannot be empty in virtualenvwrapper") + .file_name() + .to_owned() + { Some(name) => Some(name.to_string_lossy().to_owned().to_string()), None => None, }, python_executable_path: Some(env.executable.clone()), category: PythonEnvironmentCategory::VirtualEnvWrapper, version: env.version.clone(), - env_path: Some(env.path.clone()), - sys_prefix_path: Some(env.path.clone()), + env_path: env.path.clone(), + sys_prefix_path: env.path.clone(), env_manager: None, python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), project_path: None, @@ -87,3 +100,92 @@ pub fn find_and_report( } None } + +fn list_python_environments_in_work_on_path( + environment: &dyn Environment, +) -> Option> { + let mut python_envs: Vec = vec![]; + for venv_dir in fs::read_dir(get_work_on_home_path(environment)?).ok()? { + if let Ok(venv_dir) = venv_dir { + let venv_dir = venv_dir.path(); + if !venv_dir.is_dir() { + continue; + } + if let Some(executable) = find_python_binary_path(&venv_dir) { + python_envs.push(PythonEnv::new( + executable.clone(), + Some(venv_dir), + get_version(&executable), + )) + } + } + } + + Some(python_envs) +} + +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, + } + } +} + +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 { + 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; + } + false + } + + fn gather(&mut self) -> Option<()> { + let work_on_home = get_work_on_home_path(self.environment)?; + let envs = list_python_environments(&work_on_home)?; + envs.iter().for_each(|env| { + self.track_if_compatible(env); + }); + + Some(()) + } + + fn report(&self, reporter: &mut dyn MessageDispatcher) { + for env in self.environments.values() { + reporter.report_environment(env.clone()); + } + } +} diff --git a/native_locator/src/windows_python.rs b/native_locator/src/windows_python.rs index 8199a503f924..5e34ba127947 100644 --- a/native_locator/src/windows_python.rs +++ b/native_locator/src/windows_python.rs @@ -2,72 +2,107 @@ // Licensed under the MIT License. use crate::known; -use crate::messaging; -use crate::utils; +use crate::known::Environment; +use crate::locator::Locator; +use crate::messaging::MessageDispatcher; +use crate::messaging::PythonEnvironment; +use crate::utils::PythonEnv; +use std::collections::HashMap; use std::path::Path; use std::path::PathBuf; -fn report_path_python(path: &str, dispatcher: &mut impl messaging::MessageDispatcher) { - let version = utils::get_version(path); - dispatcher.report_environment(messaging::PythonEnvironment::new( - None, - Some(PathBuf::from(path)), - messaging::PythonEnvironmentCategory::WindowsStore, - version, - None, - None, - None, - None, - )); +fn is_windows_python_executable(path: &PathBuf) -> bool { + let name = path.file_name().unwrap().to_string_lossy().to_lowercase(); + name.starts_with("python3.") && name.ends_with(".exe") } - -fn report_windows_store_python( - dispatcher: &mut impl messaging::MessageDispatcher, - environment: &impl known::Environment, -) { - let home = environment.get_user_home(); - match home { - Some(home) => { - let apps_path = Path::new(&home) - .join("AppData") - .join("Local") - .join("Microsoft") - .join("WindowsApps"); - let files = std::fs::read_dir(apps_path); - match files { - Ok(files) => { - for file in files { - match file { - Ok(file) => { - let path = file.path(); - match path.file_name() { - Some(name) => { - let name = name.to_string_lossy().to_lowercase(); - if name.starts_with("python3.") && name.ends_with(".exe") { - report_path_python(&path.to_string_lossy(), dispatcher); - } - } - None => {} - } - } - Err(_) => {} - } - } +fn list_windows_store_python_executables( + environment: &dyn known::Environment, +) -> Option> { + let mut python_envs: Vec = vec![]; + let home = environment.get_user_home()?; + let apps_path = Path::new(&home) + .join("AppData") + .join("Local") + .join("Microsoft") + .join("WindowsApps"); + for file in std::fs::read_dir(apps_path).ok()? { + match file { + Ok(file) => { + let path = file.path(); + if path.is_file() && is_windows_python_executable(&path) { + python_envs.push(path); } - Err(_) => {} } + Err(_) => {} + } + } + + Some(python_envs) +} + +fn list_registry_pythons() -> Option> { + None +} + +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, } - None => {} } } -fn report_registry_pythons() {} +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 { + 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; + } + false + } + + fn gather(&mut self) -> Option<()> { + 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(envs) = list_registry_pythons() { + envs.iter().for_each(|env| { + self.track_if_compatible(&&PythonEnv::from(env.clone())); + }); + } + Some(()) + } -#[allow(dead_code)] -pub fn find_and_report( - dispatcher: &mut impl messaging::MessageDispatcher, - environment: &impl known::Environment, -) { - report_windows_store_python(dispatcher, environment); - report_registry_pythons(); + fn report(&self, reporter: &mut dyn MessageDispatcher) { + for env in self.environments.values() { + reporter.report_environment(env.clone()); + } + } } From 9a4a181855cc1c27f5522a87971fac7a4e9cb32c Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 10 May 2024 11:56:44 +1000 Subject: [PATCH 3/4] Fix tests --- native_locator/src/common_python.rs | 45 +--------------- native_locator/src/conda.rs | 57 +++----------------- native_locator/src/global_virtualenvs.rs | 22 -------- native_locator/src/homebrew.rs | 48 ++--------------- native_locator/src/locator.rs | 13 ++--- native_locator/src/main.rs | 4 +- native_locator/src/pipenv.rs | 19 ------- native_locator/src/pyenv.rs | 7 ++- native_locator/src/venv.rs | 35 +------------ native_locator/src/virtualenv.rs | 40 +------------- native_locator/src/virtualenvwrapper.rs | 61 +--------------------- native_locator/tests/common_python_test.rs | 8 +-- native_locator/tests/pyenv_test.rs | 16 ++++-- 13 files changed, 43 insertions(+), 332 deletions(-) diff --git a/native_locator/src/common_python.rs b/native_locator/src/common_python.rs index 5c9f6a29e42a..dc796c164070 100644 --- a/native_locator/src/common_python.rs +++ b/native_locator/src/common_python.rs @@ -1,11 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -use crate::known; use crate::known::Environment; use crate::locator::Locator; +use crate::messaging::MessageDispatcher; use crate::messaging::PythonEnvironment; -use crate::messaging::{self, MessageDispatcher}; use crate::utils::{self, PythonEnv}; use std::collections::HashMap; use std::env; @@ -20,48 +19,6 @@ fn get_env_path(python_executable_path: &PathBuf) -> Option { } } -// fn report_path_python( -// dispatcher: &mut impl messaging::MessageDispatcher, -// python_executable_path: &PathBuf, -// ) { -// let version = utils::get_version(python_executable_path); -// let env_path = get_env_path(python_executable_path); -// dispatcher.report_environment(messaging::PythonEnvironment::new( -// None, -// Some(PathBuf::from(python_executable_path)), -// messaging::PythonEnvironmentCategory::System, -// version, -// env_path.clone(), -// env_path, -// None, -// Some(vec![python_executable_path.to_str().unwrap().to_string()]), -// )); -// } - -// fn report_python_on_path( -// dispatcher: &mut impl messaging::MessageDispatcher, -// environment: &impl known::Environment, -// ) { -// if let Some(paths) = environment.get_env_var("PATH".to_string()) { -// let bin = if cfg!(windows) { -// "python.exe" -// } else { -// "python" -// }; -// env::split_paths(&paths) -// .map(|p| p.join(bin)) -// .filter(|p| p.exists()) -// .for_each(|full_path| report_path_python(dispatcher, &full_path)); -// } -// } - -// pub fn find_and_report( -// dispatcher: &mut impl messaging::MessageDispatcher, -// environment: &impl known::Environment, -// ) { -// report_python_on_path(dispatcher, environment); -// } - pub struct PythonOnPath<'a> { pub environments: HashMap, pub environment: &'a dyn Environment, diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index bbf9cb3b25d7..23f86f811ba0 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -384,56 +384,6 @@ fn get_distinct_conda_envs( conda_envs } -pub fn find_and_report( - dispatcher: &mut impl messaging::MessageDispatcher, - environment: &impl known::Environment, -) { - let conda_binary = find_conda_binary(environment); - match conda_binary { - Some(conda_binary) => { - let env_manager = messaging::EnvManager::new( - conda_binary.clone(), - get_conda_version(&conda_binary), - EnvManagerType::Conda, - ); - dispatcher.report_environment_manager(env_manager.clone()); - - let envs = get_distinct_conda_envs(&conda_binary, environment); - for env in envs { - let executable = find_python_binary_path(Path::new(&env.path)); - let params = messaging::PythonEnvironment::new( - Some(env.name.to_string()), - executable, - messaging::PythonEnvironmentCategory::Conda, - get_conda_python_version(&env.path), - Some(env.path.clone()), - Some(env.path.clone()), - Some(env_manager.clone()), - if env.named { - Some(vec![ - conda_binary.to_string_lossy().to_string(), - "run".to_string(), - "-n".to_string(), - env.name.to_string(), - "python".to_string(), - ]) - } else { - Some(vec![ - conda_binary.to_string_lossy().to_string(), - "run".to_string(), - "-p".to_string(), - env.path.to_string_lossy().to_string(), - "python".to_string(), - ]) - }, - ); - dispatcher.report_environment(params); - } - } - None => (), - } -} - pub struct Conda<'a> { pub environments: HashMap, pub manager: Option, @@ -503,6 +453,9 @@ 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); } } @@ -510,6 +463,10 @@ impl Locator for Conda<'_> { } fn report(&self, reporter: &mut dyn MessageDispatcher) { + if let Some(manager) = &self.manager { + reporter.report_environment_manager(manager.clone()); + } + for env in self.environments.values() { reporter.report_environment(env.clone()); } diff --git a/native_locator/src/global_virtualenvs.rs b/native_locator/src/global_virtualenvs.rs index 0b508209e570..8004775e3ee2 100644 --- a/native_locator/src/global_virtualenvs.rs +++ b/native_locator/src/global_virtualenvs.rs @@ -67,25 +67,3 @@ pub fn list_global_virtual_envs(environment: &impl known::Environment) -> Vec Option<()> { -// for env in list_global_virtualenvs(environment).iter() { -// if pipenv::find_and_report(&env, dispatcher).is_some() { -// continue; -// } -// if virtualenvwrapper::find_and_report(&env, dispatcher, environment).is_some() { -// continue; -// } -// if venv::find_and_report(&env, dispatcher).is_some() { -// continue; -// } -// if virtualenv::find_and_report(&env, dispatcher).is_some() { -// continue; -// } -// } - -// None -// } diff --git a/native_locator/src/homebrew.rs b/native_locator/src/homebrew.rs index ad0ad1aa9965..b565dbaf27ba 100644 --- a/native_locator/src/homebrew.rs +++ b/native_locator/src/homebrew.rs @@ -11,14 +11,15 @@ use std::{ use crate::{ known::Environment, locator::Locator, - messaging::{MessageDispatcher, PythonEnvironment}, utils::PythonEnv, + messaging::{MessageDispatcher, PythonEnvironment}, + utils::PythonEnv, }; use regex::Regex; fn is_symlinked_python_executable(path: Result) -> Option { let path = path.ok()?.path(); let name = path.file_name()?.to_string_lossy(); - if !name.starts_with("python") || name.ends_with("-config") { + if !name.starts_with("python") || name.ends_with("-config") || name.ends_with("-build") { return None; } let metadata = std::fs::symlink_metadata(&path).ok()?; @@ -28,49 +29,6 @@ fn is_symlinked_python_executable(path: Result) -> Option Option<()> { - // https://docs.brew.sh/Homebrew-and-Python#brewed-python-modules - // Executable Python scripts will be in $(brew --prefix)/bin. - // They are always symlinks, hence we will only look for symlinks. - - let homebrew_prefix = 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(); - 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(); - let version = match python_regex.captures(&python_version) { - Some(captures) => match captures.get(1) { - Some(version) => Some(version.as_str().to_string()), - None => None, - }, - None => None, - }; - if reported.contains(&exe.to_string_lossy().to_string()) { - continue; - } - reported.insert(exe.to_string_lossy().to_string()); - let env = crate::messaging::PythonEnvironment::new( - None, - Some(exe.clone()), - crate::messaging::PythonEnvironmentCategory::Homebrew, - version, - None, - None, - None, - Some(vec![exe.to_string_lossy().to_string()]), - ); - dispatcher.report_environment(env); - } - } - - None -} - pub struct Homebrew<'a> { pub environments: HashMap, pub environment: &'a dyn Environment, diff --git a/native_locator/src/locator.rs b/native_locator/src/locator.rs index 2d6454c9ab99..9354207fa14a 100644 --- a/native_locator/src/locator.rs +++ b/native_locator/src/locator.rs @@ -5,16 +5,10 @@ use crate::{messaging::MessageDispatcher, utils::PythonEnv}; use std::path::PathBuf; pub trait Locator { - // fn get_type(&mut self) -> String; /** * Whether the given Python executable is known to this locator. */ fn is_known(&self, python_executable: &PathBuf) -> bool; - /** - * Whether the given Python executable belongs to an environment that is - * compatible with the environments supported by this locator. - */ - // fn is_compatible(&mut 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. @@ -25,9 +19,8 @@ pub trait Locator { * Finds all environments managed by this locator. */ fn gather(&mut self) -> Option<()>; - // /** - // * Finds all environments managed by this locator. - // */ - // fn find_in_location(path: &PathBuf) -> (); + /** + * Report all of the tracked environments and managers. + */ fn report(&self, reporter: &mut dyn MessageDispatcher); } diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs index 3ca557ac518b..a01bd8bd57fd 100644 --- a/native_locator/src/main.rs +++ b/native_locator/src/main.rs @@ -1,13 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -use global_virtualenvs::{get_global_virtualenv_dirs, list_global_virtual_envs}; +use global_virtualenvs::list_global_virtual_envs; use known::EnvironmentApi; use locator::Locator; use messaging::{create_dispatcher, MessageDispatcher}; -use std::slice::Windows; use std::time::SystemTime; -use std::{fs, path::PathBuf}; mod common_python; mod conda; diff --git a/native_locator/src/pipenv.rs b/native_locator/src/pipenv.rs index adbc2abf5b96..1e266732536c 100644 --- a/native_locator/src/pipenv.rs +++ b/native_locator/src/pipenv.rs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::known::Environment; use crate::locator::Locator; use crate::messaging::{MessageDispatcher, PythonEnvironment}; use crate::utils::PythonEnv; @@ -23,24 +22,6 @@ fn get_pipenv_project(env: &PythonEnv) -> Option { None } -pub fn find_and_report(env: &PythonEnv, dispatcher: &mut impl MessageDispatcher) -> Option<()> { - if let Some(project_path) = get_pipenv_project(env) { - let env = PythonEnvironment::new_pipenv( - Some(env.executable.clone()), - env.version.clone(), - Some(env.path.clone()?), - env.path.clone(), - None, - project_path, - ); - - dispatcher.report_environment(env); - return Some(()); - } - - None -} - pub struct PipEnv { pub environments: HashMap, } diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs index 4f1dbf11f961..a5ead7d44ad2 100644 --- a/native_locator/src/pyenv.rs +++ b/native_locator/src/pyenv.rs @@ -180,6 +180,7 @@ pub fn list_pyenv_environments( pub struct PyEnv<'a> { pub environments: HashMap, pub environment: &'a dyn Environment, + pub manager: Option, } impl PyEnv<'_> { @@ -187,6 +188,7 @@ impl PyEnv<'_> { PyEnv { environments: HashMap::new(), environment, + manager: None, } } } @@ -203,7 +205,6 @@ impl Locator for PyEnv<'_> { } fn gather(&mut self) -> Option<()> { - let pyenv_dir = get_pyenv_dir(self.environment)?; let manager = match get_pyenv_binary(self.environment) { Some(pyenv_binary) => Some(messaging::EnvManager::new( pyenv_binary, @@ -212,6 +213,7 @@ impl Locator for PyEnv<'_> { )), None => None, }; + self.manager = manager.clone(); for env in list_pyenv_environments(&manager, self.environment)? { self.environments.insert( @@ -228,6 +230,9 @@ impl Locator for PyEnv<'_> { } fn report(&self, reporter: &mut dyn MessageDispatcher) { + if let Some(manager) = &self.manager { + reporter.report_environment_manager(manager.clone()); + } for env in self.environments.values() { reporter.report_environment(env.clone()); } diff --git a/native_locator/src/venv.rs b/native_locator/src/venv.rs index aeffe1f35da6..2b76c7dcf751 100644 --- a/native_locator/src/venv.rs +++ b/native_locator/src/venv.rs @@ -4,8 +4,8 @@ use std::{collections::HashMap, path::PathBuf}; use crate::{ - locator::{self, Locator}, - messaging::{MessageDispatcher, PythonEnvironment, PythonEnvironmentCategory}, + locator::Locator, + messaging::{MessageDispatcher, PythonEnvironment}, utils::{self, PythonEnv}, }; @@ -16,37 +16,6 @@ pub fn is_venv(env: &PythonEnv) -> bool { } return utils::find_pyvenv_config_path(&env.executable).is_some(); } - -pub fn find_and_report(env: &PythonEnv, dispatcher: &mut impl MessageDispatcher) -> Option<()> { - if is_venv(env) { - let env = PythonEnvironment { - name: match env - .path - .clone() - .expect("env.path can never be empty for venvs") - .file_name() - .to_owned() - { - Some(name) => Some(name.to_string_lossy().to_owned().to_string()), - None => None, - }, - python_executable_path: Some(env.executable.clone()), - category: PythonEnvironmentCategory::Venv, - version: env.version.clone(), - env_path: env.path.clone(), - sys_prefix_path: env.path.clone(), - env_manager: None, - python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), - project_path: None, - }; - - dispatcher.report_environment(env); - - return Some(()); - } - None -} - pub struct Venv { pub environments: HashMap, } diff --git a/native_locator/src/virtualenv.rs b/native_locator/src/virtualenv.rs index b1fd8adafa0a..40972b80a14d 100644 --- a/native_locator/src/virtualenv.rs +++ b/native_locator/src/virtualenv.rs @@ -2,7 +2,7 @@ // Licensed under the MIT License. use crate::locator::Locator; -use crate::messaging::{MessageDispatcher, PythonEnvironment, PythonEnvironmentCategory}; +use crate::messaging::{MessageDispatcher, PythonEnvironment}; use crate::utils::PythonEnv; use std::collections::HashMap; use std::path::PathBuf; @@ -47,36 +47,6 @@ pub fn is_virtualenv(env: &PythonEnv) -> bool { false } -pub fn find_and_report(env: &PythonEnv, dispatcher: &mut impl MessageDispatcher) -> Option<()> { - if is_virtualenv(env) { - let env = PythonEnvironment { - name: match env - .path - .clone() - .expect("env.path can never be empty for virtualenvs") - .file_name() - .to_owned() - { - Some(name) => Some(name.to_string_lossy().to_owned().to_string()), - None => None, - }, - python_executable_path: Some(env.executable.clone()), - category: PythonEnvironmentCategory::VirtualEnv, - version: env.version.clone(), - env_path: env.path.clone(), - sys_prefix_path: env.path.clone(), - env_manager: None, - python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), - project_path: None, - }; - - dispatcher.report_environment(env); - - return Some(()); - } - None -} - pub struct VirtualEnv { pub environments: HashMap, } @@ -95,14 +65,6 @@ impl Locator for VirtualEnv { .contains_key(python_executable.to_str().unwrap_or_default()) } - // fn is_compatible(&mut self, python_executable: &PathBuf) -> bool { - // is_virtualenv(&PythonEnv { - // executable: python_executable.clone(), - // path: python_executable.parent().unwrap().to_path_buf(), - // version: None, - // }) - // } - fn track_if_compatible(&mut self, env: &PythonEnv) -> bool { if is_virtualenv(env) { self.environments.insert( diff --git a/native_locator/src/virtualenvwrapper.rs b/native_locator/src/virtualenvwrapper.rs index 29d17c9ae776..e70cfc82af75 100644 --- a/native_locator/src/virtualenvwrapper.rs +++ b/native_locator/src/virtualenvwrapper.rs @@ -2,12 +2,11 @@ // Licensed under the MIT License. use crate::locator::Locator; -use crate::messaging::{PythonEnvironment, PythonEnvironmentCategory}; -use crate::utils::{find_python_binary_path, get_version, list_python_environments}; +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 std::fs; use std::path::PathBuf; #[cfg(windows)] @@ -68,62 +67,6 @@ pub fn is_virtualenvwrapper(env: &PythonEnv, environment: &dyn Environment) -> b false } -pub fn find_and_report( - env: &PythonEnv, - dispatcher: &mut impl MessageDispatcher, - environment: &impl Environment, -) -> Option<()> { - if is_virtualenvwrapper(env, environment) { - let env = PythonEnvironment { - name: match env - .path - .clone() - .expect("env.path cannot be empty in virtualenvwrapper") - .file_name() - .to_owned() - { - Some(name) => Some(name.to_string_lossy().to_owned().to_string()), - None => None, - }, - python_executable_path: Some(env.executable.clone()), - category: PythonEnvironmentCategory::VirtualEnvWrapper, - version: env.version.clone(), - env_path: env.path.clone(), - sys_prefix_path: env.path.clone(), - env_manager: None, - python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]), - project_path: None, - }; - - dispatcher.report_environment(env); - return Some(()); - } - None -} - -fn list_python_environments_in_work_on_path( - environment: &dyn Environment, -) -> Option> { - let mut python_envs: Vec = vec![]; - for venv_dir in fs::read_dir(get_work_on_home_path(environment)?).ok()? { - if let Ok(venv_dir) = venv_dir { - let venv_dir = venv_dir.path(); - if !venv_dir.is_dir() { - continue; - } - if let Some(executable) = find_python_binary_path(&venv_dir) { - python_envs.push(PythonEnv::new( - executable.clone(), - Some(venv_dir), - get_version(&executable), - )) - } - } - } - - Some(python_envs) -} - pub struct VirtualEnvWrapper<'a> { pub environments: HashMap, pub environment: &'a dyn Environment, diff --git a/native_locator/tests/common_python_test.rs b/native_locator/tests/common_python_test.rs index 9ee1c03c201e..00050fec96f4 100644 --- a/native_locator/tests/common_python_test.rs +++ b/native_locator/tests/common_python_test.rs @@ -10,7 +10,7 @@ fn find_python_in_path_this() { assert_messages, create_test_dispatcher, create_test_environment, join_test_paths, test_file_path, }; - use python_finder::{common_python, messaging::PythonEnvironment}; + use python_finder::{common_python, locator::Locator, messaging::PythonEnvironment}; use serde_json::json; use std::collections::HashMap; @@ -27,7 +27,9 @@ fn find_python_in_path_this() { Vec::new(), ); - common_python::find_and_report(&mut dispatcher, &known); + let mut locator = common_python::PythonOnPath::with(&known); + locator.gather(); + locator.report(&mut dispatcher); assert_eq!(dispatcher.messages.len(), 1); let env = PythonEnvironment { @@ -39,7 +41,7 @@ fn find_python_in_path_this() { version: None, python_run_command: Some(vec![unix_python_exe.clone().to_str().unwrap().to_string()]), env_path: Some(unix_python.clone()), - sys_prefix_path: Some(unix_python.clone()), + sys_prefix_path: None, }; assert_messages(&[json!(env)], &dispatcher); } diff --git a/native_locator/tests/pyenv_test.rs b/native_locator/tests/pyenv_test.rs index 1b9e53c4ebbf..6702d258d958 100644 --- a/native_locator/tests/pyenv_test.rs +++ b/native_locator/tests/pyenv_test.rs @@ -7,7 +7,7 @@ mod common; #[cfg(unix)] fn does_not_find_any_pyenv_envs() { use crate::common::{create_test_dispatcher, create_test_environment}; - use python_finder::pyenv; + use python_finder::{locator::Locator, pyenv}; use std::{collections::HashMap, path::PathBuf}; let mut dispatcher = create_test_dispatcher(); @@ -17,7 +17,9 @@ fn does_not_find_any_pyenv_envs() { Vec::new(), ); - pyenv::find_and_report(&mut dispatcher, &known); + let mut locator = pyenv::PyEnv::with(&known); + locator.gather(); + locator.report(&mut dispatcher); assert_eq!(dispatcher.messages.len(), 0); } @@ -29,6 +31,7 @@ fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() { assert_messages, create_test_dispatcher, create_test_environment, join_test_paths, test_file_path, }; + use python_finder::locator::Locator; use python_finder::pyenv; use serde_json::json; use std::{collections::HashMap, path::PathBuf}; @@ -43,7 +46,9 @@ fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() { vec![PathBuf::from(homebrew_bin)], ); - pyenv::find_and_report(&mut dispatcher, &known); + let mut locator = pyenv::PyEnv::with(&known); + locator.gather(); + locator.report(&mut dispatcher); assert_eq!(dispatcher.messages.len(), 1); let expected_json = json!({"executablePath":pyenv_exe,"version":null, "tool": "pyenv"}); @@ -57,6 +62,7 @@ fn find_pyenv_envs() { assert_messages, create_test_dispatcher, create_test_environment, join_test_paths, test_file_path, }; + use python_finder::locator::Locator; use python_finder::{ messaging::{EnvManager, EnvManagerType, PythonEnvironment}, pyenv, @@ -74,7 +80,9 @@ fn find_pyenv_envs() { vec![PathBuf::from(homebrew_bin)], ); - pyenv::find_and_report(&mut dispatcher, &known); + let mut locator = pyenv::PyEnv::with(&known); + locator.gather(); + locator.report(&mut dispatcher); assert_eq!(dispatcher.messages.len(), 6); let expected_manager = EnvManager { From 1ebe22cee8f687eaf974d81c72fed211b782bac0 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 10 May 2024 12:01:02 +1000 Subject: [PATCH 4/4] Fix conda tests --- native_locator/tests/conda_test.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs index 2673ea99cf7b..80798fdbfe91 100644 --- a/native_locator/tests/conda_test.rs +++ b/native_locator/tests/conda_test.rs @@ -7,7 +7,7 @@ mod common; #[cfg(unix)] fn does_not_find_any_conda_envs() { use crate::common::{create_test_dispatcher, create_test_environment}; - use python_finder::conda; + use python_finder::{conda, locator::Locator}; use std::{collections::HashMap, path::PathBuf}; let mut dispatcher = create_test_dispatcher(); @@ -17,7 +17,9 @@ fn does_not_find_any_conda_envs() { Vec::new(), ); - conda::find_and_report(&mut dispatcher, &known); + let mut locator = conda::Conda::with(&known); + locator.gather(); + locator.report(&mut dispatcher); assert_eq!(dispatcher.messages.len(), 0); } @@ -29,10 +31,8 @@ fn find_conda_exe_and_empty_envs() { assert_messages, create_test_dispatcher, create_test_environment, join_test_paths, test_file_path, }; - use python_finder::{ - conda, - messaging::{EnvManager, EnvManagerType}, - }; + use python_finder::messaging::{EnvManager, EnvManagerType}; + use python_finder::{conda, locator::Locator}; use serde_json::json; use std::{collections::HashMap, path::PathBuf}; let conda_dir = test_file_path(&["tests/unix/conda_without_envs"]); @@ -47,7 +47,9 @@ fn find_conda_exe_and_empty_envs() { Vec::new(), ); - conda::find_and_report(&mut dispatcher, &known); + let mut locator = conda::Conda::with(&known); + locator.gather(); + locator.report(&mut dispatcher); let conda_exe = join_test_paths(&[conda_dir.clone().to_str().unwrap(), "conda"]); let expected_conda_manager = EnvManager { @@ -64,8 +66,8 @@ fn finds_two_conda_envs_from_txt() { assert_messages, create_test_dispatcher, create_test_environment, join_test_paths, test_file_path, }; - use python_finder::conda; use python_finder::messaging::{EnvManager, EnvManagerType, PythonEnvironment}; + use python_finder::{conda, locator::Locator}; use serde_json::json; use std::collections::HashMap; use std::fs; @@ -92,7 +94,9 @@ fn finds_two_conda_envs_from_txt() { Vec::new(), ); - conda::find_and_report(&mut dispatcher, &known); + let mut locator = conda::Conda::with(&known); + locator.gather(); + locator.report(&mut dispatcher); 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"]);