mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-24 05:35:04 +00:00
Use windows registry to discover python (#6761)
Our current strategy of parsing the output of `py --list-paths` to get the installed python versions on windows is brittle (#6524, missing `py`, etc.) and it's slow (10ms last time i measured). Instead, we should behave spec-compliant and read the python versions from the registry following PEP 514. It's not fully clear which errors we should ignore and which ones we need to raise. We're using the official rust-for-windows crates for accessing the registry. Fixes #1521 Fixes #6524
This commit is contained in:
parent
57cb9c2957
commit
a39eb61ade
10 changed files with 156 additions and 124 deletions
|
@ -20,7 +20,8 @@ use crate::implementation::ImplementationName;
|
|||
use crate::installation::PythonInstallation;
|
||||
use crate::interpreter::Error as InterpreterError;
|
||||
use crate::managed::ManagedPythonInstallations;
|
||||
use crate::py_launcher::{self, py_list_paths};
|
||||
#[cfg(windows)]
|
||||
use crate::py_launcher::registry_pythons;
|
||||
use crate::virtualenv::{
|
||||
conda_prefix_from_env, virtualenv_from_env, virtualenv_from_working_dir,
|
||||
virtualenv_python_executable,
|
||||
|
@ -164,8 +165,8 @@ pub enum PythonSource {
|
|||
DiscoveredEnvironment,
|
||||
/// An executable was found in the search path i.e. `PATH`
|
||||
SearchPath,
|
||||
/// An executable was found via the `py` launcher
|
||||
PyLauncher,
|
||||
/// An executable was found in the Windows registry via PEP 514
|
||||
Registry,
|
||||
/// The Python installation was found in the uv managed Python directory
|
||||
Managed,
|
||||
/// The Python installation was found via the invoking interpreter i.e. via `python -m uv ...`
|
||||
|
@ -189,9 +190,9 @@ pub enum Error {
|
|||
#[error(transparent)]
|
||||
VirtualEnv(#[from] crate::virtualenv::Error),
|
||||
|
||||
/// An error was encountered when using the `py` launcher on Windows.
|
||||
#[error(transparent)]
|
||||
PyLauncher(#[from] crate::py_launcher::Error),
|
||||
#[cfg(windows)]
|
||||
#[error("Failed to query installed Python versions from the Windows registry")]
|
||||
RegistryError(#[from] windows_result::Error),
|
||||
|
||||
/// An invalid version request was given
|
||||
#[error("Invalid version request: {0}")]
|
||||
|
@ -307,23 +308,40 @@ fn python_executables_from_installed<'a>(
|
|||
})
|
||||
.flatten();
|
||||
|
||||
// TODO(konstin): Implement <https://peps.python.org/pep-0514/> to read python installations from the registry instead.
|
||||
let from_py_launcher = std::iter::once_with(move || {
|
||||
(cfg!(windows) && env::var_os("UV_TEST_PYTHON_PATH").is_none())
|
||||
.then(|| {
|
||||
py_list_paths()
|
||||
.map(|entries|
|
||||
// We can avoid querying the interpreter using versions from the py launcher output unless a patch is requested
|
||||
entries.into_iter().filter(move |entry|
|
||||
version.is_none() || version.is_some_and(|version|
|
||||
version.has_patch() || version.matches_major_minor(entry.major, entry.minor)
|
||||
)
|
||||
)
|
||||
.map(|entry| (PythonSource::PyLauncher, entry.executable_path)))
|
||||
.map_err(Error::from)
|
||||
})
|
||||
.into_iter()
|
||||
.flatten_ok()
|
||||
#[cfg(windows)]
|
||||
{
|
||||
env::var_os("UV_TEST_PYTHON_PATH")
|
||||
.is_none()
|
||||
.then(|| {
|
||||
registry_pythons()
|
||||
.map(|entries| {
|
||||
entries
|
||||
.into_iter()
|
||||
.filter(move |entry| {
|
||||
// Skip interpreter probing if we already know the version
|
||||
// doesn't match.
|
||||
if let Some(version_request) = version {
|
||||
if let Some(version) = &entry.version {
|
||||
version_request.matches_version(version)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.map(|entry| (PythonSource::Registry, entry.path))
|
||||
})
|
||||
.map_err(Error::from)
|
||||
})
|
||||
.into_iter()
|
||||
.flatten_ok()
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
Vec::new()
|
||||
}
|
||||
})
|
||||
.flatten();
|
||||
|
||||
|
@ -626,11 +644,6 @@ impl Error {
|
|||
false
|
||||
}
|
||||
},
|
||||
// Ignore `py` if it's not installed
|
||||
Error::PyLauncher(py_launcher::Error::NotFound) => {
|
||||
debug!("The `py` launcher could not be found to query for Python versions");
|
||||
false
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
@ -1293,7 +1306,7 @@ impl PythonPreference {
|
|||
// If not dealing with a system interpreter source, we don't care about the preference
|
||||
if !matches!(
|
||||
source,
|
||||
PythonSource::Managed | PythonSource::SearchPath | PythonSource::PyLauncher
|
||||
PythonSource::Managed | PythonSource::SearchPath | PythonSource::Registry
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
@ -1302,10 +1315,10 @@ impl PythonPreference {
|
|||
PythonPreference::OnlyManaged => matches!(source, PythonSource::Managed),
|
||||
Self::Managed | Self::System => matches!(
|
||||
source,
|
||||
PythonSource::Managed | PythonSource::SearchPath | PythonSource::PyLauncher
|
||||
PythonSource::Managed | PythonSource::SearchPath | PythonSource::Registry
|
||||
),
|
||||
PythonPreference::OnlySystem => {
|
||||
matches!(source, PythonSource::SearchPath | PythonSource::PyLauncher)
|
||||
matches!(source, PythonSource::SearchPath | PythonSource::Registry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1619,7 +1632,7 @@ impl fmt::Display for PythonSource {
|
|||
Self::CondaPrefix => f.write_str("conda prefix"),
|
||||
Self::DiscoveredEnvironment => f.write_str("virtual environment"),
|
||||
Self::SearchPath => f.write_str("search path"),
|
||||
Self::PyLauncher => f.write_str("`py` launcher output"),
|
||||
Self::Registry => f.write_str("registry"),
|
||||
Self::Managed => f.write_str("managed installations"),
|
||||
Self::ParentInterpreter => f.write_str("parent interpreter"),
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue