Skip to content
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
25 changes: 25 additions & 0 deletions crates/pet-conda/src/environment_locations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use crate::{
conda_rc::{get_conda_rc_search_paths, Condarc},
env_variables::EnvVariables,
manager::find_conda_binary,
utils::{is_conda_env, is_conda_install},
};
use log::trace;
Expand Down Expand Up @@ -265,6 +266,14 @@ pub fn get_known_conda_install_locations(
) -> Vec<PathBuf> {
use pet_fs::path::norm_case;

// First, try to find conda from PATH - this handles conda installations on mapped drives
// and other non-standard locations that aren't in the hardcoded search paths.
let conda_from_path = if conda_executable.is_none() {
find_conda_binary(env_vars)
} else {
None
};

let user_profile = env_vars.userprofile.clone().unwrap_or_default();
let program_data = env_vars.programdata.clone().unwrap_or_default();
let all_user_profile = env_vars.allusersprofile.clone().unwrap_or_default();
Expand Down Expand Up @@ -359,6 +368,10 @@ pub fn get_known_conda_install_locations(
if let Some(conda_dir) = get_conda_dir_from_exe(conda_executable) {
known_paths.push(conda_dir);
}
// Add conda installation found from PATH (handles mapped drives and non-standard locations)
if let Some(conda_dir) = get_conda_dir_from_exe(&conda_from_path) {
known_paths.push(conda_dir);
}
known_paths.sort();
known_paths.dedup();

Expand All @@ -370,6 +383,14 @@ pub fn get_known_conda_install_locations(
env_vars: &EnvVariables,
conda_executable: &Option<PathBuf>,
) -> Vec<PathBuf> {
// First, try to find conda from PATH - this handles conda installations in
// non-standard locations that aren't in the hardcoded search paths.
let conda_from_path = if conda_executable.is_none() {
find_conda_binary(env_vars)
} else {
None
};

let mut known_paths = vec![
// We need to look in `/anaconda3` and `/miniconda3` as well.
PathBuf::from("/anaconda"),
Expand Down Expand Up @@ -431,6 +452,10 @@ pub fn get_known_conda_install_locations(
if let Some(conda_dir) = get_conda_dir_from_exe(conda_executable) {
known_paths.push(conda_dir);
}
// Add conda installation found from PATH (handles non-standard locations)
if let Some(conda_dir) = get_conda_dir_from_exe(&conda_from_path) {
known_paths.push(conda_dir);
}
known_paths.sort();
known_paths.dedup();
known_paths.into_iter().filter(|f| f.exists()).collect()
Expand Down
110 changes: 110 additions & 0 deletions crates/pet-conda/tests/environment_locations_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,113 @@ fn list_conda_envs_discovers_base_from_another_child_env() {
]
);
}

/// Test that get_known_conda_install_locations discovers conda installations from PATH
/// when no explicit conda_executable is provided. This is important for discovering
/// conda installations on mapped drives and other non-standard locations.
/// Fixes https://github.com/microsoft/python-environment-tools/issues/194
#[cfg(unix)]
#[test]
fn discovers_conda_install_from_path() {
use common::{create_test_environment, resolve_test_path};
use pet_conda::env_variables::EnvVariables;
use pet_conda::environment_locations::get_known_conda_install_locations;
use std::collections::HashMap;

// Set up PATH to include the conda bin directory (simulating conda on a mapped drive)
let anaconda_bin = resolve_test_path(&["unix", "anaconda3-2023.03", "bin"]);
let path_value = anaconda_bin.to_string_lossy().to_string();

let mut vars = HashMap::new();
vars.insert("PATH".to_string(), path_value);

let env = create_test_environment(vars, None, vec![], None);
let env_vars = EnvVariables::from(&env);

// Call get_known_conda_install_locations without an explicit conda_executable
let locations = get_known_conda_install_locations(&env_vars, &None);

// The anaconda3-2023.03 install should be discovered from PATH
let expected_conda_install = resolve_test_path(&["unix", "anaconda3-2023.03"]);
assert!(
locations.contains(&expected_conda_install),
"Expected {:?} to be in {:?}",
expected_conda_install,
locations
);
}

/// Test that get_known_conda_install_locations discovers conda installations from condabin in PATH.
/// This simulates the typical Windows Miniforge/Anaconda setup where condabin is added to PATH.
/// Fixes https://github.com/microsoft/python-environment-tools/issues/194
#[cfg(unix)]
#[test]
fn discovers_conda_install_from_condabin_in_path() {
use common::{create_test_environment, resolve_test_path};
use pet_conda::env_variables::EnvVariables;
use pet_conda::environment_locations::get_known_conda_install_locations;
use std::collections::HashMap;

// Set up PATH to include the condabin directory (typical Miniforge/Anaconda setup on Windows)
let anaconda_condabin = resolve_test_path(&["unix", "anaconda3-2023.03", "condabin"]);
let path_value = anaconda_condabin.to_string_lossy().to_string();

let mut vars = HashMap::new();
vars.insert("PATH".to_string(), path_value);

let env = create_test_environment(vars, None, vec![], None);
let env_vars = EnvVariables::from(&env);

// Call get_known_conda_install_locations without an explicit conda_executable
let locations = get_known_conda_install_locations(&env_vars, &None);

// The anaconda3-2023.03 install should be discovered from PATH via condabin
let expected_conda_install = resolve_test_path(&["unix", "anaconda3-2023.03"]);
assert!(
locations.contains(&expected_conda_install),
"Expected {:?} to be in {:?}",
expected_conda_install,
locations
);
}

/// Test that when an explicit conda_executable is provided, PATH lookup is skipped.
/// This ensures we don't do unnecessary work when the user has configured a conda path.
#[cfg(unix)]
#[test]
fn skips_path_lookup_when_conda_executable_provided() {
use common::{create_test_environment, resolve_test_path};
use pet_conda::env_variables::EnvVariables;
use pet_conda::environment_locations::get_known_conda_install_locations;
use std::collections::HashMap;

// Set up PATH to include a conda directory
let anaconda_bin = resolve_test_path(&["unix", "anaconda3-2023.03", "bin"]);
let path_value = anaconda_bin.to_string_lossy().to_string();

let mut vars = HashMap::new();
vars.insert("PATH".to_string(), path_value);

let env = create_test_environment(vars, None, vec![], None);
let env_vars = EnvVariables::from(&env);

// Provide an explicit conda_executable
let conda_executable = Some(resolve_test_path(&[
"unix",
"anaconda3-2023.03",
"bin",
"conda",
]));

// Call get_known_conda_install_locations with an explicit conda_executable
let locations = get_known_conda_install_locations(&env_vars, &conda_executable);

// The conda install should still be discovered (from the explicit path, not PATH)
let expected_conda_install = resolve_test_path(&["unix", "anaconda3-2023.03"]);
assert!(
locations.contains(&expected_conda_install),
"Expected {:?} to be in {:?}",
expected_conda_install,
locations
);
}
81 changes: 81 additions & 0 deletions crates/pet-conda/tests/manager_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,84 @@ fn does_not_find_conda_env_for_bogus_dirs() {

assert!(CondaManager::from(&path).is_none());
}

/// Test that find_conda_binary finds conda from the PATH environment variable.
/// This is important for discovering conda installations on mapped drives and
/// other non-standard locations (fixes https://github.com/microsoft/python-environment-tools/issues/194).
#[cfg(unix)]
#[test]
fn finds_conda_binary_from_path() {
use common::{create_test_environment, resolve_test_path};
use pet_conda::env_variables::EnvVariables;
use pet_conda::manager::find_conda_binary;
use std::collections::HashMap;

let anaconda_bin = resolve_test_path(&["unix", "anaconda3-2023.03", "bin"]);
let path_value = anaconda_bin.to_string_lossy().to_string();

let mut vars = HashMap::new();
vars.insert("PATH".to_string(), path_value);

let env = create_test_environment(vars, None, vec![], None);
let env_vars = EnvVariables::from(&env);

let conda_binary = find_conda_binary(&env_vars);

assert!(conda_binary.is_some());
assert_eq!(
conda_binary.unwrap(),
resolve_test_path(&["unix", "anaconda3-2023.03", "bin", "conda"])
);
}

/// Test that find_conda_binary also works when conda is in the condabin directory
/// (common on Windows with Miniforge/Anaconda where condabin is added to PATH).
#[cfg(unix)]
#[test]
fn finds_conda_binary_from_condabin_path() {
use common::{create_test_environment, resolve_test_path};
use pet_conda::env_variables::EnvVariables;
use pet_conda::manager::find_conda_binary;
use std::collections::HashMap;

let anaconda_condabin = resolve_test_path(&["unix", "anaconda3-2023.03", "condabin"]);
let path_value = anaconda_condabin.to_string_lossy().to_string();

let mut vars = HashMap::new();
vars.insert("PATH".to_string(), path_value);

let env = create_test_environment(vars, None, vec![], None);
let env_vars = EnvVariables::from(&env);

let conda_binary = find_conda_binary(&env_vars);

assert!(conda_binary.is_some());
assert_eq!(
conda_binary.unwrap(),
resolve_test_path(&["unix", "anaconda3-2023.03", "condabin", "conda"])
);
}

/// Test that find_conda_binary returns None when conda is not on PATH.
#[cfg(unix)]
#[test]
fn does_not_find_conda_binary_when_not_on_path() {
use common::{create_test_environment, resolve_test_path};
use pet_conda::env_variables::EnvVariables;
use pet_conda::manager::find_conda_binary;
use std::collections::HashMap;

// Use a path that doesn't have conda
let some_other_path = resolve_test_path(&["unix", "bogus_directory"]);
let path_value = some_other_path.to_string_lossy().to_string();

let mut vars = HashMap::new();
vars.insert("PATH".to_string(), path_value);

let env = create_test_environment(vars, None, vec![], None);
let env_vars = EnvVariables::from(&env);

let conda_binary = find_conda_binary(&env_vars);

assert!(conda_binary.is_none());
}
Loading