diff --git a/Cargo.lock b/Cargo.lock index 8bfdb2c7..db600348 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -494,6 +494,7 @@ dependencies = [ "regex", "serde", "serde_json", + "tempfile", "yaml-rust2", ] diff --git a/crates/pet-conda/Cargo.toml b/crates/pet-conda/Cargo.toml index fa5f85e7..579dd3d8 100644 --- a/crates/pet-conda/Cargo.toml +++ b/crates/pet-conda/Cargo.toml @@ -21,5 +21,8 @@ env_logger = "0.10.2" yaml-rust2 = "0.8.1" rayon = "1.11.0" +[dev-dependencies] +tempfile = "3.13" + [features] ci = [] diff --git a/crates/pet-conda/src/environment_locations.rs b/crates/pet-conda/src/environment_locations.rs index ee7d06d7..0211a63e 100644 --- a/crates/pet-conda/src/environment_locations.rs +++ b/crates/pet-conda/src/environment_locations.rs @@ -319,6 +319,25 @@ pub fn get_conda_envs_from_environment_txt(env_vars: &EnvVariables) -> Vec PathBuf { + let Some(parent) = path.parent() else { + return path; + }; + let Some(file_name) = path.file_name() else { + return path; + }; + let Ok(entries) = fs::read_dir(parent) else { + return path; + }; + + entries + .filter_map(Result::ok) + .find(|entry| entry.file_name().eq_ignore_ascii_case(file_name)) + .map(|entry| entry.path()) + .unwrap_or(path) +} + #[cfg(windows)] pub fn get_known_conda_install_locations( env_vars: &EnvVariables, @@ -416,15 +435,6 @@ pub fn get_known_conda_install_locations( .join("conda"), ); } - known_paths.sort(); - known_paths.dedup(); - // Ensure the casing of the paths are correct. - // Its possible the actual path is in a different case. - // E.g. instead of C:\username\miniconda it might bt C:\username\Miniconda - // We use lower cases above, but it could be in any case on disc. - // We do not want to have duplicates in different cases. - // & we'd like to preserve the case of the original path as on disc. - known_paths = known_paths.iter().map(norm_case).collect(); if let Some(conda_dir) = get_conda_dir_from_exe(conda_executable) { known_paths.push(conda_dir); } @@ -436,6 +446,13 @@ pub fn get_known_conda_install_locations( if let Some(mamba_dir) = get_conda_dir_from_exe(&find_mamba_binary(env_vars)) { known_paths.push(mamba_dir); } + + known_paths = known_paths + .into_iter() + .filter(|path| path.exists()) + .map(norm_case) + .map(restore_existing_leaf_case) + .collect(); known_paths.sort(); known_paths.dedup(); diff --git a/crates/pet-conda/tests/environment_locations_test.rs b/crates/pet-conda/tests/environment_locations_test.rs index c1c401e0..092bc645 100644 --- a/crates/pet-conda/tests/environment_locations_test.rs +++ b/crates/pet-conda/tests/environment_locations_test.rs @@ -205,3 +205,46 @@ fn skips_path_lookup_when_conda_executable_provided() { locations ); } + +#[cfg(windows)] +#[test] +fn deduplicates_windows_install_aliases_and_preserves_disk_casing() { + use common::create_env_variables; + use pet_conda::environment_locations::get_conda_environment_paths; + use pet_fs::path::norm_case; + use std::fs; + + let temp_dir = tempfile::tempdir().expect("failed to create temporary test directory"); + let home = temp_dir.path(); + let install = home.join("Miniconda3"); + let child = install.join("envs").join("MyEnv"); + + fs::create_dir_all(install.join("conda-meta")) + .expect("failed to create base conda-meta directory"); + fs::create_dir_all(install.join("condabin")).expect("failed to create base condabin directory"); + fs::create_dir_all(child.join("conda-meta")) + .expect("failed to create child conda-meta directory"); + + let conda_state = home.join(".conda"); + fs::create_dir_all(&conda_state).expect("failed to create .conda directory"); + fs::write( + conda_state.join("environments.txt"), + format!("{}\n{}\n", install.display(), child.display()), + ) + .expect("failed to write environments.txt"); + + let mut env = create_env_variables(home.to_path_buf(), home.to_path_buf()); + env.userprofile = Some(home.to_string_lossy().into_owned()); + + let environments = get_conda_environment_paths(&env, &None); + let normalized_home = norm_case(home); + let mut local_environments = environments + .into_iter() + .filter(|path| path.starts_with(&normalized_home)) + .collect::>(); + local_environments.sort(); + + let mut expected = vec![norm_case(install), norm_case(child)]; + expected.sort(); + assert_eq!(local_environments, expected); +}