diff --git a/crates/pet-fs/src/path.rs b/crates/pet-fs/src/path.rs index d64621f3..c4ead747 100644 --- a/crates/pet-fs/src/path.rs +++ b/crates/pet-fs/src/path.rs @@ -3,9 +3,12 @@ use std::{ env, - path::{Path, PathBuf, MAIN_SEPARATOR}, + path::{Path, PathBuf}, }; +#[cfg(unix)] +use std::path::MAIN_SEPARATOR; + /// Strips trailing path separators from a path, preserving root paths. /// /// This function removes trailing `/` or `\` from paths while ensuring that root paths diff --git a/crates/pet-windows-store/src/environments.rs b/crates/pet-windows-store/src/environments.rs index 2d520e52..4de6dc4f 100644 --- a/crates/pet-windows-store/src/environments.rs +++ b/crates/pet-windows-store/src/environments.rs @@ -56,6 +56,33 @@ impl PotentialPython { if let Some(result) = get_package_display_name_and_location(&name, hkcu) { let env_path = norm_case(PathBuf::from(result.env_path)); + // Build the base symlinks list + // parent = WindowsApps folder (e.g., C:\Users\...\AppData\Local\Microsoft\WindowsApps) + // path = Package folder inside WindowsApps (e.g., WindowsApps\PythonSoftwareFoundation.Python.3.12_...) + // env_path = Program Files location (e.g., C:\Program Files\WindowsApps\PythonSoftwareFoundation...) + let mut symlinks = vec![ + // Symlinks in the user WindowsApps folder + parent.join(format!("python{}.exe", self.version)), + parent.join("python3.exe"), + parent.join("python.exe"), + // Symlinks in the package subfolder under user WindowsApps + path.join("python.exe"), + path.join("python3.exe"), + path.join(format!("python{}.exe", self.version)), + // Symlinks in Program Files + env_path.join("python.exe"), + env_path.join("python3.exe"), + env_path.join(format!("python{}.exe", self.version)), + ]; + + // Add symlinks discovered by find_symlinks (includes python.exe and python3.exe + // from WindowsApps when there's only one Python version installed) + for symlink in &self.symlinks { + if !symlinks.contains(symlink) { + symlinks.push(symlink.clone()); + } + } + Some( PythonEnvironmentBuilder::new(Some( pet_core::python_environment::PythonEnvironmentKind::WindowsStore, @@ -70,14 +97,7 @@ impl PotentialPython { }) // We only have the partial version, no point returning bogus info. // .version(Some(self.version.clone())) - .symlinks(Some(vec![ - parent.join(format!("python{}.exe", self.version)), - path.join("python.exe"), - path.join("python3.exe"), - path.join(format!("python{}.exe", self.version)), - env_path.join("python.exe"), - env_path.join(format!("python{}.exe", self.version)), - ])) + .symlinks(Some(symlinks)) .build(), ) } else { diff --git a/crates/pet-windows-store/src/lib.rs b/crates/pet-windows-store/src/lib.rs index 16f79d44..77ccafc6 100644 --- a/crates/pet-windows-store/src/lib.rs +++ b/crates/pet-windows-store/src/lib.rs @@ -67,25 +67,42 @@ impl Locator for WindowsStore { use std::path::PathBuf; use pet_core::python_environment::PythonEnvironmentBuilder; + use pet_fs::path::norm_case; use pet_virtualenv::is_virtualenv; + // Helper to normalize paths for comparison by stripping \\?\ prefix + fn normalize_for_comparison(path: &PathBuf) -> PathBuf { + let normalized = norm_case(path); + let path_str = normalized.to_string_lossy(); + if path_str.starts_with(r"\\?\") { + PathBuf::from(path_str.trim_start_matches(r"\\?\")) + } else { + normalized + } + } + // Assume we create a virtual env from a python install, // Then the exe in the virtual env bin will be a symlink to the homebrew python install. // Hence the first part of the condition will be true, but the second part will be false. if is_virtualenv(env) { return None; } - let list_of_possible_exes = vec![env.executable.clone()] + // Normalize paths to handle \\?\ prefix differences + let list_of_possible_exes: Vec = vec![env.executable.clone()] .into_iter() .chain(env.symlinks.clone().unwrap_or_default()) - .collect::>(); + .map(|p| normalize_for_comparison(&p)) + .collect(); if let Some(environments) = self.find_with_cache() { for found_env in environments { if let Some(symlinks) = &found_env.symlinks { + // Normalize symlinks for comparison + let normalized_symlinks: Vec = + symlinks.iter().map(normalize_for_comparison).collect(); // Check if we have found this exe. if list_of_possible_exes .iter() - .any(|exe| symlinks.contains(exe)) + .any(|exe| normalized_symlinks.contains(exe)) { // Its possible the env discovery was not aware of the symlink // E.g. if we are asked to resolve `../WindowsApp/python.exe`