mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 02:48:17 +00:00
Discover Microsoft Store Pythons (#6807)
Microsoft Store Pythons do not always register themselves in the
registry, so we port
<58ce131037/PC/launcher2.c (L1744)
>
and look them up on the filesystem in known locations.
## Test Plan
So far I've confirmed that we find a store Python when I use `cargo run
python list`, can we make this a part of any of the platform tests
maybe?
This commit is contained in:
parent
a39eb61ade
commit
9814852295
4 changed files with 155 additions and 23 deletions
|
@ -21,7 +21,9 @@ use crate::installation::PythonInstallation;
|
|||
use crate::interpreter::Error as InterpreterError;
|
||||
use crate::managed::ManagedPythonInstallations;
|
||||
#[cfg(windows)]
|
||||
use crate::py_launcher::registry_pythons;
|
||||
use crate::microsoft_store::find_microsoft_store_pythons;
|
||||
#[cfg(windows)]
|
||||
use crate::py_launcher::{registry_pythons, WindowsPython};
|
||||
use crate::virtualenv::{
|
||||
conda_prefix_from_env, virtualenv_from_env, virtualenv_from_working_dir,
|
||||
virtualenv_python_executable,
|
||||
|
@ -167,6 +169,8 @@ pub enum PythonSource {
|
|||
SearchPath,
|
||||
/// An executable was found in the Windows registry via PEP 514
|
||||
Registry,
|
||||
/// An executable was found in the known Microsoft Store locations
|
||||
MicrosoftStore,
|
||||
/// 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 ...`
|
||||
|
@ -308,19 +312,11 @@ fn python_executables_from_installed<'a>(
|
|||
})
|
||||
.flatten();
|
||||
|
||||
let from_py_launcher = std::iter::once_with(move || {
|
||||
let from_windows = std::iter::once_with(move || {
|
||||
#[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.
|
||||
// Skip interpreter probing if we already know the version doesn't match.
|
||||
let version_filter = move |entry: &WindowsPython| {
|
||||
if let Some(version_request) = version {
|
||||
if let Some(version) = &entry.version {
|
||||
version_request.matches_version(version)
|
||||
|
@ -330,8 +326,22 @@ fn python_executables_from_installed<'a>(
|
|||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
env::var_os("UV_TEST_PYTHON_PATH")
|
||||
.is_none()
|
||||
.then(|| {
|
||||
registry_pythons()
|
||||
.map(|entries| {
|
||||
entries
|
||||
.into_iter()
|
||||
.filter(version_filter)
|
||||
.map(|entry| (PythonSource::Registry, entry.path))
|
||||
.chain(
|
||||
find_microsoft_store_pythons()
|
||||
.filter(version_filter)
|
||||
.map(|entry| (PythonSource::MicrosoftStore, entry.path)),
|
||||
)
|
||||
})
|
||||
.map_err(Error::from)
|
||||
})
|
||||
|
@ -350,14 +360,14 @@ fn python_executables_from_installed<'a>(
|
|||
PythonPreference::Managed => Box::new(
|
||||
from_managed_installations
|
||||
.chain(from_search_path)
|
||||
.chain(from_py_launcher),
|
||||
.chain(from_windows),
|
||||
),
|
||||
PythonPreference::System => Box::new(
|
||||
from_search_path
|
||||
.chain(from_py_launcher)
|
||||
.chain(from_windows)
|
||||
.chain(from_managed_installations),
|
||||
),
|
||||
PythonPreference::OnlySystem => Box::new(from_search_path.chain(from_py_launcher)),
|
||||
PythonPreference::OnlySystem => Box::new(from_search_path.chain(from_windows)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1633,6 +1643,7 @@ impl fmt::Display for PythonSource {
|
|||
Self::DiscoveredEnvironment => f.write_str("virtual environment"),
|
||||
Self::SearchPath => f.write_str("search path"),
|
||||
Self::Registry => f.write_str("registry"),
|
||||
Self::MicrosoftStore => f.write_str("Microsoft Store"),
|
||||
Self::Managed => f.write_str("managed installations"),
|
||||
Self::ParentInterpreter => f.write_str("parent interpreter"),
|
||||
}
|
||||
|
|
|
@ -26,6 +26,8 @@ mod installation;
|
|||
mod interpreter;
|
||||
mod libc;
|
||||
pub mod managed;
|
||||
#[cfg(windows)]
|
||||
mod microsoft_store;
|
||||
pub mod platform;
|
||||
mod pointer_size;
|
||||
mod prefix;
|
||||
|
|
118
crates/uv-python/src/microsoft_store.rs
Normal file
118
crates/uv-python/src/microsoft_store.rs
Normal file
|
@ -0,0 +1,118 @@
|
|||
//! Microsoft Store Pythons don't register themselves in the registry, so we have to look for them
|
||||
//! in known locations.
|
||||
//!
|
||||
//! Effectively a port of <https://github.com/python/cpython/blob/58ce131037ecb34d506a613f21993cde2056f628/PC/launcher2.c#L1744>
|
||||
|
||||
use crate::py_launcher::WindowsPython;
|
||||
use crate::PythonVersion;
|
||||
use itertools::Either;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use tracing::debug;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MicrosoftStorePython {
|
||||
family_name: &'static str,
|
||||
version: &'static str,
|
||||
}
|
||||
|
||||
/// List of known Microsoft Store Pythons.
|
||||
///
|
||||
/// Copied from <https://github.com/python/cpython/blob/58ce131037ecb34d506a613f21993cde2056f628/PC/launcher2.c#L1963-L1985>,
|
||||
/// please update when upstream changes.
|
||||
const MICROSOFT_STORE_PYTHONS: &[MicrosoftStorePython] = &[
|
||||
// Releases made through the Store
|
||||
MicrosoftStorePython {
|
||||
family_name: "PythonSoftwareFoundation.Python.3.13_qbz5n2kfra8p0",
|
||||
version: "3.13",
|
||||
},
|
||||
MicrosoftStorePython {
|
||||
family_name: "PythonSoftwareFoundation.Python.3.12_qbz5n2kfra8p0",
|
||||
version: "3.12",
|
||||
},
|
||||
MicrosoftStorePython {
|
||||
family_name: "PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0",
|
||||
version: "3.11",
|
||||
},
|
||||
MicrosoftStorePython {
|
||||
family_name: "PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0",
|
||||
version: "3.10",
|
||||
},
|
||||
MicrosoftStorePython {
|
||||
family_name: "PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0",
|
||||
version: "3.9",
|
||||
},
|
||||
MicrosoftStorePython {
|
||||
family_name: "PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0",
|
||||
version: "3.8",
|
||||
},
|
||||
// Side-loadable releases
|
||||
MicrosoftStorePython {
|
||||
family_name: "PythonSoftwareFoundation.Python.3.13_3847v3x7pw1km",
|
||||
version: "3.13",
|
||||
},
|
||||
MicrosoftStorePython {
|
||||
family_name: "PythonSoftwareFoundation.Python.3.12_3847v3x7pw1km",
|
||||
version: "3.12",
|
||||
},
|
||||
MicrosoftStorePython {
|
||||
family_name: "PythonSoftwareFoundation.Python.3.11_3847v3x7pw1km",
|
||||
version: "3.11",
|
||||
},
|
||||
MicrosoftStorePython {
|
||||
family_name: "PythonSoftwareFoundation.Python.3.11_hd69rhyc2wevp",
|
||||
version: "3.11",
|
||||
},
|
||||
MicrosoftStorePython {
|
||||
family_name: "PythonSoftwareFoundation.Python.3.10_3847v3x7pw1km",
|
||||
version: "3.10",
|
||||
},
|
||||
MicrosoftStorePython {
|
||||
family_name: "PythonSoftwareFoundation.Python.3.10_hd69rhyc2wevp",
|
||||
version: "3.10",
|
||||
},
|
||||
MicrosoftStorePython {
|
||||
family_name: "PythonSoftwareFoundation.Python.3.9_3847v3x7pw1km",
|
||||
version: "3.9",
|
||||
},
|
||||
MicrosoftStorePython {
|
||||
family_name: "PythonSoftwareFoundation.Python.3.9_hd69rhyc2wevp",
|
||||
version: "3.9",
|
||||
},
|
||||
MicrosoftStorePython {
|
||||
family_name: "PythonSoftwareFoundation.Python.3.8_hd69rhyc2wevp",
|
||||
version: "3.8",
|
||||
},
|
||||
];
|
||||
|
||||
/// Microsoft Store Pythons don't register themselves in the registry, so we have to look for them
|
||||
/// in known locations.
|
||||
///
|
||||
/// Effectively a port of <https://github.com/python/cpython/blob/58ce131037ecb34d506a613f21993cde2056f628/PC/launcher2.c#L1744>
|
||||
pub(crate) fn find_microsoft_store_pythons() -> impl Iterator<Item = WindowsPython> {
|
||||
let Ok(local_app_data) = env::var("LOCALAPPDATA") else {
|
||||
debug!("`LOCALAPPDATA` not set, ignoring Microsoft store Pythons");
|
||||
return Either::Left(std::iter::empty());
|
||||
};
|
||||
|
||||
let windows_apps = PathBuf::from(local_app_data)
|
||||
.join("Microsoft")
|
||||
.join("WindowsApps");
|
||||
|
||||
Either::Right(
|
||||
MICROSOFT_STORE_PYTHONS
|
||||
.iter()
|
||||
.map(move |store_python| {
|
||||
let path = windows_apps
|
||||
.join(store_python.family_name)
|
||||
.join("python.exe");
|
||||
WindowsPython {
|
||||
path,
|
||||
// All versions are constants, we know they are valid.
|
||||
version: Some(PythonVersion::from_str(store_python.version).unwrap()),
|
||||
}
|
||||
})
|
||||
.filter(|windows_python| windows_python.path.is_file()),
|
||||
)
|
||||
}
|
|
@ -4,12 +4,13 @@ use std::str::FromStr;
|
|||
use tracing::debug;
|
||||
use windows_registry::{Key, Value, CURRENT_USER, LOCAL_MACHINE};
|
||||
|
||||
/// A Python interpreter found in the Windows registry through PEP 514.
|
||||
/// A Python interpreter found in the Windows registry through PEP 514 or from a known Microsoft
|
||||
/// Store path.
|
||||
///
|
||||
/// There are a lot more (optional) fields defined in PEP 514, but we only care about path and
|
||||
/// version here, for everything else we probe with a Python script.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct RegistryPython {
|
||||
pub(crate) struct WindowsPython {
|
||||
pub(crate) path: PathBuf,
|
||||
pub(crate) version: Option<PythonVersion>,
|
||||
}
|
||||
|
@ -24,7 +25,7 @@ fn value_to_string(value: Value) -> Option<String> {
|
|||
}
|
||||
|
||||
/// Find all Pythons registered in the Windows registry following PEP 514.
|
||||
pub(crate) fn registry_pythons() -> Result<Vec<RegistryPython>, windows_result::Error> {
|
||||
pub(crate) fn registry_pythons() -> Result<Vec<WindowsPython>, windows_result::Error> {
|
||||
let mut registry_pythons = Vec::new();
|
||||
for root_key in [CURRENT_USER, LOCAL_MACHINE] {
|
||||
let Ok(key_python) = root_key.open(r"Software\Python") else {
|
||||
|
@ -67,7 +68,7 @@ pub(crate) fn registry_pythons() -> Result<Vec<RegistryPython>, windows_result::
|
|||
Ok(registry_pythons)
|
||||
}
|
||||
|
||||
fn read_registry_entry(company: &str, tag: &str, tag_key: &Key) -> Option<RegistryPython> {
|
||||
fn read_registry_entry(company: &str, tag: &str, tag_key: &Key) -> Option<WindowsPython> {
|
||||
// `ExecutablePath` is mandatory for executable Pythons.
|
||||
let Some(executable_path) = tag_key
|
||||
.open("InstallPath")
|
||||
|
@ -101,7 +102,7 @@ fn read_registry_entry(company: &str, tag: &str, tag_key: &Key) -> Option<Regist
|
|||
}
|
||||
});
|
||||
|
||||
Some(RegistryPython {
|
||||
Some(WindowsPython {
|
||||
path: PathBuf::from(executable_path),
|
||||
version,
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue