Skip to content

Preserve case for Windows paths as found on disc #15

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 5, 2024
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
5 changes: 4 additions & 1 deletion crates/pet-conda/src/conda_rc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

use crate::env_variables::EnvVariables;
use log::trace;
use pet_utils::path::fix_file_path_casing;
use std::{fs, path::PathBuf};

#[derive(Debug)]
Expand Down Expand Up @@ -154,7 +155,9 @@ fn parse_conda_rc(conda_rc: &PathBuf) -> Option<Condarc> {
if line.trim().starts_with('-') {
if let Some(env_dir) = line.split_once('-').map(|x| x.1) {
// Directories in conda-rc are where `envs` are stored.
env_dirs.push(PathBuf::from(env_dir.trim()).join("envs"));
env_dirs.push(fix_file_path_casing(
&PathBuf::from(env_dir.trim()).join("envs"),
));
}
continue;
} else {
Expand Down
20 changes: 19 additions & 1 deletion crates/pet-conda/src/environment_locations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::{
utils::{is_conda_env, is_conda_install},
};
use log::trace;
use pet_utils::path::fix_file_path_casing;
use std::{
fs,
path::{Path, PathBuf},
Expand Down Expand Up @@ -34,6 +35,7 @@ pub fn get_conda_environment_paths(env_vars: &EnvVariables) -> Vec<PathBuf> {
envs
});

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

#[cfg(windows)]
pub fn get_known_conda_install_locations(env_vars: &EnvVariables) -> Vec<PathBuf> {
use pet_utils::path::fix_file_path_casing;

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 @@ -200,6 +204,19 @@ pub fn get_known_conda_install_locations(env_vars: &EnvVariables) -> Vec<PathBuf
}
known_paths.sort();
known_paths.dedup();
// Ensure the casing of the paths are correct.
// Its possible the actual path is in a different case.
// E.g. instead of C:\username\miniconda it might bt C:\username\Miniconda
// We use lower cases above, but it could be in any case on disc.
// We do not want to have duplicates in different cases.
// & we'd like to preserve the case of the original path as on disc.
known_paths = known_paths
.iter()
.map(|p| fix_file_path_casing(p))
.collect();
known_paths.sort();
known_paths.dedup();

known_paths
}

Expand Down Expand Up @@ -243,6 +260,7 @@ pub fn get_known_conda_install_locations(env_vars: &EnvVariables) -> Vec<PathBuf
known_paths.append(get_known_conda_locations(env_vars).as_mut());
known_paths.sort();
known_paths.dedup();

known_paths
}

Expand Down
16 changes: 13 additions & 3 deletions crates/pet-conda/src/environments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use pet_core::{
manager::EnvManager,
python_environment::{PythonEnvironment, PythonEnvironmentBuilder, PythonEnvironmentCategory},
};
use pet_utils::executable::find_executable;
use pet_utils::{executable::find_executable, path::fix_file_path_casing};
use std::{
fs,
path::{Path, PathBuf},
Expand Down Expand Up @@ -175,7 +175,12 @@ fn get_conda_dir_from_cmd(cmd_line: String) -> Option<PathBuf> {
|| conda_dir.to_ascii_lowercase() == "scripts"
{
if let Some(conda_dir) = cmd_line.parent() {
return Some(conda_dir.to_path_buf());
// Ensure the casing of the paths are correct.
// Its possible the actual path is in a different case.
// The casing in history might not be same as that on disc
// We do not want to have duplicates in different cases.
// & we'd like to preserve the case of the original path as on disc.
return Some(fix_file_path_casing(conda_dir).to_path_buf());
}
}
// Sometimes we can have paths like
Expand All @@ -202,7 +207,12 @@ fn get_conda_dir_from_cmd(cmd_line: String) -> Option<PathBuf> {
let _ = cmd_line.pop();
}
}
return Some(cmd_line.to_path_buf());
// Ensure the casing of the paths are correct.
// Its possible the actual path is in a different case.
// The casing in history might not be same as that on disc
// We do not want to have duplicates in different cases.
// & we'd like to preserve the case of the original path as on disc.
return Some(fix_file_path_casing(&cmd_line).to_path_buf());
}
}
None
Expand Down
12 changes: 6 additions & 6 deletions crates/pet-global-virtualenvs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

use pet_conda::utils::is_conda_env;
use pet_utils::path::fix_file_path_casing;
use std::{fs, path::PathBuf};

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

if let Some(work_on_home) = work_on_home_env_var {
if let Ok(work_on_home) = fs::canonicalize(work_on_home) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately theres a known issue in rust and this returns UNC prefix, which we do not need
rust-lang/rust#42869

if work_on_home.exists() {
venv_dirs.push(work_on_home);
}
let work_on_home = fix_file_path_casing(&PathBuf::from(work_on_home));
if fs::metadata(&work_on_home).is_ok() {
venv_dirs.push(work_on_home);
}
}

