diff --git a/crates/pet-conda/src/conda_rc.rs b/crates/pet-conda/src/conda_rc.rs index 9510f93e..98990f8c 100644 --- a/crates/pet-conda/src/conda_rc.rs +++ b/crates/pet-conda/src/conda_rc.rs @@ -6,7 +6,7 @@ use log::trace; use pet_fs::path::expand_path; use std::{ collections::HashSet, - fs, + env, fs, path::{Path, PathBuf}, }; use yaml_rust2::YamlLoader; @@ -29,7 +29,7 @@ impl Condarc { // Search paths documented here // https://conda.io/projects/conda/en/latest/user-guide/configuration/use-condarc.html#searching-for-condarc // https://github.com/conda/conda/blob/3ae5d7cf6cbe2b0ff9532359456b7244ae1ea5ef/conda/base/constants.py#L28 -fn get_conda_rc_search_paths(env_vars: &EnvVariables) -> Vec { +pub fn get_conda_rc_search_paths(env_vars: &EnvVariables) -> Vec { use crate::utils::change_root_of_path; let mut search_paths: Vec = vec![]; @@ -40,6 +40,22 @@ fn get_conda_rc_search_paths(env_vars: &EnvVariables) -> Vec { "C:\\ProgramData\\conda\\.condarc", "C:\\ProgramData\\conda\\condarc", "C:\\ProgramData\\conda\\condarc.d", + "C:\\ProgramData\\conda\\.mambarc", + format!( + "{}:\\ProgramData\\conda\\.condarc", + env::var("SYSTEMDRIVE").unwrap_or("C".to_string()) + ) + .as_str(), + format!( + "{}:\\ProgramData\\conda\\condarc", + env::var("SYSTEMDRIVE").unwrap_or("C".to_string()) + ) + .as_str(), + format!( + "{}:\\ProgramData\\conda\\condarc.d", + env::var("SYSTEMDRIVE").unwrap_or("C".to_string()) + ) + .as_str(), ] .iter() .map(PathBuf::from) @@ -51,9 +67,11 @@ fn get_conda_rc_search_paths(env_vars: &EnvVariables) -> Vec { "/etc/conda/.condarc", "/etc/conda/condarc", "/etc/conda/condarc.d", + "/etc/conda/mambarc", "/var/lib/conda/.condarc", "/var/lib/conda/condarc", "/var/lib/conda/condarc.d", + "/var/lib/conda/.mambarc", ] .iter() .map(PathBuf::from) @@ -63,10 +81,12 @@ fn get_conda_rc_search_paths(env_vars: &EnvVariables) -> Vec { ); } if let Some(ref conda_root) = env_vars.conda_root { + let conda_root = expand_path(PathBuf::from(conda_root.clone())); 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"), + conda_root.join(".condarc"), + conda_root.join("condarc"), + conda_root.join(".condarc.d"), + conda_root.join(".mambarc"), ]); } if let Some(ref xdg_config_home) = env_vars.xdg_config_home { @@ -74,6 +94,7 @@ fn get_conda_rc_search_paths(env_vars: &EnvVariables) -> 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"), + PathBuf::from(xdg_config_home.clone()).join(".mambarc"), ]); } if let Some(ref home) = env_vars.home { @@ -85,24 +106,33 @@ fn get_conda_rc_search_paths(env_vars: &EnvVariables) -> Vec { home.join(".conda").join("condarc"), home.join(".conda").join("condarc.d"), home.join(".condarc"), + home.join("condarc"), + home.join("condarc.d"), + home.join(".mambarc"), ]); } if let Some(ref conda_prefix) = env_vars.conda_prefix { + let conda_prefix = expand_path(PathBuf::from(conda_prefix.clone())); 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"), + conda_prefix.join(".condarc"), + conda_prefix.join("condarc"), + conda_prefix.join(".condarc.d"), + conda_prefix.join(".mamabarc"), ]); } if let Some(ref conda_dir) = env_vars.conda_dir { + let conda_dir = expand_path(PathBuf::from(conda_dir.clone())); search_paths.append(&mut vec![ - PathBuf::from(conda_dir.clone()).join(".condarc"), - PathBuf::from(conda_dir.clone()).join("condarc"), - PathBuf::from(conda_dir.clone()).join(".condarc.d"), + conda_dir.join(".condarc"), + conda_dir.join("condarc"), + conda_dir.join(".condarc.d"), ]); } if let Some(ref condarc) = env_vars.condarc { - search_paths.append(&mut vec![PathBuf::from(condarc)]); + search_paths.append(&mut vec![expand_path(PathBuf::from(condarc))]); + } + if let Some(ref mambarc) = env_vars.mambarc { + search_paths.append(&mut vec![expand_path(PathBuf::from(mambarc))]); } let search_paths: HashSet<_> = search_paths.into_iter().collect(); @@ -157,15 +187,21 @@ fn get_conda_conda_rc_from_path(conda_rc: &PathBuf) -> Option { .map(|e| e.path()) .filter(|p| p.is_file()) { - let file_name = path.file_name().unwrap().to_str().unwrap_or_default(); + let file_name = path + .file_name() + .unwrap() + .to_str() + .unwrap_or_default() + .to_lowercase(); let extension = path .extension() .unwrap_or_default() .to_str() - .unwrap_or_default(); + .unwrap_or_default() + .to_lowercase(); - if POSSIBLE_CONDA_RC_FILES.contains(&file_name) - || SUPPORTED_EXTENSIONS.contains(&extension) + if POSSIBLE_CONDA_RC_FILES.contains(&file_name.as_str()) + || SUPPORTED_EXTENSIONS.contains(&extension.as_str()) || file_name.contains("condarc") { if let Some(ref mut cfg) = parse_conda_rc(&path) { @@ -186,13 +222,14 @@ fn get_conda_conda_rc_from_path(conda_rc: &PathBuf) -> Option { fn parse_conda_rc(conda_rc: &Path) -> Option { let reader = fs::read_to_string(conda_rc).ok()?; - trace!("Possible conda_rc: {:?}", conda_rc); if let Some(cfg) = parse_conda_rc_contents(&reader) { + trace!("conda_rc: {:?} with env_dirs {:?}", conda_rc, cfg.env_dirs); Some(Condarc { env_dirs: cfg.env_dirs, files: vec![conda_rc.to_path_buf()], }) } else { + trace!("Failed to parse or empty conda_rc: {:?}", conda_rc); Some(Condarc { env_dirs: vec![], files: vec![conda_rc.to_path_buf()], @@ -220,7 +257,9 @@ fn parse_conda_rc_contents(contents: &str) -> Option { if item_str.is_empty() { continue; } - env_dirs.push(expand_path(PathBuf::from(item_str.trim()))); + let env_dir = expand_path(PathBuf::from(item_str.trim())); + trace!("env_dir: {:?} parsed as {:?}", item_str.trim(), env_dir); + env_dirs.push(env_dir); } } if let Some(items) = doc["envs_path"].as_vec() { @@ -229,7 +268,9 @@ fn parse_conda_rc_contents(contents: &str) -> Option { if item_str.is_empty() { continue; } - env_dirs.push(expand_path(PathBuf::from(item_str.trim()))); + let env_dir = expand_path(PathBuf::from(item_str.trim())); + trace!("env_path: {:?} parsed as {:?}", item_str.trim(), env_dir); + env_dirs.push(env_dir); } } } diff --git a/crates/pet-conda/src/env_variables.rs b/crates/pet-conda/src/env_variables.rs index 1559c397..e2bd79c2 100644 --- a/crates/pet-conda/src/env_variables.rs +++ b/crates/pet-conda/src/env_variables.rs @@ -22,9 +22,15 @@ pub struct EnvVariables { pub conda_dir: Option, pub conda: Option, pub conda_prefix: Option, + pub mamba_root_prefix: Option, /// https://docs.conda.io/projects/conda/en/22.11.x/user-guide/configuration/use-condarc.html pub conda_envs_path: Option, pub condarc: Option, + pub mambarc: Option, + // https://anaconda-project.readthedocs.io/en/latest/config.html + pub anaconda_project_envs_path: Option, + // https://anaconda-project.readthedocs.io/en/latest/config.html + pub project_dir: Option, pub xdg_config_home: Option, pub known_global_search_locations: Vec, } @@ -43,8 +49,12 @@ impl EnvVariables { conda_root: env.get_env_var("CONDA_ROOT".to_string()), conda: env.get_env_var("CONDA".to_string()), conda_prefix: env.get_env_var("CONDA_PREFIX".to_string()), + mamba_root_prefix: env.get_env_var("MAMBA_ROOT_PREFIX".to_string()), conda_envs_path: env.get_env_var("CONDA_ENVS_PATH".to_string()), + anaconda_project_envs_path: env.get_env_var("ANACONDA_PROJECT_ENVS_PATH".to_string()), + project_dir: env.get_env_var("PROJECT_DIR".to_string()), condarc: env.get_env_var("CONDARC".to_string()), + mambarc: env.get_env_var("MAMBARC".to_string()), xdg_config_home: env.get_env_var("XDG_CONFIG_HOME".to_string()), known_global_search_locations: env.get_know_global_search_locations(), } diff --git a/crates/pet-conda/src/environment_locations.rs b/crates/pet-conda/src/environment_locations.rs index 8f67c2c5..0bfc0f1c 100644 --- a/crates/pet-conda/src/environment_locations.rs +++ b/crates/pet-conda/src/environment_locations.rs @@ -2,7 +2,7 @@ // Licensed under the MIT License. use crate::{ - conda_rc::Condarc, + conda_rc::{get_conda_rc_search_paths, Condarc}, env_variables::EnvVariables, utils::{is_conda_env, is_conda_install}, }; @@ -10,7 +10,7 @@ use log::trace; use pet_fs::path::{expand_path, norm_case}; use pet_python_utils::platform_dirs::Platformdirs; use std::{ - fs, + env, fs, path::{Path, PathBuf}, thread, }; @@ -66,13 +66,25 @@ pub fn get_conda_environment_paths( * /AppData/Local/conda/conda/envs */ fn get_conda_environment_paths_from_conda_rc(env_vars: &EnvVariables) -> Vec { + // Use the conda rc directories as well. + let mut env_dirs = vec![]; + for rc_file_dir in get_conda_rc_search_paths(env_vars) { + if rc_file_dir.is_dir() { + env_dirs.push(rc_file_dir); + } else if rc_file_dir.is_file() { + if let Some(dir) = rc_file_dir.parent() { + env_dirs.push(dir.to_path_buf()); + } + } + } + if let Some(conda_rc) = Condarc::from(env_vars) { trace!("Conda environments in .condarc {:?}", conda_rc.env_dirs); - conda_rc.env_dirs + env_dirs.append(&mut conda_rc.env_dirs.clone()); } else { trace!("No Conda environments in .condarc"); - vec![] } + env_dirs } fn get_conda_environment_paths_from_known_paths(env_vars: &EnvVariables) -> Vec { @@ -100,8 +112,19 @@ fn get_conda_environment_paths_from_known_paths(env_vars: &EnvVariables) -> Vec< // Expland variables in some of these // https://docs.conda.io/projects/conda/en/4.13.x/user-guide/configuration/use-condarc.html#expansion-of-environment-variables if let Some(conda_envs_path) = &env_vars.conda_envs_path { - let conda_envs_path = expand_path(PathBuf::from(conda_envs_path.clone())); - known_conda_paths.push(conda_envs_path); + for path in env::split_paths(&conda_envs_path) { + known_conda_paths.push(expand_path(path)); + } + } + // https://anaconda-project.readthedocs.io/en/latest/config.html + if let Some(conda_envs_path) = &env_vars.anaconda_project_envs_path { + for path in env::split_paths(&conda_envs_path) { + known_conda_paths.push(expand_path(path)); + } + } + // https://anaconda-project.readthedocs.io/en/latest/config.html + if let Some(project_dir) = &env_vars.project_dir { + known_conda_paths.push(expand_path(PathBuf::from(project_dir))); } for path in known_conda_paths { @@ -225,17 +248,53 @@ pub fn get_known_conda_install_locations( if let Some(ref conda_prefix) = env_vars.conda_prefix { known_paths.push(expand_path(PathBuf::from(conda_prefix.clone()))); } + if let Some(ref conda_dir) = env_vars.conda_dir { + known_paths.push(expand_path(PathBuf::from(conda_dir.clone()))); + } if let Some(ref conda) = env_vars.conda { - known_paths.push(PathBuf::from(conda)); + known_paths.push(expand_path(PathBuf::from(conda))); } + let app_data = PathBuf::from(env::var("LOCALAPPDATA").unwrap_or_default()); if let Some(home) = env_vars.clone().home { - known_paths.push(home.clone().join("anaconda3")); - known_paths.push(home.clone().join("miniconda3")); - known_paths.push(home.clone().join("miniforge3")); - known_paths.push(home.clone().join("micromamba")); + for prefix in [ + home.clone(), + // https://stackoverflow.com/questions/35709497/anaconda-python-where-are-the-virtual-environments-stored + home.join(".conda"), + // https://stackoverflow.com/questions/35709497/anaconda-python-where-are-the-virtual-environments-stored + home.join(".local"), + // https://stackoverflow.com/questions/35709497/anaconda-python-where-are-the-virtual-environments-stored + PathBuf::from("C:\\ProgramData"), + PathBuf::from(format!( + "{}:\\ProgramData", + env::var("SYSTEMDRIVE").unwrap_or("C".to_string()) + )), + // https://docs.conda.io/projects/conda/en/latest/user-guide/concepts/environments.html + PathBuf::from("C:\\"), + PathBuf::from(format!( + "{}:\\", + env::var("SYSTEMDRIVE").unwrap_or("C".to_string()) + )), + // https://community.anaconda.cloud/t/conda-update-anaconda/43656/7 + app_data.clone(), + ] { + known_paths.push(prefix.clone().join("anaconda")); + known_paths.push(prefix.clone().join("anaconda3")); + known_paths.push(prefix.clone().join("miniconda")); + known_paths.push(prefix.clone().join("miniconda3")); + known_paths.push(prefix.clone().join("miniforge3")); + known_paths.push(prefix.clone().join("micromamba")); + } + // From ./conda/base/constants.py (conda repo) + known_paths.push(PathBuf::from("C:\\ProgramData\\conda\\conda")); + known_paths.push(PathBuf::from(format!( + "{}:\\ProgramData\\conda\\conda", + env::var("SYSTEMDRIVE").unwrap_or("C".to_string()) + ))); // E.g. C:\Users\user name\.conda where we have `envs`` under this directory. known_paths.push(home.join(".conda")); + known_paths.push(home.join(".local")); // E.g. C:\Users\user name\AppData\Local\conda\conda\envs + known_paths.push(app_data.join("conda").join("conda")); known_paths.push( home.join("AppData") .join("Local") @@ -266,45 +325,58 @@ pub fn get_known_conda_install_locations( env_vars: &EnvVariables, conda_executable: &Option, ) -> Vec { - let mut known_paths = vec![]; - let home_value = env_vars.clone().home.unwrap_or_default(); - let directories_to_look_in = [ - "/opt", - "/usr/share", - "/usr/local", - "/usr", - home_value.to_str().unwrap_or_default(), - "", // We need to look in `/anaconda3` and `/miniconda3` as well. + let mut known_paths = vec![ + // We need to look in `/anaconda3` and `/miniconda3` as well. + PathBuf::from("/anaconda"), + PathBuf::from("/anaconda3"), + PathBuf::from("/miniconda"), + PathBuf::from("/miniconda3"), + PathBuf::from("/miniforge"), + PathBuf::from("/miniforge3"), + PathBuf::from("/micromamba"), ]; - for directory in directories_to_look_in.iter() { - known_paths.push(PathBuf::from(format!("{directory}/anaconda"))); - known_paths.push(PathBuf::from(format!("{directory}/anaconda3"))); - known_paths.push(PathBuf::from(format!("{directory}/miniconda"))); - known_paths.push(PathBuf::from(format!("{directory}/miniconda3"))); - known_paths.push(PathBuf::from(format!("{directory}/miniforge"))); - known_paths.push(PathBuf::from(format!("{directory}/miniforge3"))); - known_paths.push(PathBuf::from(format!("{directory}/micromamba"))); - } if let Some(ref conda_root) = env_vars.conda_root { known_paths.push(expand_path(PathBuf::from(conda_root.clone()))); } if let Some(ref conda_prefix) = env_vars.conda_prefix { known_paths.push(expand_path(PathBuf::from(conda_prefix.clone()))); } + if let Some(ref mamba_root_prefix) = env_vars.mamba_root_prefix { + known_paths.push(expand_path(PathBuf::from(mamba_root_prefix.clone()))); + } if let Some(ref conda_dir) = env_vars.conda_dir { known_paths.push(expand_path(PathBuf::from(conda_dir.clone()))); } if let Some(ref conda) = env_vars.conda { known_paths.push(expand_path(PathBuf::from(conda))); } - if let Some(ref home) = env_vars.home { - known_paths.push(home.clone().join("anaconda")); - known_paths.push(home.clone().join("anaconda3")); - known_paths.push(home.clone().join("miniconda")); - known_paths.push(home.clone().join("miniconda3")); - known_paths.push(home.clone().join("miniforge3")); - known_paths.push(home.clone().join("micromamba")); + if let Some(home) = env_vars.home.clone() { + // https://stackoverflow.com/questions/35709497/anaconda-python-where-are-the-virtual-environments-stored + for prefix in [ + home.clone(), + // https://towardsdatascience.com/manage-your-python-virtual-environment-with-conda-a0d2934d5195 + home.join("opt"), + home.join(".conda"), + home.join(".local"), + // https://docs.conda.io/projects/conda/en/latest/user-guide/concepts/environments.html + PathBuf::from("/opt"), + PathBuf::from("/usr/share"), + PathBuf::from("/usr/local"), + PathBuf::from("/usr"), + PathBuf::from("/opt/homebrew"), + PathBuf::from("/home/linuxbrew/.linuxbrew"), + ] { + known_paths.push(prefix.clone().join("anaconda")); + known_paths.push(prefix.clone().join("anaconda3")); + known_paths.push(prefix.clone().join("miniconda")); + known_paths.push(prefix.clone().join("miniconda3")); + known_paths.push(prefix.clone().join("miniforge3")); + known_paths.push(prefix.clone().join("micromamba")); + } + + known_paths.push(PathBuf::from("/opt").join("conda")); known_paths.push(home.join(".conda")); + known_paths.push(home.join(".local")); } if let Some(conda_dir) = get_conda_dir_from_exe(conda_executable) { known_paths.push(conda_dir); diff --git a/crates/pet-conda/tests/common.rs b/crates/pet-conda/tests/common.rs index 4519d16b..1a282083 100644 --- a/crates/pet-conda/tests/common.rs +++ b/crates/pet-conda/tests/common.rs @@ -32,6 +32,10 @@ pub fn create_env_variables(home: PathBuf, root: PathBuf) -> EnvVariables { xdg_config_home: None, conda_envs_path: None, conda_dir: None, + anaconda_project_envs_path: None, + project_dir: None, + mamba_root_prefix: None, + mambarc: None, } } diff --git a/crates/pet-fs/src/path.rs b/crates/pet-fs/src/path.rs index c3c15bf6..39a2e823 100644 --- a/crates/pet-fs/src/path.rs +++ b/crates/pet-fs/src/path.rs @@ -79,6 +79,24 @@ pub fn expand_path(path: PathBuf) -> PathBuf { } } } + + // Specifically for https://docs.conda.io/projects/conda/en/23.1.x/user-guide/configuration/use-condarc.html#expansion-of-environment-variables + if path.to_str().unwrap_or_default().contains("${USERNAME}") + || path.to_str().unwrap_or_default().contains("${HOME}") + { + let username = env::var("USERNAME") + .or(env::var("USER")) + .unwrap_or_default(); + let home = env::var("HOME") + .or_else(|_| env::var("USERPROFILE")) + .unwrap_or_default(); + return PathBuf::from( + path.to_str() + .unwrap() + .replace("${USERNAME}", &username) + .replace("${HOME}", &home), + ); + } path }