Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates to the native windows store locator #23426

Merged
merged 2 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion native_locator/src/common_python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::locator::{Locator, LocatorResult};
use crate::messaging::PythonEnvironment;
use crate::utils::{self, PythonEnv};
use std::env;
use std::path::PathBuf;
use std::path::{Path, PathBuf};

fn get_env_path(python_executable_path: &PathBuf) -> Option<PathBuf> {
let parent = python_executable_path.parent()?;
Expand Down Expand Up @@ -58,8 +58,18 @@ impl Locator for PythonOnPath<'_> {
} else {
"python"
};

// Exclude files from this folder, as they would have been discovered elsewhere (widows_store)
// Also the exe is merely a pointer to another file.
let home = self.environment.get_user_home()?;
let apps_path = Path::new(&home)
.join("AppData")
.join("Local")
.join("Microsoft")
.join("WindowsApps");
let mut environments: Vec<PythonEnvironment> = vec![];
env::split_paths(&paths)
.filter(|p| !p.starts_with(apps_path.clone()))
.map(|p| p.join(bin))
.filter(|p| p.exists())
.for_each(|full_path| {
Expand Down
101 changes: 84 additions & 17 deletions native_locator/src/windows_store.rs
Original file line number Diff line number Diff line change
@@ -1,56 +1,130 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#[cfg(windows)]
use crate::known;
#[cfg(windows)]
use crate::known::Environment;
#[cfg(windows)]
use crate::locator::{Locator, LocatorResult};
#[cfg(windows)]
use crate::messaging::PythonEnvironment;
#[cfg(windows)]
use crate::utils::PythonEnv;
#[cfg(windows)]
use std::path::Path;
#[cfg(windows)]
use std::path::PathBuf;
#[cfg(windows)]
use winreg::RegKey;

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

#[cfg(windows)]
fn list_windows_store_python_executables(
environment: &dyn known::Environment,
) -> Option<Vec<PathBuf>> {
let mut python_envs: Vec<PathBuf> = vec![];
) -> Option<Vec<PythonEnvironment>> {
let mut python_envs: Vec<PythonEnvironment> = vec![];
let home = environment.get_user_home()?;
let apps_path = Path::new(&home)
.join("AppData")
.join("Local")
.join("Microsoft")
.join("WindowsApps");
for file in std::fs::read_dir(apps_path).ok()? {
match file {
Ok(file) => {
let path = file.path();
if path.is_file() && is_windows_python_executable(&path) {
python_envs.push(path);
let hkcu = winreg::RegKey::predef(winreg::enums::HKEY_CURRENT_USER);
for file in std::fs::read_dir(apps_path).ok()?.filter_map(Result::ok) {
let path = file.path();
if let Some(name) = path.file_name() {
let exe = path.join("python.exe");
if name
.to_str()
.unwrap_or_default()
.starts_with("PythonSoftwareFoundation.Python.")
&& exe.is_file()
&& exe.exists()
{
if let Some(result) =
get_package_display_name_and_location(name.to_string_lossy().to_string(), &hkcu)
{
let env = PythonEnvironment {
display_name: Some(result.display_name),
name: None,
python_executable_path: Some(exe.clone()),
version: result.version,
category: crate::messaging::PythonEnvironmentCategory::WindowsStore,
sys_prefix_path: Some(PathBuf::from(result.env_path.clone())),
env_path: Some(PathBuf::from(result.env_path.clone())),
env_manager: None,
project_path: None,
python_run_command: Some(vec![exe.to_string_lossy().to_string()]),
};
python_envs.push(env);
}
}
Err(_) => {}
}
}

Some(python_envs)
}

#[cfg(windows)]
fn get_package_full_name_from_registry(name: String, hkcu: &RegKey) -> Option<String> {
let key = format!("Software\\Classes\\Local Settings\\Software\\Microsoft\\Windows\\CurrentVersion\\AppModel\\SystemAppData\\{}\\Schemas", name);
let package_key = hkcu.open_subkey(key).ok()?;
let value = package_key.get_value("PackageFullName").unwrap_or_default();
Some(value)
}

#[derive(Debug)]
#[cfg(windows)]
struct StorePythonInfo {
display_name: String,
version: Option<String>,
env_path: String,
}

#[cfg(windows)]
fn get_package_display_name_and_location(name: String, hkcu: &RegKey) -> Option<StorePythonInfo> {
if let Some(name) = get_package_full_name_from_registry(name, &hkcu) {
let key = format!("Software\\Classes\\Local Settings\\Software\\Microsoft\\Windows\\CurrentVersion\\AppModel\\Repository\\Packages\\{}", name);
let package_key = hkcu.open_subkey(key).ok()?;
let display_name = package_key.get_value("DisplayName").ok()?;
let env_path = package_key.get_value("PackageRootFolder").ok()?;

let regex = regex::Regex::new("PythonSoftwareFoundation.Python.((\\d+\\.?)*)_.*").ok()?;
let version = match regex.captures(&name)?.get(1) {
Some(version) => Some(version.as_str().to_string()),
None => None,
};

return Some(StorePythonInfo {
display_name,
version,
env_path,
});
}
None
}

#[cfg(windows)]
pub struct WindowsStore<'a> {
pub environment: &'a dyn Environment,
}

#[cfg(windows)]
impl WindowsStore<'_> {
#[allow(dead_code)]
pub fn with<'a>(environment: &'a impl Environment) -> WindowsStore {
WindowsStore { environment }
}
}

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

fn find(&mut self) -> Option<LocatorResult> {
let mut environments: Vec<PythonEnvironment> = vec![];
if let Some(envs) = list_windows_store_python_executables(self.environment) {
envs.iter().for_each(|env| {
if let Some(env) = self.resolve(&&PythonEnv::from(env.clone())) {
environments.push(env);
}
});
}
let environments = list_windows_store_python_executables(self.environment)?;

if environments.is_empty() {
None
Expand Down
Loading