|
2 | 2 | // Licensed under the MIT License. |
3 | 3 |
|
4 | 4 | use env_variables::EnvVariables; |
| 5 | +use log::trace; |
| 6 | +use manager::PipenvManager; |
5 | 7 | use pet_core::env::PythonEnv; |
6 | 8 | use pet_core::os_environment::Environment; |
7 | 9 | use pet_core::LocatorKind; |
8 | 10 | use pet_core::{ |
9 | 11 | python_environment::{PythonEnvironment, PythonEnvironmentBuilder, PythonEnvironmentKind}, |
10 | 12 | reporter::Reporter, |
11 | | - Locator, |
| 13 | + Configuration, Locator, |
12 | 14 | }; |
13 | 15 | use pet_fs::path::norm_case; |
14 | 16 | use pet_python_utils::executable::find_executables; |
15 | 17 | use pet_python_utils::version; |
16 | 18 | use std::path::Path; |
| 19 | +use std::sync::{Arc, RwLock}; |
17 | 20 | use std::{fs, path::PathBuf}; |
18 | 21 |
|
19 | 22 | mod env_variables; |
| 23 | +pub mod manager; |
20 | 24 |
|
21 | 25 | fn get_pipenv_project(env: &PythonEnv) -> Option<PathBuf> { |
22 | 26 | if let Some(prefix) = &env.prefix { |
@@ -128,21 +132,129 @@ fn is_pipenv(env: &PythonEnv, env_vars: &EnvVariables) -> bool { |
128 | 132 | } |
129 | 133 | } |
130 | 134 |
|
| 135 | +/// Get the default virtualenvs directory for pipenv |
| 136 | +/// - If WORKON_HOME is set, use that |
| 137 | +/// - Linux/macOS: ~/.local/share/virtualenvs/ |
| 138 | +/// - Windows: %USERPROFILE%\.virtualenvs\ |
| 139 | +fn get_virtualenvs_dir(env_vars: &EnvVariables) -> Option<PathBuf> { |
| 140 | + // First check WORKON_HOME environment variable |
| 141 | + if let Some(workon_home) = &env_vars.workon_home { |
| 142 | + if workon_home.is_dir() { |
| 143 | + return Some(workon_home.clone()); |
| 144 | + } |
| 145 | + } |
| 146 | + |
| 147 | + // Fall back to default locations |
| 148 | + if let Some(home) = &env_vars.home { |
| 149 | + if std::env::consts::OS == "windows" { |
| 150 | + let dir = home.join(".virtualenvs"); |
| 151 | + if dir.is_dir() { |
| 152 | + return Some(dir); |
| 153 | + } |
| 154 | + } else { |
| 155 | + let dir = home.join(".local").join("share").join("virtualenvs"); |
| 156 | + if dir.is_dir() { |
| 157 | + return Some(dir); |
| 158 | + } |
| 159 | + } |
| 160 | + } |
| 161 | + |
| 162 | + None |
| 163 | +} |
| 164 | + |
| 165 | +/// Discover pipenv environments from the virtualenvs directory |
| 166 | +fn list_environments(env_vars: &EnvVariables) -> Vec<PythonEnvironment> { |
| 167 | + let mut environments = vec![]; |
| 168 | + |
| 169 | + if let Some(virtualenvs_dir) = get_virtualenvs_dir(env_vars) { |
| 170 | + trace!("Searching for pipenv environments in {:?}", virtualenvs_dir); |
| 171 | + |
| 172 | + if let Ok(entries) = fs::read_dir(&virtualenvs_dir) { |
| 173 | + for entry in entries.flatten() { |
| 174 | + let path = entry.path(); |
| 175 | + if !path.is_dir() { |
| 176 | + continue; |
| 177 | + } |
| 178 | + |
| 179 | + // Check if this directory is a valid virtualenv with a .project file |
| 180 | + let project_file = path.join(".project"); |
| 181 | + if !project_file.exists() { |
| 182 | + continue; |
| 183 | + } |
| 184 | + |
| 185 | + // Read the project path from .project file |
| 186 | + if let Ok(project_contents) = fs::read_to_string(&project_file) { |
| 187 | + let project_path = PathBuf::from(project_contents.trim()); |
| 188 | + let project_path = norm_case(project_path); |
| 189 | + |
| 190 | + // Check if the project has a Pipfile |
| 191 | + if !project_path.join(&env_vars.pipenv_pipfile).exists() { |
| 192 | + continue; |
| 193 | + } |
| 194 | + |
| 195 | + // Find the Python executable in the virtualenv |
| 196 | + let bin_dir = if std::env::consts::OS == "windows" { |
| 197 | + path.join("Scripts") |
| 198 | + } else { |
| 199 | + path.join("bin") |
| 200 | + }; |
| 201 | + |
| 202 | + let python_exe = if std::env::consts::OS == "windows" { |
| 203 | + bin_dir.join("python.exe") |
| 204 | + } else { |
| 205 | + bin_dir.join("python") |
| 206 | + }; |
| 207 | + |
| 208 | + if python_exe.is_file() { |
| 209 | + let symlinks = find_executables(&bin_dir); |
| 210 | + let version = version::from_creator_for_virtual_env(&path); |
| 211 | + |
| 212 | + let env = PythonEnvironmentBuilder::new(Some( |
| 213 | + PythonEnvironmentKind::Pipenv, |
| 214 | + )) |
| 215 | + .executable(Some(norm_case(python_exe))) |
| 216 | + .version(version) |
| 217 | + .prefix(Some(norm_case(path.clone()))) |
| 218 | + .project(Some(project_path)) |
| 219 | + .symlinks(Some(symlinks)) |
| 220 | + .build(); |
| 221 | + |
| 222 | + trace!("Found pipenv environment: {:?}", env); |
| 223 | + environments.push(env); |
| 224 | + } |
| 225 | + } |
| 226 | + } |
| 227 | + } |
| 228 | + } |
| 229 | + |
| 230 | + environments |
| 231 | +} |
| 232 | + |
131 | 233 | pub struct PipEnv { |
132 | 234 | env_vars: EnvVariables, |
| 235 | + pipenv_executable: Arc<RwLock<Option<PathBuf>>>, |
133 | 236 | } |
134 | 237 |
|
135 | 238 | impl PipEnv { |
136 | 239 | pub fn from(environment: &dyn Environment) -> PipEnv { |
137 | 240 | PipEnv { |
138 | 241 | env_vars: EnvVariables::from(environment), |
| 242 | + pipenv_executable: Arc::new(RwLock::new(None)), |
139 | 243 | } |
140 | 244 | } |
141 | 245 | } |
| 246 | + |
142 | 247 | impl Locator for PipEnv { |
143 | 248 | fn get_kind(&self) -> LocatorKind { |
144 | 249 | LocatorKind::PipEnv |
145 | 250 | } |
| 251 | + |
| 252 | + fn configure(&self, config: &Configuration) { |
| 253 | + if let Some(exe) = &config.pipenv_executable { |
| 254 | + self.pipenv_executable.write().unwrap().replace(exe.clone()); |
| 255 | + } |
| 256 | + } |
| 257 | + |
146 | 258 | fn supported_categories(&self) -> Vec<PythonEnvironmentKind> { |
147 | 259 | vec![PythonEnvironmentKind::Pipenv] |
148 | 260 | } |
@@ -183,8 +295,19 @@ impl Locator for PipEnv { |
183 | 295 | ) |
184 | 296 | } |
185 | 297 |
|
186 | | - fn find(&self, _reporter: &dyn Reporter) { |
187 | | - // |
| 298 | + fn find(&self, reporter: &dyn Reporter) { |
| 299 | + // First, find and report the pipenv manager |
| 300 | + let pipenv_exe = self.pipenv_executable.read().unwrap().clone(); |
| 301 | + if let Some(manager) = PipenvManager::find(pipenv_exe, &self.env_vars) { |
| 302 | + trace!("Found pipenv manager: {:?}", manager); |
| 303 | + reporter.report_manager(&manager.to_manager()); |
| 304 | + } |
| 305 | + |
| 306 | + // Then discover and report pipenv environments |
| 307 | + let environments = list_environments(&self.env_vars); |
| 308 | + for env in environments { |
| 309 | + reporter.report_environment(&env); |
| 310 | + } |
188 | 311 | } |
189 | 312 | } |
190 | 313 |
|
@@ -243,7 +366,11 @@ mod tests { |
243 | 366 | env_vars: EnvVariables { |
244 | 367 | pipenv_max_depth: 3, |
245 | 368 | pipenv_pipfile: "Pipfile".to_string(), |
| 369 | + home: None, |
| 370 | + workon_home: None, |
| 371 | + path: None, |
246 | 372 | }, |
| 373 | + pipenv_executable: Arc::new(RwLock::new(None)), |
247 | 374 | }; |
248 | 375 | let result = locator |
249 | 376 | .try_from(&env) |
|
0 commit comments