diff --git a/crates/uv-interpreter/src/python_query.rs b/crates/uv-interpreter/src/python_query.rs index 3984e5ae0..549a559d3 100644 --- a/crates/uv-interpreter/src/python_query.rs +++ b/crates/uv-interpreter/src/python_query.rs @@ -330,7 +330,7 @@ impl PythonVersionSelector { } mod windows { - use std::path::PathBuf; + use std::path::{Path, PathBuf}; use std::process::Command; use once_cell::sync::Lazy; @@ -412,17 +412,56 @@ mod windows { /// does not want us to do this as the format is unstable. So this is a best effort way. /// we just hope that the reparse point has the python redirector in it, when it's not /// pointing to a valid Python. - pub(super) fn is_windows_store_shim(path: &std::path::Path) -> bool { + /// + /// Matches against paths like: + /// `C:\Users\crmar\AppData\Local\Microsoft\WindowsApps\python.exe` + pub(super) fn is_windows_store_shim(path: &Path) -> bool { // Rye uses a more sophisticated test to identify the windows store shim. // Unfortunately, it only works with the `python.exe` shim but not `python3.exe`. // What we do here is a very naive implementation but probably sufficient for all we need. // There's the risk of false positives but I consider it rare, considering how specific // the path is. - // Rye Shim detection: https://github.com/mitsuhiko/rye/blob/78bf4d010d5e2e88ebce1ba636c7acec97fd454d/rye/src/cli/shim.rs#L100-L172 - path.to_str().map_or(false, |path| { - path.ends_with("Local\\Microsoft\\WindowsApps\\python.exe") - || path.ends_with("Local\\Microsoft\\WindowsApps\\python3.exe") - }) + if !path.is_absolute() { + return false; + } + + let mut components = path.components().rev(); + + // Ex) `python.exe` or `python3.exe` or `python3.12.exe` + if !components + .next() + .and_then(|component| component.as_os_str().to_str()) + .and_then(|component| component.rsplit_once('.')) + .is_some_and(|(name, extension)| name.starts_with("python") && extension == "exe") + { + return false; + } + + // Ex) `WindowsApps` + if !components + .next() + .is_some_and(|component| component.as_os_str() == "WindowsApps") + { + return false; + } + + // Ex) `Microsoft` + if !components + .next() + .is_some_and(|component| component.as_os_str() == "Microsoft") + { + return false; + } + + // Ex) `Local` + if !components + .next() + .is_some_and(|component| component.as_os_str() == "Local") + { + return false; + } + + true } #[cfg(test)] @@ -464,6 +503,22 @@ mod windows { "###); }); } + + #[test] + fn detect_shim() { + assert!(super::is_windows_store_shim( + r"C:\Users\crmar\AppData\Local\Microsoft\WindowsApps\python.exe".as_ref() + )); + assert!(super::is_windows_store_shim( + r"C:\Users\crmar\AppData\Local\Microsoft\WindowsApps\python3.exe".as_ref() + )); + assert!(super::is_windows_store_shim( + r"C:\Users\crmar\AppData\Local\Microsoft\WindowsApps\python3.12.exe".as_ref() + )); + assert!(!super::is_windows_store_shim( + r"C:\Users\crmar\AppData\Local\Microsoft\WindowsApps\PythonSoftwareFoundation.Python.3.11_qbs5n2kfra8p0\python.exe".as_ref() + )); + } } }