diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs index 4233461fc264..929a991689cc 100644 --- a/native_locator/src/conda.rs +++ b/native_locator/src/conda.rs @@ -11,8 +11,10 @@ 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::HashMap; use std::collections::HashSet; use std::env; use std::path::{Path, PathBuf}; @@ -207,40 +209,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, }); } } @@ -248,6 +255,7 @@ fn get_conda_environment_info(env_path: &PathBuf, named: bool) -> Option Option> { @@ -272,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()); } @@ -287,66 +296,188 @@ struct Condarc { } /** - * The .condarc file contains a list of directories where conda environments are created. - * https://conda.io/projects/conda/en/latest/configuration.html#envs-dirs - * - * TODO: Search for the .condarc file in the following locations: - * https://conda.io/projects/conda/en/latest/user-guide/configuration/use-condarc.html#searching-for-condarc + * Get the list of conda environments found in other locations such as + * /.conda/envs + * /AppData/Local/conda/conda/envs */ -fn get_conda_conda_rc(environment: &dyn known::Environment) -> Option { +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 { + vec![] + } +} + +fn get_conda_environment_paths_from_known_paths( + environment: &dyn known::Environment, +) -> Vec { 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 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()); } } - return Some(Condarc { env_dirs }); + None::<()> + }); + return env_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"), + ]); } - None + 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 } -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); +/** + * The .condarc file contains a list of directories where conda environments are created. + * https://conda.io/projects/conda/en/latest/configuration.html#envs-dirs + * + * TODO: Search for the .condarc file in the following locations: + * 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 { + 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()).join("envs"); + if env_dir.exists() { + env_dirs.push(env_dir); } } + continue; + } else { + break; } } } - - Some(envs) + return Some(Condarc { env_dirs }); } /** @@ -359,20 +490,17 @@ fn get_conda_envs_from_conda_rc( * 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; } } @@ -380,61 +508,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) -} - -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) + None } #[cfg(windows)] @@ -509,7 +617,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(), ]) } @@ -543,21 +651,21 @@ fn get_root_python_environment(path: &PathBuf, manager: &EnvManager) -> Option

