From becef27ca3958b6251df86dc036856ef5dd216bd Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 14 May 2024 16:59:50 +1000 Subject: [PATCH 01/10] Search for conda envs in known locations --- native_locator/src/conda.rs | 178 +++++++++++++++++++++++------------- 1 file changed, 116 insertions(+), 62 deletions(-) diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 4233461fc264..6b3851b8a519 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -11,6 +11,7 @@ use crate::messaging::EnvManagerType; use crate::messaging::PythonEnvironment; use crate::utils::PythonEnv; use crate::utils::{find_python_binary_path, get_environment_key, get_environment_manager_key}; +use log::trace; use log::warn; use regex::Regex; use std::collections::HashSet; @@ -286,6 +287,47 @@ struct Condarc { env_dirs: Vec, } +/** + * Get the list of conda environments found in other locations such as + * /.conda/envs + * /AppData/Local/conda/conda/envs + */ +fn get_conda_environment_paths_from_conda_rc(environment: &dyn known::Environment) -> Vec { + if let Some(paths) = get_conda_conda_rc(environment) { + paths.env_dirs + } else { + vec![] + } +} + +fn get_conda_environment_paths_from_known_paths( + environment: &dyn known::Environment, +) -> Vec { + if let Some(home) = environment.get_user_home() { + let mut env_paths: Vec = vec![]; + let _ = [ + PathBuf::from(".conda").join("envs"), + PathBuf::from("AppData") + .join("Local") + .join("conda") + .join("conda") + .join("envs"), + ] + .iter() + .map(|path| { + let full_path = home.join(path); + for entry in std::fs::read_dir(full_path).ok()?.filter_map(Result::ok) { + if entry.path().is_dir() { + trace!("Search for conda envs in location {:?}", entry.path()); + env_paths.push(entry.path()); + } + } + None::<()> + }); + return env_paths; + } + vec![] +} /** * The .condarc file contains a list of directories where conda environments are created. * https://conda.io/projects/conda/en/latest/configuration.html#envs-dirs @@ -324,31 +366,6 @@ fn get_conda_conda_rc(environment: &dyn known::Environment) -> Option { None } -fn get_conda_envs_from_conda_rc( - root_conda_path: &PathBuf, - environment: &dyn known::Environment, -) -> Option> { - let mut envs: Vec = vec![]; - for env in get_conda_conda_rc(environment)?.env_dirs { - if let Ok(reader) = std::fs::read_dir(env) { - for entry in reader.filter_map(Result::ok) { - if entry.path().is_dir() - && was_conda_environment_created_by_specific_conda( - &entry.path(), - root_conda_path, - ) - { - if let Some(env) = get_conda_environment_info(&entry.path(), false) { - envs.push(env); - } - } - } - } - } - - Some(envs) -} - /** * When we create conda environments in specific folder using the -p argument, the location of the conda executable is not know. * If the user has multiple conda installations, any one of those could have created that specific environment. @@ -415,27 +432,6 @@ fn get_environments_from_environments_txt_belonging_to_conda_directory( Some(envs) } -fn get_conda_environments_from_conda_directory( - path: &PathBuf, - environment: &dyn known::Environment, -) -> Option> { - let mut all_envs: Vec = vec![]; - if let Some(envs) = get_environments_from_envs_folder_in_conda_directory(path) { - envs.iter().for_each(|env| all_envs.push(env.clone())); - } - - if let Some(envs) = - get_environments_from_environments_txt_belonging_to_conda_directory(path, environment) - { - envs.iter().for_each(|env| all_envs.push(env.clone())); - } - - if let Some(envs) = get_conda_envs_from_conda_rc(path, environment) { - envs.iter().for_each(|env| all_envs.push(env.clone())); - } - - Some(all_envs) -} #[cfg(windows)] fn get_known_conda_install_locations(environment: &dyn known::Environment) -> Vec { @@ -546,6 +542,7 @@ fn get_root_python_environment(path: &PathBuf, manager: &EnvManager) -> Option

, ) -> Option { let mut managers: Vec = vec![]; let mut environments: Vec = vec![]; @@ -553,8 +550,36 @@ pub fn get_conda_environments_in_specified_path( let mut detected_managers: HashSet = HashSet::new(); if possible_conda_folder.is_dir() && possible_conda_folder.exists() { if let Some(manager) = get_conda_manager(&possible_conda_folder) { - let envs = - get_conda_environments_from_conda_directory(&possible_conda_folder, environment); + let mut envs: Vec = vec![]; + if let Some(environments) = + get_environments_from_envs_folder_in_conda_directory(possible_conda_folder) + { + environments.iter().for_each(|env| envs.push(env.clone())); + } + + get_known_conda_envs_from_various_locations(environment) + .iter() + .for_each(|path| { + let env_path = path.to_string_lossy().to_string(); + // Only include those environments that were created by the specific conda installation + // Ignore environments that are in the env sub directory of the conda folder, as those would have been + // tracked elsewhere, we're only interested in conda envs located in other parts of the file system created using the -p flag. + if env_path.contains(possible_conda_folder.to_str().unwrap()) { + return; + } + if !path.is_dir() { + return; + } + if !was_conda_environment_created_by_specific_conda( + &path, + possible_conda_folder, + ) { + return; + } + if let Some(env) = get_conda_environment_info(&path, false) { + envs.push(env.clone()); + } + }); if let Some(env) = get_root_python_environment(&possible_conda_folder, &manager) { if let Some(key) = get_environment_key(&env) { @@ -565,7 +590,7 @@ pub fn get_conda_environments_in_specified_path( } } - envs.unwrap_or_default().iter().for_each(|env| { + envs.iter().for_each(|env| { let exe = env.python_executable_path.clone(); let env = PythonEnvironment::new( None, @@ -610,11 +635,14 @@ fn find_conda_environments_from_known_conda_install_locations( let mut environments: Vec = vec![]; let mut detected_envs: HashSet = HashSet::new(); let mut detected_managers: HashSet = HashSet::new(); + let possible_conda_envs = get_known_conda_envs_from_various_locations(environment); for possible_conda_folder in get_known_conda_install_locations(environment) { - if let Some(result) = - get_conda_environments_in_specified_path(&possible_conda_folder, environment) - { + if let Some(result) = get_conda_environments_in_specified_path( + &possible_conda_folder, + environment, + &possible_conda_envs, + ) { result.managers.iter().for_each(|m| { let key = get_environment_manager_key(m); if !detected_managers.contains(&key) { @@ -661,25 +689,49 @@ pub fn get_conda_version(conda_binary: &PathBuf) -> Option { } } -fn get_conda_environments_from_environments_txt_that_have_not_been_discovered( +fn get_known_conda_envs_from_various_locations( + environment: &dyn known::Environment, +) -> Vec { + let mut env_paths = get_conda_envs_from_environment_txt(environment) + .iter() + .map(|e| PathBuf::from(e)) + .collect::>(); + + let mut env_paths_from_conda_rc = get_conda_environment_paths_from_conda_rc(environment); + env_paths.append(&mut env_paths_from_conda_rc); + + let mut envs_from_known_paths = get_conda_environment_paths_from_known_paths(environment); + env_paths.append(&mut envs_from_known_paths); + + // let mut envs: Vec = vec![]; + // env_paths.iter().for_each(|path| { + // if let Some(env) = get_conda_environment_info(&path, false) { + // envs.push(env); + // } + // }); + + env_paths +} + +fn get_conda_environments_from_known_locations_that_have_not_been_discovered( known_environment_keys: &HashSet, known_environment: &Vec, environment: &dyn known::Environment, ) -> Option { - let binding = get_conda_envs_from_environment_txt(environment); - let undiscovered_environments_in_txt = binding + let possible_conda_envs = get_known_conda_envs_from_various_locations(environment); + let undiscovered_environments = possible_conda_envs .iter() - .filter(|env| { + .filter(|path| { for known in known_environment_keys.iter() { - if known.contains(*env) { + if known.contains(&path.to_string_lossy().to_string()) { return false; } } true }) - .collect::>(); + .collect::>(); - if undiscovered_environments_in_txt.len() == 0 { + if undiscovered_environments.is_empty() { return None; } @@ -687,7 +739,7 @@ fn get_conda_environments_from_environments_txt_that_have_not_been_discovered( // Let's try to discover it. warn!( "Found environments in environments.txt that were not discovered: {:?}", - undiscovered_environments_in_txt + undiscovered_environments ); let manager = match known_environment @@ -708,7 +760,7 @@ fn get_conda_environments_from_environments_txt_that_have_not_been_discovered( if let Some(manager) = manager { let mut environments: Vec = vec![]; - for env in undiscovered_environments_in_txt { + for env in undiscovered_environments { if let Some(env) = get_conda_environment_info(&PathBuf::from(env), false) { let exe = env.python_executable_path.clone(); let env = PythonEnvironment::new( @@ -802,9 +854,11 @@ impl Conda<'_> { impl CondaLocator for Conda<'_> { fn find_in(&mut self, possible_conda_folder: &PathBuf) -> Option { + let possible_conda_envs = get_known_conda_envs_from_various_locations(self.environment); self.filter_result(get_conda_environments_in_specified_path( possible_conda_folder, self.environment, + &possible_conda_envs, )) } } @@ -836,7 +890,7 @@ impl Locator for Conda<'_> { } if let Some(result) = self.filter_result( - get_conda_environments_from_environments_txt_that_have_not_been_discovered( + get_conda_environments_from_known_locations_that_have_not_been_discovered( &self.discovered_environments, &environments, self.environment, From 1bdc23a5fcdff93d9bddb002ddc4fd2c9ed75844 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 14 May 2024 18:25:23 +1000 Subject: [PATCH 02/10] Fixes --- native_locator/src/conda.rs | 67 +++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 6b3851b8a519..6573cd6ebac7 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -432,7 +432,6 @@ fn get_environments_from_environments_txt_belonging_to_conda_directory( Some(envs) } - #[cfg(windows)] fn get_known_conda_install_locations(environment: &dyn known::Environment) -> Vec { let user_profile = environment.get_env_var("USERPROFILE".to_string()).unwrap(); @@ -542,7 +541,7 @@ fn get_root_python_environment(path: &PathBuf, manager: &EnvManager) -> Option

, + possible_conda_envs: &mut Vec, ) -> Option { let mut managers: Vec = vec![]; let mut environments: Vec = vec![]; @@ -557,29 +556,28 @@ pub fn get_conda_environments_in_specified_path( environments.iter().for_each(|env| envs.push(env.clone())); } - get_known_conda_envs_from_various_locations(environment) + for (index, path) in get_known_conda_envs_from_various_locations(environment) .iter() - .for_each(|path| { - let env_path = path.to_string_lossy().to_string(); - // Only include those environments that were created by the specific conda installation - // Ignore environments that are in the env sub directory of the conda folder, as those would have been - // tracked elsewhere, we're only interested in conda envs located in other parts of the file system created using the -p flag. - if env_path.contains(possible_conda_folder.to_str().unwrap()) { - return; - } - if !path.is_dir() { - return; - } - if !was_conda_environment_created_by_specific_conda( - &path, - possible_conda_folder, - ) { - return; - } - if let Some(env) = get_conda_environment_info(&path, false) { - envs.push(env.clone()); - } - }); + .enumerate() + { + let env_path = path.to_string_lossy().to_string(); + // Only include those environments that were created by the specific conda installation + // Ignore environments that are in the env sub directory of the conda folder, as those would have been + // tracked elsewhere, we're only interested in conda envs located in other parts of the file system created using the -p flag. + if env_path.contains(possible_conda_folder.to_str().unwrap()) { + continue; + } + if !path.is_dir() { + continue; + } + if !was_conda_environment_created_by_specific_conda(&path, possible_conda_folder) { + continue; + } + if let Some(env) = get_conda_environment_info(&path, false) { + envs.push(env.clone()); + possible_conda_envs.remove(index); + } + } if let Some(env) = get_root_python_environment(&possible_conda_folder, &manager) { if let Some(key) = get_environment_key(&env) { @@ -630,18 +628,18 @@ pub fn get_conda_environments_in_specified_path( fn find_conda_environments_from_known_conda_install_locations( environment: &dyn known::Environment, + possible_conda_envs: &mut Vec, ) -> Option { let mut managers: Vec = vec![]; let mut environments: Vec = vec![]; let mut detected_envs: HashSet = HashSet::new(); let mut detected_managers: HashSet = HashSet::new(); - let possible_conda_envs = get_known_conda_envs_from_various_locations(environment); for possible_conda_folder in get_known_conda_install_locations(environment) { if let Some(result) = get_conda_environments_in_specified_path( &possible_conda_folder, environment, - &possible_conda_envs, + possible_conda_envs, ) { result.managers.iter().for_each(|m| { let key = get_environment_manager_key(m); @@ -717,8 +715,8 @@ fn get_conda_environments_from_known_locations_that_have_not_been_discovered( known_environment_keys: &HashSet, known_environment: &Vec, environment: &dyn known::Environment, + possible_conda_envs: &mut Vec, ) -> Option { - let possible_conda_envs = get_known_conda_envs_from_various_locations(environment); let undiscovered_environments = possible_conda_envs .iter() .filter(|path| { @@ -854,11 +852,11 @@ impl Conda<'_> { impl CondaLocator for Conda<'_> { fn find_in(&mut self, possible_conda_folder: &PathBuf) -> Option { - let possible_conda_envs = get_known_conda_envs_from_various_locations(self.environment); + let mut possible_conda_envs = get_known_conda_envs_from_various_locations(self.environment); self.filter_result(get_conda_environments_in_specified_path( possible_conda_folder, self.environment, - &possible_conda_envs, + &mut possible_conda_envs, )) } } @@ -873,10 +871,14 @@ impl Locator for Conda<'_> { let mut managers: Vec = vec![]; let mut environments: Vec = vec![]; let mut detected_managers: HashSet = HashSet::new(); + let mut possible_conda_envs = get_known_conda_envs_from_various_locations(self.environment); - if let Some(result) = self.filter_result( - find_conda_environments_from_known_conda_install_locations(self.environment), - ) { + if let Some(result) = + self.filter_result(find_conda_environments_from_known_conda_install_locations( + self.environment, + &mut possible_conda_envs, + )) + { result.managers.iter().for_each(|m| { let key = get_environment_manager_key(m); detected_managers.insert(key); @@ -894,6 +896,7 @@ impl Locator for Conda<'_> { &self.discovered_environments, &environments, self.environment, + &mut possible_conda_envs, ), ) { result.managers.iter().for_each(|m| { From 5cebdb327d75a726e6efe96768d72242e7f38091 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 15 May 2024 10:37:44 +1000 Subject: [PATCH 03/10] Add tests --- native_locator/src/conda.rs | 480 +++++++++++------- native_locator/src/known.rs | 10 + native_locator/src/locator.rs | 2 +- native_locator/tests/common.rs | 6 + native_locator/tests/common_python_test.rs | 11 +- native_locator/tests/conda_test.rs | 176 ++++++- native_locator/tests/pyenv_test.rs | 20 +- .../tests/unix/conda/.conda/environments.txt | 2 - .../conda/user_home/.conda/environments.txt | 1 + .../conda/{ => user_home}/anaconda3/bin/conda | 0 .../conda-meta/conda-4.0.2-pyhd3eb1b0_0.json | 0 .../python-10.0.1-hdf0ec26_0_cpython.json | 0 .../python-slugify-5.0.2-pyhd3eb1b0_0.json | 0 .../python-10.0.1-hdf0ec26_0_cpython.json | 0 .../python-slugify-5.0.2-pyhd3eb1b0_0.json | 0 .../{ => user_home}/anaconda3/envs/one/python | 0 .../{ => user_home}/anaconda3/envs/two/python | 0 .../user_home}/.conda/environments.txt | 0 .../some_location}/anaconda3/bin/conda | 0 .../some_location/anaconda3/bin}/python | 0 .../conda-meta/conda-4.0.2-pyhd3eb1b0_0.json | 0 .../python-10.0.1-hdf0ec26_0_cpython.json | 0 .../python-slugify-5.0.2-pyhd3eb1b0_0.json | 0 .../user_home/.conda/environments.txt} | 0 .../user_home/anaconda3/bin/conda} | 0 .../conda-meta/conda-4.0.2-pyhd3eb1b0_0.json} | 0 .../python-10.0.1-hdf0ec26_0_cpython.json | 1 + .../python-slugify-5.0.2-pyhd3eb1b0_0.json} | 0 .../bin => known/user_home}/python | 0 .../unix/known/{ => user_home}/python.version | 0 .../pyenv/{ => home}/opt/homebrew/bin/pyenv | 0 .../.pyenv/versions/3.12.1}/bin/python | 0 .../.pyenv/versions/3.12.1a3}/bin/python | 0 .../.pyenv/versions/3.13-dev}/bin/python | 0 .../.pyenv/versions/3.9.9}/bin/python | 0 .../versions/anaconda-4.0.0}/bin/python | 0 .../versions/anaconda3-2021.04}/bin/python | 0 .../versions/mambaforge-4.10.1-4}/bin/python | 0 .../.pyenv/versions/mambaforge}/bin/python | 0 .../versions/miniconda-latest}/bin/python | 0 .../miniconda3-3.10-22.11.1-1}/bin/python | 0 .../versions/miniconda3-3.10.1}/bin/python | 0 .../versions/miniconda3-4.0.5}/bin/python | 0 .../versions/miniforge3-4.11.0-1}/bin/python | 0 .../versions/my-virtual-env/bin/python} | 0 .../.pyenv/versions/my-virtual-env/pyvenv.cfg | 0 .../.pyenv/versions/nogil-3.9.10/bin/python | 0 .../versions/pypy3.10-7.3.14/bin/python | 0 .../.pyenv/versions/pyston-2.3.5/bin/python | 0 .../versions/stacklets-3.7.5/bin/python | 0 .../home/opt/homebrew/bin/pyenv | 0 51 files changed, 515 insertions(+), 194 deletions(-) create mode 100644 native_locator/tests/unix/conda/user_home/.conda/environments.txt rename native_locator/tests/unix/conda/{ => user_home}/anaconda3/bin/conda (100%) rename native_locator/tests/unix/conda/{ => user_home}/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json (100%) rename native_locator/tests/unix/conda/{ => user_home}/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json (100%) rename native_locator/tests/unix/conda/{ => user_home}/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json (100%) rename native_locator/tests/unix/conda/{ => user_home}/anaconda3/envs/one/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json (100%) rename native_locator/tests/unix/conda/{ => user_home}/anaconda3/envs/one/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json (100%) rename native_locator/tests/unix/conda/{ => user_home}/anaconda3/envs/one/python (100%) rename native_locator/tests/unix/conda/{ => user_home}/anaconda3/envs/two/python (100%) rename native_locator/tests/unix/{conda_without_envs => conda_custom_install_path/user_home}/.conda/environments.txt (100%) rename native_locator/tests/unix/{conda_without_envs => conda_custom_install_path/user_home/some_location}/anaconda3/bin/conda (100%) rename native_locator/tests/unix/{known => conda_custom_install_path/user_home/some_location/anaconda3/bin}/python (100%) rename native_locator/tests/unix/{conda_without_envs => conda_custom_install_path/user_home/some_location}/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json (100%) rename native_locator/tests/unix/{conda_without_envs => conda_custom_install_path/user_home/some_location}/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json (100%) rename native_locator/tests/unix/{conda_without_envs => conda_custom_install_path/user_home/some_location}/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json (100%) rename native_locator/tests/unix/{pyenv/.pyenv/versions/3.12.1/bin/python => conda_without_envs/user_home/.conda/environments.txt} (100%) rename native_locator/tests/unix/{pyenv/.pyenv/versions/3.12.1a3/bin/python => conda_without_envs/user_home/anaconda3/bin/conda} (100%) rename native_locator/tests/unix/{pyenv/.pyenv/versions/3.13-dev/bin/python => conda_without_envs/user_home/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json} (100%) create mode 100644 native_locator/tests/unix/conda_without_envs/user_home/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json rename native_locator/tests/unix/{pyenv/.pyenv/versions/3.9.9/bin/python => conda_without_envs/user_home/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json} (100%) rename native_locator/tests/unix/{pyenv/.pyenv/versions/anaconda-4.0.0/bin => known/user_home}/python (100%) rename native_locator/tests/unix/known/{ => user_home}/python.version (100%) rename native_locator/tests/unix/pyenv/{ => home}/opt/homebrew/bin/pyenv (100%) rename native_locator/tests/unix/pyenv/{.pyenv/versions/anaconda3-2021.04 => user_home/.pyenv/versions/3.12.1}/bin/python (100%) rename native_locator/tests/unix/pyenv/{.pyenv/versions/mambaforge-4.10.1-4 => user_home/.pyenv/versions/3.12.1a3}/bin/python (100%) rename native_locator/tests/unix/pyenv/{.pyenv/versions/mambaforge => user_home/.pyenv/versions/3.13-dev}/bin/python (100%) rename native_locator/tests/unix/pyenv/{.pyenv/versions/miniconda-latest => user_home/.pyenv/versions/3.9.9}/bin/python (100%) rename native_locator/tests/unix/pyenv/{.pyenv/versions/miniconda3-3.10-22.11.1-1 => user_home/.pyenv/versions/anaconda-4.0.0}/bin/python (100%) rename native_locator/tests/unix/pyenv/{.pyenv/versions/miniconda3-3.10.1 => user_home/.pyenv/versions/anaconda3-2021.04}/bin/python (100%) rename native_locator/tests/unix/pyenv/{.pyenv/versions/miniconda3-4.0.5 => user_home/.pyenv/versions/mambaforge-4.10.1-4}/bin/python (100%) rename native_locator/tests/unix/pyenv/{.pyenv/versions/miniforge3-4.11.0-1 => user_home/.pyenv/versions/mambaforge}/bin/python (100%) rename native_locator/tests/unix/pyenv/{.pyenv/versions/my-virtual-env => user_home/.pyenv/versions/miniconda-latest}/bin/python (100%) rename native_locator/tests/unix/pyenv/{.pyenv/versions/nogil-3.9.10 => user_home/.pyenv/versions/miniconda3-3.10-22.11.1-1}/bin/python (100%) rename native_locator/tests/unix/pyenv/{.pyenv/versions/pypy3.10-7.3.14 => user_home/.pyenv/versions/miniconda3-3.10.1}/bin/python (100%) rename native_locator/tests/unix/pyenv/{.pyenv/versions/pyston-2.3.5 => user_home/.pyenv/versions/miniconda3-4.0.5}/bin/python (100%) rename native_locator/tests/unix/pyenv/{.pyenv/versions/stacklets-3.7.5 => user_home/.pyenv/versions/miniforge3-4.11.0-1}/bin/python (100%) rename native_locator/tests/unix/{pyenv_without_envs/opt/homebrew/bin/pyenv => pyenv/user_home/.pyenv/versions/my-virtual-env/bin/python} (100%) rename native_locator/tests/unix/pyenv/{ => user_home}/.pyenv/versions/my-virtual-env/pyvenv.cfg (100%) create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/nogil-3.9.10/bin/python create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/pypy3.10-7.3.14/bin/python create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/pyston-2.3.5/bin/python create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/stacklets-3.7.5/bin/python create mode 100644 native_locator/tests/unix/pyenv_without_envs/home/opt/homebrew/bin/pyenv diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 6573cd6ebac7..1139ce8ffe19 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -208,40 +208,45 @@ fn get_conda_manager(path: &PathBuf) -> Option { struct CondaEnvironment { name: String, named: bool, - path: PathBuf, + env_path: PathBuf, python_executable_path: Option, version: Option, + conda_install_folder: Option, } fn get_conda_environment_info(env_path: &PathBuf, named: bool) -> Option { let metadata = env_path.metadata(); if let Ok(metadata) = metadata { if metadata.is_dir() { - let path = env_path.clone(); - if let Some(python_binary) = find_python_binary_path(&path) { - if let Some(package_info) = get_conda_package_json_path(&path, "python") { + let conda_install_folder = get_conda_installation_used_to_create_conda_env(env_path); + let env_path = env_path.clone(); + if let Some(python_binary) = find_python_binary_path(&env_path) { + if let Some(package_info) = get_conda_package_json_path(&env_path, "python") { return Some(CondaEnvironment { - name: path.file_name()?.to_string_lossy().to_string(), - path, + name: env_path.file_name()?.to_string_lossy().to_string(), + env_path, named, python_executable_path: Some(python_binary), version: Some(package_info.version), + conda_install_folder, }); } else { return Some(CondaEnvironment { - name: path.file_name()?.to_string_lossy().to_string(), - path, + name: env_path.file_name()?.to_string_lossy().to_string(), + env_path, named, python_executable_path: Some(python_binary), version: None, + conda_install_folder, }); } } else { return Some(CondaEnvironment { - name: path.file_name()?.to_string_lossy().to_string(), - path, + name: env_path.file_name()?.to_string_lossy().to_string(), + env_path, named, python_executable_path: None, version: None, + conda_install_folder, }); } } @@ -249,6 +254,7 @@ fn get_conda_environment_info(env_path: &PathBuf, named: bool) -> Option Option> { @@ -292,7 +298,9 @@ struct Condarc { * /.conda/envs * /AppData/Local/conda/conda/envs */ -fn get_conda_environment_paths_from_conda_rc(environment: &dyn known::Environment) -> Vec { +pub fn get_conda_environment_paths_from_conda_rc( + environment: &dyn known::Environment, +) -> Vec { if let Some(paths) = get_conda_conda_rc(environment) { paths.env_dirs } else { @@ -328,6 +336,111 @@ fn get_conda_environment_paths_from_known_paths( } vec![] } + +#[cfg(windows)] +fn get_conda_rc_search_paths(environment: &dyn known::Environment) -> Vec { + let mut search_paths: Vec = vec![ + "C:\\ProgramData\\conda\\.condarc", + "C:\\ProgramData\\conda\\condarc", + "C:\\ProgramData\\conda\\condarc.d", + ] + .iter() + .map(|p| PathBuf::from(p)) + .collect(); + + if let Some(conda_root) = environment.get_env_var("CONDA_ROOT".to_string()) { + search_paths.append(&mut vec![ + PathBuf::from(conda_root.clone()).join(".condarc"), + PathBuf::from(conda_root.clone()).join("condarc"), + PathBuf::from(conda_root.clone()).join(".condarc.d"), + ]); + } + if let Some(home) = environment.get_user_home() { + search_paths.append(&mut vec![ + home.join(".config").join("conda").join(".condarc"), + home.join(".config").join("conda").join("condarc"), + home.join(".config").join("conda").join("condarc.d"), + home.join(".conda").join(".condarc"), + home.join(".conda").join("condarc"), + home.join(".conda").join("condarc.d"), + home.join(".condarc"), + ]); + } + if let Some(conda_prefix) = environment.get_env_var("CONDA_PREFIX".to_string()) { + search_paths.append(&mut vec![ + PathBuf::from(conda_prefix.clone()).join(".condarc"), + PathBuf::from(conda_prefix.clone()).join("condarc"), + PathBuf::from(conda_prefix.clone()).join(".condarc.d"), + ]); + } + if let Some(condarc) = environment.get_env_var("CONDARC".to_string()) { + search_paths.append(&mut vec![PathBuf::from(condarc)]); + } + + search_paths +} +#[cfg(unix)] +fn get_conda_rc_search_paths(environment: &dyn known::Environment) -> Vec { + let mut search_paths: Vec = vec![ + "/etc/conda/.condarc", + "/etc/conda/condarc", + "/etc/conda/condarc.d/", + "/var/lib/conda/.condarc", + "/var/lib/conda/condarc", + "/var/lib/conda/condarc.d/", + ] + .iter() + .map(|p| PathBuf::from(p)) + .map(|p| { + // This only applies in tests. + // We need this, as the root folder cannot be mocked. + if let Some(root) = environment.get_root() { + root.join(p.to_string_lossy()[1..].to_string()) + } else { + p + } + }) + .collect(); + + if let Some(conda_root) = environment.get_env_var("CONDA_ROOT".to_string()) { + search_paths.append(&mut vec![ + PathBuf::from(conda_root.clone()).join(".condarc"), + PathBuf::from(conda_root.clone()).join("condarc"), + PathBuf::from(conda_root.clone()).join(".condarc.d"), + ]); + } + if let Some(xdg_config_home) = environment.get_env_var("XDG_CONFIG_HOME".to_string()) { + search_paths.append(&mut vec![ + PathBuf::from(xdg_config_home.clone()).join(".condarc"), + PathBuf::from(xdg_config_home.clone()).join("condarc"), + PathBuf::from(xdg_config_home.clone()).join(".condarc.d"), + ]); + } + if let Some(home) = environment.get_user_home() { + search_paths.append(&mut vec![ + home.join(".config").join("conda").join(".condarc"), + home.join(".config").join("conda").join("condarc"), + home.join(".config").join("conda").join("condarc.d"), + home.join(".conda").join(".condarc"), + home.join(".conda").join("condarc"), + home.join(".conda").join("condarc.d"), + home.join(".condarc"), + ]); + } + if let Some(conda_prefix) = environment.get_env_var("CONDA_PREFIX".to_string()) { + search_paths.append(&mut vec![ + PathBuf::from(conda_prefix.clone()).join(".condarc"), + PathBuf::from(conda_prefix.clone()).join("condarc"), + PathBuf::from(conda_prefix.clone()).join(".condarc.d"), + ]); + } + if let Some(condarc) = environment.get_env_var("CONDARC".to_string()) { + search_paths.append(&mut vec![PathBuf::from(condarc)]); + } + + search_paths +} + /** * The .condarc file contains a list of directories where conda environments are created. * https://conda.io/projects/conda/en/latest/configuration.html#envs-dirs @@ -336,34 +449,33 @@ fn get_conda_environment_paths_from_known_paths( * https://conda.io/projects/conda/en/latest/user-guide/configuration/use-condarc.html#searching-for-condarc */ fn get_conda_conda_rc(environment: &dyn known::Environment) -> Option { - if let Some(home) = environment.get_user_home() { - let conda_rc = Path::new(&home).join(".condarc"); - let mut start_consuming_values = false; - if let Ok(reader) = std::fs::read_to_string(conda_rc) { - let mut env_dirs = vec![]; - for line in reader.lines() { - if line.starts_with("envs_dirs:") && !start_consuming_values { - start_consuming_values = true; - continue; - } - if start_consuming_values { - if line.trim().starts_with("-") { - if let Some(env_dir) = line.splitn(2, '-').nth(1) { - let env_dir = PathBuf::from(env_dir.trim()); - if env_dir.exists() { - env_dirs.push(env_dir); - } - } - continue; - } else { - break; + let conda_rc = get_conda_rc_search_paths(environment) + .into_iter() + .find(|p| p.exists())?; + let mut start_consuming_values = false; + trace!("conda_rc: {:?}", conda_rc); + let reader = std::fs::read_to_string(conda_rc).ok()?; + let mut env_dirs = vec![]; + for line in reader.lines() { + if line.starts_with("envs_dirs:") && !start_consuming_values { + start_consuming_values = true; + continue; + } + if start_consuming_values { + if line.trim().starts_with("-") { + if let Some(env_dir) = line.splitn(2, '-').nth(1) { + let env_dir = PathBuf::from(env_dir.trim()); + if env_dir.exists() { + env_dirs.push(env_dir); } } + continue; + } else { + break; } - return Some(Condarc { env_dirs }); } } - None + return Some(Condarc { env_dirs }); } /** @@ -376,20 +488,17 @@ fn get_conda_conda_rc(environment: &dyn known::Environment) -> Option { * Thus all we need to do is to look for the 'cmd' line in the file and extract the path to the conda executable and match that against the path provided. */ fn was_conda_environment_created_by_specific_conda( - env_path: &PathBuf, + env: &CondaEnvironment, root_conda_path: &PathBuf, ) -> bool { - let conda_meta_history = env_path.join("conda-meta").join("history"); - if let Ok(reader) = std::fs::read_to_string(conda_meta_history.clone()) { - for line in reader.lines() { - let line = line.to_lowercase(); - if line.starts_with("# cmd:") && line.contains(" create ") { - if line.contains(&root_conda_path.to_str().unwrap().to_lowercase()) { - return true; - } else { - return false; - } - } + if let Some(cmd_line) = env.conda_install_folder.clone() { + if cmd_line + .to_lowercase() + .contains(&root_conda_path.to_string_lossy().to_lowercase()) + { + return true; + } else { + return false; } } @@ -397,39 +506,41 @@ fn was_conda_environment_created_by_specific_conda( } /** - * When we create conda environments in specific folder using the -p argument, the location of the conda executable is not know. - * If the user has multiple conda installations, any one of those could have created that specific environment. - * Fortunately the conda-meta/history file contains the path to the conda executable (script) that was used to create the environment. - * The format of the file is as follows: - * # cmd: C:\Users\user\miniconda3\Scripts\conda-script.py create --name myenv + * The conda-meta/history file in conda environments contain the command used to create the conda environment. + * And example is `# cmd: \Scripts\conda-script.py create -n sample`` + * And example is `# cmd: conda create -n sample`` * - * Thus all we need to do is to look for the 'cmd' line in the file and extract the path to the conda executable and match that against the path provided. + * Sometimes the cmd line contains the fully qualified path to the conda install folder. + * This function returns the path to the conda installation that was used to create the environment. */ -fn get_environments_from_environments_txt_belonging_to_conda_directory( - path: &PathBuf, - environment: &dyn known::Environment, -) -> Option> { - let mut envs: Vec = vec![]; - for env in get_conda_envs_from_environment_txt(environment) { - // Only include those environments that were created by the specific conda installation - // Ignore environments that are in the env sub directory of the conda folder, as those would have been - // tracked elsewhere, we're only interested in conda envs located in other parts of the file system created using the -p flag. - if env.contains(path.to_str().unwrap()) { - continue; - } - - let env_path = PathBuf::from(env); - if !env_path.is_dir() { - continue; - } - if was_conda_environment_created_by_specific_conda(&env_path, path) { - if let Some(env) = get_conda_environment_info(&env_path, false) { - envs.push(env); +fn get_conda_installation_used_to_create_conda_env(env_path: &PathBuf) -> Option { + let conda_meta_history = env_path.join("conda-meta").join("history"); + if let Ok(reader) = std::fs::read_to_string(conda_meta_history.clone()) { + if let Some(line) = reader.lines().map(|l| l.trim()).find(|l| { + l.to_lowercase().starts_with("# cmd:") && l.to_lowercase().contains(" create -") + }) { + // Sample lines + // # cmd: \Scripts\conda-script.py create -n samlpe1 + // # cmd: \Scripts\conda-script.py create -p + // # cmd: /Users/donjayamanne/miniconda3/bin/conda create -n conda1 + let start_index = line.to_lowercase().find("# cmd:")? + "# cmd:".len(); + let end_index = line.to_lowercase().find(" create -")?; + let cmd_line = PathBuf::from(line[start_index..end_index].trim().to_string()); + if let Some(cmd_line) = cmd_line.parent() { + if let Some(name) = cmd_line.file_name() { + if name.to_ascii_lowercase() == "bin" || name.to_ascii_lowercase() == "scripts" + { + if let Some(cmd_line) = cmd_line.parent() { + return Some(cmd_line.to_str()?.to_string()); + } + } + return Some(cmd_line.to_str()?.to_string()); + } } } } - Some(envs) + None } #[cfg(windows)] @@ -504,7 +615,7 @@ fn get_activation_command(env: &CondaEnvironment, manager: &EnvManager) -> Optio conda_exe, "run".to_string(), "-p".to_string(), - env.path.to_str().unwrap().to_string(), + env.env_path.to_str().unwrap().to_string(), "python".to_string(), ]) } @@ -538,56 +649,63 @@ fn get_root_python_environment(path: &PathBuf, manager: &EnvManager) -> Option

, +fn get_conda_environments_in_specified_install_path( + conda_install_folder: &PathBuf, + possible_conda_envs: &mut Vec, ) -> Option { let mut managers: Vec = vec![]; let mut environments: Vec = vec![]; let mut detected_envs: HashSet = HashSet::new(); let mut detected_managers: HashSet = HashSet::new(); - if possible_conda_folder.is_dir() && possible_conda_folder.exists() { - if let Some(manager) = get_conda_manager(&possible_conda_folder) { + if conda_install_folder.is_dir() && conda_install_folder.exists() { + if let Some(manager) = get_conda_manager(&conda_install_folder) { + // 1. Base environment. + if let Some(env) = get_root_python_environment(&conda_install_folder, &manager) { + if let Some(key) = get_environment_key(&env) { + if !detected_envs.contains(&key) { + detected_envs.insert(key); + environments.push(env); + } + } + } + + // 2. All environments in the `/envs` folder let mut envs: Vec = vec![]; if let Some(environments) = - get_environments_from_envs_folder_in_conda_directory(possible_conda_folder) + get_environments_from_envs_folder_in_conda_directory(conda_install_folder) { environments.iter().for_each(|env| envs.push(env.clone())); } - for (index, path) in get_known_conda_envs_from_various_locations(environment) - .iter() - .enumerate() - { - let env_path = path.to_string_lossy().to_string(); - // Only include those environments that were created by the specific conda installation - // Ignore environments that are in the env sub directory of the conda folder, as those would have been - // tracked elsewhere, we're only interested in conda envs located in other parts of the file system created using the -p flag. - if env_path.contains(possible_conda_folder.to_str().unwrap()) { + // 3. All environments in the environments.txt and other locations (such as `conda config --show envs_dirs`) + // Only include those environments that were created by the specific conda installation + // Ignore environments that are in the env sub directory of the conda folder, as those would have been + // tracked elsewhere, we're only interested in conda envs located in other parts of the file system created using the -p flag. + // E.g conda_install_folder is `/` + // Then all folders such as `//envs/env1` can be ignored + // As these would have been discovered in previous step. + let mut indices_to_remove: Vec = vec![]; + for (index, env) in possible_conda_envs.iter().enumerate() { + if env + .env_path + .to_string_lossy() + .contains(conda_install_folder.to_str().unwrap()) + { continue; } - if !path.is_dir() { - continue; - } - if !was_conda_environment_created_by_specific_conda(&path, possible_conda_folder) { - continue; - } - if let Some(env) = get_conda_environment_info(&path, false) { + if was_conda_environment_created_by_specific_conda(&env, conda_install_folder) { envs.push(env.clone()); - possible_conda_envs.remove(index); + indices_to_remove.push(index); } } - if let Some(env) = get_root_python_environment(&possible_conda_folder, &manager) { - if let Some(key) = get_environment_key(&env) { - if !detected_envs.contains(&key) { - detected_envs.insert(key); - environments.push(env); - } - } - } + indices_to_remove.reverse(); + indices_to_remove.iter().for_each(|i| { + possible_conda_envs.remove(*i); + return (); + }); + // Finally construct the PythonEnvironment objects envs.iter().for_each(|env| { let exe = env.python_executable_path.clone(); let env = PythonEnvironment::new( @@ -596,7 +714,7 @@ pub fn get_conda_environments_in_specified_path( exe.clone(), messaging::PythonEnvironmentCategory::Conda, env.version.clone(), - Some(env.path.clone()), + Some(env.env_path.clone()), Some(manager.clone()), get_activation_command(env, &manager), ); @@ -628,38 +746,59 @@ pub fn get_conda_environments_in_specified_path( fn find_conda_environments_from_known_conda_install_locations( environment: &dyn known::Environment, - possible_conda_envs: &mut Vec, + possible_conda_envs: &mut Vec, ) -> Option { let mut managers: Vec = vec![]; let mut environments: Vec = vec![]; - let mut detected_envs: HashSet = HashSet::new(); - let mut detected_managers: HashSet = HashSet::new(); - for possible_conda_folder in get_known_conda_install_locations(environment) { - if let Some(result) = get_conda_environments_in_specified_path( - &possible_conda_folder, - environment, + // We know conda is installed in `/Anaconda3`, `/miniforge3`, etc + // Look for these and discover all environments in these locations + for possible_conda_install_folder in get_known_conda_install_locations(environment) { + if let Some(mut result) = get_conda_environments_in_specified_install_path( + &possible_conda_install_folder, possible_conda_envs, ) { - result.managers.iter().for_each(|m| { - let key = get_environment_manager_key(m); - if !detected_managers.contains(&key) { - detected_managers.insert(key); - managers.push(m.clone()); - } - }); + managers.append(&mut result.managers); + environments.append(&mut result.environments); + } + } - result.environments.iter().for_each(|e| { - if let Some(key) = get_environment_key(e) { - if !detected_envs.contains(&key) { - detected_envs.insert(key); - environments.push(e.clone()); - } - } - }); + // We know conda environments are listed in the `environments.txt` file + // Sometimes the base environment is also listed in these paths + // Go through them an look for possible conda install folders in these paths. + // & then look for conda environments in each of them. + // This accounts for cases where Conda install location is in some un-common (custom) location + let mut env_paths_to_remove: Vec = vec![]; + for env in possible_conda_envs + .clone() + .iter() + .filter(|env| is_conda_install_location(&env.env_path)) + { + if let Some(mut result) = + get_conda_environments_in_specified_install_path(&env.env_path, possible_conda_envs) + { + managers.append(&mut result.managers); + environments.append(&mut result.environments); + env_paths_to_remove.push(env.env_path.clone()); } } + let mut indices_to_remove: Vec = vec![]; + env_paths_to_remove.iter().for_each(|p| { + if let Some(index) = possible_conda_envs + .iter() + .position(|env| env.env_path == *p) + { + indices_to_remove.push(index); + } + }); + indices_to_remove.sort(); + indices_to_remove.reverse(); + indices_to_remove.iter().for_each(|i| { + possible_conda_envs.remove(*i); + return (); + }); + if managers.is_empty() && environments.is_empty() { return None; } @@ -670,6 +809,11 @@ fn find_conda_environments_from_known_conda_install_locations( }) } +fn is_conda_install_location(path: &PathBuf) -> bool { + let envs_path = path.join("envs"); + return envs_path.exists() && envs_path.is_dir(); +} + pub fn get_conda_version(conda_binary: &PathBuf) -> Option { let mut parent = conda_binary.parent()?; if parent.ends_with("bin") { @@ -689,7 +833,7 @@ pub fn get_conda_version(conda_binary: &PathBuf) -> Option { fn get_known_conda_envs_from_various_locations( environment: &dyn known::Environment, -) -> Vec { +) -> Vec { let mut env_paths = get_conda_envs_from_environment_txt(environment) .iter() .map(|e| PathBuf::from(e)) @@ -701,33 +845,29 @@ fn get_known_conda_envs_from_various_locations( let mut envs_from_known_paths = get_conda_environment_paths_from_known_paths(environment); env_paths.append(&mut envs_from_known_paths); - // let mut envs: Vec = vec![]; - // env_paths.iter().for_each(|path| { - // if let Some(env) = get_conda_environment_info(&path, false) { - // envs.push(env); - // } - // }); + let mut envs: Vec = vec![]; + env_paths.iter().for_each(|path| { + if !path.exists() { + return; + } + if let Some(env) = get_conda_environment_info(&path, false) { + envs.push(env); + } + }); - env_paths + envs } fn get_conda_environments_from_known_locations_that_have_not_been_discovered( - known_environment_keys: &HashSet, + discovered_environment_paths: &HashSet, known_environment: &Vec, environment: &dyn known::Environment, - possible_conda_envs: &mut Vec, + possible_conda_envs: &mut Vec, ) -> Option { let undiscovered_environments = possible_conda_envs .iter() - .filter(|path| { - for known in known_environment_keys.iter() { - if known.contains(&path.to_string_lossy().to_string()) { - return false; - } - } - true - }) - .collect::>(); + .filter(|env| !discovered_environment_paths.contains(&env.env_path)) + .collect::>(); if undiscovered_environments.is_empty() { return None; @@ -759,20 +899,18 @@ fn get_conda_environments_from_known_locations_that_have_not_been_discovered( if let Some(manager) = manager { let mut environments: Vec = vec![]; for env in undiscovered_environments { - if let Some(env) = get_conda_environment_info(&PathBuf::from(env), false) { - let exe = env.python_executable_path.clone(); - let env = PythonEnvironment::new( - None, - Some(env.name.clone()), - exe.clone(), - messaging::PythonEnvironmentCategory::Conda, - env.version.clone(), - Some(env.path.clone()), - Some(manager.clone()), - get_activation_command(&env, &manager), - ); - environments.push(env); - } + let exe = env.python_executable_path.clone(); + let env = PythonEnvironment::new( + None, + Some(env.name.clone()), + exe.clone(), + messaging::PythonEnvironmentCategory::Conda, + env.version.clone(), + Some(env.env_path.clone()), + Some(manager.clone()), + get_activation_command(&env, &manager), + ); + environments.push(env); } if environments.len() > 0 { return Some(LocatorResult { @@ -790,7 +928,7 @@ fn get_conda_environments_from_known_locations_that_have_not_been_discovered( pub struct Conda<'a> { pub manager: Option, pub environment: &'a dyn Environment, - pub discovered_environments: HashSet, + pub discovered_environment_paths: HashSet, pub discovered_managers: HashSet, } @@ -803,7 +941,7 @@ impl Conda<'_> { Conda { environment, manager: None, - discovered_environments: HashSet::new(), + discovered_environment_paths: HashSet::new(), discovered_managers: HashSet::new(), } } @@ -813,11 +951,11 @@ impl Conda<'_> { .environments .iter() .filter(|e| { - if let Some(key) = get_environment_key(e) { - if self.discovered_environments.contains(&key) { + if let Some(env_path) = e.env_path.clone() { + if self.discovered_environment_paths.contains(&env_path) { return false; } - self.discovered_environments.insert(key); + self.discovered_environment_paths.insert(env_path); return true; } false @@ -853,9 +991,8 @@ impl Conda<'_> { impl CondaLocator for Conda<'_> { fn find_in(&mut self, possible_conda_folder: &PathBuf) -> Option { let mut possible_conda_envs = get_known_conda_envs_from_various_locations(self.environment); - self.filter_result(get_conda_environments_in_specified_path( + self.filter_result(get_conda_environments_in_specified_install_path( possible_conda_folder, - self.environment, &mut possible_conda_envs, )) } @@ -880,8 +1017,7 @@ impl Locator for Conda<'_> { )) { result.managers.iter().for_each(|m| { - let key = get_environment_manager_key(m); - detected_managers.insert(key); + detected_managers.insert(get_environment_manager_key(m)); managers.push(m.clone()); }); @@ -893,7 +1029,7 @@ impl Locator for Conda<'_> { if let Some(result) = self.filter_result( get_conda_environments_from_known_locations_that_have_not_been_discovered( - &self.discovered_environments, + &self.discovered_environment_paths, &environments, self.environment, &mut possible_conda_envs, diff --git a/native_locator/src/known.rs b/native_locator/src/known.rs index 52392343eee9..6e37d897157e 100644 --- a/native_locator/src/known.rs +++ b/native_locator/src/known.rs @@ -4,6 +4,10 @@ use std::{env, path::PathBuf}; pub trait Environment { fn get_user_home(&self) -> Option; + /** + * Only used in tests, this is the root `/`. + */ + fn get_root(&self) -> Option; fn get_env_var(&self, key: String) -> Option; fn get_know_global_search_locations(&self) -> Vec; } @@ -15,6 +19,9 @@ impl Environment for EnvironmentApi { fn get_user_home(&self) -> Option { get_user_home() } + fn get_root(&self) -> Option { + None + } fn get_env_var(&self, key: String) -> Option { get_env_var(key) } @@ -28,6 +35,9 @@ impl Environment for EnvironmentApi { fn get_user_home(&self) -> Option { get_user_home() } + fn get_root(&self) -> Option { + None + } fn get_env_var(&self, key: String) -> Option { get_env_var(key) } diff --git a/native_locator/src/locator.rs b/native_locator/src/locator.rs index 18d529a80564..a318c102230a 100644 --- a/native_locator/src/locator.rs +++ b/native_locator/src/locator.rs @@ -6,7 +6,7 @@ use crate::{ utils::PythonEnv, }; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct LocatorResult { pub managers: Vec, pub environments: Vec, diff --git a/native_locator/tests/common.rs b/native_locator/tests/common.rs index 407e93707b7e..ca7599b7b703 100644 --- a/native_locator/tests/common.rs +++ b/native_locator/tests/common.rs @@ -32,6 +32,7 @@ pub trait TestMessages { pub struct TestEnvironment { vars: HashMap, home: Option, + root: Option, globals_locations: Vec, } #[allow(dead_code)] @@ -39,11 +40,15 @@ pub fn create_test_environment( vars: HashMap, home: Option, globals_locations: Vec, + root: Option, ) -> TestEnvironment { impl Environment for TestEnvironment { fn get_env_var(&self, key: String) -> Option { self.vars.get(&key).cloned() } + fn get_root(&self) -> Option { + self.root.clone() + } fn get_user_home(&self) -> Option { self.home.clone() } @@ -54,6 +59,7 @@ pub fn create_test_environment( TestEnvironment { vars, home, + root, globals_locations, } } diff --git a/native_locator/tests/common_python_test.rs b/native_locator/tests/common_python_test.rs index 7db280c9496b..13d853d5cebd 100644 --- a/native_locator/tests/common_python_test.rs +++ b/native_locator/tests/common_python_test.rs @@ -14,16 +14,17 @@ fn find_python_in_path_this() { use serde_json::json; use std::collections::HashMap; - let unix_python = test_file_path(&["tests/unix/known"]); - let unix_python_exe = join_test_paths(&[unix_python.clone().to_str().unwrap(), "python"]); + let user_home = test_file_path(&["tests/unix/known/user_home"]); + let unix_python_exe = join_test_paths(&[user_home.clone().to_str().unwrap(), "python"]); let known = create_test_environment( HashMap::from([( "PATH".to_string(), - unix_python.clone().to_str().unwrap().to_string(), + user_home.clone().to_string_lossy().to_string(), )]), - Some(unix_python.clone()), + Some(user_home.clone()), Vec::new(), + None, ); let mut locator = common_python::PythonOnPath::with(&known); @@ -41,7 +42,7 @@ fn find_python_in_path_this() { category: python_finder::messaging::PythonEnvironmentCategory::System, version: None, python_run_command: Some(vec![unix_python_exe.clone().to_str().unwrap().to_string()]), - env_path: Some(unix_python.clone()), + env_path: Some(user_home.clone()), }; assert_messages( &[json!(env)], diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs index 1705dc72bf47..998d8fc9bbb6 100644 --- a/native_locator/tests/conda_test.rs +++ b/native_locator/tests/conda_test.rs @@ -14,6 +14,7 @@ fn does_not_find_any_conda_envs() { HashMap::from([("PATH".to_string(), "".to_string())]), Some(PathBuf::from("SOME_BOGUS_HOME_DIR")), Vec::new(), + None, ); let mut locator = conda::Conda::with(&known); @@ -23,6 +24,82 @@ fn does_not_find_any_conda_envs() { assert_eq!(environments.len(), 0); } +#[test] +#[cfg(unix)] +fn no_paths_from_conda_rc_if_conda_rc_does_not_exist() { + use crate::common::{create_test_environment, test_file_path}; + use python_finder::conda::get_conda_environment_paths_from_conda_rc; + use std::collections::HashMap; + + let user_home = test_file_path(&["tests/unix/no_conda_rc/user_home"]); + let root = test_file_path(&["tests/unix/no_conda_rc/root"]); + + let known = create_test_environment( + HashMap::from([("PATH".to_string(), "".to_string())]), + Some(user_home), + Vec::new(), + Some(root), + ); + + let result = get_conda_environment_paths_from_conda_rc(&known); + + assert_eq!(result.len(), 0); +} + +#[test] +#[cfg(unix)] +fn paths_from_conda_rc() { + use crate::common::{create_test_environment, test_file_path}; + use python_finder::conda::get_conda_environment_paths_from_conda_rc; + use std::{collections::HashMap, fs, path::PathBuf}; + + fn create_conda_rc(file: &PathBuf, paths: &Vec) { + use std::fs::File; + use std::io::Write; + let mut file = File::create(file).unwrap(); + + writeln!(file, "envs_dirs:").unwrap(); + for path in paths { + writeln!(file, " - {}", path.to_string_lossy()).unwrap(); + } + } + + fn test_with(conda_rc_file: &PathBuf) { + let home = test_file_path(&["tests/unix/conda_rc/user_home"]); + let root = test_file_path(&["tests/unix/conda_rc/root"]); + let envs = home.join(".conda").join("envs"); + + let known = create_test_environment( + HashMap::from([("PATH".to_string(), "".to_string())]), + Some(home.clone()), + Vec::new(), + Some(root.clone()), + ); + fs::remove_dir_all(home.clone()).unwrap_or_default(); + fs::remove_dir_all(root.clone()).unwrap_or_default(); + + fs::create_dir_all(home.clone()).unwrap_or_default(); + fs::create_dir_all(root.clone()).unwrap_or_default(); + fs::create_dir_all(envs.clone()).unwrap_or_default(); + fs::create_dir_all(conda_rc_file.parent().unwrap()).unwrap_or_default(); + + create_conda_rc(conda_rc_file, &vec![envs.clone()]); + + let result = get_conda_environment_paths_from_conda_rc(&known); + assert_eq!(result.len(), 1); + assert_eq!(result[0], envs); + + fs::remove_dir_all(home.clone()).unwrap_or_default(); + fs::remove_dir_all(root.clone()).unwrap_or_default(); + } + + let home = test_file_path(&["tests/unix/conda_rc/user_home"]); + let root = test_file_path(&["tests/unix/conda_rc/root"]); + + test_with(&root.join("etc/conda/.condarc")); + test_with(&home.join(".condarc")); +} + #[test] #[cfg(unix)] fn find_conda_exe_and_empty_envs() { @@ -34,8 +111,8 @@ fn find_conda_exe_and_empty_envs() { use python_finder::{conda, locator::Locator}; use serde_json::json; use std::collections::HashMap; - let user_home = test_file_path(&["tests/unix/conda_without_envs"]); - let conda_dir = test_file_path(&["tests/unix/conda_without_envs"]); + let user_home = test_file_path(&["tests/unix/conda_without_envs/user_home"]); + let conda_dir = test_file_path(&["tests/unix/conda_without_envs/user_home"]); let known = create_test_environment( HashMap::from([( @@ -44,6 +121,7 @@ fn find_conda_exe_and_empty_envs() { )]), Some(user_home), Vec::new(), + None, ); let mut locator = conda::Conda::with(&known); @@ -67,6 +145,77 @@ fn find_conda_exe_and_empty_envs() { &managers.iter().map(|e| json!(e)).collect::>(), ) } + +#[test] +#[cfg(unix)] +fn find_conda_from_custom_install_location() { + use crate::common::{create_test_environment, test_file_path}; + use python_finder::messaging::{EnvManager, EnvManagerType, PythonEnvironment}; + use python_finder::{conda, locator::Locator}; + use serde_json::json; + use std::collections::HashMap; + use std::fs; + + let home = test_file_path(&["tests/unix/conda_custom_install_path/user_home"]); + let conda_dir = + test_file_path(&["tests/unix/conda_custom_install_path/user_home/some_location/anaconda3"]); + let environments_txt = + test_file_path(&["tests/unix/conda_custom_install_path/user_home/.conda/environments.txt"]); + + fs::create_dir_all(environments_txt.parent().unwrap()).unwrap_or_default(); + fs::write( + environments_txt.clone(), + format!("{}", conda_dir.clone().to_str().unwrap().to_string()), + ) + .unwrap(); + + let known = create_test_environment( + HashMap::from([( + "PATH".to_string(), + conda_dir.clone().to_str().unwrap().to_string(), + )]), + Some(home), + Vec::new(), + None, + ); + + let mut locator = conda::Conda::with(&known); + let result = locator.find().unwrap(); + + assert_eq!(result.managers.len(), 1); + assert_eq!(result.environments.len(), 1); + + let conda_exe = conda_dir.clone().join("bin").join("conda"); + let expected_conda_manager = EnvManager { + executable_path: conda_exe.clone(), + version: Some("4.0.2".to_string()), + tool: EnvManagerType::Conda, + }; + assert_eq!(json!(expected_conda_manager), json!(result.managers[0])); + + let expected_conda_env = PythonEnvironment { + display_name: None, + name: None, + project_path: None, + python_executable_path: Some(conda_dir.clone().join("bin").join("python")), + category: python_finder::messaging::PythonEnvironmentCategory::Conda, + version: Some("10.0.1".to_string()), + env_path: Some(conda_dir.clone()), + env_manager: Some(expected_conda_manager.clone()), + python_run_command: Some(vec![ + conda_exe.clone().to_str().unwrap().to_string(), + "run".to_string(), + "-p".to_string(), + conda_dir.to_string_lossy().to_string(), + "python".to_string(), + ]), + }; + assert_eq!(json!(expected_conda_env), json!(result.environments[0])); + + // Reset environments.txt + fs::write(environments_txt.clone(), "").unwrap(); +} + #[test] #[cfg(unix)] fn finds_two_conda_envs_from_txt() { @@ -80,18 +229,22 @@ fn finds_two_conda_envs_from_txt() { use std::collections::HashMap; use std::fs; - let home = test_file_path(&["tests/unix/conda"]); - let conda_dir = test_file_path(&["tests/unix/conda/anaconda3"]); + let home = test_file_path(&["tests/unix/conda/user_home"]); + let conda_dir = test_file_path(&["tests/unix/conda/user_home/anaconda3"]); let conda_1 = join_test_paths(&[conda_dir.clone().to_str().unwrap(), "envs/one"]); let conda_2 = join_test_paths(&[conda_dir.clone().to_str().unwrap(), "envs/two"]); - let _ = fs::write( - "tests/unix/conda/.conda/environments.txt", + let environments_txt = test_file_path(&["tests/unix/conda/.conda/environments.txt"]); + + fs::create_dir_all(environments_txt.parent().unwrap()).unwrap_or_default(); + fs::write( + environments_txt.clone(), format!( "{}\n{}", conda_1.clone().to_str().unwrap().to_string(), conda_2.clone().to_str().unwrap().to_string() ), - ); + ) + .unwrap_or_default(); let known = create_test_environment( HashMap::from([( @@ -100,6 +253,7 @@ fn finds_two_conda_envs_from_txt() { )]), Some(home), Vec::new(), + None, ); let mut locator = conda::Conda::with(&known); @@ -133,9 +287,9 @@ fn finds_two_conda_envs_from_txt() { "-n".to_string(), "one".to_string(), "python".to_string(), - ]), - }; - let expected_conda_2 = PythonEnvironment { + ]), + }; + let expected_conda_2 = PythonEnvironment { display_name: None, name: Some("two".to_string()), project_path: None, @@ -160,4 +314,6 @@ fn finds_two_conda_envs_from_txt() { &[json!(expected_conda_1), json!(expected_conda_2)], &environments.iter().map(|e| json!(e)).collect::>(), ); + + fs::write(environments_txt, "").unwrap_or_default(); } diff --git a/native_locator/tests/pyenv_test.rs b/native_locator/tests/pyenv_test.rs index 42249586864d..dbc222ec494c 100644 --- a/native_locator/tests/pyenv_test.rs +++ b/native_locator/tests/pyenv_test.rs @@ -16,6 +16,7 @@ fn does_not_find_any_pyenv_envs() { HashMap::new(), Some(PathBuf::from("SOME_BOGUS_HOME_DIR")), Vec::new(), + None, ); let mut conda = Conda::with(&known); @@ -38,13 +39,22 @@ fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() { use serde_json::json; use std::{collections::HashMap, path::PathBuf}; - let home = test_file_path(&["tests", "unix", "pyenv_without_envs"]); - let homebrew_bin = join_test_paths(&[home.to_str().unwrap(), "opt", "homebrew", "bin"]); + let home = test_file_path(&["tests", "unix", "pyenv_without_envs", "user_home"]); + let homebrew_bin = test_file_path(&[ + "tests", + "unix", + "pyenv_without_envs", + "home", + "opt", + "homebrew", + "bin", + ]); let pyenv_exe = join_test_paths(&[homebrew_bin.to_str().unwrap(), "pyenv"]); let known = create_test_environment( HashMap::new(), Some(home.clone()), vec![PathBuf::from(homebrew_bin)], + None, ); let mut conda = Conda::with(&known); @@ -77,13 +87,15 @@ fn find_pyenv_envs() { use serde_json::json; use std::{collections::HashMap, path::PathBuf}; - let home = test_file_path(&["tests", "unix", "pyenv"]); - let homebrew_bin = join_test_paths(&[home.to_str().unwrap(), "opt", "homebrew", "bin"]); + let home = test_file_path(&["tests", "unix", "pyenv", "user_home"]); + let homebrew_bin = + test_file_path(&["tests", "unix", "pyenv", "home", "opt", "homebrew", "bin"]); let pyenv_exe = join_test_paths(&[homebrew_bin.to_str().unwrap(), "pyenv"]); let known = create_test_environment( HashMap::new(), Some(home.clone()), vec![PathBuf::from(homebrew_bin)], + None, ); let mut conda = Conda::with(&known); diff --git a/native_locator/tests/unix/conda/.conda/environments.txt b/native_locator/tests/unix/conda/.conda/environments.txt index 3a9e625c3050..e69de29bb2d1 100644 --- a/native_locator/tests/unix/conda/.conda/environments.txt +++ b/native_locator/tests/unix/conda/.conda/environments.txt @@ -1,2 +0,0 @@ -/Users/donjayamanne/Development/vsc/vscode-python/native_locator/tests/unix/conda/anaconda3/envs/one -/Users/donjayamanne/Development/vsc/vscode-python/native_locator/tests/unix/conda/anaconda3/envs/two \ No newline at end of file diff --git a/native_locator/tests/unix/conda/user_home/.conda/environments.txt b/native_locator/tests/unix/conda/user_home/.conda/environments.txt new file mode 100644 index 000000000000..c0564f346362 --- /dev/null +++ b/native_locator/tests/unix/conda/user_home/.conda/environments.txt @@ -0,0 +1 @@ +/Users/donjayamanne/Development/vsc/vscode-python/native_locator/tests/unix/conda/user_home/anaconda3 \ No newline at end of file diff --git a/native_locator/tests/unix/conda/anaconda3/bin/conda b/native_locator/tests/unix/conda/user_home/anaconda3/bin/conda similarity index 100% rename from native_locator/tests/unix/conda/anaconda3/bin/conda rename to native_locator/tests/unix/conda/user_home/anaconda3/bin/conda diff --git a/native_locator/tests/unix/conda/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json b/native_locator/tests/unix/conda/user_home/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json similarity index 100% rename from native_locator/tests/unix/conda/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json rename to native_locator/tests/unix/conda/user_home/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json diff --git a/native_locator/tests/unix/conda/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json b/native_locator/tests/unix/conda/user_home/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json similarity index 100% rename from native_locator/tests/unix/conda/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json rename to native_locator/tests/unix/conda/user_home/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json diff --git a/native_locator/tests/unix/conda/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json b/native_locator/tests/unix/conda/user_home/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json similarity index 100% rename from native_locator/tests/unix/conda/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json rename to native_locator/tests/unix/conda/user_home/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json diff --git a/native_locator/tests/unix/conda/anaconda3/envs/one/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json b/native_locator/tests/unix/conda/user_home/anaconda3/envs/one/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json similarity index 100% rename from native_locator/tests/unix/conda/anaconda3/envs/one/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json rename to native_locator/tests/unix/conda/user_home/anaconda3/envs/one/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json diff --git a/native_locator/tests/unix/conda/anaconda3/envs/one/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json b/native_locator/tests/unix/conda/user_home/anaconda3/envs/one/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json similarity index 100% rename from native_locator/tests/unix/conda/anaconda3/envs/one/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json rename to native_locator/tests/unix/conda/user_home/anaconda3/envs/one/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json diff --git a/native_locator/tests/unix/conda/anaconda3/envs/one/python b/native_locator/tests/unix/conda/user_home/anaconda3/envs/one/python similarity index 100% rename from native_locator/tests/unix/conda/anaconda3/envs/one/python rename to native_locator/tests/unix/conda/user_home/anaconda3/envs/one/python diff --git a/native_locator/tests/unix/conda/anaconda3/envs/two/python b/native_locator/tests/unix/conda/user_home/anaconda3/envs/two/python similarity index 100% rename from native_locator/tests/unix/conda/anaconda3/envs/two/python rename to native_locator/tests/unix/conda/user_home/anaconda3/envs/two/python diff --git a/native_locator/tests/unix/conda_without_envs/.conda/environments.txt b/native_locator/tests/unix/conda_custom_install_path/user_home/.conda/environments.txt similarity index 100% rename from native_locator/tests/unix/conda_without_envs/.conda/environments.txt rename to native_locator/tests/unix/conda_custom_install_path/user_home/.conda/environments.txt diff --git a/native_locator/tests/unix/conda_without_envs/anaconda3/bin/conda b/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/bin/conda similarity index 100% rename from native_locator/tests/unix/conda_without_envs/anaconda3/bin/conda rename to native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/bin/conda diff --git a/native_locator/tests/unix/known/python b/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/bin/python similarity index 100% rename from native_locator/tests/unix/known/python rename to native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/bin/python diff --git a/native_locator/tests/unix/conda_without_envs/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json b/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json similarity index 100% rename from native_locator/tests/unix/conda_without_envs/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json rename to native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json diff --git a/native_locator/tests/unix/conda_without_envs/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json b/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json similarity index 100% rename from native_locator/tests/unix/conda_without_envs/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json rename to native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json diff --git a/native_locator/tests/unix/conda_without_envs/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json b/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json similarity index 100% rename from native_locator/tests/unix/conda_without_envs/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json rename to native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/3.12.1/bin/python b/native_locator/tests/unix/conda_without_envs/user_home/.conda/environments.txt similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/3.12.1/bin/python rename to native_locator/tests/unix/conda_without_envs/user_home/.conda/environments.txt diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/3.12.1a3/bin/python b/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/bin/conda similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/3.12.1a3/bin/python rename to native_locator/tests/unix/conda_without_envs/user_home/anaconda3/bin/conda diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/3.13-dev/bin/python b/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/3.13-dev/bin/python rename to native_locator/tests/unix/conda_without_envs/user_home/anaconda3/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json diff --git a/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json b/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json new file mode 100644 index 000000000000..23127993ac05 --- /dev/null +++ b/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/conda-meta/python-10.0.1-hdf0ec26_0_cpython.json @@ -0,0 +1 @@ +10.1.1 diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/3.9.9/bin/python b/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/3.9.9/bin/python rename to native_locator/tests/unix/conda_without_envs/user_home/anaconda3/conda-meta/python-slugify-5.0.2-pyhd3eb1b0_0.json diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/bin/python b/native_locator/tests/unix/known/user_home/python similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/bin/python rename to native_locator/tests/unix/known/user_home/python diff --git a/native_locator/tests/unix/known/python.version b/native_locator/tests/unix/known/user_home/python.version similarity index 100% rename from native_locator/tests/unix/known/python.version rename to native_locator/tests/unix/known/user_home/python.version diff --git a/native_locator/tests/unix/pyenv/opt/homebrew/bin/pyenv b/native_locator/tests/unix/pyenv/home/opt/homebrew/bin/pyenv similarity index 100% rename from native_locator/tests/unix/pyenv/opt/homebrew/bin/pyenv rename to native_locator/tests/unix/pyenv/home/opt/homebrew/bin/pyenv diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.12.1/bin/python similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/anaconda3-2021.04/bin/python rename to native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.12.1/bin/python diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.12.1a3/bin/python similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge-4.10.1-4/bin/python rename to native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.12.1a3/bin/python diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.13-dev/bin/python similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/bin/python rename to native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.13-dev/bin/python diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda-latest/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.9.9/bin/python similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/miniconda-latest/bin/python rename to native_locator/tests/unix/pyenv/user_home/.pyenv/versions/3.9.9/bin/python diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda-4.0.0/bin/python similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/python rename to native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda-4.0.0/bin/python diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10.1/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda3-2021.04/bin/python similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-3.10.1/bin/python rename to native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda3-2021.04/bin/python diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-4.0.5/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge-4.10.1-4/bin/python similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/miniconda3-4.0.5/bin/python rename to native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge-4.10.1-4/bin/python diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge/bin/python similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/miniforge3-4.11.0-1/bin/python rename to native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge/bin/python diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/my-virtual-env/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda-latest/bin/python similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/my-virtual-env/bin/python rename to native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda-latest/bin/python diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/nogil-3.9.10/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/python similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/nogil-3.9.10/bin/python rename to native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/python diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/pypy3.10-7.3.14/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10.1/bin/python similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/pypy3.10-7.3.14/bin/python rename to native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10.1/bin/python diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/pyston-2.3.5/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-4.0.5/bin/python similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/pyston-2.3.5/bin/python rename to native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-4.0.5/bin/python diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/stacklets-3.7.5/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniforge3-4.11.0-1/bin/python similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/stacklets-3.7.5/bin/python rename to native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniforge3-4.11.0-1/bin/python diff --git a/native_locator/tests/unix/pyenv_without_envs/opt/homebrew/bin/pyenv b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/my-virtual-env/bin/python similarity index 100% rename from native_locator/tests/unix/pyenv_without_envs/opt/homebrew/bin/pyenv rename to native_locator/tests/unix/pyenv/user_home/.pyenv/versions/my-virtual-env/bin/python diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/my-virtual-env/pyvenv.cfg b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/my-virtual-env/pyvenv.cfg similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/my-virtual-env/pyvenv.cfg rename to native_locator/tests/unix/pyenv/user_home/.pyenv/versions/my-virtual-env/pyvenv.cfg diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/nogil-3.9.10/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/nogil-3.9.10/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/pypy3.10-7.3.14/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/pypy3.10-7.3.14/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/pyston-2.3.5/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/pyston-2.3.5/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/stacklets-3.7.5/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/stacklets-3.7.5/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv_without_envs/home/opt/homebrew/bin/pyenv b/native_locator/tests/unix/pyenv_without_envs/home/opt/homebrew/bin/pyenv new file mode 100644 index 000000000000..e69de29bb2d1 From e24c51942044bb5a90713946074984355a5a5df1 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 15 May 2024 11:28:10 +1000 Subject: [PATCH 04/10] Fix tests --- native_locator/src/conda.rs | 73 ++++++++-------------- native_locator/tests/common.rs | 16 ----- native_locator/tests/common_python_test.rs | 14 +++-- native_locator/tests/conda_test.rs | 46 ++++++-------- native_locator/tests/pyenv_test.rs | 36 +++++------ 5 files changed, 70 insertions(+), 115 deletions(-) diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 1139ce8ffe19..929a991689cc 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -14,6 +14,7 @@ use crate::utils::{find_python_binary_path, get_environment_key, get_environment use log::trace; use log::warn; use regex::Regex; +use std::collections::HashMap; use std::collections::HashSet; use std::env; use std::path::{Path, PathBuf}; @@ -279,7 +280,8 @@ fn get_conda_envs_from_environment_txt(environment: &dyn known::Environment) -> if let Some(home) = environment.get_user_home() { let home = Path::new(&home); let environment_txt = home.join(".conda").join("environments.txt"); - if let Ok(reader) = std::fs::read_to_string(environment_txt) { + if let Ok(reader) = std::fs::read_to_string(environment_txt.clone()) { + trace!("Found environments.txt file {:?}", environment_txt); for line in reader.lines() { envs.push(line.to_string()); } @@ -464,7 +466,7 @@ fn get_conda_conda_rc(environment: &dyn known::Environment) -> Option { if start_consuming_values { if line.trim().starts_with("-") { if let Some(env_dir) = line.splitn(2, '-').nth(1) { - let env_dir = PathBuf::from(env_dir.trim()); + let env_dir = PathBuf::from(env_dir.trim()).join("envs"); if env_dir.exists() { env_dirs.push(env_dir); } @@ -651,7 +653,7 @@ fn get_root_python_environment(path: &PathBuf, manager: &EnvManager) -> Option

, + possible_conda_envs: &mut HashMap, ) -> Option { let mut managers: Vec = vec![]; let mut environments: Vec = vec![]; @@ -661,7 +663,9 @@ fn get_conda_environments_in_specified_install_path( if let Some(manager) = get_conda_manager(&conda_install_folder) { // 1. Base environment. if let Some(env) = get_root_python_environment(&conda_install_folder, &manager) { - if let Some(key) = get_environment_key(&env) { + if let Some(env_path) = env.clone().env_path { + possible_conda_envs.remove(&env_path); + let key = env_path.to_string_lossy().to_string(); if !detected_envs.contains(&key) { detected_envs.insert(key); environments.push(env); @@ -674,7 +678,10 @@ fn get_conda_environments_in_specified_install_path( if let Some(environments) = get_environments_from_envs_folder_in_conda_directory(conda_install_folder) { - environments.iter().for_each(|env| envs.push(env.clone())); + environments.iter().for_each(|env| { + possible_conda_envs.remove(&env.env_path); + envs.push(env.clone()); + }); } // 3. All environments in the environments.txt and other locations (such as `conda config --show envs_dirs`) @@ -684,8 +691,7 @@ fn get_conda_environments_in_specified_install_path( // E.g conda_install_folder is `/` // Then all folders such as `//envs/env1` can be ignored // As these would have been discovered in previous step. - let mut indices_to_remove: Vec = vec![]; - for (index, env) in possible_conda_envs.iter().enumerate() { + for (key, env) in possible_conda_envs.clone().iter() { if env .env_path .to_string_lossy() @@ -695,16 +701,10 @@ fn get_conda_environments_in_specified_install_path( } if was_conda_environment_created_by_specific_conda(&env, conda_install_folder) { envs.push(env.clone()); - indices_to_remove.push(index); + possible_conda_envs.remove(key); } } - indices_to_remove.reverse(); - indices_to_remove.iter().for_each(|i| { - possible_conda_envs.remove(*i); - return (); - }); - // Finally construct the PythonEnvironment objects envs.iter().for_each(|env| { let exe = env.python_executable_path.clone(); @@ -746,7 +746,7 @@ fn get_conda_environments_in_specified_install_path( fn find_conda_environments_from_known_conda_install_locations( environment: &dyn known::Environment, - possible_conda_envs: &mut Vec, + possible_conda_envs: &mut HashMap, ) -> Option { let mut managers: Vec = vec![]; let mut environments: Vec = vec![]; @@ -769,36 +769,21 @@ fn find_conda_environments_from_known_conda_install_locations( // & then look for conda environments in each of them. // This accounts for cases where Conda install location is in some un-common (custom) location let mut env_paths_to_remove: Vec = vec![]; - for env in possible_conda_envs - .clone() - .iter() - .filter(|env| is_conda_install_location(&env.env_path)) + for (key, env) in possible_conda_envs + .clone() + .iter() + .filter(|(_, env)| is_conda_install_location(&env.env_path)) { if let Some(mut result) = get_conda_environments_in_specified_install_path(&env.env_path, possible_conda_envs) { + possible_conda_envs.remove(key); managers.append(&mut result.managers); environments.append(&mut result.environments); env_paths_to_remove.push(env.env_path.clone()); } } - let mut indices_to_remove: Vec = vec![]; - env_paths_to_remove.iter().for_each(|p| { - if let Some(index) = possible_conda_envs - .iter() - .position(|env| env.env_path == *p) - { - indices_to_remove.push(index); - } - }); - indices_to_remove.sort(); - indices_to_remove.reverse(); - indices_to_remove.iter().for_each(|i| { - possible_conda_envs.remove(*i); - return (); - }); - if managers.is_empty() && environments.is_empty() { return None; } @@ -833,7 +818,7 @@ pub fn get_conda_version(conda_binary: &PathBuf) -> Option { fn get_known_conda_envs_from_various_locations( environment: &dyn known::Environment, -) -> Vec { +) -> HashMap { let mut env_paths = get_conda_envs_from_environment_txt(environment) .iter() .map(|e| PathBuf::from(e)) @@ -855,20 +840,17 @@ fn get_known_conda_envs_from_various_locations( } }); - envs + envs.into_iter().fold(HashMap::new(), |mut acc, env| { + acc.insert(env.env_path.clone(), env); + acc + }) } fn get_conda_environments_from_known_locations_that_have_not_been_discovered( - discovered_environment_paths: &HashSet, known_environment: &Vec, environment: &dyn known::Environment, - possible_conda_envs: &mut Vec, + undiscovered_environments: &mut HashMap, ) -> Option { - let undiscovered_environments = possible_conda_envs - .iter() - .filter(|env| !discovered_environment_paths.contains(&env.env_path)) - .collect::>(); - if undiscovered_environments.is_empty() { return None; } @@ -898,7 +880,7 @@ fn get_conda_environments_from_known_locations_that_have_not_been_discovered( if let Some(manager) = manager { let mut environments: Vec = vec![]; - for env in undiscovered_environments { + for (_, env) in undiscovered_environments { let exe = env.python_executable_path.clone(); let env = PythonEnvironment::new( None, @@ -1029,7 +1011,6 @@ impl Locator for Conda<'_> { if let Some(result) = self.filter_result( get_conda_environments_from_known_locations_that_have_not_been_discovered( - &self.discovered_environment_paths, &environments, self.environment, &mut possible_conda_envs, diff --git a/native_locator/tests/common.rs b/native_locator/tests/common.rs index ca7599b7b703..40eceae82603 100644 --- a/native_locator/tests/common.rs +++ b/native_locator/tests/common.rs @@ -148,19 +148,3 @@ pub fn assert_messages(expected_json: &[Value], actual_json: &[Value]) { } } } - -#[allow(dead_code)] -pub fn get_environments_from_result(result: &Option) -> Vec { - match result { - Some(result) => result.environments.clone(), - None => vec![], - } -} - -#[allow(dead_code)] -pub fn get_managers_from_result(result: &Option) -> Vec { - match result { - Some(result) => result.managers.clone(), - None => vec![], - } -} diff --git a/native_locator/tests/common_python_test.rs b/native_locator/tests/common_python_test.rs index 13d853d5cebd..d7d71fd5cce1 100644 --- a/native_locator/tests/common_python_test.rs +++ b/native_locator/tests/common_python_test.rs @@ -7,8 +7,7 @@ mod common; #[cfg(unix)] fn find_python_in_path_this() { use crate::common::{ - assert_messages, create_test_environment, get_environments_from_result, join_test_paths, - test_file_path, + assert_messages, create_test_environment, join_test_paths, test_file_path, }; use python_finder::{common_python, locator::Locator, messaging::PythonEnvironment}; use serde_json::json; @@ -28,10 +27,9 @@ fn find_python_in_path_this() { ); let mut locator = common_python::PythonOnPath::with(&known); - let result = locator.find(); + let result = locator.find().unwrap(); - let environments = get_environments_from_result(&result); - assert_eq!(environments.len(), 1); + assert_eq!(result.environments.len(), 1); let env = PythonEnvironment { display_name: None, @@ -46,6 +44,10 @@ fn find_python_in_path_this() { }; assert_messages( &[json!(env)], - &environments.iter().map(|e| json!(e)).collect::>(), + &result + .environments + .iter() + .map(|e| json!(e)) + .collect::>(), ); } diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs index 998d8fc9bbb6..d3ca5d7fc2f2 100644 --- a/native_locator/tests/conda_test.rs +++ b/native_locator/tests/conda_test.rs @@ -6,7 +6,7 @@ mod common; #[test] #[cfg(unix)] fn does_not_find_any_conda_envs() { - use crate::common::{create_test_environment, get_environments_from_result}; + use crate::common::create_test_environment; use python_finder::{conda, locator::Locator}; use std::{collections::HashMap, path::PathBuf}; @@ -20,8 +20,7 @@ fn does_not_find_any_conda_envs() { let mut locator = conda::Conda::with(&known); let result = locator.find(); - let environments = get_environments_from_result(&result); - assert_eq!(environments.len(), 0); + assert_eq!(result.is_none(), true); } #[test] @@ -67,7 +66,8 @@ fn paths_from_conda_rc() { fn test_with(conda_rc_file: &PathBuf) { let home = test_file_path(&["tests/unix/conda_rc/user_home"]); let root = test_file_path(&["tests/unix/conda_rc/root"]); - let envs = home.join(".conda").join("envs"); + let conda_dir = home.join(".conda"); + let conda_envs = conda_dir.join("envs"); let known = create_test_environment( HashMap::from([("PATH".to_string(), "".to_string())]), @@ -80,14 +80,14 @@ fn paths_from_conda_rc() { fs::create_dir_all(home.clone()).unwrap_or_default(); fs::create_dir_all(root.clone()).unwrap_or_default(); - fs::create_dir_all(envs.clone()).unwrap_or_default(); + fs::create_dir_all(conda_envs.clone()).unwrap_or_default(); fs::create_dir_all(conda_rc_file.parent().unwrap()).unwrap_or_default(); - create_conda_rc(conda_rc_file, &vec![envs.clone()]); + create_conda_rc(conda_rc_file, &vec![conda_dir.clone()]); let result = get_conda_environment_paths_from_conda_rc(&known); assert_eq!(result.len(), 1); - assert_eq!(result[0], envs); + assert_eq!(result[0], conda_envs); fs::remove_dir_all(home.clone()).unwrap_or_default(); fs::remove_dir_all(root.clone()).unwrap_or_default(); @@ -103,10 +103,7 @@ fn paths_from_conda_rc() { #[test] #[cfg(unix)] fn find_conda_exe_and_empty_envs() { - use crate::common::{ - assert_messages, create_test_environment, get_managers_from_result, join_test_paths, - test_file_path, - }; + use crate::common::{create_test_environment, join_test_paths, test_file_path}; use python_finder::messaging::{EnvManager, EnvManagerType}; use python_finder::{conda, locator::Locator}; use serde_json::json; @@ -125,8 +122,8 @@ fn find_conda_exe_and_empty_envs() { ); let mut locator = conda::Conda::with(&known); - let result = locator.find(); - let managers = get_managers_from_result(&result); + let result = locator.find().unwrap(); + let managers = result.managers; assert_eq!(managers.len(), 1); let conda_exe = join_test_paths(&[ @@ -140,10 +137,8 @@ fn find_conda_exe_and_empty_envs() { version: Some("4.0.2".to_string()), tool: EnvManagerType::Conda, }; - assert_messages( - &[json!(expected_conda_manager)], - &managers.iter().map(|e| json!(e)).collect::>(), - ) + assert_eq!(managers.len(), 1); + assert_eq!(json!(expected_conda_manager), json!(managers[0])); } #[test] @@ -220,8 +215,7 @@ fn find_conda_from_custom_install_location() { #[cfg(unix)] fn finds_two_conda_envs_from_txt() { use crate::common::{ - assert_messages, create_test_environment, get_environments_from_result, - get_managers_from_result, join_test_paths, test_file_path, + assert_messages, create_test_environment, join_test_paths, test_file_path, }; use python_finder::messaging::{EnvManager, EnvManagerType, PythonEnvironment}; use python_finder::{conda, locator::Locator}; @@ -257,10 +251,10 @@ fn finds_two_conda_envs_from_txt() { ); let mut locator = conda::Conda::with(&known); - let result = locator.find(); + let result = locator.find().unwrap(); - let managers = get_managers_from_result(&result); - let environments = get_environments_from_result(&result); + let managers = result.managers; + let environments = result.environments; assert_eq!(managers.len(), 1); let conda_exe = join_test_paths(&[conda_dir.clone().to_str().unwrap(), "bin", "conda"]); @@ -272,6 +266,10 @@ fn finds_two_conda_envs_from_txt() { version: Some("4.0.2".to_string()), tool: EnvManagerType::Conda, }; + + assert_eq!(managers.len(), 1); + assert_eq!(json!(expected_conda_manager), json!(managers[0])); + let expected_conda_1 = PythonEnvironment { display_name: None, name: Some("one".to_string()), @@ -306,10 +304,6 @@ fn finds_two_conda_envs_from_txt() { "python".to_string(), ]), }; - assert_messages( - &[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 dbc222ec494c..87761114089d 100644 --- a/native_locator/tests/pyenv_test.rs +++ b/native_locator/tests/pyenv_test.rs @@ -6,9 +6,7 @@ mod common; #[test] #[cfg(unix)] fn does_not_find_any_pyenv_envs() { - use crate::common::{ - create_test_environment, get_environments_from_result, get_managers_from_result, - }; + use crate::common::create_test_environment; use python_finder::{conda::Conda, locator::Locator, pyenv}; use std::{collections::HashMap, path::PathBuf}; @@ -23,16 +21,14 @@ fn does_not_find_any_pyenv_envs() { let mut locator = pyenv::PyEnv::with(&known, &mut conda); let result = locator.find(); - assert_eq!(get_managers_from_result(&result).len(), 0); - assert_eq!(get_environments_from_result(&result).len(), 0); + assert_eq!(result.is_none(), true); } #[test] #[cfg(unix)] fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() { use crate::common::{ - assert_messages, create_test_environment, get_managers_from_result, join_test_paths, - test_file_path, + assert_messages, create_test_environment, join_test_paths, test_file_path, }; use python_finder::pyenv; use python_finder::{conda::Conda, locator::Locator}; @@ -59,9 +55,9 @@ fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() { let mut conda = Conda::with(&known); let mut locator = pyenv::PyEnv::with(&known, &mut conda); - let result = locator.find(); + let result = locator.find().unwrap(); - let managers = get_managers_from_result(&result); + let managers = result.managers; assert_eq!(managers.len(), 1); let expected_json = json!({"executablePath":pyenv_exe,"version":null, "tool": "pyenv"}); @@ -75,8 +71,8 @@ fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() { #[cfg(unix)] fn find_pyenv_envs() { use crate::common::{ - assert_messages, create_test_environment, get_environments_from_result, - get_managers_from_result, join_test_paths, test_file_path, + assert_messages, create_test_environment, join_test_paths, + test_file_path, }; use python_finder::conda::Conda; use python_finder::locator::Locator; @@ -100,21 +96,16 @@ fn find_pyenv_envs() { let mut conda = Conda::with(&known); let mut locator = pyenv::PyEnv::with(&known, &mut conda); - let result = locator.find(); + let result = locator.find().unwrap(); - let managers = get_managers_from_result(&result); - assert_eq!(managers.len(), 1); + assert_eq!(result.managers.len(), 1); 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::>(), - ); + assert_eq!(json!(expected_manager), json!(result.managers[0])); let expected_3_9_9 = json!(PythonEnvironment { display_name: None, @@ -231,7 +222,6 @@ fn find_pyenv_envs() { ])), env_manager: Some(expected_manager.clone()), }; - let environments = get_environments_from_result(&result); assert_messages( &[ @@ -241,6 +231,10 @@ fn find_pyenv_envs() { json!(expected_3_13_dev), json!(expected_3_12_1a3), ], - &environments.iter().map(|e| json!(e)).collect::>(), + &result + .environments + .iter() + .map(|e| json!(e)) + .collect::>(), ) } From ff069c75894e9a7dfbd1739a8cc91985e2187c5f Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 15 May 2024 11:29:09 +1000 Subject: [PATCH 05/10] Fix compillation errors --- native_locator/tests/common.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/native_locator/tests/common.rs b/native_locator/tests/common.rs index 40eceae82603..f133812cee9c 100644 --- a/native_locator/tests/common.rs +++ b/native_locator/tests/common.rs @@ -1,11 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use python_finder::{ - known::Environment, - locator::LocatorResult, - messaging::{EnvManager, PythonEnvironment}, -}; +use python_finder::known::Environment; use serde_json::Value; use std::{collections::HashMap, path::PathBuf}; From 9894ee40cb752063ecac3bb815d0cb618c54f8d5 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 15 May 2024 11:29:30 +1000 Subject: [PATCH 06/10] Fixes --- native_locator/src/utils.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/native_locator/src/utils.rs b/native_locator/src/utils.rs index b0cfb0e6e412..c70efe9654ef 100644 --- a/native_locator/src/utils.rs +++ b/native_locator/src/utils.rs @@ -24,13 +24,6 @@ impl PythonEnv { version, } } - pub fn from(executable: PathBuf) -> Self { - Self { - executable, - path: None, - version: None, - } - } } #[derive(Debug)] From 58a03fd0bd794ece502ac53dca62dfcdaf842478 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 15 May 2024 11:30:47 +1000 Subject: [PATCH 07/10] Fixes --- native_locator/tests/common.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/native_locator/tests/common.rs b/native_locator/tests/common.rs index f133812cee9c..bf4c54617f16 100644 --- a/native_locator/tests/common.rs +++ b/native_locator/tests/common.rs @@ -7,7 +7,6 @@ use std::{collections::HashMap, path::PathBuf}; #[allow(dead_code)] pub fn test_file_path(paths: &[&str]) -> PathBuf { - // let parts: Vec = paths.iter().map(|p| p.to_string()).collect(); let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); paths.iter().for_each(|p| root.push(p)); From 8dfd878237aab594c456d06318ae727710cf1fc4 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 15 May 2024 11:50:49 +1000 Subject: [PATCH 08/10] Fix tests --- native_locator/tests/conda_test.rs | 12 ++---------- .../user_home/anaconda3/envs/.conda_envs_dir_test | 0 .../anaconda3/envs/.conda_envs_dir_test | 0 .../user_home/anaconda3/envs/.conda_envs_dir_test | 0 .../anaconda-4.0.0/bin/envs/.conda_envs_dir_test | 0 .../anaconda3-2021.04/bin/envs/.conda_envs_dir_test | 0 .../bin/envs/.conda_envs_dir_test | 0 .../mambaforge/bin/envs/.conda_envs_dir_test | 0 .../miniconda-latest/bin/envs/.conda_envs_dir_test | 0 .../bin/envs/.conda_envs_dir_test | 0 .../miniconda3-3.10.1/bin/envs/.conda_envs_dir_test | 0 .../miniconda3-4.0.5/bin/envs/.conda_envs_dir_test | 0 .../bin/envs/.conda_envs_dir_test | 0 13 files changed, 2 insertions(+), 10 deletions(-) create mode 100644 native_locator/tests/unix/conda/user_home/anaconda3/envs/.conda_envs_dir_test create mode 100644 native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/envs/.conda_envs_dir_test create mode 100644 native_locator/tests/unix/conda_without_envs/user_home/anaconda3/envs/.conda_envs_dir_test create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda-4.0.0/bin/envs/.conda_envs_dir_test create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda3-2021.04/bin/envs/.conda_envs_dir_test create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge-4.10.1-4/bin/envs/.conda_envs_dir_test create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge/bin/envs/.conda_envs_dir_test create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda-latest/bin/envs/.conda_envs_dir_test create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/envs/.conda_envs_dir_test create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10.1/bin/envs/.conda_envs_dir_test create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-4.0.5/bin/envs/.conda_envs_dir_test create mode 100644 native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniforge3-4.11.0-1/bin/envs/.conda_envs_dir_test diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs index d3ca5d7fc2f2..f8ea34fcf12c 100644 --- a/native_locator/tests/conda_test.rs +++ b/native_locator/tests/conda_test.rs @@ -143,7 +143,7 @@ fn find_conda_exe_and_empty_envs() { #[test] #[cfg(unix)] -fn find_conda_from_custom_install_location() { +fn find_conda_from_custom_install_locationxxx() { use crate::common::{create_test_environment, test_file_path}; use python_finder::messaging::{EnvManager, EnvManagerType, PythonEnvironment}; use python_finder::{conda, locator::Locator}; @@ -164,15 +164,7 @@ fn find_conda_from_custom_install_location() { ) .unwrap(); - let known = create_test_environment( - HashMap::from([( - "PATH".to_string(), - conda_dir.clone().to_str().unwrap().to_string(), - )]), - Some(home), - Vec::new(), - None, - ); + let known = create_test_environment(HashMap::new(), Some(home), Vec::new(), None); let mut locator = conda::Conda::with(&known); let result = locator.find().unwrap(); diff --git a/native_locator/tests/unix/conda/user_home/anaconda3/envs/.conda_envs_dir_test b/native_locator/tests/unix/conda/user_home/anaconda3/envs/.conda_envs_dir_test new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/envs/.conda_envs_dir_test b/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/envs/.conda_envs_dir_test new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/envs/.conda_envs_dir_test b/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/envs/.conda_envs_dir_test new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda-4.0.0/bin/envs/.conda_envs_dir_test b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda-4.0.0/bin/envs/.conda_envs_dir_test new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda3-2021.04/bin/envs/.conda_envs_dir_test b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda3-2021.04/bin/envs/.conda_envs_dir_test new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge-4.10.1-4/bin/envs/.conda_envs_dir_test b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge-4.10.1-4/bin/envs/.conda_envs_dir_test new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge/bin/envs/.conda_envs_dir_test b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge/bin/envs/.conda_envs_dir_test new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda-latest/bin/envs/.conda_envs_dir_test b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda-latest/bin/envs/.conda_envs_dir_test new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/envs/.conda_envs_dir_test b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/envs/.conda_envs_dir_test new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10.1/bin/envs/.conda_envs_dir_test b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10.1/bin/envs/.conda_envs_dir_test new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-4.0.5/bin/envs/.conda_envs_dir_test b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-4.0.5/bin/envs/.conda_envs_dir_test new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniforge3-4.11.0-1/bin/envs/.conda_envs_dir_test b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniforge3-4.11.0-1/bin/envs/.conda_envs_dir_test new file mode 100644 index 000000000000..e69de29bb2d1 From 9e9871154d31f4e7a9cf08e0627e9c80dfa4e530 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 15 May 2024 11:52:35 +1000 Subject: [PATCH 09/10] Remove dead code --- native_locator/src/messaging.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs index 34a2ca1d8c73..73e708dcac5f 100644 --- a/native_locator/src/messaging.rs +++ b/native_locator/src/messaging.rs @@ -25,7 +25,7 @@ pub enum EnvManagerType { Pyenv, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] #[derive(Debug)] pub struct EnvManager { @@ -44,16 +44,6 @@ impl EnvManager { } } -impl Clone for EnvManager { - fn clone(&self) -> Self { - Self { - executable_path: self.executable_path.clone(), - version: self.version.clone(), - tool: self.tool.clone(), - } - } -} - #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[derive(Debug)] From f767a8deeda36f63c9de0f555e0c7a54c15d82a2 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 15 May 2024 12:00:14 +1000 Subject: [PATCH 10/10] Fix tests --- native_locator/tests/conda_test.rs | 19 ++----------------- .../tests/unix/conda/.conda/environments.txt | 0 .../conda/user_home/.conda/environments.txt | 1 - 3 files changed, 2 insertions(+), 18 deletions(-) delete mode 100644 native_locator/tests/unix/conda/.conda/environments.txt diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs index f8ea34fcf12c..397ed42b1de6 100644 --- a/native_locator/tests/conda_test.rs +++ b/native_locator/tests/conda_test.rs @@ -143,7 +143,7 @@ fn find_conda_exe_and_empty_envs() { #[test] #[cfg(unix)] -fn find_conda_from_custom_install_locationxxx() { +fn find_conda_from_custom_install_location() { use crate::common::{create_test_environment, test_file_path}; use python_finder::messaging::{EnvManager, EnvManagerType, PythonEnvironment}; use python_finder::{conda, locator::Locator}; @@ -205,7 +205,7 @@ fn find_conda_from_custom_install_locationxxx() { #[test] #[cfg(unix)] -fn finds_two_conda_envs_from_txt() { +fn finds_two_conda_envs_from_known_location() { use crate::common::{ assert_messages, create_test_environment, join_test_paths, test_file_path, }; @@ -213,24 +213,11 @@ fn finds_two_conda_envs_from_txt() { use python_finder::{conda, locator::Locator}; use serde_json::json; use std::collections::HashMap; - use std::fs; let home = test_file_path(&["tests/unix/conda/user_home"]); let conda_dir = test_file_path(&["tests/unix/conda/user_home/anaconda3"]); let conda_1 = join_test_paths(&[conda_dir.clone().to_str().unwrap(), "envs/one"]); let conda_2 = join_test_paths(&[conda_dir.clone().to_str().unwrap(), "envs/two"]); - let environments_txt = test_file_path(&["tests/unix/conda/.conda/environments.txt"]); - - fs::create_dir_all(environments_txt.parent().unwrap()).unwrap_or_default(); - fs::write( - environments_txt.clone(), - format!( - "{}\n{}", - conda_1.clone().to_str().unwrap().to_string(), - conda_2.clone().to_str().unwrap().to_string() - ), - ) - .unwrap_or_default(); let known = create_test_environment( HashMap::from([( @@ -300,6 +287,4 @@ fn finds_two_conda_envs_from_txt() { &[json!(expected_conda_1), json!(expected_conda_2)], &environments.iter().map(|e| json!(e)).collect::>(), ); - - fs::write(environments_txt, "").unwrap_or_default(); } diff --git a/native_locator/tests/unix/conda/.conda/environments.txt b/native_locator/tests/unix/conda/.conda/environments.txt deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/native_locator/tests/unix/conda/user_home/.conda/environments.txt b/native_locator/tests/unix/conda/user_home/.conda/environments.txt index c0564f346362..e69de29bb2d1 100644 --- a/native_locator/tests/unix/conda/user_home/.conda/environments.txt +++ b/native_locator/tests/unix/conda/user_home/.conda/environments.txt @@ -1 +0,0 @@ -/Users/donjayamanne/Development/vsc/vscode-python/native_locator/tests/unix/conda/user_home/anaconda3 \ No newline at end of file