diff --git a/native_locator/src/common_python.rs b/native_locator/src/common_python.rs index 76ce116ea995..f3b7fbbe20c4 100644 --- a/native_locator/src/common_python.rs +++ b/native_locator/src/common_python.rs @@ -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 { let parent = python_executable_path.parent()?; @@ -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 = 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| { diff --git a/native_locator/src/windows_store.rs b/native_locator/src/windows_store.rs index 5a0fd7b6ebd0..85f67ffe503e 100644 --- a/native_locator/src/windows_store.rs +++ b/native_locator/src/windows_store.rs @@ -1,49 +1,122 @@ // 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> { - let mut python_envs: Vec = vec![]; +) -> Option> { + let mut python_envs: Vec = 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 { + 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, + env_path: String, +} + +#[cfg(windows)] +fn get_package_display_name_and_location(name: String, hkcu: &RegKey) -> Option { + 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 { @@ -51,6 +124,7 @@ impl WindowsStore<'_> { } } +#[cfg(windows)] impl Locator for WindowsStore<'_> { fn resolve(&self, env: &PythonEnv) -> Option { if is_windows_python_executable(&env.executable) { @@ -71,14 +145,7 @@ impl Locator for WindowsStore<'_> { } fn find(&mut self) -> Option { - let mut environments: Vec = 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