Expand All @@ -27,13 +27,13 @@ fn get_global_virtualenv_dirs(
PathBuf::from(".local").join("share").join("virtualenvs"),
] {
let venv_dir = home.join(dir);
if venv_dir.exists() {
if fs::metadata(&venv_dir).is_ok() {
venv_dirs.push(venv_dir);
}
}
if cfg!(target_os = "linux") {
let envs = PathBuf::from("Envs");
if envs.exists() {
if fs::metadata(&envs).is_ok() {
venv_dirs.push(envs);
}
}
Expand Down
4 changes: 2 additions & 2 deletions crates/pet-pipenv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ use pet_core::{
python_environment::{PythonEnvironment, PythonEnvironmentBuilder, PythonEnvironmentCategory},
Locator, LocatorResult,
};
use pet_utils::env::PythonEnv;
use pet_utils::{env::PythonEnv, path::fix_file_path_casing};

fn get_pipenv_project(env: &PythonEnv) -> Option<PathBuf> {
let project_file = env.prefix.clone()?.join(".project");
let contents = fs::read_to_string(project_file).ok()?;
let project_folder = PathBuf::from(contents.trim().to_string());
let project_folder = fix_file_path_casing(&PathBuf::from(contents.trim().to_string()));
if fs::metadata(&project_folder).is_ok() {
Some(project_folder)
} else {
Expand Down
1 change: 1 addition & 0 deletions crates/pet-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
pub mod env;
pub mod executable;
pub mod headers;
pub mod path;
pub mod pyvenv_cfg;
pub mod sys_prefix;
30 changes: 30 additions & 0 deletions crates/pet-utils/src/path.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use std::{
fs,
path::{Path, PathBuf},
};

// This function is used to fix the casing of the file path.
// by returning the actual path with the correct casing as found on the OS.
// This is a noop for Unix systems.
// I.e. this function is only useful on Windows.
pub fn fix_file_path_casing(path: &Path) -> PathBuf {
// Return the path as is.
if cfg!(unix) {
return path.to_path_buf();
}
let has_unc_prefix = path.to_string_lossy().starts_with(r"\\?\");
if let Ok(resolved) = fs::canonicalize(path) {
if resolved.to_string_lossy().starts_with(r"\\?\") && !has_unc_prefix {
// If the resolved path has a UNC prefix, but the original path did not,
// we need to remove the UNC prefix.
PathBuf::from(resolved.to_string_lossy().trim_start_matches(r"\\?\"))
} else {
resolved
}
} else {
path.to_path_buf()
}
}
6 changes: 4 additions & 2 deletions crates/pet-virtualenvwrapper/src/environments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// Licensed under the MIT License.

use crate::{env_variables::EnvVariables, environment_locations::get_work_on_home_path};
use pet_utils::{env::PythonEnv, executable::find_executable, pyvenv_cfg::PyVenvCfg};
use pet_utils::{
env::PythonEnv, executable::find_executable, path::fix_file_path_casing, pyvenv_cfg::PyVenvCfg,
};
use pet_virtualenv::is_virtualenv;
use std::{fs, path::PathBuf};

Expand All @@ -26,7 +28,7 @@ pub fn is_virtualenvwrapper(env: &PythonEnv, environment: &EnvVariables) -> bool
pub fn get_project(env: &PythonEnv) -> Option<PathBuf> {
let project_file = env.prefix.clone()?.join(".project");
let contents = fs::read_to_string(project_file).ok()?;
let project_folder = PathBuf::from(contents.trim().to_string());
let project_folder = fix_file_path_casing(&PathBuf::from(contents.trim().to_string()));
if fs::metadata(&project_folder).is_ok() {
Some(project_folder)
} else {
Expand Down
6 changes: 4 additions & 2 deletions crates/pet-windows-registry/src/environments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use pet_core::{
LocatorResult,
};
#[cfg(windows)]
use pet_utils::path::fix_file_path_casing;
#[cfg(windows)]
use pet_windows_store::is_windows_app_folder_in_program_files;
#[cfg(windows)]
use std::{path::PathBuf, sync::Arc};
Expand Down Expand Up @@ -94,7 +96,7 @@ fn get_registry_pythons_from_key_for_company(
Ok(install_path_key) => {
let env_path: String =
install_path_key.get_value("").ok().unwrap_or_default();
let env_path = PathBuf::from(env_path);
let env_path = fix_file_path_casing(&PathBuf::from(env_path));
if is_windows_app_folder_in_program_files(&env_path) {
trace!(
"Found Python ({}) in {}\\Software\\Python\\{}\\{}, but skipping as this is a Windows Store Python",
Expand Down Expand Up @@ -152,7 +154,7 @@ fn get_registry_pythons_from_key_for_company(
);
continue;
}
let executable = PathBuf::from(executable);
let executable = fix_file_path_casing(&PathBuf::from(executable));
if !executable.exists() {
warn!(
"Python executable ({}) file not found for {}\\Software\\Python\\{}\\{}",
Expand Down
4 changes: 3 additions & 1 deletion crates/pet-windows-store/src/environments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use pet_core::python_environment::PythonEnvironment;
#[cfg(windows)]
use pet_core::{arch::Architecture, python_environment::PythonEnvironmentBuilder};
#[cfg(windows)]
use pet_utils::path::fix_file_path_casing;
#[cfg(windows)]
use regex::Regex;
use std::path::PathBuf;
#[cfg(windows)]
Expand Down Expand Up @@ -47,7 +49,7 @@ impl PotentialPython {
let exe = self.exe.clone().unwrap_or_default();
let parent = path.parent()?.to_path_buf(); // This dir definitely exists.
if let Some(result) = get_package_display_name_and_location(&name, hkcu) {
let env_path = PathBuf::from(result.env_path);
let env_path = fix_file_path_casing(&PathBuf::from(result.env_path));

Some(
PythonEnvironmentBuilder::new(
Expand Down