33
44use env_variables:: EnvVariables ;
55use log:: trace;
6+ use manager:: PipenvManager ;
67use pet_core:: env:: PythonEnv ;
78use pet_core:: os_environment:: Environment ;
89use pet_core:: LocatorKind ;
910use pet_core:: {
1011 python_environment:: { PythonEnvironment , PythonEnvironmentBuilder , PythonEnvironmentKind } ,
1112 reporter:: Reporter ,
12- Locator ,
13+ Configuration , Locator ,
1314} ;
1415use pet_fs:: path:: norm_case;
1516use pet_python_utils:: executable:: find_executables;
1617use pet_python_utils:: version;
1718use std:: path:: Path ;
19+ use std:: sync:: { Arc , RwLock } ;
1820use std:: { fs, path:: PathBuf } ;
1921
2022mod env_variables;
23+ pub mod manager;
2124
2225/// Returns the list of directories where pipenv stores centralized virtual environments.
2326/// These are the known locations where pipenv creates virtualenvs when not using in-project mode.
@@ -272,21 +275,128 @@ fn is_pipenv(env: &PythonEnv, env_vars: &EnvVariables) -> bool {
272275 false
273276}
274277
278+ /// Get the default virtualenvs directory for pipenv
279+ /// - If WORKON_HOME is set, use that
280+ /// - Linux/macOS: ~/.local/share/virtualenvs/
281+ /// - Windows: %USERPROFILE%\.virtualenvs\
282+ fn get_virtualenvs_dir ( env_vars : & EnvVariables ) -> Option < PathBuf > {
283+ // First check WORKON_HOME environment variable
284+ if let Some ( workon_home) = & env_vars. workon_home {
285+ if workon_home. is_dir ( ) {
286+ return Some ( workon_home. clone ( ) ) ;
287+ }
288+ }
289+
290+ // Fall back to default locations
291+ if let Some ( home) = & env_vars. home {
292+ if std:: env:: consts:: OS == "windows" {
293+ let dir = home. join ( ".virtualenvs" ) ;
294+ if dir. is_dir ( ) {
295+ return Some ( dir) ;
296+ }
297+ } else {
298+ let dir = home. join ( ".local" ) . join ( "share" ) . join ( "virtualenvs" ) ;
299+ if dir. is_dir ( ) {
300+ return Some ( dir) ;
301+ }
302+ }
303+ }
304+
305+ None
306+ }
307+
308+ /// Discover pipenv environments from the virtualenvs directory
309+ fn list_environments ( env_vars : & EnvVariables ) -> Vec < PythonEnvironment > {
310+ let mut environments = vec ! [ ] ;
311+
312+ if let Some ( virtualenvs_dir) = get_virtualenvs_dir ( env_vars) {
313+ trace ! ( "Searching for pipenv environments in {:?}" , virtualenvs_dir) ;
314+
315+ if let Ok ( entries) = fs:: read_dir ( & virtualenvs_dir) {
316+ for entry in entries. flatten ( ) {
317+ let path = entry. path ( ) ;
318+ if !path. is_dir ( ) {
319+ continue ;
320+ }
321+
322+ // Check if this directory is a valid virtualenv with a .project file
323+ let project_file = path. join ( ".project" ) ;
324+ if !project_file. exists ( ) {
325+ continue ;
326+ }
327+
328+ // Read the project path from .project file
329+ if let Ok ( project_contents) = fs:: read_to_string ( & project_file) {
330+ let project_path = PathBuf :: from ( project_contents. trim ( ) ) ;
331+ let project_path = norm_case ( project_path) ;
332+
333+ // Check if the project has a Pipfile
334+ if !project_path. join ( & env_vars. pipenv_pipfile ) . exists ( ) {
335+ continue ;
336+ }
337+
338+ // Find the Python executable in the virtualenv
339+ let bin_dir = if std:: env:: consts:: OS == "windows" {
340+ path. join ( "Scripts" )
341+ } else {
342+ path. join ( "bin" )
343+ } ;
344+
345+ let python_exe = if std:: env:: consts:: OS == "windows" {
346+ bin_dir. join ( "python.exe" )
347+ } else {
348+ bin_dir. join ( "python" )
349+ } ;
350+
351+ if python_exe. is_file ( ) {
352+ let symlinks = find_executables ( & bin_dir) ;
353+ let version = version:: from_creator_for_virtual_env ( & path) ;
354+
355+ let env =
356+ PythonEnvironmentBuilder :: new ( Some ( PythonEnvironmentKind :: Pipenv ) )
357+ . executable ( Some ( norm_case ( python_exe) ) )
358+ . version ( version)
359+ . prefix ( Some ( norm_case ( path. clone ( ) ) ) )
360+ . project ( Some ( project_path) )
361+ . symlinks ( Some ( symlinks) )
362+ . build ( ) ;
363+
364+ trace ! ( "Found pipenv environment: {:?}" , env) ;
365+ environments. push ( env) ;
366+ }
367+ }
368+ }
369+ }
370+ }
371+
372+ environments
373+ }
374+
275375pub struct PipEnv {
276376 env_vars : EnvVariables ,
377+ pipenv_executable : Arc < RwLock < Option < PathBuf > > > ,
277378}
278379
279380impl PipEnv {
280381 pub fn from ( environment : & dyn Environment ) -> PipEnv {
281382 PipEnv {
282383 env_vars : EnvVariables :: from ( environment) ,
384+ pipenv_executable : Arc :: new ( RwLock :: new ( None ) ) ,
283385 }
284386 }
285387}
388+
286389impl Locator for PipEnv {
287390 fn get_kind ( & self ) -> LocatorKind {
288391 LocatorKind :: PipEnv
289392 }
393+
394+ fn configure ( & self , config : & Configuration ) {
395+ if let Some ( exe) = & config. pipenv_executable {
396+ self . pipenv_executable . write ( ) . unwrap ( ) . replace ( exe. clone ( ) ) ;
397+ }
398+ }
399+
290400 fn supported_categories ( & self ) -> Vec < PythonEnvironmentKind > {
291401 vec ! [ PythonEnvironmentKind :: Pipenv ]
292402 }
@@ -334,8 +444,19 @@ impl Locator for PipEnv {
334444 )
335445 }
336446
337- fn find ( & self , _reporter : & dyn Reporter ) {
338- //
447+ fn find ( & self , reporter : & dyn Reporter ) {
448+ // First, find and report the pipenv manager
449+ let pipenv_exe = self . pipenv_executable . read ( ) . unwrap ( ) . clone ( ) ;
450+ if let Some ( manager) = PipenvManager :: find ( pipenv_exe, & self . env_vars ) {
451+ trace ! ( "Found pipenv manager: {:?}" , manager) ;
452+ reporter. report_manager ( & manager. to_manager ( ) ) ;
453+ }
454+
455+ // Then discover and report pipenv environments
456+ let environments = list_environments ( & self . env_vars ) ;
457+ for env in environments {
458+ reporter. report_environment ( & env) ;
459+ }
339460 }
340461}
341462
@@ -361,6 +482,7 @@ mod tests {
361482 home,
362483 xdg_data_home : None ,
363484 workon_home : None ,
485+ path : None ,
364486 }
365487 }
366488
@@ -402,6 +524,7 @@ mod tests {
402524 // Validate locator populates project
403525 let locator = PipEnv {
404526 env_vars : create_test_env_vars ( None ) ,
527+ pipenv_executable : Arc :: new ( RwLock :: new ( None ) ) ,
405528 } ;
406529 let result = locator
407530 . try_from ( & env)
@@ -460,6 +583,7 @@ mod tests {
460583 home : Some ( temp_home. clone ( ) ) ,
461584 xdg_data_home : None ,
462585 workon_home : None ,
586+ path : None ,
463587 } ;
464588
465589 // Validate is_in_pipenv_centralized_dir detects it
@@ -475,7 +599,10 @@ mod tests {
475599 ) ;
476600
477601 // Validate locator returns the environment
478- let locator = PipEnv { env_vars } ;
602+ let locator = PipEnv {
603+ env_vars,
604+ pipenv_executable : Arc :: new ( RwLock :: new ( None ) ) ,
605+ } ;
479606 let result = locator
480607 . try_from ( & env)
481608 . expect ( "expected locator to return environment" ) ;
@@ -525,6 +652,7 @@ mod tests {
525652 home : Some ( temp_home. clone ( ) ) ,
526653 xdg_data_home : None ,
527654 workon_home : None ,
655+ path : None ,
528656 } ;
529657
530658 // Should still be detected as pipenv (centralized directory + .project file)
@@ -538,7 +666,10 @@ mod tests {
538666 ) ;
539667
540668 // Locator should return the environment, but project will point to non-existent path
541- let locator = PipEnv { env_vars } ;
669+ let locator = PipEnv {
670+ env_vars,
671+ pipenv_executable : Arc :: new ( RwLock :: new ( None ) ) ,
672+ } ;
542673 let result = locator
543674 . try_from ( & env)
544675 . expect ( "expected locator to return environment" ) ;
0 commit comments