Skip to content

Commit a095fbb

Browse files
authored
Preserve case for Windows paths as found on disc (#15)
Fixes #14
1 parent aa73566 commit a095fbb

File tree

10 files changed

+86
-18
lines changed

10 files changed

+86
-18
lines changed

crates/pet-conda/src/conda_rc.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
use crate::env_variables::EnvVariables;
55
use log::trace;
6+
use pet_utils::path::fix_file_path_casing;
67
use std::{fs, path::PathBuf};
78

89
#[derive(Debug)]
@@ -154,7 +155,9 @@ fn parse_conda_rc(conda_rc: &PathBuf) -> Option<Condarc> {
154155
if line.trim().starts_with('-') {
155156
if let Some(env_dir) = line.split_once('-').map(|x| x.1) {
156157
// Directories in conda-rc are where `envs` are stored.
157-
env_dirs.push(PathBuf::from(env_dir.trim()).join("envs"));
158+
env_dirs.push(fix_file_path_casing(
159+
&PathBuf::from(env_dir.trim()).join("envs"),
160+
));
158161
}
159162
continue;
160163
} else {

crates/pet-conda/src/environment_locations.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::{
77
utils::{is_conda_env, is_conda_install},
88
};
99
use log::trace;
10+
use pet_utils::path::fix_file_path_casing;
1011
use std::{
1112
fs,
1213
path::{Path, PathBuf},
@@ -34,6 +35,7 @@ pub fn get_conda_environment_paths(env_vars: &EnvVariables) -> Vec<PathBuf> {
3435
envs
3536
});
3637

38+
env_paths = env_paths.iter().map(|p| fix_file_path_casing(p)).collect();
3739
env_paths.sort();
3840
env_paths.dedup();
3941
// For each env, check if we have a conda install directory in them and
@@ -145,7 +147,7 @@ pub fn get_conda_envs_from_environment_txt(env_vars: &EnvVariables) -> Vec<PathB
145147
if let Ok(reader) = fs::read_to_string(environment_txt.clone()) {
146148
trace!("Found environments.txt file {:?}", environment_txt);
147149
for line in reader.lines() {
148-
envs.push(PathBuf::from(line.to_string()));
150+
envs.push(fix_file_path_casing(&PathBuf::from(line.to_string())));
149151
}
150152
}
151153
}
@@ -155,6 +157,8 @@ pub fn get_conda_envs_from_environment_txt(env_vars: &EnvVariables) -> Vec<PathB
155157

156158
#[cfg(windows)]
157159
pub fn get_known_conda_install_locations(env_vars: &EnvVariables) -> Vec<PathBuf> {
160+
use pet_utils::path::fix_file_path_casing;
161+
158162
let user_profile = env_vars.userprofile.clone().unwrap_or_default();
159163
let program_data = env_vars.programdata.clone().unwrap_or_default();
160164
let all_user_profile = env_vars.allusersprofile.clone().unwrap_or_default();
@@ -200,6 +204,19 @@ pub fn get_known_conda_install_locations(env_vars: &EnvVariables) -> Vec<PathBuf
200204
}
201205
known_paths.sort();
202206
known_paths.dedup();
207+
// Ensure the casing of the paths are correct.
208+
// Its possible the actual path is in a different case.
209+
// E.g. instead of C:\username\miniconda it might bt C:\username\Miniconda
210+
// We use lower cases above, but it could be in any case on disc.
211+
// We do not want to have duplicates in different cases.
212+
// & we'd like to preserve the case of the original path as on disc.
213+
known_paths = known_paths
214+
.iter()
215+
.map(|p| fix_file_path_casing(p))
216+
.collect();
217+
known_paths.sort();
218+
known_paths.dedup();
219+
203220
known_paths
204221
}
205222

@@ -243,6 +260,7 @@ pub fn get_known_conda_install_locations(env_vars: &EnvVariables) -> Vec<PathBuf
243260
known_paths.append(get_known_conda_locations(env_vars).as_mut());
244261
known_paths.sort();
245262
known_paths.dedup();
263+
246264
known_paths
247265
}
248266

crates/pet-conda/src/environments.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use pet_core::{
1212
manager::EnvManager,
1313
python_environment::{PythonEnvironment, PythonEnvironmentBuilder, PythonEnvironmentCategory},
1414
};
15-
use pet_utils::executable::find_executable;
15+
use pet_utils::{executable::find_executable, path::fix_file_path_casing};
1616
use std::{
1717
fs,
1818
path::{Path, PathBuf},
@@ -175,7 +175,12 @@ fn get_conda_dir_from_cmd(cmd_line: String) -> Option<PathBuf> {
175175
|| conda_dir.to_ascii_lowercase() == "scripts"
176176
{
177177
if let Some(conda_dir) = cmd_line.parent() {
178-
return Some(conda_dir.to_path_buf());
178+
// Ensure the casing of the paths are correct.
179+
// Its possible the actual path is in a different case.
180+
// The casing in history might not be same as that on disc
181+
// We do not want to have duplicates in different cases.
182+
// & we'd like to preserve the case of the original path as on disc.
183+
return Some(fix_file_path_casing(conda_dir).to_path_buf());
179184
}
180185
}
181186
// Sometimes we can have paths like
@@ -202,7 +207,12 @@ fn get_conda_dir_from_cmd(cmd_line: String) -> Option<PathBuf> {
202207
let _ = cmd_line.pop();
203208
}
204209
}
205-
return Some(cmd_line.to_path_buf());
210+
// Ensure the casing of the paths are correct.
211+
// Its possible the actual path is in a different case.
212+
// The casing in history might not be same as that on disc
213+
// We do not want to have duplicates in different cases.
214+
// & we'd like to preserve the case of the original path as on disc.
215+
return Some(fix_file_path_casing(&cmd_line).to_path_buf());
206216
}
207217
}
208218
None

crates/pet-global-virtualenvs/src/lib.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
use pet_conda::utils::is_conda_env;
5+
use pet_utils::path::fix_file_path_casing;
56
use std::{fs, path::PathBuf};
67

