mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-16 01:35:00 +00:00
Improve Python executable name discovery when using alternative implementations (#7649)
There are two parts to this. The first is a restructuring and refactoring. We had some debt around expected executable name generation, which we address here by consolidating into a single function that generates a combination of names. This includes a bit of extra code around free-threaded variants because this was written on top of #7431 — I'll rebase that on top of this. The second addresses some bugs around alternative implementations. Notably, `uv python list` does not discovery executables with alternative implementation names. Now, we properly generate all of the executable names for `VersionRequest::Any` (originally implemented in https://github.com/astral-sh/uv/pull/7508) to properly show all the implementations we can find: ``` ❯ cargo run -q -- python list --no-python-downloads cpython-3.12.6-macos-aarch64-none /opt/homebrew/opt/python@3.12/bin/python3.12 -> ../Frameworks/Python.framework/Versions/3.12/bin/python3.12 cpython-3.11.10-macos-aarch64-none /opt/homebrew/opt/python@3.11/bin/python3.11 -> ../Frameworks/Python.framework/Versions/3.11/bin/python3.11 cpython-3.9.6-macos-aarch64-none /Library/Developer/CommandLineTools/usr/bin/python3 -> ../../Library/Frameworks/Python3.framework/Versions/3.9/bin/python3 pypy-3.10.14-macos-aarch64-none /opt/homebrew/bin/pypy3 -> ../Cellar/pypy3.10/7.3.17/bin/pypy3 ``` While doing both of these changes, I ended up changing the priority of interpreter discovery slightly. For example, given that the executables are in the same directory, do we query `python` or `python3.10` first when you request `--python 3.10`? Previously, we'd check `python3.10` but I think that was an incorrect optimization. I think we should always prefer the bare name (i.e. `python`) first. Similarly, this applies to `python` and an executable for an alternative implementation like `pypy`. If it's not compatible with the request, we'll skip it anyway. We might have to query more interpreters with this approach but it seems rare. Closes https://github.com/astral-sh/uv/issues/7286 superseding https://github.com/astral-sh/uv/pull/7508
This commit is contained in:
parent
63b60bc0c8
commit
0dea932d83
3 changed files with 372 additions and 218 deletions
|
@ -1983,124 +1983,6 @@ mod tests {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_python_pypy_prefers_executable_with_implementation_name() -> Result<()> {
|
||||
let mut context = TestContext::new()?;
|
||||
|
||||
// We should prefer `pypy` executables over `python` executables in the same directory
|
||||
// even if they are both pypy
|
||||
TestContext::create_mock_interpreter(
|
||||
&context.tempdir.join("python"),
|
||||
&PythonVersion::from_str("3.10.0").unwrap(),
|
||||
ImplementationName::PyPy,
|
||||
true,
|
||||
)?;
|
||||
TestContext::create_mock_interpreter(
|
||||
&context.tempdir.join("pypy"),
|
||||
&PythonVersion::from_str("3.10.1").unwrap(),
|
||||
ImplementationName::PyPy,
|
||||
true,
|
||||
)?;
|
||||
context.add_to_search_path(context.tempdir.to_path_buf());
|
||||
|
||||
let python = context.run(|| {
|
||||
find_python_installation(
|
||||
&PythonRequest::parse("pypy@3.10"),
|
||||
EnvironmentPreference::Any,
|
||||
PythonPreference::OnlySystem,
|
||||
&context.cache,
|
||||
)
|
||||
})??;
|
||||
assert_eq!(
|
||||
python.interpreter().python_full_version().to_string(),
|
||||
"3.10.1",
|
||||
);
|
||||
|
||||
// But `python` executables earlier in the search path will take precedence
|
||||
context.reset_search_path();
|
||||
context.add_python_interpreters(&[
|
||||
(true, ImplementationName::PyPy, "python", "3.10.2"),
|
||||
(true, ImplementationName::PyPy, "pypy", "3.10.3"),
|
||||
])?;
|
||||
let python = context.run(|| {
|
||||
find_python_installation(
|
||||
&PythonRequest::parse("pypy@3.10"),
|
||||
EnvironmentPreference::Any,
|
||||
PythonPreference::OnlySystem,
|
||||
&context.cache,
|
||||
)
|
||||
})??;
|
||||
assert_eq!(
|
||||
python.interpreter().python_full_version().to_string(),
|
||||
"3.10.2",
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_python_pypy_prefers_executable_with_version() -> Result<()> {
|
||||
let mut context = TestContext::new()?;
|
||||
TestContext::create_mock_interpreter(
|
||||
&context.tempdir.join("pypy3.10"),
|
||||
&PythonVersion::from_str("3.10.0").unwrap(),
|
||||
ImplementationName::PyPy,
|
||||
true,
|
||||
)?;
|
||||
TestContext::create_mock_interpreter(
|
||||
&context.tempdir.join("pypy"),
|
||||
&PythonVersion::from_str("3.10.1").unwrap(),
|
||||
ImplementationName::PyPy,
|
||||
true,
|
||||
)?;
|
||||
context.add_to_search_path(context.tempdir.to_path_buf());
|
||||
|
||||
let python = context.run(|| {
|
||||
find_python_installation(
|
||||
&PythonRequest::parse("pypy@3.10"),
|
||||
EnvironmentPreference::Any,
|
||||
PythonPreference::OnlySystem,
|
||||
&context.cache,
|
||||
)
|
||||
})??;
|
||||
assert_eq!(
|
||||
python.interpreter().python_full_version().to_string(),
|
||||
"3.10.0",
|
||||
"We should prefer executables with the version number over those with implementation names"
|
||||
);
|
||||
|
||||
let mut context = TestContext::new()?;
|
||||
TestContext::create_mock_interpreter(
|
||||
&context.tempdir.join("python3.10"),
|
||||
&PythonVersion::from_str("3.10.0").unwrap(),
|
||||
ImplementationName::PyPy,
|
||||
true,
|
||||
)?;
|
||||
TestContext::create_mock_interpreter(
|
||||
&context.tempdir.join("pypy"),
|
||||
&PythonVersion::from_str("3.10.1").unwrap(),
|
||||
ImplementationName::PyPy,
|
||||
true,
|
||||
)?;
|
||||
context.add_to_search_path(context.tempdir.to_path_buf());
|
||||
|
||||
let python = context.run(|| {
|
||||
find_python_installation(
|
||||
&PythonRequest::parse("pypy@3.10"),
|
||||
EnvironmentPreference::Any,
|
||||
PythonPreference::OnlySystem,
|
||||
&context.cache,
|
||||
)
|
||||
})??;
|
||||
assert_eq!(
|
||||
python.interpreter().python_full_version().to_string(),
|
||||
"3.10.1",
|
||||
"We should prefer an implementation name executable over a generic name with a version"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_python_graalpy() -> Result<()> {
|
||||
let mut context = TestContext::new()?;
|
||||
|
@ -2203,11 +2085,11 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn find_python_graalpy_prefers_executable_with_implementation_name() -> Result<()> {
|
||||
fn find_python_prefers_generic_executable_over_implementation_name() -> Result<()> {
|
||||
let mut context = TestContext::new()?;
|
||||
|
||||
// We should prefer `graalpy` executables over `python` executables in the same directory
|
||||
// even if they are both graalpy
|
||||
// We prefer `python` executables over `graalpy` executables in the same directory
|
||||
// if they are both GraalPy
|
||||
TestContext::create_mock_interpreter(
|
||||
&context.tempdir.join("python"),
|
||||
&PythonVersion::from_str("3.10.0").unwrap(),
|
||||
|
@ -2232,10 +2114,10 @@ mod tests {
|
|||
})??;
|
||||
assert_eq!(
|
||||
python.interpreter().python_full_version().to_string(),
|
||||
"3.10.1",
|
||||
"3.10.0",
|
||||
);
|
||||
|
||||
// But `python` executables earlier in the search path will take precedence
|
||||
// And `python` executables earlier in the search path will take precedence
|
||||
context.reset_search_path();
|
||||
context.add_python_interpreters(&[
|
||||
(true, ImplementationName::GraalPy, "python", "3.10.2"),
|
||||
|
@ -2254,6 +2136,88 @@ mod tests {
|
|||
"3.10.2",
|
||||
);
|
||||
|
||||
// But `graalpy` executables earlier in the search path will take precedence
|
||||
context.reset_search_path();
|
||||
context.add_python_interpreters(&[
|
||||
(true, ImplementationName::GraalPy, "graalpy", "3.10.3"),
|
||||
(true, ImplementationName::GraalPy, "python", "3.10.2"),
|
||||
])?;
|
||||
let python = context.run(|| {
|
||||
find_python_installation(
|
||||
&PythonRequest::parse("graalpy@3.10"),
|
||||
EnvironmentPreference::Any,
|
||||
PythonPreference::OnlySystem,
|
||||
&context.cache,
|
||||
)
|
||||
})??;
|
||||
assert_eq!(
|
||||
python.interpreter().python_full_version().to_string(),
|
||||
"3.10.3",
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_python_prefers_generic_executable_over_one_with_version() -> Result<()> {
|
||||
let mut context = TestContext::new()?;
|
||||
TestContext::create_mock_interpreter(
|
||||
&context.tempdir.join("pypy3.10"),
|
||||
&PythonVersion::from_str("3.10.0").unwrap(),
|
||||
ImplementationName::PyPy,
|
||||
true,
|
||||
)?;
|
||||
TestContext::create_mock_interpreter(
|
||||
&context.tempdir.join("pypy"),
|
||||
&PythonVersion::from_str("3.10.1").unwrap(),
|
||||
ImplementationName::PyPy,
|
||||
true,
|
||||
)?;
|
||||
context.add_to_search_path(context.tempdir.to_path_buf());
|
||||
|
||||
let python = context.run(|| {
|
||||
find_python_installation(
|
||||
&PythonRequest::parse("pypy@3.10"),
|
||||
EnvironmentPreference::Any,
|
||||
PythonPreference::OnlySystem,
|
||||
&context.cache,
|
||||
)
|
||||
})??;
|
||||
assert_eq!(
|
||||
python.interpreter().python_full_version().to_string(),
|
||||
"3.10.1",
|
||||
"We should prefer the generic executable over one with the version number"
|
||||
);
|
||||
|
||||
let mut context = TestContext::new()?;
|
||||
TestContext::create_mock_interpreter(
|
||||
&context.tempdir.join("python3.10"),
|
||||
&PythonVersion::from_str("3.10.0").unwrap(),
|
||||
ImplementationName::PyPy,
|
||||
true,
|
||||
)?;
|
||||
TestContext::create_mock_interpreter(
|
||||
&context.tempdir.join("pypy"),
|
||||
&PythonVersion::from_str("3.10.1").unwrap(),
|
||||
ImplementationName::PyPy,
|
||||
true,
|
||||
)?;
|
||||
context.add_to_search_path(context.tempdir.to_path_buf());
|
||||
|
||||
let python = context.run(|| {
|
||||
find_python_installation(
|
||||
&PythonRequest::parse("pypy@3.10"),
|
||||
EnvironmentPreference::Any,
|
||||
PythonPreference::OnlySystem,
|
||||
&context.cache,
|
||||
)
|
||||
})??;
|
||||
assert_eq!(
|
||||
python.interpreter().python_full_version().to_string(),
|
||||
"3.10.0",
|
||||
"We should prefer the generic name with a version over one the implementation name"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue