Skip to content

Commit 594c9c0

Browse files
authored
Ensure Windows Store takes precedence over Registry and fix Conda locators (#23463)
1 parent 7066f82 commit 594c9c0

22 files changed

+533
-213
lines changed

native_locator/src/common_python.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,12 @@ impl Locator for PythonOnPath<'_> {
3939
}
4040
Some(PythonEnvironment {
4141
display_name: None,
42-
name: None,
4342
python_executable_path: Some(env.executable.clone()),
4443
version: env.version.clone(),
4544
category: crate::messaging::PythonEnvironmentCategory::System,
4645
env_path: env.path.clone(),
47-
env_manager: None,
48-
project_path: None,
4946
python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]),
50-
arch: None,
47+
..Default::default()
5148
})
5249
}
5350

native_locator/src/conda.rs

Lines changed: 123 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::known::Environment;
66
use crate::locator::Locator;
77
use crate::locator::LocatorResult;
88
use crate::messaging;
9+
use crate::messaging::Architecture;
910
use crate::messaging::EnvManager;
1011
use crate::messaging::EnvManagerType;
1112
use crate::messaging::PythonEnvironment;
@@ -14,9 +15,11 @@ use crate::utils::{find_python_binary_path, get_environment_key, get_environment
1415
use log::trace;
1516
use log::warn;
1617
use regex::Regex;
18+
use serde::Deserialize;
1719
use std::collections::HashMap;
1820
use std::collections::HashSet;
1921
use std::env;
22+
use std::fs::read_to_string;
2023
use std::path::{Path, PathBuf};
2124

2225
/// Specifically returns the file names that are valid for 'conda' on windows
@@ -57,6 +60,13 @@ struct CondaPackage {
5760
#[allow(dead_code)]
5861
path: PathBuf,
5962
version: String,
63+
arch: Option<Architecture>,
64+
}
65+
66+
#[derive(Deserialize, Debug)]
67+
struct CondaMetaPackageStructure {
68+
channel: Option<String>,
69+
// version: Option<String>,
6070
}
6171

6272
/// Get the path to the json file along with the version of a package in the conda environment from the 'conda-meta' directory.
@@ -72,16 +82,38 @@ fn get_conda_package_json_path(path: &Path, package: &str) -> Option<CondaPackag
7282
let path = entry.path();
7383
let file_name = path.file_name()?.to_string_lossy();
7484
if file_name.starts_with(&package_name) && file_name.ends_with(".json") {
75-
match regex.clone().ok().unwrap().captures(&file_name)?.get(1) {
76-
Some(version) => Some(CondaPackage {
85+
if let Some(version) = regex.clone().ok().unwrap().captures(&file_name)?.get(1) {
86+
let mut arch: Option<Architecture> = None;
87+
// Sample contents
88+
// {
89+
// "build": "h966fe2a_2",
90+
// "build_number": 2,
91+
// "channel": "https://repo.anaconda.com/pkgs/main/win-64",
92+
// "constrains": [],
93+
// }
94+
// 32bit channel is https://repo.anaconda.com/pkgs/main/win-32/
95+
// 64bit channel is "channel": "https://repo.anaconda.com/pkgs/main/osx-arm64",
96+
if let Some(contents) = read_to_string(&path).ok() {
97+
if let Some(js) =
98+
serde_json::from_str::<CondaMetaPackageStructure>(&contents).ok()
99+
{
100+
if let Some(channel) = js.channel {
101+
if channel.ends_with("64") {
102+
arch = Some(Architecture::X64);
103+
} else if channel.ends_with("32") {
104+
arch = Some(Architecture::X86);
105+
}
106+
}
107+
}
108+
}
109+
return Some(CondaPackage {
77110
path: path.clone(),
78111
version: version.as_str().to_string(),
79-
}),
80-
None => None,
112+
arch,
113+
});
81114
}
82-
} else {
83-
None
84115
}
116+
None
85117
})
86118
}
87119

