Skip to content

Commit 248575c

Browse files
authored
fix: conda installation detection via PATH lookups (#327)
Fixes #194
1 parent f51b777 commit 248575c

File tree

3 files changed

+216
-0
lines changed

3 files changed

+216
-0
lines changed

crates/pet-conda/src/environment_locations.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use crate::{
55
conda_rc::{get_conda_rc_search_paths, Condarc},
66
env_variables::EnvVariables,
7+
manager::find_conda_binary,
78
utils::{is_conda_env, is_conda_install},
89
};
910
use log::trace;
@@ -265,6 +266,14 @@ pub fn get_known_conda_install_locations(
265266
) -> Vec<PathBuf> {
266267
use pet_fs::path::norm_case;
267268

269+
// First, try to find conda from PATH - this handles conda installations on mapped drives
270+
// and other non-standard locations that aren't in the hardcoded search paths.
271+
let conda_from_path = if conda_executable.is_none() {
272+
find_conda_binary(env_vars)
273+
} else {
274+
None
275+
};
276+
268277
let user_profile = env_vars.userprofile.clone().unwrap_or_default();
269278
let program_data = env_vars.programdata.clone().unwrap_or_default();
270279
let all_user_profile = env_vars.allusersprofile.clone().unwrap_or_default();
@@ -359,6 +368,10 @@ pub fn get_known_conda_install_locations(
359368
if let Some(conda_dir) = get_conda_dir_from_exe(conda_executable) {
360369
known_paths.push(conda_dir);
361370
}
371+
// Add conda installation found from PATH (handles mapped drives and non-standard locations)
372+
if let Some(conda_dir) = get_conda_dir_from_exe(&conda_from_path) {
373+
known_paths.push(conda_dir);
374+
}
362375
known_paths.sort();
363376
known_paths.dedup();
364377

@@ -370,6 +383,14 @@ pub fn get_known_conda_install_locations(
370383
env_vars: &EnvVariables,
371384
conda_executable: &Option<PathBuf>,
372385
) -> Vec<PathBuf> {
386+
// First, try to find conda from PATH - this handles conda installations in
387+
// non-standard locations that aren't in the hardcoded search paths.
388+
let conda_from_path = if conda_executable.is_none() {
389+
find_conda_binary(env_vars)
390+
} else {
391+
None
392+
};
393+
373394
let mut known_paths = vec![
374395
// We need to look in `/anaconda3` and `/miniconda3` as well.
375396
PathBuf::from("/anaconda"),
@@ -431,6 +452,10 @@ pub fn get_known_conda_install_locations(
431452
if let Some(conda_dir) = get_conda_dir_from_exe(conda_executable) {
432453
known_paths.push(conda_dir);
433454
}
455+
// Add conda installation found from PATH (handles non-standard locations)
456+
if let Some(conda_dir) = get_conda_dir_from_exe(&conda_from_path) {
457+
known_paths.push(conda_dir);
458+
}
434459
known_paths.sort();
435460
known_paths.dedup();
436461
known_paths.into_iter().filter(|f| f.exists()).collect()

crates/pet-conda/tests/environment_locations_test.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,113 @@ fn list_conda_envs_discovers_base_from_another_child_env() {
9595
]
9696
);
9797
}
98+
99+
/// Test that get_known_conda_install_locations discovers conda installations from PATH
100+
/// when no explicit conda_executable is provided. This is important for discovering
101+
/// conda installations on mapped drives and other non-standard locations.
102+
/// Fixes https://github.com/microsoft/python-environment-tools/issues/194
103+
#[cfg(unix)]
104+
#[test]
105+
fn discovers_conda_install_from_path() {
106+
use common::{create_test_environment, resolve_test_path};
107+
use pet_conda::env_variables::EnvVariables;
108+
use pet_conda::environment_locations::get_known_conda_install_locations;
109+
use std::collections::HashMap;
110+
111+
// Set up PATH to include the conda bin directory (simulating conda on a mapped drive)
112+
let anaconda_bin = resolve_test_path(&["unix", "anaconda3-2023.03", "bin"]);
113+
let path_value = anaconda_bin.to_string_lossy().to_string();
114+
115+
let mut vars = HashMap::new();
116+
vars.insert("PATH".to_string(), path_value);
117+
118+
let env = create_test_environment(vars, None, vec![], None);
119+
let env_vars = EnvVariables::from(&env);
120+
121+
// Call get_known_conda_install_locations without an explicit conda_executable
122+
let locations = get_known_conda_install_locations(&env_vars, &None);
123+
124+
// The anaconda3-2023.03 install should be discovered from PATH
125+
let expected_conda_install = resolve_test_path(&["unix", "anaconda3-2023.03"]);
126+
assert!(
127+
locations.contains(&expected_conda_install),
128+
"Expected {:?} to be in {:?}",
129+
expected_conda_install,
130+
locations
131+
);
132+
}
133+
134+
/// Test that get_known_conda_install_locations discovers conda installations from condabin in PATH.
135+
/// This simulates the typical Windows Miniforge/Anaconda setup where condabin is added to PATH.
136+
/// Fixes https://github.com/microsoft/python-environment-tools/issues/194
137+
#[cfg(unix)]
138+
#[test]
139+
fn discovers_conda_install_from_condabin_in_path() {
140+
use common::{create_test_environment, resolve_test_path};
141+
use pet_conda::env_variables::EnvVariables;
142+
use pet_conda::environment_locations::get_known_conda_install_locations;
143+
use std::collections::HashMap;
144+
145+
// Set up PATH to include the condabin directory (typical Miniforge/Anaconda setup on Windows)
146+
let anaconda_condabin = resolve_test_path(&["unix", "anaconda3-2023.03", "condabin"]);
147+
let path_value = anaconda_condabin.to_string_lossy().to_string();
148+
149+
let mut vars = HashMap::new();
150+
vars.insert("PATH".to_string(), path_value);
151+
152+
let env = create_test_environment(vars, None, vec![], None);
153+
let env_vars = EnvVariables::from(&env);
154+
155+
// Call get_known_conda_install_locations without an explicit conda_executable
156+
let locations = get_known_conda_install_locations(&env_vars, &None);
157+
158+
// The anaconda3-2023.03 install should be discovered from PATH via condabin
159+
let expected_conda_install = resolve_test_path(&["unix", "anaconda3-2023.03"]);
160+
assert!(
161+
locations.contains(&expected_conda_install),
162+
"Expected {:?} to be in {:?}",
163+
expected_conda_install,
164+
locations
165+
);
166+
}
167+
168+
/// Test that when an explicit conda_executable is provided, PATH lookup is skipped.
169+
/// This ensures we don't do unnecessary work when the user has configured a conda path.
170+
#[cfg(unix)]
171+
#[test]
172+
fn skips_path_lookup_when_conda_executable_provided() {
173+
use common::{create_test_environment, resolve_test_path};
174+
use pet_conda::env_variables::EnvVariables;
175+
use pet_conda::environment_locations::get_known_conda_install_locations;
176+
use std::collections::HashMap;
177+
178+
// Set up PATH to include a conda directory
179+
let anaconda_bin = resolve_test_path(&["unix", "anaconda3-2023.03", "bin"]);
180+
let path_value = anaconda_bin.to_string_lossy().to_string();
181+
182+
let mut vars = HashMap::new();
183+
vars.insert("PATH".to_string(), path_value);
184+
185+
let env = create_test_environment(vars, None, vec![], None);
186+
let env_vars = EnvVariables::from(&env);
187+
188+
// Provide an explicit conda_executable
189+
let conda_executable = Some(resolve_test_path(&[
190+
"unix",
191+
"anaconda3-2023.03",
192+
"bin",
193+
"conda",
194+
]));
195+
196+
// Call get_known_conda_install_locations with an explicit conda_executable
197+
let locations = get_known_conda_install_locations(&env_vars, &conda_executable);
198+
199+
// The conda install should still be discovered (from the explicit path, not PATH)
200+
let expected_conda_install = resolve_test_path(&["unix", "anaconda3-2023.03"]);
201+
assert!(
202+
locations.contains(&expected_conda_install),
203+
"Expected {:?} to be in {:?}",
204+
expected_conda_install,
205+
locations
206+
);
207+
}

crates/pet-conda/tests/manager_test.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,84 @@ fn does_not_find_conda_env_for_bogus_dirs() {
5050

5151
assert!(CondaManager::from(&path).is_none());
5252
}
53+
54+
/// Test that find_conda_binary finds conda from the PATH environment variable.
55+
/// This is important for discovering conda installations on mapped drives and
56+
/// other non-standard locations (fixes https://github.com/microsoft/python-environment-tools/issues/194).
57+
#[cfg(unix)]
58+
#[test]
59+
fn finds_conda_binary_from_path() {
60+
use common::{create_test_environment, resolve_test_path};
61+
use pet_conda::env_variables::EnvVariables;
62+
use pet_conda::manager::find_conda_binary;
63+
use std::collections::HashMap;
64+
65+
let anaconda_bin = resolve_test_path(&["unix", "anaconda3-2023.03", "bin"]);
66+
let path_value = anaconda_bin.to_string_lossy().to_string();
67+
68+
let mut vars = HashMap::new();
69+
vars.insert("PATH".to_string(), path_value);
70+
71+
let env = create_test_environment(vars, None, vec![], None);
72+
let env_vars = EnvVariables::from(&env);
73+
74+
let conda_binary = find_conda_binary(&env_vars);
75+
76+
assert!(conda_binary.is_some());
77+
assert_eq!(
78+
conda_binary.unwrap(),
79+
resolve_test_path(&["unix", "anaconda3-2023.03", "bin", "conda"])
80+
);
81+
}
82+
83+
/// Test that find_conda_binary also works when conda is in the condabin directory
84+
/// (common on Windows with Miniforge/Anaconda where condabin is added to PATH).
85+
#[cfg(unix)]
86+
#[test]
87+
fn finds_conda_binary_from_condabin_path() {
88+
use common::{create_test_environment, resolve_test_path};
89+
use pet_conda::env_variables::EnvVariables;
90+
use pet_conda::manager::find_conda_binary;
91+
use std::collections::HashMap;
92+
93+
let anaconda_condabin = resolve_test_path(&["unix", "anaconda3-2023.03", "condabin"]);
94+
let path_value = anaconda_condabin.to_string_lossy().to_string();
95+
96+
let mut vars = HashMap::new();
97+
vars.insert("PATH".to_string(), path_value);
98+
99+
let env = create_test_environment(vars, None, vec![], None);
100+
let env_vars = EnvVariables::from(&env);
101+
102+
let conda_binary = find_conda_binary(&env_vars);
103+
104+
assert!(conda_binary.is_some());
105+
assert_eq!(
106+
conda_binary.unwrap(),
107+
resolve_test_path(&["unix", "anaconda3-2023.03", "condabin", "conda"])
108+
);
109+
}
110+
111+
/// Test that find_conda_binary returns None when conda is not on PATH.
112+
#[cfg(unix)]
113+
#[test]
114+
fn does_not_find_conda_binary_when_not_on_path() {
115+
use common::{create_test_environment, resolve_test_path};
116+
use pet_conda::env_variables::EnvVariables;
117+
use pet_conda::manager::find_conda_binary;
118+
use std::collections::HashMap;
119+
120+
// Use a path that doesn't have conda
121+
let some_other_path = resolve_test_path(&["unix", "bogus_directory"]);
122+
let path_value = some_other_path.to_string_lossy().to_string();
123+
124+
let mut vars = HashMap::new();
125+
vars.insert("PATH".to_string(), path_value);
126+
127+
let env = create_test_environment(vars, None, vec![], None);
128+
let env_vars = EnvVariables::from(&env);
129+
130+
let conda_binary = find_conda_binary(&env_vars);
131+
132+
assert!(conda_binary.is_none());
133+
}

0 commit comments

Comments
 (0)