Skip to content

Commit 2684143

Browse files
authored
Updates to the native windows store locator (#23426)
1 parent d24f9e7 commit 2684143

File tree

2 files changed

+95
-18
lines changed

2 files changed

+95
-18
lines changed

native_locator/src/common_python.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::locator::{Locator, LocatorResult};
66
use crate::messaging::PythonEnvironment;
77
use crate::utils::{self, PythonEnv};
88
use std::env;
9-
use std::path::PathBuf;
9+
use std::path::{Path, PathBuf};
1010

1111
fn get_env_path(python_executable_path: &PathBuf) -> Option<PathBuf> {
1212
let parent = python_executable_path.parent()?;
@@ -58,8 +58,18 @@ impl Locator for PythonOnPath<'_> {
5858
} else {
5959
"python"
6060
};
61+
62+
// Exclude files from this folder, as they would have been discovered elsewhere (widows_store)
63+
// Also the exe is merely a pointer to another file.
64+
let home = self.environment.get_user_home()?;
65+
let apps_path = Path::new(&home)
66+
.join("AppData")
67+
.join("Local")
68+
.join("Microsoft")
69+
.join("WindowsApps");
6170
let mut environments: Vec<PythonEnvironment> = vec![];
6271
env::split_paths(&paths)
72+
.filter(|p| !p.starts_with(apps_path.clone()))
6373
.map(|p| p.join(bin))
6474
.filter(|p| p.exists())
6575
.for_each(|full_path| {

native_locator/src/windows_store.rs

Lines changed: 84 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,130 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4+
#[cfg(windows)]
45
use crate::known;
6+
#[cfg(windows)]
57
use crate::known::Environment;
8+
#[cfg(windows)]
69
use crate::locator::{Locator, LocatorResult};
10+
#[cfg(windows)]
711
use crate::messaging::PythonEnvironment;
12+
#[cfg(windows)]
813
use crate::utils::PythonEnv;
14+
#[cfg(windows)]
915
use std::path::Path;
16+
#[cfg(windows)]
1017
use std::path::PathBuf;
18+
#[cfg(windows)]
19+
use winreg::RegKey;
1120

21+
#[cfg(windows)]
1222
pub fn is_windows_python_executable(path: &PathBuf) -> bool {
1323
let name = path.file_name().unwrap().to_string_lossy().to_lowercase();
1424
// TODO: Is it safe to assume the number 3?
1525
name.starts_with("python3.") && name.ends_with(".exe")
1626
}
1727

28+
#[cfg(windows)]
1829
fn list_windows_store_python_executables(
1930
environment: &dyn known::Environment,
20-
) -> Option<Vec<PathBuf>> {
21-
let mut python_envs: Vec<PathBuf> = vec![];
31+
) -> Option<Vec<PythonEnvironment>> {
32+
let mut python_envs: Vec<PythonEnvironment> = vec![];
2233
let home = environment.get_user_home()?;
2334
let apps_path = Path::new(&home)
2435
.join("AppData")
2536
.join("Local")
2637
.join("Microsoft")
2738
.join("WindowsApps");
28-
for file in std::fs::read_dir(apps_path).ok()? {
29-
match file {
30-
Ok(file) => {
31-
let path = file.path();
32-
if path.is_file() && is_windows_python_executable(&path) {
33-
python_envs.push(path);
39+
let hkcu = winreg::RegKey::predef(winreg::enums::HKEY_CURRENT_USER);
40+
for file in std::fs::read_dir(apps_path).ok()?.filter_map(Result::ok) {
41+
let path = file.path();
42+
if let Some(name) = path.file_name() {
43+
let exe = path.join("python.exe");
44+
if name
45+
.to_str()
46+
.unwrap_or_default()
47+
.starts_with("PythonSoftwareFoundation.Python.")
48+
&& exe.is_file()
49+
&& exe.exists()
50+
{
51+
if let Some(result) =
52+
get_package_display_name_and_location(name.to_string_lossy().to_string(), &hkcu)
53+
{
54+
let env = PythonEnvironment {
55+
display_name: Some(result.display_name),
56+
name: None,
57+
python_executable_path: Some(exe.clone()),
58+
version: result.version,
59+
category: crate::messaging::PythonEnvironmentCategory::WindowsStore,
60+
sys_prefix_path: Some(PathBuf::from(result.env_path.clone())),
61+
env_path: Some(PathBuf::from(result.env_path.clone())),
62+
env_manager: None,
63+
project_path: None,
64+
python_run_command: Some(vec![exe.to_string_lossy().to_string()]),
65+
};
66+
python_envs.push(env);
3467
}
3568
}
36-
Err(_) => {}
3769
}
3870
}
3971

4072
Some(python_envs)
4173
}
4274

75+
#[cfg(windows)]
76+
fn get_package_full_name_from_registry(name: String, hkcu: &RegKey) -> Option<String> {
77+
let key = format!("Software\\Classes\\Local Settings\\Software\\Microsoft\\Windows\\CurrentVersion\\AppModel\\SystemAppData\\{}\\Schemas", name);
78+
let package_key = hkcu.open_subkey(key).ok()?;
79+
let value = package_key.get_value("PackageFullName").unwrap_or_default();
80+
Some(value)
81+
}
82+
83+
#[derive(Debug)]
84+
#[cfg(windows)]
85+
struct StorePythonInfo {
86+
display_name: String,
87+
version: Option<String>,
88+
env_path: String,
89+
}
90+
91+
#[cfg(windows)]
92+
fn get_package_display_name_and_location(name: String, hkcu: &RegKey) -> Option<StorePythonInfo> {
93+
if let Some(name) = get_package_full_name_from_registry(name, &hkcu) {
94+
let key = format!("Software\\Classes\\Local Settings\\Software\\Microsoft\\Windows\\CurrentVersion\\AppModel\\Repository\\Packages\\{}", name);
95+
let package_key = hkcu.open_subkey(key).ok()?;
96+
let display_name = package_key.get_value("DisplayName").ok()?;
97+
let env_path = package_key.get_value("PackageRootFolder").ok()?;
98+
99+
let regex = regex::Regex::new("PythonSoftwareFoundation.Python.((\\d+\\.?)*)_.*").ok()?;
100+
let version = match regex.captures(&name)?.get(1) {
101+
Some(version) => Some(version.as_str().to_string()),
102+
None => None,
103+
};
104+
105+
return Some(StorePythonInfo {
106+
display_name,
107+
version,
108+
env_path,
109+
});
110+
}
111+
None
112+
}
113+
114+
#[cfg(windows)]
43115
pub struct WindowsStore<'a> {
44116
pub environment: &'a dyn Environment,
45117
}
46118

119+
#[cfg(windows)]
47120
impl WindowsStore<'_> {
48121
#[allow(dead_code)]
49122
pub fn with<'a>(environment: &'a impl Environment) -> WindowsStore {
50123
WindowsStore { environment }
51124
}
52125
}
53126

127+
#[cfg(windows)]
54128
impl Locator for WindowsStore<'_> {
55129
fn resolve(&self, env: &PythonEnv) -> Option<PythonEnvironment> {
56130
if is_windows_python_executable(&env.executable) {
@@ -71,14 +145,7 @@ impl Locator for WindowsStore<'_> {
71145
}
72146

73147
fn find(&mut self) -> Option<LocatorResult> {
74-
let mut environments: Vec<PythonEnvironment> = vec![];
75-
if let Some(envs) = list_windows_store_python_executables(self.environment) {
76-
envs.iter().for_each(|env| {
77-
if let Some(env) = self.resolve(&&PythonEnv::from(env.clone())) {
78-
environments.push(env);
79-
}
80-
});
81-
}
148+
let environments = list_windows_store_python_executables(self.environment)?;
82149

83150
if environments.is_empty() {
84151
None

0 commit comments

Comments
 (0)