Skip to content

Commit

Permalink
Updates to the native windows store locator (#23426)
Browse files Browse the repository at this point in the history
  • Loading branch information
DonJayamanne committed Jun 24, 2024
1 parent a6214e2 commit 22496cc
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 18 deletions.
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

0 comments on commit 22496cc

Please sign in to comment.