diff --git a/crates/pet-env-var-path/src/lib.rs b/crates/pet-env-var-path/src/lib.rs index bd9aef9a..d6a91f19 100644 --- a/crates/pet-env-var-path/src/lib.rs +++ b/crates/pet-env-var-path/src/lib.rs @@ -3,7 +3,6 @@ use pet_core::os_environment::Environment; use std::collections::HashSet; -use std::fs; use std::path::PathBuf; pub fn get_search_paths_from_env_variables(environment: &dyn Environment) -> Vec { @@ -19,7 +18,7 @@ pub fn get_search_paths_from_env_variables(environment: &dyn Environment) -> Vec environment .get_know_global_search_locations() .into_iter() - .map(|p| fs::canonicalize(&p).unwrap_or(p)) + .map(normalize_search_path) .collect::>() .into_iter() .filter(|p| !p.starts_with(apps_path.clone())) @@ -28,3 +27,28 @@ pub fn get_search_paths_from_env_variables(environment: &dyn Environment) -> Vec Vec::new() } } + +/// Normalizes a search path for deduplication purposes. +/// +/// On Unix: Uses fs::canonicalize to resolve symlinks. This is important for merged-usr +/// systems where /bin, /sbin, /usr/sbin are symlinks to /usr/bin - we don't want to +/// report the same Python installation multiple times. +/// See: https://github.com/microsoft/python-environment-tools/pull/200 +/// +/// On Windows: Uses norm_case (GetLongPathNameW) to normalize case WITHOUT resolving +/// directory junctions. This is important for tools like Scoop that use junctions +/// (e.g., python\current -> python\3.13.3). Using fs::canonicalize would resolve +/// the junction, causing symlink tracking to fail when the shim points to the +/// junction path but executables are discovered from the resolved path. +/// See: https://github.com/microsoft/python-environment-tools/issues/187 +fn normalize_search_path(path: PathBuf) -> PathBuf { + #[cfg(unix)] + { + std::fs::canonicalize(&path).unwrap_or(path) + } + + #[cfg(windows)] + { + pet_fs::path::norm_case(&path) + } +}