Skip to content

Commit 34acfe4

Browse files
Respect py --list-paths fallback in --python python3 invocations (#2214)
## Summary This makes `--python python3` and `--python 3.10` more consistent on Windows. Closes #2213. ## Test Plan Ran `cargo run venv --python python3.12` with the Windows Store Python.
1 parent aeb80e3 commit 34acfe4

File tree

1 file changed

+74
-41
lines changed

1 file changed

+74
-41
lines changed

crates/uv-interpreter/src/python_query.rs

Lines changed: 74 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ fn find_python(
115115
#[allow(non_snake_case)]
116116
let UV_TEST_PYTHON_PATH = env::var_os("UV_TEST_PYTHON_PATH");
117117

118-
let override_path = UV_TEST_PYTHON_PATH.is_some();
118+
let use_override = UV_TEST_PYTHON_PATH.is_some();
119119
let possible_names = selector.possible_names();
120120

121121
#[allow(non_snake_case)]
@@ -182,11 +182,17 @@ fn find_python(
182182
}
183183
}
184184

185-
if cfg!(windows) && !override_path {
185+
if cfg!(windows) && !use_override {
186186
// Use `py` to find the python installation on the system.
187-
match windows::py_list_paths(selector, platform, cache) {
188-
Ok(Some(interpreter)) => return Ok(Some(interpreter)),
189-
Ok(None) => {}
187+
match windows::py_list_paths() {
188+
Ok(paths) => {
189+
for entry in paths {
190+
let installation = PythonInstallation::PyListPath(entry);
191+
if let Some(interpreter) = installation.select(selector, platform, cache)? {
192+
return Ok(Some(interpreter));
193+
}
194+
}
195+
}
190196
Err(Error::PyList(error)) => {
191197
if error.kind() == std::io::ErrorKind::NotFound {
192198
debug!("`py` is not installed");
@@ -209,6 +215,8 @@ fn find_executable<R: AsRef<OsStr> + Into<OsString> + Copy>(
209215
#[allow(non_snake_case)]
210216
let UV_TEST_PYTHON_PATH = env::var_os("UV_TEST_PYTHON_PATH");
211217

218+
let use_override = UV_TEST_PYTHON_PATH.is_some();
219+
212220
#[allow(non_snake_case)]
213221
let PATH = UV_TEST_PYTHON_PATH
214222
.or(env::var_os("PATH"))
@@ -231,30 +239,62 @@ fn find_executable<R: AsRef<OsStr> + Into<OsString> + Copy>(
231239
}
232240
}
233241

242+
if cfg!(windows) && !use_override {
243+
// Use `py` to find the python installation on the system.
244+
match windows::py_list_paths() {
245+
Ok(paths) => {
246+
for entry in paths {
247+
// Ex) `--python python3.12.exe`
248+
if entry.executable_path.file_name() == Some(requested.as_ref()) {
249+
return Ok(Some(entry.executable_path));
250+
}
251+
252+
// Ex) `--python python3.12`
253+
if entry
254+
.executable_path
255+
.file_stem()
256+
.is_some_and(|stem| stem == requested.as_ref())
257+
{
258+
return Ok(Some(entry.executable_path));
259+
}
260+
}
261+
}
262+
Err(Error::PyList(error)) => {
263+
if error.kind() == std::io::ErrorKind::NotFound {
264+
debug!("`py` is not installed");
265+
}
266+
}
267+
Err(error) => return Err(error),
268+
}
269+
}
270+
234271
Ok(None)
235272
}
236273

274+
#[derive(Debug, Clone)]
275+
struct PyListPath {
276+
major: u8,
277+
minor: u8,
278+
executable_path: PathBuf,
279+
}
280+
237281
#[derive(Debug, Clone)]
238282
enum PythonInstallation {
239-
PyListPath {
240-
major: u8,
241-
minor: u8,
242-
executable_path: PathBuf,
243-
},
283+
PyListPath(PyListPath),
244284
Interpreter(Interpreter),
245285
}
246286

247287
impl PythonInstallation {
248288
fn major(&self) -> u8 {
249289
match self {
250-
Self::PyListPath { major, .. } => *major,
290+
Self::PyListPath(PyListPath { major, .. }) => *major,
251291
Self::Interpreter(interpreter) => interpreter.python_major(),
252292
}
253293
}
254294

255295
fn minor(&self) -> u8 {
256296
match self {
257-
Self::PyListPath { minor, .. } => *minor,
297+
Self::PyListPath(PyListPath { minor, .. }) => *minor,
258298
Self::Interpreter(interpreter) => interpreter.python_minor(),
259299
}
260300
}
@@ -268,6 +308,7 @@ impl PythonInstallation {
268308
) -> Result<Option<Interpreter>, Error> {
269309
let selected = match selector {
270310
PythonVersionSelector::Default => true,
311+
271312
PythonVersionSelector::Major(major) => self.major() == major,
272313

273314
PythonVersionSelector::MajorMinor(major, minor) => {
@@ -302,9 +343,9 @@ impl PythonInstallation {
302343
cache: &Cache,
303344
) -> Result<Interpreter, Error> {
304345
match self {
305-
Self::PyListPath {
346+
Self::PyListPath(PyListPath {
306347
executable_path, ..
307-
} => Interpreter::query(&executable_path, platform.clone(), cache),
348+
}) => Interpreter::query(&executable_path, platform.clone(), cache),
308349
Self::Interpreter(interpreter) => Ok(interpreter),
309350
}
310351
}
@@ -373,11 +414,8 @@ mod windows {
373414
use regex::Regex;
374415
use tracing::info_span;
375416

376-
use platform_host::Platform;
377-
use uv_cache::Cache;
378-
379-
use crate::python_query::{PythonInstallation, PythonVersionSelector};
380-
use crate::{Error, Interpreter};
417+
use crate::python_query::PyListPath;
418+
use crate::Error;
381419

382420
/// ```text
383421
/// -V:3.12 C:\Users\Ferris\AppData\Local\Programs\Python\Python312\python.exe
@@ -392,11 +430,7 @@ mod windows {
392430
///
393431
/// The command takes 8ms on my machine.
394432
/// TODO(konstin): Implement <https://peps.python.org/pep-0514/> to read python installations from the registry instead.
395-
pub(super) fn py_list_paths(
396-
selector: PythonVersionSelector,
397-
platform: &Platform,
398-
cache: &Cache,
399-
) -> Result<Option<Interpreter>, Error> {
433+
pub(super) fn py_list_paths() -> Result<Vec<PyListPath>, Error> {
400434
let output = info_span!("py_list_paths")
401435
.in_scope(|| Command::new("py").arg("--list-paths").output())
402436
.map_err(Error::PyList)?;
@@ -421,24 +455,23 @@ mod windows {
421455
stderr: String::from_utf8_lossy(&output.stderr).trim().to_string(),
422456
})?;
423457

424-
for captures in PY_LIST_PATHS.captures_iter(&stdout) {
425-
let (_, [major, minor, path]) = captures.extract();
426-
427-
if let (Some(major), Some(minor)) = (major.parse::<u8>().ok(), minor.parse::<u8>().ok())
428-
{
429-
let installation = PythonInstallation::PyListPath {
430-
major,
431-
minor,
432-
executable_path: PathBuf::from(path),
433-
};
434-
435-
if let Some(interpreter) = installation.select(selector, platform, cache)? {
436-
return Ok(Some(interpreter));
458+
Ok(PY_LIST_PATHS
459+
.captures_iter(&stdout)
460+
.filter_map(|captures| {
461+
let (_, [major, minor, path]) = captures.extract();
462+
if let (Some(major), Some(minor)) =
463+
(major.parse::<u8>().ok(), minor.parse::<u8>().ok())
464+
{
465+
Some(PyListPath {
466+
major,
467+
minor,
468+
executable_path: PathBuf::from(path),
469+
})
470+
} else {
471+
None
437472
}
438-
}
439-
}
440-
441-
Ok(None)
473+
})
474+
.collect())
442475
}
443476

444477
/// On Windows we might encounter the windows store proxy shim (Enabled in Settings/Apps/Advanced app settings/App execution aliases).

0 commit comments

Comments
 (0)