, ) -> 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) { - let envs = - get_conda_environments_from_conda_directory(&possible_conda_folder, environment); - - if let Some(env) = get_root_python_environment(&possible_conda_folder, &manager) { - if let Some(key) = get_environment_key(&env) { + 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(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); @@ -565,7 +673,40 @@ pub fn get_conda_environments_in_specified_path( } } - envs.unwrap_or_default().iter().for_each(|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(conda_install_folder) + { + 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`) + // 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. + for (key, env) in possible_conda_envs.clone().iter() { + if env + .env_path + .to_string_lossy() + .contains(conda_install_folder.to_str().unwrap()) + { + continue; + } + if was_conda_environment_created_by_specific_conda(&env, conda_install_folder) { + envs.push(env.clone()); + possible_conda_envs.remove(key); + } + } + + // Finally construct the PythonEnvironment objects + envs.iter().for_each(|env| { let exe = env.python_executable_path.clone(); let env = PythonEnvironment::new( None, @@ -573,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), ); @@ -605,32 +746,41 @@ 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 HashMap, ) -> 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) - { - 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()); - } - }); + // 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, + ) { + 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 (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()); } } @@ -644,6 +794,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") { @@ -661,25 +816,42 @@ pub fn get_conda_version(conda_binary: &PathBuf) -> Option { } } -fn get_conda_environments_from_environments_txt_that_have_not_been_discovered( - known_environment_keys: &HashSet, - known_environment: &Vec, +fn get_known_conda_envs_from_various_locations( environment: &dyn known::Environment, -) -> Option { - let binding = get_conda_envs_from_environment_txt(environment); - let undiscovered_environments_in_txt = binding +) -> HashMap { + let mut env_paths = get_conda_envs_from_environment_txt(environment) .iter() - .filter(|env| { - for known in known_environment_keys.iter() { - if known.contains(*env) { - return false; - } - } - true - }) - .collect::>(); + .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 !path.exists() { + return; + } + if let Some(env) = get_conda_environment_info(&path, false) { + envs.push(env); + } + }); + + envs.into_iter().fold(HashMap::new(), |mut acc, env| { + acc.insert(env.env_path.clone(), env); + acc + }) +} - if undiscovered_environments_in_txt.len() == 0 { +fn get_conda_environments_from_known_locations_that_have_not_been_discovered( + known_environment: &Vec, + environment: &dyn known::Environment, + undiscovered_environments: &mut HashMap, +) -> Option { + if undiscovered_environments.is_empty() { return None; } @@ -687,7 +859,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,21 +880,19 @@ 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 { - 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); - } + for (_, env) in undiscovered_environments { + 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 { @@ -740,7 +910,7 @@ fn get_conda_environments_from_environments_txt_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, } @@ -753,7 +923,7 @@ impl Conda<'_> { Conda { environment, manager: None, - discovered_environments: HashSet::new(), + discovered_environment_paths: HashSet::new(), discovered_managers: HashSet::new(), } } @@ -763,11 +933,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 @@ -802,9 +972,10 @@ impl Conda<'_> { impl CondaLocator for Conda<'_> { fn find_in(&mut self, possible_conda_folder: &PathBuf) -> Option { - self.filter_result(get_conda_environments_in_specified_path( + let mut possible_conda_envs = get_known_conda_envs_from_various_locations(self.environment); + self.filter_result(get_conda_environments_in_specified_install_path( possible_conda_folder, - self.environment, + &mut possible_conda_envs, )) } } @@ -819,13 +990,16 @@ 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); + detected_managers.insert(get_environment_manager_key(m)); managers.push(m.clone()); }); @@ -836,10 +1010,10 @@ impl Locator for Conda<'_> { } if let Some(result) = self.filter_result( - get_conda_environments_from_environments_txt_that_have_not_been_discovered( - &self.discovered_environments, + get_conda_environments_from_known_locations_that_have_not_been_discovered( &environments, self.environment, + &mut possible_conda_envs, ), ) { result.managers.iter().for_each(|m| { 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/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)] 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)] diff --git a/native_locator/tests/common.rs b/native_locator/tests/common.rs index 407e93707b7e..bf4c54617f16 100644 --- a/native_locator/tests/common.rs +++ b/native_locator/tests/common.rs @@ -1,17 +1,12 @@ // 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}; #[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)); @@ -32,6 +27,7 @@ pub trait TestMessages { pub struct TestEnvironment { vars: HashMap, home: Option, + root: Option, globals_locations: Vec, } #[allow(dead_code)] @@ -39,11 +35,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 +54,7 @@ pub fn create_test_environment( TestEnvironment { vars, home, + root, globals_locations, } } @@ -142,19 +143,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 7db280c9496b..d7d71fd5cce1 100644 --- a/native_locator/tests/common_python_test.rs +++ b/native_locator/tests/common_python_test.rs @@ -7,30 +7,29 @@ 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; 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); - 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, @@ -41,10 +40,14 @@ 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)], - &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 1705dc72bf47..397ed42b1de6 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}; @@ -14,28 +14,102 @@ 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); let result = locator.find(); - let environments = get_environments_from_result(&result); - assert_eq!(environments.len(), 0); + assert_eq!(result.is_none(), true); +} + +#[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 conda_dir = home.join(".conda"); + let conda_envs = conda_dir.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(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![conda_dir.clone()]); + + let result = get_conda_environment_paths_from_conda_rc(&known); + assert_eq!(result.len(), 1); + assert_eq!(result[0], conda_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() { - 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; 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,11 +118,12 @@ fn find_conda_exe_and_empty_envs() { )]), Some(user_home), Vec::new(), + None, ); 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(&[ @@ -62,36 +137,87 @@ 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] +#[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::new(), 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() { +fn finds_two_conda_envs_from_known_location() { 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}; use serde_json::json; 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", - format!( - "{}\n{}", - conda_1.clone().to_str().unwrap().to_string(), - conda_2.clone().to_str().unwrap().to_string() - ), - ); let known = create_test_environment( HashMap::from([( @@ -100,13 +226,14 @@ fn finds_two_conda_envs_from_txt() { )]), Some(home), Vec::new(), + None, ); 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"]); @@ -118,6 +245,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()), @@ -133,9 +264,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, @@ -152,10 +283,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 42249586864d..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}; @@ -16,42 +14,50 @@ 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); 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}; 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); 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"}); @@ -65,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; @@ -77,32 +83,29 @@ 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); 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, @@ -219,7 +222,6 @@ fn find_pyenv_envs() { ])), env_manager: Some(expected_manager.clone()), }; - let environments = get_environments_from_result(&result); assert_messages( &[ @@ -229,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::>(), ) } 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 3a9e625c3050..000000000000 --- a/native_locator/tests/unix/conda/.conda/environments.txt +++ /dev/null @@ -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_without_envs/.conda/environments.txt b/native_locator/tests/unix/conda/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/user_home/.conda/environments.txt 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-slugify-5.0.2-pyhd3eb1b0_0.json b/native_locator/tests/unix/conda/user_home/anaconda3/envs/.conda_envs_dir_test 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/.conda_envs_dir_test 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_without_envs/anaconda3/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_without_envs/anaconda3/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/anaconda3/bin/conda 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/anaconda3/bin/conda 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/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json 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/conda-meta/conda-4.0.2-pyhd3eb1b0_0.json 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/pyenv/.pyenv/versions/3.12.1/bin/python 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/pyenv/.pyenv/versions/3.12.1/bin/python 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/pyenv/.pyenv/versions/3.12.1a3/bin/python 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/pyenv/.pyenv/versions/3.12.1a3/bin/python 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.13-dev/bin/python b/native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/envs/.conda_envs_dir_test similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/3.13-dev/bin/python rename to native_locator/tests/unix/conda_custom_install_path/user_home/some_location/anaconda3/envs/.conda_envs_dir_test 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/.conda/environments.txt 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/.conda/environments.txt diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/anaconda-4.0.0/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/anaconda-4.0.0/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/anaconda3-2021.04/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/anaconda3-2021.04/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/mambaforge-4.10.1-4/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/mambaforge-4.10.1-4/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/mambaforge/bin/python b/native_locator/tests/unix/conda_without_envs/user_home/anaconda3/envs/.conda_envs_dir_test similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/mambaforge/bin/python rename to native_locator/tests/unix/conda_without_envs/user_home/anaconda3/envs/.conda_envs_dir_test diff --git a/native_locator/tests/unix/pyenv/.pyenv/versions/miniconda-latest/bin/python b/native_locator/tests/unix/known/user_home/python similarity index 100% rename from native_locator/tests/unix/pyenv/.pyenv/versions/miniconda-latest/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/miniconda3-3.10-22.11.1-1/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/miniconda3-3.10-22.11.1-1/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/miniconda3-3.10.1/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/miniconda3-3.10.1/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/miniconda3-4.0.5/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/miniconda3-4.0.5/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/miniforge3-4.11.0-1/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/miniforge3-4.11.0-1/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/my-virtual-env/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/anaconda-4.0.0/bin/envs/.conda_envs_dir_test 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/anaconda-4.0.0/bin/envs/.conda_envs_dir_test 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/anaconda-4.0.0/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/anaconda-4.0.0/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/anaconda3-2021.04/bin/envs/.conda_envs_dir_test 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/anaconda3-2021.04/bin/envs/.conda_envs_dir_test 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/anaconda3-2021.04/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/anaconda3-2021.04/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/mambaforge-4.10.1-4/bin/envs/.conda_envs_dir_test 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/mambaforge-4.10.1-4/bin/envs/.conda_envs_dir_test diff --git a/native_locator/tests/unix/pyenv_without_envs/opt/homebrew/bin/pyenv 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_without_envs/opt/homebrew/bin/pyenv 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/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/mambaforge/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/mambaforge/bin/python 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/miniconda-latest/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda-latest/bin/python 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-22.11.1-1/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10-22.11.1-1/bin/python 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-3.10.1/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-3.10.1/bin/python 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/miniconda3-4.0.5/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniconda3-4.0.5/bin/python 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 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniforge3-4.11.0-1/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/miniforge3-4.11.0-1/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/my-virtual-env/bin/python b/native_locator/tests/unix/pyenv/user_home/.pyenv/versions/my-virtual-env/bin/python new file mode 100644 index 000000000000..e69de29bb2d1 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