78
fn get_global_virtualenv_dirs(
@@ -11,10 +12,9 @@ fn get_global_virtualenv_dirs(
1112
let mut venv_dirs: Vec<PathBuf> = vec![];
1213

1314
if let Some(work_on_home) = work_on_home_env_var {
14-
if let Ok(work_on_home) = fs::canonicalize(work_on_home) {
15-
if work_on_home.exists() {
16-
venv_dirs.push(work_on_home);
17-
}
15+
let work_on_home = fix_file_path_casing(&PathBuf::from(work_on_home));
16+
if fs::metadata(&work_on_home).is_ok() {
17+
venv_dirs.push(work_on_home);
1818
}
1919
}
2020

@@ -27,13 +27,13 @@ fn get_global_virtualenv_dirs(
2727
PathBuf::from(".local").join("share").join("virtualenvs"),
2828
] {
2929
let venv_dir = home.join(dir);
30-
if venv_dir.exists() {
30+
if fs::metadata(&venv_dir).is_ok() {
3131
venv_dirs.push(venv_dir);
3232
}
3333
}
3434
if cfg!(target_os = "linux") {
3535
let envs = PathBuf::from("Envs");
36-
if envs.exists() {
36+
if fs::metadata(&envs).is_ok() {
3737
venv_dirs.push(envs);
3838
}
3939
}

crates/pet-pipenv/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ use pet_core::{
77
python_environment::{PythonEnvironment, PythonEnvironmentBuilder, PythonEnvironmentCategory},
88
Locator, LocatorResult,
99
};
10-
use pet_utils::env::PythonEnv;
10+
use pet_utils::{env::PythonEnv, path::fix_file_path_casing};
1111

1212
fn get_pipenv_project(env: &PythonEnv) -> Option<PathBuf> {
1313
let project_file = env.prefix.clone()?.join(".project");
1414
let contents = fs::read_to_string(project_file).ok()?;
15-
let project_folder = PathBuf::from(contents.trim().to_string());
15+
let project_folder = fix_file_path_casing(&PathBuf::from(contents.trim().to_string()));
1616
if fs::metadata(&project_folder).is_ok() {
1717
Some(project_folder)
1818
} else {

crates/pet-utils/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
pub mod env;
55
pub mod executable;
66
pub mod headers;
7+
pub mod path;
78
pub mod pyvenv_cfg;
89
pub mod sys_prefix;

crates/pet-utils/src/path.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use std::{
5+
fs,
6+
path::{Path, PathBuf},
7+
};
8+
9+
// This function is used to fix the casing of the file path.
10+
// by returning the actual path with the correct casing as found on the OS.
11+
// This is a noop for Unix systems.
12+
// I.e. this function is only useful on Windows.
13+
pub fn fix_file_path_casing(path: &Path) -> PathBuf {
14+
// Return the path as is.
15+
if cfg!(unix) {
16+
return path.to_path_buf();
17+
}
18+
let has_unc_prefix = path.to_string_lossy().starts_with(r"\\?\");
19+
if let Ok(resolved) = fs::canonicalize(path) {
20+
if resolved.to_string_lossy().starts_with(r"\\?\") && !has_unc_prefix {
21+
// If the resolved path has a UNC prefix, but the original path did not,
22+
// we need to remove the UNC prefix.
23+
PathBuf::from(resolved.to_string_lossy().trim_start_matches(r"\\?\"))
24+
} else {
25+
resolved
26+
}
27+
} else {
28+
path.to_path_buf()
29+
}
30+
}

crates/pet-virtualenvwrapper/src/environments.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
// Licensed under the MIT License.
33

44
use crate::{env_variables::EnvVariables, environment_locations::get_work_on_home_path};
5-
use pet_utils::{env::PythonEnv, executable::find_executable, pyvenv_cfg::PyVenvCfg};
5+
use pet_utils::{
6+
env::PythonEnv, executable::find_executable, path::fix_file_path_casing, pyvenv_cfg::PyVenvCfg,
7+
};
68
use pet_virtualenv::is_virtualenv;
79
use std::{fs, path::PathBuf};
810

@@ -26,7 +28,7 @@ pub fn is_virtualenvwrapper(env: &PythonEnv, environment: &EnvVariables) -> bool
2628
pub fn get_project(env: &PythonEnv) -> Option<PathBuf> {
2729
let project_file = env.prefix.clone()?.join(".project");
2830
let contents = fs::read_to_string(project_file).ok()?;
29-
let project_folder = PathBuf::from(contents.trim().to_string());
31+
let project_folder = fix_file_path_casing(&PathBuf::from(contents.trim().to_string()));
3032
if fs::metadata(&project_folder).is_ok() {
3133
Some(project_folder)
3234
} else {

crates/pet-windows-registry/src/environments.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ use pet_core::{
1111
LocatorResult,
1212
};
1313
#[cfg(windows)]
14+
use pet_utils::path::fix_file_path_casing;
15+
#[cfg(windows)]
1416
use pet_windows_store::is_windows_app_folder_in_program_files;
1517
#[cfg(windows)]
1618
use std::{path::PathBuf, sync::Arc};
@@ -94,7 +96,7 @@ fn get_registry_pythons_from_key_for_company(
9496
Ok(install_path_key) => {
9597
let env_path: String =
9698
install_path_key.get_value("").ok().unwrap_or_default();
97-
let env_path = PathBuf::from(env_path);
99+
let env_path = fix_file_path_casing(&PathBuf::from(env_path));
98100
if is_windows_app_folder_in_program_files(&env_path) {
99101
trace!(
100102
"Found Python ({}) in {}\\Software\\Python\\{}\\{}, but skipping as this is a Windows Store Python",
@@ -152,7 +154,7 @@ fn get_registry_pythons_from_key_for_company(
152154
);
153155
continue;
154156
}
155-
let executable = PathBuf::from(executable);
157+
let executable = fix_file_path_casing(&PathBuf::from(executable));
156158
if !executable.exists() {
157159
warn!(
158160
"Python executable ({}) file not found for {}\\Software\\Python\\{}\\{}",

crates/pet-windows-store/src/environments.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ use pet_core::python_environment::PythonEnvironment;
1212
#[cfg(windows)]
1313
use pet_core::{arch::Architecture, python_environment::PythonEnvironmentBuilder};
1414
#[cfg(windows)]
15+
use pet_utils::path::fix_file_path_casing;
16+
#[cfg(windows)]
1517
use regex::Regex;
1618
use std::path::PathBuf;
1719
#[cfg(windows)]
@@ -47,7 +49,7 @@ impl PotentialPython {
4749
let exe = self.exe.clone().unwrap_or_default();
4850
let parent = path.parent()?.to_path_buf(); // This dir definitely exists.
4951
if let Some(result) = get_package_display_name_and_location(&name, hkcu) {
50-
let env_path = PathBuf::from(result.env_path);
52+
let env_path = fix_file_path_casing(&PathBuf::from(result.env_path));
5153

5254
Some(
5355
PythonEnvironmentBuilder::new(

0 commit comments

Comments
 (0)