@@ -202,6 +234,8 @@ fn get_conda_manager(path: &PathBuf) -> Option<EnvManager> {
202234
executable_path: conda_exe,
203235
version: Some(conda_pkg.version),
204236
tool: EnvManagerType::Conda,
237+
company: None,
238+
company_display_name: None,
205239
})
206240
}
207241

@@ -213,6 +247,7 @@ struct CondaEnvironment {
213247
python_executable_path: Option<PathBuf>,
214248
version: Option<String>,
215249
conda_install_folder: Option<String>,
250+
arch: Option<Architecture>,
216251
}
217252
fn get_conda_environment_info(env_path: &PathBuf, named: bool) -> Option<CondaEnvironment> {
218253
let metadata = env_path.metadata();
@@ -229,6 +264,7 @@ fn get_conda_environment_info(env_path: &PathBuf, named: bool) -> Option<CondaEn
229264
python_executable_path: Some(python_binary),
230265
version: Some(package_info.version),
231266
conda_install_folder,
267+
arch: package_info.arch,
232268
});
233269
} else {
234270
return Some(CondaEnvironment {
@@ -238,6 +274,7 @@ fn get_conda_environment_info(env_path: &PathBuf, named: bool) -> Option<CondaEn
238274
python_executable_path: Some(python_binary),
239275
version: None,
240276
conda_install_folder,
277+
arch: None,
241278
});
242279
}
243280
} else {
@@ -248,6 +285,7 @@ fn get_conda_environment_info(env_path: &PathBuf, named: bool) -> Option<CondaEn
248285
python_executable_path: None,
249286
version: None,
250287
conda_install_folder,
288+
arch: None,
251289
});
252290
}
253291
}
@@ -631,11 +669,17 @@ fn get_root_python_environment(path: &PathBuf, manager: &EnvManager) -> Option<P
631669
if let Some(package_info) = get_conda_package_json_path(&path, "python") {
632670
let conda_exe = manager.executable_path.to_str().unwrap().to_string();
633671
return Some(PythonEnvironment {
634-
display_name: None,
635-
name: Some("base".to_string()),
672+
// Do not set the name to `base`
673+
// Ideally we would like to see this idetnfieid as a base env.
674+
// However if user has 2 conda installations, then the second base env
675+
// will be activated in python extension using first conda executable and -n base,
676+
// I.e. base env of the first install will be activated instead of this.
677+
// Hence lets always just give the path.
678+
// name: Some("base".to_string()),
636679
category: messaging::PythonEnvironmentCategory::Conda,
637680
python_executable_path: Some(python_exe),
638681
version: Some(package_info.version),
682+
arch: package_info.arch,
639683
env_path: Some(path.clone()),
640684
env_manager: Some(manager.clone()),
641685
python_run_command: Some(vec![
@@ -645,8 +689,7 @@ fn get_root_python_environment(path: &PathBuf, manager: &EnvManager) -> Option<P
645689
path.to_str().unwrap().to_string(),
646690
"python".to_string(),
647691
]),
648-
project_path: None,
649-
arch: None,
692+
..Default::default()
650693
});
651694
}
652695
None
@@ -660,78 +703,82 @@ fn get_conda_environments_in_specified_install_path(
660703
let mut environments: Vec<PythonEnvironment> = vec![];
661704
let mut detected_envs: HashSet<String> = HashSet::new();
662705
let mut detected_managers: HashSet<String> = HashSet::new();
663-
if conda_install_folder.is_dir() && conda_install_folder.exists() {
664-
if let Some(manager) = get_conda_manager(&conda_install_folder) {
665-
// 1. Base environment.
666-
if let Some(env) = get_root_python_environment(&conda_install_folder, &manager) {
667-
if let Some(env_path) = env.clone().env_path {
668-
possible_conda_envs.remove(&env_path);
669-
let key = env_path.to_string_lossy().to_string();
670-
if !detected_envs.contains(&key) {
671-
detected_envs.insert(key);
672-
environments.push(env);
673-
}
706+
if !conda_install_folder.is_dir() || !conda_install_folder.exists() {
707+
return None;
708+
}
709+
710+
if let Some(manager) = get_conda_manager(&conda_install_folder) {
711+
// 1. Base environment.
712+
if let Some(env) = get_root_python_environment(&conda_install_folder, &manager) {
713+
if let Some(env_path) = env.clone().env_path {
714+
possible_conda_envs.remove(&env_path);
715+
let key = env_path.to_string_lossy().to_string();
716+
if !detected_envs.contains(&key) {
717+
detected_envs.insert(key);
718+
environments.push(env);
674719
}
675720
}
721+
}
722+
723+
// 2. All environments in the `<conda install folder>/envs` folder
724+
let mut envs: Vec<CondaEnvironment> = vec![];
725+
if let Some(environments) =
726+
get_environments_from_envs_folder_in_conda_directory(conda_install_folder)
727+
{
728+
environments.iter().for_each(|env| {
729+
possible_conda_envs.remove(&env.env_path);
730+
envs.push(env.clone());
731+
});
732+
}
676733

677-
// 2. All environments in the `<conda install folder>/envs` folder
678-
let mut envs: Vec<CondaEnvironment> = vec![];
679-
if let Some(environments) =
680-
get_environments_from_envs_folder_in_conda_directory(conda_install_folder)
734+
// 3. All environments in the environments.txt and other locations (such as `conda config --show envs_dirs`)
735+
// Only include those environments that were created by the specific conda installation
736+
// Ignore environments that are in the env sub directory of the conda folder, as those would have been
737+
// tracked elsewhere, we're only interested in conda envs located in other parts of the file system created using the -p flag.
738+
// E.g conda_install_folder is `<home>/<conda install folder>`
739+
// Then all folders such as `<home>/<conda install folder>/envs/env1` can be ignored
740+
// As these would have been discovered in previous step.
741+
for (key, env) in possible_conda_envs.clone().iter() {
742+
if env
743+
.env_path
744+
.to_string_lossy()
745+
.contains(conda_install_folder.to_str().unwrap())
681746
{
682-
environments.iter().for_each(|env| {
683-
possible_conda_envs.remove(&env.env_path);
684-
envs.push(env.clone());
685-
});
747+
continue;
686748
}
687-
688-
// 3. All environments in the environments.txt and other locations (such as `conda config --show envs_dirs`)
689-
// Only include those environments that were created by the specific conda installation
690-
// Ignore environments that are in the env sub directory of the conda folder, as those would have been
691-
// tracked elsewhere, we're only interested in conda envs located in other parts of the file system created using the -p flag.
692-
// E.g conda_install_folder is `<home>/<conda install folder>`
693-
// Then all folders such as `<home>/<conda install folder>/envs/env1` can be ignored
694-
// As these would have been discovered in previous step.
695-
for (key, env) in possible_conda_envs.clone().iter() {
696-
if env
697-
.env_path
698-
.to_string_lossy()
699-
.contains(conda_install_folder.to_str().unwrap())
700-
{
701-
continue;
702-
}
703-
if was_conda_environment_created_by_specific_conda(&env, conda_install_folder) {
704-
envs.push(env.clone());
705-
possible_conda_envs.remove(key);
706-
}
749+
if was_conda_environment_created_by_specific_conda(&env, conda_install_folder) {
750+
envs.push(env.clone());
751+
possible_conda_envs.remove(key);
707752
}
753+
}
708754

709-
// Finally construct the PythonEnvironment objects
710-
envs.iter().for_each(|env| {
711-
let exe = env.python_executable_path.clone();
712-
let env = PythonEnvironment::new(
713-
None,
714-
Some(env.name.clone()),
715-
exe.clone(),
716-
messaging::PythonEnvironmentCategory::Conda,
717-
env.version.clone(),
718-
Some(env.env_path.clone()),
719-
Some(manager.clone()),
720-
get_activation_command(env, &manager),
721-
);
722-
if let Some(key) = get_environment_key(&env) {
723-
if !detected_envs.contains(&key) {
724-
detected_envs.insert(key);
725-
environments.push(env);
726-
}
755+
// Finally construct the PythonEnvironment objects
756+
envs.iter().for_each(|env| {
757+
let exe = env.python_executable_path.clone();
758+
let arch = env.arch.clone();
759+
let mut env = PythonEnvironment::new(
760+
None,
761+
Some(env.name.clone()),
762+
exe.clone(),
763+
messaging::PythonEnvironmentCategory::Conda,
764+
env.version.clone(),
765+
Some(env.env_path.clone()),
766+
Some(manager.clone()),
767+
get_activation_command(env, &manager),
768+
);
769+
env.arch = arch;
770+
if let Some(key) = get_environment_key(&env) {
771+
if !detected_envs.contains(&key) {
772+
detected_envs.insert(key);
773+
environments.push(env);
727774
}
728-
});
729-
730-
let key = get_environment_manager_key(&manager);
731-
if !detected_managers.contains(&key) {
732-
detected_managers.insert(key);
733-
managers.push(manager);
734775
}
776+
});
777+
778+
let key = get_environment_manager_key(&manager);
779+
if !detected_managers.contains(&key) {
780+
detected_managers.insert(key);
781+
managers.push(manager);
735782
}
736783
}
737784

@@ -973,6 +1020,9 @@ impl Conda<'_> {
9731020

9741021
impl CondaLocator for Conda<'_> {
9751022
fn find_in(&mut self, possible_conda_folder: &PathBuf) -> Option<LocatorResult> {
1023+
if !is_conda_install_location(possible_conda_folder) {
1024+
return None;
1025+
}
9761026
let mut possible_conda_envs = get_known_conda_envs_from_various_locations(self.environment);
9771027
self.filter_result(get_conda_environments_in_specified_install_path(
9781028
possible_conda_folder,

native_locator/src/homebrew.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ pub struct Homebrew<'a> {
170170
}
171171

172172
impl Homebrew<'_> {
173+
#[cfg(unix)]
173174
pub fn with<'a>(environment: &'a impl Environment) -> Homebrew {
174175
Homebrew { environment }
175176
}

native_locator/src/known.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub trait Environment {
77
/**
88
* Only used in tests, this is the root `/`.
99
*/
10+
#[allow(dead_code)]
1011
fn get_root(&self) -> Option<PathBuf>;
1112
fn get_env_var(&self, key: String) -> Option<String>;
1213
fn get_know_global_search_locations(&self) -> Vec<PathBuf>;

native_locator/src/main.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ mod windows_store;
2929

3030
fn main() {
3131
let environment = EnvironmentApi {};
32-
initialize_logger(LevelFilter::Debug);
32+
initialize_logger(LevelFilter::Trace);
3333

3434
log::info!("Starting Native Locator");
3535
let now = SystemTime::now();
@@ -52,15 +52,15 @@ fn main() {
5252
// Step 1: These environments take precedence over all others.
5353
// As they are very specific and guaranteed to be specific type.
5454
#[cfg(windows)]
55+
find_environments(&mut windows_store, &mut dispatcher);
56+
#[cfg(windows)]
5557
find_environments(&mut windows_registry, &mut dispatcher);
5658
let mut pyenv_locator = pyenv::PyEnv::with(&environment, &mut conda_locator);
5759
find_environments(&mut virtualenvwrapper, &mut dispatcher);
5860
find_environments(&mut pyenv_locator, &mut dispatcher);
5961
#[cfg(unix)]
6062
find_environments(&mut homebrew_locator, &mut dispatcher);
6163
find_environments(&mut conda_locator, &mut dispatcher);
62-
#[cfg(windows)]
63-
find_environments(&mut windows_store, &mut dispatcher);
6464

6565
// Step 2: Search in some global locations for virtual envs.
6666
for env in list_global_virtual_envs(&environment).iter() {

0 commit comments

Comments
 (0)