diff --git a/native_locator/src/common_python.rs b/native_locator/src/common_python.rs
index 3d206529035c..ef6c057b1826 100644
--- a/native_locator/src/common_python.rs
+++ b/native_locator/src/common_python.rs
@@ -47,6 +47,7 @@ impl Locator for PythonOnPath<'_> {
env_manager: None,
project_path: None,
python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]),
+ arch: None,
})
}
diff --git a/native_locator/src/conda.rs b/native_locator/src/conda.rs
index 23a8689ec2a7..bec00693d21b 100644
--- a/native_locator/src/conda.rs
+++ b/native_locator/src/conda.rs
@@ -646,6 +646,7 @@ fn get_root_python_environment(path: &PathBuf, manager: &EnvManager) -> Option
Option {
- let path = path.path();
+fn is_symlinked_python_executable(path: &PathBuf) -> Option {
let name = path.file_name()?.to_string_lossy();
if !name.starts_with("python") || name.ends_with("-config") || name.ends_with("-build") {
return None;
@@ -23,6 +22,149 @@ fn is_symlinked_python_executable(path: &DirEntry) -> Option {
Some(std::fs::canonicalize(path).ok()?)
}
+fn get_homebrew_prefix_env_var(environment: &dyn Environment) -> Option {
+ if let Some(homebrew_prefix) = environment.get_env_var("HOMEBREW_PREFIX".to_string()) {
+ let homebrew_prefix_bin = PathBuf::from(homebrew_prefix).join("bin");
+ if homebrew_prefix_bin.exists() {
+ return Some(homebrew_prefix_bin);
+ }
+ }
+ None
+}
+
+fn get_homebrew_prefix_bin(environment: &dyn Environment) -> Option {
+ if let Some(homebrew_prefix) = get_homebrew_prefix_env_var(environment) {
+ return Some(homebrew_prefix);
+ }
+
+ // Homebrew install folders documented here https://docs.brew.sh/Installation
+ // /opt/homebrew for Apple Silicon,
+ // /usr/local for macOS Intel
+ // /home/linuxbrew/.linuxbrew for Linux
+ [
+ "/home/linuxbrew/.linuxbrew/bin",
+ "/opt/homebrew/bin",
+ "/usr/local/bin",
+ ]
+ .iter()
+ .map(|p| PathBuf::from(p))
+ .find(|p| p.exists())
+}
+
+fn get_env_path(python_exe_from_bin_dir: &PathBuf, resolved_file: &PathBuf) -> Option {
+ // If the fully resolved file path contains the words `/homebrew/` or `/linuxbrew/`
+ // Then we know this is definitely a home brew version of python.
+ // And in these cases we can compute the sysprefix.
+
+ let resolved_file = resolved_file.to_str()?;
+ // 1. MacOS Silicon
+ if python_exe_from_bin_dir
+ .to_string_lossy()
+ .to_lowercase()
+ .starts_with("/opt/homebrew/bin/python")
+ {
+ // Resolved exe is something like `/opt/homebrew/Cellar/python@3.12/3.12.3/Frameworks/Python.framework/Versions/3.12/bin/python3.12`
+ let reg_ex = Regex::new("/opt/homebrew/Cellar/python@((\\d+\\.?)*)/(\\d+\\.?)*/Frameworks/Python.framework/Versions/(\\d+\\.?)*/bin/python(\\d+\\.?)*").unwrap();
+ let captures = reg_ex.captures(&resolved_file)?;
+ let version = captures.get(1).map(|m| m.as_str()).unwrap_or_default();
+ // SysPrefix- /opt/homebrew/opt/python@3.12/Frameworks/Python.framework/Versions/3.12
+ let sys_prefix = PathBuf::from(format!(
+ "/opt/homebrew/opt/python@{}/Frameworks/Python.framework/Versions/{}",
+ version, version
+ ));
+
+ return if sys_prefix.exists() {
+ Some(sys_prefix)
+ } else {
+ None
+ };
+ }
+
+ // 2. Linux
+ if python_exe_from_bin_dir
+ .to_string_lossy()
+ .to_lowercase()
+ .starts_with("/usr/local/bin/python")
+ {
+ // Resolved exe is something like `/home/linuxbrew/.linuxbrew/Cellar/python@3.12/3.12.3/bin/python3.12`
+ let reg_ex = Regex::new("/home/linuxbrew/.linuxbrew/Cellar/python@(\\d+\\.?\\d+\\.?)/(\\d+\\.?\\d+\\.?\\d+\\.?)/bin/python.*").unwrap();
+ let captures = reg_ex.captures(&resolved_file)?;
+ let version = captures.get(1).map(|m| m.as_str()).unwrap_or_default();
+ let full_version = captures.get(2).map(|m| m.as_str()).unwrap_or_default();
+ // SysPrefix- /home/linuxbrew/.linuxbrew/Cellar/python@3.12/3.12.3
+ let sys_prefix = PathBuf::from(format!(
+ "/home/linuxbrew/.linuxbrew/Cellar/python@{}/{}",
+ version, full_version
+ ));
+
+ return if sys_prefix.exists() {
+ Some(sys_prefix)
+ } else {
+ None
+ };
+ }
+
+ // 3. MacOS Intel
+ if python_exe_from_bin_dir
+ .to_string_lossy()
+ .to_lowercase()
+ .starts_with("/usr/local/bin/python")
+ {
+ // Resolved exe is something like `/usr/local/Cellar/python@3.12/3.12.3/Frameworks/Python.framework/Versions/3.12/bin/python3.12`
+ let reg_ex = Regex::new("/usr/local/Cellar/python@(\\d+\\.?\\d+\\.?)/(\\d+\\.?\\d+\\.?\\d+\\.?)/Frameworks/Python.framework/Versions/(\\d+\\.?\\d+\\.?)/bin/python.*").unwrap();
+ let captures = reg_ex.captures(&resolved_file)?;
+ let version = captures.get(1).map(|m| m.as_str()).unwrap_or_default();
+ let full_version = captures.get(2).map(|m| m.as_str()).unwrap_or_default();
+ // SysPrefix- /usr/local/Cellar/python@3.8/3.8.19/Frameworks/Python.framework/Versions/3.8
+ let sys_prefix = PathBuf::from(format!(
+ "/usr/local/Cellar/python@{}/{}/Frameworks/Python.framework/Versions/{}",
+ version, full_version, version
+ ));
+
+ return if sys_prefix.exists() {
+ Some(sys_prefix)
+ } else {
+ None
+ };
+ }
+ None
+}
+
+fn get_python_info(
+ python_exe_from_bin_dir: &PathBuf,
+ reported: &mut HashSet,
+ python_version_regex: &Regex,
+) -> Option {
+ // Possible we do not have python3.12 or the like in bin directory
+ // & we have only python3, in that case we should add python3 to the list
+ if let Some(resolved_exe) = is_symlinked_python_executable(python_exe_from_bin_dir) {
+ let user_friendly_exe = python_exe_from_bin_dir;
+ let python_version = resolved_exe.to_string_lossy().to_string();
+ let version = match python_version_regex.captures(&python_version) {
+ Some(captures) => match captures.get(1) {
+ Some(version) => Some(version.as_str().to_string()),
+ None => None,
+ },
+ None => None,
+ };
+ if reported.contains(&resolved_exe.to_string_lossy().to_string()) {
+ return None;
+ }
+ reported.insert(resolved_exe.to_string_lossy().to_string());
+ return Some(PythonEnvironment::new(
+ None,
+ None,
+ Some(user_friendly_exe.clone()),
+ crate::messaging::PythonEnvironmentCategory::Homebrew,
+ version,
+ get_env_path(python_exe_from_bin_dir, &resolved_exe),
+ None,
+ Some(vec![user_friendly_exe.to_string_lossy().to_string()]),
+ ));
+ }
+ None
+}
+
pub struct Homebrew<'a> {
pub environment: &'a dyn Environment,
}
@@ -34,64 +176,100 @@ impl Homebrew<'_> {
}
impl Locator for Homebrew<'_> {
- fn resolve(&self, _env: &PythonEnv) -> Option {
- None
+ fn resolve(&self, env: &PythonEnv) -> Option {
+ let python_regex = Regex::new(r"/(\d+\.\d+\.\d+)/").unwrap();
+ let exe = env.executable.clone();
+ let exe_file_name = exe.file_name()?;
+ let mut reported: HashSet = HashSet::new();
+ if exe.starts_with("/opt/homebrew/bin/python")
+ || exe.starts_with("/opt/homebrew/Cellar/python@")
+ || exe.starts_with("/opt/homebrew/opt/python@")
+ || exe.starts_with("/opt/homebrew/opt/python")
+ || exe.starts_with("/opt/homebrew/Frameworks/Python.framework/Versions/")
+ {
+ // Symlink - /opt/homebrew/bin/python3.12
+ // Symlink - /opt/homebrew/opt/python3/bin/python3.12
+ // Symlink - /opt/homebrew/Cellar/python@3.12/3.12.3/bin/python3.12
+ // Symlink - /opt/homebrew/opt/python@3.12/bin/python3.12
+ // Symlink - /opt/homebrew/Cellar/python@3.12/3.12.3/Frameworks/Python.framework/Versions/3.12/bin/python3.12
+ // Symlink - /opt/homebrew/Cellar/python@3.12/3.12.3/Frameworks/Python.framework/Versions/Current/bin/python3.12
+ // Symlink - /opt/homebrew/Frameworks/Python.framework/Versions/3.12/bin/python3.12
+ // Symlink - /opt/homebrew/Frameworks/Python.framework/Versions/Current/bin/python3.12
+ // Real exe - /opt/homebrew/Cellar/python@3.12/3.12.3/Frameworks/Python.framework/Versions/3.12/bin/python3.12
+ // SysPrefix- /opt/homebrew/opt/python@3.12/Frameworks/Python.framework/Versions/3.12
+ get_python_info(
+ &PathBuf::from("/opt/homebrew/bin").join(exe_file_name),
+ &mut reported,
+ &python_regex,
+ )
+ } else if exe.starts_with("/usr/local/bin/python")
+ || exe.starts_with("/usr/local/opt/python@")
+ || exe.starts_with("/usr/local/Cellar/python@")
+ {
+ // Symlink - /usr/local/bin/python3.8
+ // Symlink - /usr/local/opt/python@3.8/bin/python3.8
+ // Symlink - /usr/local/Cellar/python@3.8/3.8.19/bin/python3.8
+ // Real exe - /usr/local/Cellar/python@3.8/3.8.19/Frameworks/Python.framework/Versions/3.8/bin/python3.8
+ // SysPrefix- /usr/local/Cellar/python@3.8/3.8.19/Frameworks/Python.framework/Versions/3.8
+ get_python_info(
+ &PathBuf::from("/usr/local/bin").join(exe_file_name),
+ &mut reported,
+ &python_regex,
+ )
+ } else if exe.starts_with("/usr/local/bin/python")
+ || exe.starts_with("/home/linuxbrew/.linuxbrew/bin/python")
+ || exe.starts_with("/home/linuxbrew/.linuxbrew/opt/python@")
+ || exe.starts_with("/home/linuxbrew/.linuxbrew/Cellar/python")
+ {
+ // Symlink - /usr/local/bin/python3.12
+ // Symlink - /home/linuxbrew/.linuxbrew/bin/python3.12
+ // Symlink - /home/linuxbrew/.linuxbrew/opt/python@3.12/bin/python3.12
+ // Real exe - /home/linuxbrew/.linuxbrew/Cellar/python@3.12/3.12.3/bin/python3.12
+ // SysPrefix- /home/linuxbrew/.linuxbrew/Cellar/python@3.12/3.12.3
+
+ get_python_info(
+ &PathBuf::from("/usr/local/bin").join(exe_file_name),
+ &mut reported,
+ &python_regex,
+ )
+ } else {
+ None
+ }
}
fn find(&mut self) -> Option {
- let homebrew_prefix = self
- .environment
- .get_env_var("HOMEBREW_PREFIX".to_string())?;
- let homebrew_prefix_bin = PathBuf::from(homebrew_prefix).join("bin");
+ let homebrew_prefix_bin = get_homebrew_prefix_bin(self.environment)?;
let mut reported: HashSet = HashSet::new();
let python_regex = Regex::new(r"/(\d+\.\d+\.\d+)/").unwrap();
let mut environments: Vec = vec![];
- for file in std::fs::read_dir(homebrew_prefix_bin)
+ for file in std::fs::read_dir(&homebrew_prefix_bin)
.ok()?
.filter_map(Result::ok)
{
- if let Some(exe) = is_symlinked_python_executable(&file) {
- let python_version = exe.to_string_lossy().to_string();
- let version = match python_regex.captures(&python_version) {
- Some(captures) => match captures.get(1) {
- Some(version) => Some(version.as_str().to_string()),
- None => None,
- },
- None => None,
- };
- if reported.contains(&exe.to_string_lossy().to_string()) {
+ // If this file name is `python3`, then ignore this for now.
+ // We would prefer to use `python3.x` instead of `python3`.
+ // That way its more consistent and future proof
+ if let Some(file_name) = file.file_name().to_str() {
+ if file_name.to_lowercase() == "python3" {
continue;
}
- let env_path = match exe.parent() {
- Some(path) => {
- if let Some(name) = path.file_name() {
- if name.to_ascii_lowercase() == "bin"
- || name.to_ascii_lowercase() == "Scripts"
- {
- Some(path.parent()?.to_path_buf())
- } else {
- Some(path.to_path_buf())
- }
- } else {
- None
- }
- }
- None => continue,
- };
- reported.insert(exe.to_string_lossy().to_string());
- let env = crate::messaging::PythonEnvironment::new(
- None,
- None,
- Some(exe.clone()),
- crate::messaging::PythonEnvironmentCategory::Homebrew,
- version,
- env_path,
- None,
- Some(vec![exe.to_string_lossy().to_string()]),
- );
+ }
+
+ if let Some(env) = get_python_info(&file.path(), &mut reported, &python_regex) {
environments.push(env);
}
}
+
+ // Possible we do not have python3.12 or the like in bin directory
+ // & we have only python3, in that case we should add python3 to the list
+ if let Some(env) = get_python_info(
+ &homebrew_prefix_bin.join("python3"),
+ &mut reported,
+ &python_regex,
+ ) {
+ environments.push(env);
+ }
+
if environments.is_empty() {
None
} else {
diff --git a/native_locator/src/main.rs b/native_locator/src/main.rs
index ee976bf756d2..2e9989347929 100644
--- a/native_locator/src/main.rs
+++ b/native_locator/src/main.rs
@@ -37,7 +37,7 @@ fn main() {
let virtualenv_locator = virtualenv::VirtualEnv::new();
let venv_locator = venv::Venv::new();
- let virtualenvwrapper_locator = virtualenvwrapper::VirtualEnvWrapper::with(&environment);
+ let mut virtualenvwrapper = virtualenvwrapper::VirtualEnvWrapper::with(&environment);
let pipenv_locator = pipenv::PipEnv::new();
let mut path_locator = common_python::PythonOnPath::with(&environment);
let mut conda_locator = conda::Conda::with(&environment);
@@ -54,6 +54,7 @@ fn main() {
#[cfg(windows)]
find_environments(&mut windows_registry, &mut dispatcher);
let mut pyenv_locator = pyenv::PyEnv::with(&environment, &mut conda_locator);
+ find_environments(&mut virtualenvwrapper, &mut dispatcher);
find_environments(&mut pyenv_locator, &mut dispatcher);
#[cfg(unix)]
find_environments(&mut homebrew_locator, &mut dispatcher);
@@ -61,14 +62,27 @@ fn main() {
#[cfg(windows)]
find_environments(&mut windows_store, &mut dispatcher);
- // Step 2: Search in some global locations.
+ // Step 2: Search in some global locations for virtual envs.
for env in list_global_virtual_envs(&environment).iter() {
if dispatcher.was_environment_reported(&env) {
continue;
}
- let _ = resolve_environment(&pipenv_locator, env, &mut dispatcher)
- || resolve_environment(&virtualenvwrapper_locator, env, &mut dispatcher)
+ // First must be homebrew, as it is the most specific and supports symlinks
+ #[cfg(unix)]
+ let homebrew_result = resolve_environment(&homebrew_locator, env, &mut dispatcher);
+ #[cfg(unix)]
+ if homebrew_result {
+ continue;
+ }
+
+ let _ = // Pipeenv before virtualenvwrapper as it is more specific.
+ // Because pipenv environments are also virtualenvwrapper environments.
+ resolve_environment(&pipenv_locator, env, &mut dispatcher)
+ // Before venv, as all venvs are also virtualenvwrapper environments.
+ || resolve_environment(&virtualenvwrapper, env, &mut dispatcher)
+ // Before virtualenv as this is more specific.
+ // All venvs are also virtualenvs environments.
|| resolve_environment(&venv_locator, env, &mut dispatcher)
|| resolve_environment(&virtualenv_locator, env, &mut dispatcher);
}
diff --git a/native_locator/src/messaging.rs b/native_locator/src/messaging.rs
index 73e708dcac5f..1365949e703e 100644
--- a/native_locator/src/messaging.rs
+++ b/native_locator/src/messaging.rs
@@ -80,6 +80,14 @@ pub enum PythonEnvironmentCategory {
VirtualEnv,
}
+#[derive(Serialize, Deserialize, Clone)]
+#[serde(rename_all = "camelCase")]
+#[derive(Debug)]
+pub enum Architecture {
+ X64,
+ X86,
+}
+
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
#[derive(Debug)]
@@ -96,6 +104,7 @@ pub struct PythonEnvironment {
* The project path for the Pipenv environment.
*/
pub project_path: Option,
+ pub arch: Option,
}
impl PythonEnvironment {
@@ -119,6 +128,7 @@ impl PythonEnvironment {
env_manager,
python_run_command,
project_path: None,
+ arch: None,
}
}
}
diff --git a/native_locator/src/pipenv.rs b/native_locator/src/pipenv.rs
index bb5eab5776fe..6eacb3601dba 100644
--- a/native_locator/src/pipenv.rs
+++ b/native_locator/src/pipenv.rs
@@ -18,6 +18,17 @@ fn get_pipenv_project(env: &PythonEnv) -> Option {
None
}
+fn is_pipenv(env: &PythonEnv) -> bool {
+ // If we have a Pipfile, then this is a pipenv environment.
+ // Else likely a virtualenvwrapper or the like.
+ if let Some(project_path) = get_pipenv_project(env) {
+ if project_path.join("Pipfile").exists() {
+ return true;
+ }
+ }
+ false
+}
+
pub struct PipEnv {}
impl PipEnv {
@@ -28,6 +39,9 @@ impl PipEnv {
impl Locator for PipEnv {
fn resolve(&self, env: &PythonEnv) -> Option {
+ if !is_pipenv(env) {
+ return None;
+ }
let project_path = get_pipenv_project(env)?;
Some(PythonEnvironment {
display_name: None,
@@ -39,6 +53,7 @@ impl Locator for PipEnv {
env_manager: None,
python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]),
project_path: Some(project_path),
+ arch: None,
})
}
diff --git a/native_locator/src/pyenv.rs b/native_locator/src/pyenv.rs
index 9137a80df1f7..ba0395f2738c 100644
--- a/native_locator/src/pyenv.rs
+++ b/native_locator/src/pyenv.rs
@@ -66,7 +66,7 @@ fn get_pyenv_binary(environment: &dyn known::Environment) -> Option {
}
}
-fn get_pyenv_version(folder_name: &String) -> Option {
+fn get_version(folder_name: &String) -> Option {
// Stable Versions = like 3.10.10
let python_regex = Regex::new(r"^(\d+\.\d+\.\d+)$").unwrap();
match python_regex.captures(&folder_name) {
@@ -90,7 +90,17 @@ fn get_pyenv_version(folder_name: &String) -> Option {
Some(version) => Some(version.as_str().to_string()),
None => None,
},
- None => None,
+ None => {
+ // win32 versions, rc Versions = like 3.11.0a-win32
+ let python_regex = Regex::new(r"^(\d+\.\d+.\d+\w\d+)-win32").unwrap();
+ match python_regex.captures(&folder_name) {
+ Some(captures) => match captures.get(1) {
+ Some(version) => Some(version.as_str().to_string()),
+ None => None,
+ },
+ None => None,
+ }
+ }
}
}
}
@@ -103,8 +113,9 @@ fn get_pure_python_environment(
path: &PathBuf,
manager: &Option,
) -> Option {
- let version = get_pyenv_version(&path.file_name().unwrap().to_string_lossy().to_string())?;
- Some(messaging::PythonEnvironment::new(
+ let file_name = path.file_name()?.to_string_lossy().to_string();
+ let version = get_version(&file_name)?;
+ let mut env = messaging::PythonEnvironment::new(
None,
None,
Some(executable.clone()),
@@ -117,7 +128,12 @@ fn get_pure_python_environment(
.into_os_string()
.into_string()
.unwrap()]),
- ))
+ );
+ if file_name.ends_with("-win32") {
+ env.arch = Some(messaging::Architecture::X86);
+ }
+
+ Some(env)
}
fn is_conda_environment(path: &PathBuf) -> bool {
@@ -189,6 +205,47 @@ pub fn list_pyenv_environments(
Some(envs)
}
+#[cfg(windows)]
+fn get_pyenv_manager_version(
+ pyenv_binary_path: &PathBuf,
+ environment: &dyn known::Environment,
+) -> Option {
+ // In windows, the version is stored in the `.pyenv/.version` file
+ let pyenv_dir = get_pyenv_dir(environment)?;
+ let mut version_file = PathBuf::from(&pyenv_dir).join(".version");
+ if !version_file.exists() {
+ // We might have got the path `~/.pyenv/pyenv-win`
+ version_file = pyenv_dir.parent()?.join(".version");
+ if !version_file.exists() {
+ return None;
+ }
+ }
+ let version = fs::read_to_string(version_file).ok()?;
+ let version_regex = Regex::new(r"(\d+\.\d+\.\d+)").unwrap();
+ let captures = version_regex.captures(&version)?.get(1)?;
+ Some(captures.as_str().to_string())
+}
+
+#[cfg(unix)]
+fn get_pyenv_manager_version(
+ pyenv_binary_path: &PathBuf,
+ _environment: &dyn known::Environment,
+) -> Option {
+ // Look for version in path
+ // Sample /opt/homebrew/Cellar/pyenv/2.4.0/libexec/pyenv
+ if !pyenv_binary_path.to_string_lossy().contains("/pyenv/") {
+ return None;
+ }
+ // Find the real path, generally we have a symlink.
+ let real_path = fs::read_link(pyenv_binary_path)
+ .ok()?
+ .to_string_lossy()
+ .to_string();
+ let version_regex = Regex::new(r"pyenv/(\d+\.\d+\.\d+)/").unwrap();
+ let captures = version_regex.captures(&real_path)?.get(1)?;
+ Some(captures.as_str().to_string())
+}
+
pub struct PyEnv<'a> {
pub environment: &'a dyn Environment,
pub conda_locator: &'a mut dyn CondaLocator,
@@ -214,7 +271,8 @@ impl Locator for PyEnv<'_> {
fn find(&mut self) -> Option {
let pyenv_binary = get_pyenv_binary(self.environment)?;
- let manager = messaging::EnvManager::new(pyenv_binary, None, EnvManagerType::Pyenv);
+ let version = get_pyenv_manager_version(&pyenv_binary, self.environment);
+ let manager = messaging::EnvManager::new(pyenv_binary, version, EnvManagerType::Pyenv);
let mut environments: Vec = vec![];
if let Some(envs) =
list_pyenv_environments(&Some(manager.clone()), self.environment, self.conda_locator)
diff --git a/native_locator/src/utils.rs b/native_locator/src/utils.rs
index c70efe9654ef..d9a30c3a7f8a 100644
--- a/native_locator/src/utils.rs
+++ b/native_locator/src/utils.rs
@@ -6,7 +6,6 @@ use regex::Regex;
use std::{
fs,
path::{Path, PathBuf},
- process::Command,
};
#[derive(Debug)]
@@ -96,16 +95,7 @@ pub fn get_version(python_executable: &PathBuf) -> Option {
return Some(pyenv_cfg.version);
}
}
-
- let output = Command::new(python_executable)
- .arg("-c")
- .arg("import sys; print(sys.version)")
- .output()
- .ok()?;
- let output = String::from_utf8(output.stdout).ok()?;
- let output = output.trim();
- let output = output.split_whitespace().next().unwrap_or(output);
- Some(output.to_string())
+ None
}
pub fn find_python_binary_path(env_path: &Path) -> Option {
diff --git a/native_locator/src/venv.rs b/native_locator/src/venv.rs
index 94040a536989..702bf8b6dcc9 100644
--- a/native_locator/src/venv.rs
+++ b/native_locator/src/venv.rs
@@ -43,6 +43,7 @@ impl Locator for Venv {
env_manager: None,
project_path: None,
python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]),
+ arch: None,
});
}
None
diff --git a/native_locator/src/virtualenv.rs b/native_locator/src/virtualenv.rs
index 2a6909e63fa2..209d72c5f533 100644
--- a/native_locator/src/virtualenv.rs
+++ b/native_locator/src/virtualenv.rs
@@ -74,6 +74,7 @@ impl Locator for VirtualEnv {
env_manager: None,
project_path: None,
python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]),
+ arch: None,
});
}
None
diff --git a/native_locator/src/virtualenvwrapper.rs b/native_locator/src/virtualenvwrapper.rs
index d55a89e09dca..54728b4cb644 100644
--- a/native_locator/src/virtualenvwrapper.rs
+++ b/native_locator/src/virtualenvwrapper.rs
@@ -6,6 +6,7 @@ use crate::messaging::PythonEnvironment;
use crate::utils::list_python_environments;
use crate::virtualenv;
use crate::{known::Environment, utils::PythonEnv};
+use std::fs;
use std::path::PathBuf;
#[cfg(windows)]
@@ -29,7 +30,7 @@ fn get_default_virtualenvwrapper_path(environment: &dyn Environment) -> Option Option {
if let Some(home) = environment.get_user_home() {
- let home = PathBuf::from(home).join("virtualenvs");
+ let home = PathBuf::from(home).join(".virtualenvs");
if home.exists() {
return Some(home);
}
@@ -37,7 +38,7 @@ fn get_default_virtualenvwrapper_path(environment: &dyn Environment) -> Option Option {
+pub fn get_work_on_home_path(environment: &dyn Environment) -> Option {
// The WORKON_HOME variable contains the path to the root directory of all virtualenvwrapper environments.
// If the interpreter path belongs to one of them then it is a virtualenvwrapper type of environment.
if let Some(work_on_home) = environment.get_env_var("WORKON_HOME".to_string()) {
@@ -54,6 +55,7 @@ pub fn is_virtualenvwrapper(env: &PythonEnv, environment: &dyn Environment) -> b
if env.path.is_none() {
return false;
}
+
// For environment to be a virtualenvwrapper based it has to follow these two rules:
// 1. It should be in a sub-directory under the WORKON_HOME
// 2. It should be a valid virtualenv environment
@@ -66,6 +68,17 @@ pub fn is_virtualenvwrapper(env: &PythonEnv, environment: &dyn Environment) -> b
false
}
+fn get_project(env: &PythonEnv) -> Option {
+ let project_file = env.path.clone()?.join(".project");
+ if let Ok(contents) = fs::read_to_string(project_file) {
+ let project_folder = PathBuf::from(contents.trim().to_string());
+ if project_folder.exists() {
+ return Some(project_folder);
+ }
+ }
+ None
+}
+
pub struct VirtualEnvWrapper<'a> {
pub environment: &'a dyn Environment,
}
@@ -78,28 +91,29 @@ impl VirtualEnvWrapper<'_> {
impl Locator for VirtualEnvWrapper<'_> {
fn resolve(&self, env: &PythonEnv) -> Option {
- if is_virtualenvwrapper(env, self.environment) {
- return Some(PythonEnvironment {
- display_name: None,
- name: Some(
- env.path
- .clone()
- .expect("env.path cannot be empty for virtualenv rapper")
- .file_name()
- .unwrap()
- .to_string_lossy()
- .to_string(),
- ),
- python_executable_path: Some(env.executable.clone()),
- version: env.version.clone(),
- category: crate::messaging::PythonEnvironmentCategory::Venv,
- env_path: env.path.clone(),
- env_manager: None,
- project_path: None,
- python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]),
- });
+ if !is_virtualenvwrapper(env, self.environment) {
+ return None;
}
- None
+ Some(PythonEnvironment {
+ display_name: None,
+ name: Some(
+ env.path
+ .clone()
+ .expect("env.path cannot be empty for virtualenv rapper")
+ .file_name()
+ .unwrap()
+ .to_string_lossy()
+ .to_string(),
+ ),
+ python_executable_path: Some(env.executable.clone()),
+ version: env.version.clone(),
+ category: crate::messaging::PythonEnvironmentCategory::VirtualEnvWrapper,
+ env_path: env.path.clone(),
+ env_manager: None,
+ project_path: get_project(env),
+ python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]),
+ arch: None,
+ })
}
fn find(&mut self) -> Option {
@@ -111,7 +125,6 @@ impl Locator for VirtualEnvWrapper<'_> {
environments.push(env);
}
});
-
if environments.is_empty() {
None
} else {
diff --git a/native_locator/src/windows_store.rs b/native_locator/src/windows_store.rs
index f08622d08127..39f3a01f4ac9 100644
--- a/native_locator/src/windows_store.rs
+++ b/native_locator/src/windows_store.rs
@@ -61,6 +61,7 @@ fn list_windows_store_python_executables(
env_manager: None,
project_path: None,
python_run_command: Some(vec![exe.to_string_lossy().to_string()]),
+ arch: None,
};
python_envs.push(env);
}
@@ -94,8 +95,6 @@ fn get_package_display_name_and_location(name: String, hkcu: &RegKey) -> Option<
let display_name = package_key.get_value("DisplayName").ok()?;
let env_path = package_key.get_value("PackageRootFolder").ok()?;
- let regex = regex::Regex::new("PythonSoftwareFoundation.Python.((\\d+\\.?)*)_.*").ok()?;
-
return Some(StorePythonInfo {
display_name,
env_path,
@@ -131,6 +130,7 @@ impl Locator for WindowsStore<'_> {
env_manager: None,
project_path: None,
python_run_command: Some(vec![env.executable.to_str().unwrap().to_string()]),
+ arch: None,
});
}
None
diff --git a/native_locator/tests/common.rs b/native_locator/tests/common.rs
index bf4c54617f16..1df03a005a73 100644
--- a/native_locator/tests/common.rs
+++ b/native_locator/tests/common.rs
@@ -20,10 +20,12 @@ pub fn join_test_paths(paths: &[&str]) -> PathBuf {
path
}
+#[allow(dead_code)]
pub trait TestMessages {
fn get_messages(&self) -> Vec;
}
+#[allow(dead_code)]
pub struct TestEnvironment {
vars: HashMap,
home: Option,
diff --git a/native_locator/tests/common_python_test.rs b/native_locator/tests/common_python_test.rs
index d7d71fd5cce1..c8a5baa3b10a 100644
--- a/native_locator/tests/common_python_test.rs
+++ b/native_locator/tests/common_python_test.rs
@@ -41,6 +41,7 @@ fn find_python_in_path_this() {
version: None,
python_run_command: Some(vec![unix_python_exe.clone().to_str().unwrap().to_string()]),
env_path: Some(user_home.clone()),
+ arch: None,
};
assert_messages(
&[json!(env)],
diff --git a/native_locator/tests/conda_test.rs b/native_locator/tests/conda_test.rs
index 925219b098bb..95e48917bd82 100644
--- a/native_locator/tests/conda_test.rs
+++ b/native_locator/tests/conda_test.rs
@@ -196,6 +196,7 @@ fn find_conda_from_custom_install_location() {
conda_dir.to_string_lossy().to_string(),
"python".to_string(),
]),
+ arch: None,
};
assert_eq!(json!(expected_conda_env), json!(result.environments[0]));
@@ -265,6 +266,7 @@ fn finds_two_conda_envs_from_known_location() {
"one".to_string(),
"python".to_string(),
]),
+ arch: None,
};
let expected_conda_2 = PythonEnvironment {
display_name: None,
@@ -282,6 +284,7 @@ fn finds_two_conda_envs_from_known_location() {
"two".to_string(),
"python".to_string(),
]),
+ arch: None,
};
assert_messages(
&[json!(expected_conda_1), json!(expected_conda_2)],
diff --git a/native_locator/tests/pyenv_test.rs b/native_locator/tests/pyenv_test.rs
index 87761114089d..c9782e9c5d35 100644
--- a/native_locator/tests/pyenv_test.rs
+++ b/native_locator/tests/pyenv_test.rs
@@ -71,8 +71,7 @@ fn does_not_find_any_pyenv_envs_even_with_pyenv_installed() {
#[cfg(unix)]
fn find_pyenv_envs() {
use crate::common::{
- assert_messages, create_test_environment, join_test_paths,
- test_file_path,
+ assert_messages, create_test_environment, join_test_paths, test_file_path,
};
use python_finder::conda::Conda;
use python_finder::locator::Locator;
@@ -128,7 +127,8 @@ fn find_pyenv_envs() {
home.to_str().unwrap(),
".pyenv/versions/3.9.9"
])),
- env_manager: Some(expected_manager.clone())
+ env_manager: Some(expected_manager.clone()),
+ arch: None
});
let expected_virtual_env = PythonEnvironment {
display_name: None,
@@ -152,6 +152,7 @@ fn find_pyenv_envs() {
".pyenv/versions/my-virtual-env",
])),
env_manager: Some(expected_manager.clone()),
+ arch: None,
};
let expected_3_12_1 = PythonEnvironment {
display_name: None,
@@ -175,6 +176,7 @@ fn find_pyenv_envs() {
".pyenv/versions/3.12.1",
])),
env_manager: Some(expected_manager.clone()),
+ arch: None,
};
let expected_3_13_dev = PythonEnvironment {
display_name: None,
@@ -198,6 +200,7 @@ fn find_pyenv_envs() {
".pyenv/versions/3.13-dev",
])),
env_manager: Some(expected_manager.clone()),
+ arch: None,
};
let expected_3_12_1a3 = PythonEnvironment {
display_name: None,
@@ -221,6 +224,7 @@ fn find_pyenv_envs() {
".pyenv/versions/3.12.1a3",
])),
env_manager: Some(expected_manager.clone()),
+ arch: None,
};
assert_messages(