9
9
//! on Linux.)
10
10
11
11
use std:: fmt;
12
+ use std:: fmt:: Display ;
12
13
use std:: io;
13
14
use std:: num:: NonZeroUsize ;
14
15
use std:: ops:: Deref ;
@@ -43,23 +44,28 @@ pub(crate) struct VirtualEnvironment {
43
44
impl VirtualEnvironment {
44
45
pub ( crate ) fn new (
45
46
path : impl AsRef < SystemPath > ,
47
+ origin : SysPrefixPathOrigin ,
46
48
system : & dyn System ,
47
49
) -> SitePackagesDiscoveryResult < Self > {
48
- Self :: new_impl ( path. as_ref ( ) , system)
50
+ Self :: new_impl ( path. as_ref ( ) , origin , system)
49
51
}
50
52
51
- fn new_impl ( path : & SystemPath , system : & dyn System ) -> SitePackagesDiscoveryResult < Self > {
53
+ fn new_impl (
54
+ path : & SystemPath ,
55
+ origin : SysPrefixPathOrigin ,
56
+ system : & dyn System ,
57
+ ) -> SitePackagesDiscoveryResult < Self > {
52
58
fn pyvenv_cfg_line_number ( index : usize ) -> NonZeroUsize {
53
59
index. checked_add ( 1 ) . and_then ( NonZeroUsize :: new) . unwrap ( )
54
60
}
55
61
56
- let venv_path = SysPrefixPath :: new ( path, system) ?;
62
+ let venv_path = SysPrefixPath :: new ( path, origin , system) ?;
57
63
let pyvenv_cfg_path = venv_path. join ( "pyvenv.cfg" ) ;
58
64
tracing:: debug!( "Attempting to parse virtual environment metadata at '{pyvenv_cfg_path}'" ) ;
59
65
60
66
let pyvenv_cfg = system
61
67
. read_to_string ( & pyvenv_cfg_path)
62
- . map_err ( SitePackagesDiscoveryError :: NoPyvenvCfgFile ) ?;
68
+ . map_err ( |io_err| SitePackagesDiscoveryError :: NoPyvenvCfgFile ( origin , io_err ) ) ?;
63
69
64
70
let mut include_system_site_packages = false ;
65
71
let mut base_executable_home_path = None ;
@@ -205,12 +211,12 @@ System site-packages will not be used for module resolution.",
205
211
206
212
#[ derive( Debug , thiserror:: Error ) ]
207
213
pub ( crate ) enum SitePackagesDiscoveryError {
208
- #[ error( "Invalid --python argument : `{0}` could not be canonicalized" ) ]
209
- VenvDirCanonicalizationError ( SystemPathBuf , #[ source] io:: Error ) ,
210
- #[ error( "Invalid --python argument : `{0}` does not point to a directory on disk" ) ]
211
- VenvDirIsNotADirectory ( SystemPathBuf ) ,
212
- #[ error( "--python points to a broken venv with no pyvenv.cfg file" ) ]
213
- NoPyvenvCfgFile ( #[ source] io:: Error ) ,
214
+ #[ error( "Invalid {1} : `{0}` could not be canonicalized" ) ]
215
+ VenvDirCanonicalizationError ( SystemPathBuf , SysPrefixPathOrigin , #[ source] io:: Error ) ,
216
+ #[ error( "Invalid {1} : `{0}` does not point to a directory on disk" ) ]
217
+ VenvDirIsNotADirectory ( SystemPathBuf , SysPrefixPathOrigin ) ,
218
+ #[ error( "{0} points to a broken venv with no pyvenv.cfg file" ) ]
219
+ NoPyvenvCfgFile ( SysPrefixPathOrigin , #[ source] io:: Error ) ,
214
220
#[ error( "Failed to parse the pyvenv.cfg file at {0} because {1}" ) ]
215
221
PyvenvCfgParseError ( SystemPathBuf , PyvenvCfgParseErrorKind ) ,
216
222
#[ error( "Failed to search the `lib` directory of the Python installation at {1} for `site-packages`" ) ]
@@ -370,18 +376,23 @@ fn site_packages_directory_from_sys_prefix(
370
376
///
371
377
/// [`sys.prefix`]: https://docs.python.org/3/library/sys.html#sys.prefix
372
378
#[ derive( Debug , PartialEq , Eq , Clone ) ]
373
- pub ( crate ) struct SysPrefixPath ( SystemPathBuf ) ;
379
+ pub ( crate ) struct SysPrefixPath {
380
+ inner : SystemPathBuf ,
381
+ origin : SysPrefixPathOrigin ,
382
+ }
374
383
375
384
impl SysPrefixPath {
376
385
fn new (
377
386
unvalidated_path : impl AsRef < SystemPath > ,
387
+ origin : SysPrefixPathOrigin ,
378
388
system : & dyn System ,
379
389
) -> SitePackagesDiscoveryResult < Self > {
380
- Self :: new_impl ( unvalidated_path. as_ref ( ) , system)
390
+ Self :: new_impl ( unvalidated_path. as_ref ( ) , origin , system)
381
391
}
382
392
383
393
fn new_impl (
384
394
unvalidated_path : & SystemPath ,
395
+ origin : SysPrefixPathOrigin ,
385
396
system : & dyn System ,
386
397
) -> SitePackagesDiscoveryResult < Self > {
387
398
// It's important to resolve symlinks here rather than simply making the path absolute,
@@ -392,14 +403,21 @@ impl SysPrefixPath {
392
403
. map_err ( |io_err| {
393
404
SitePackagesDiscoveryError :: VenvDirCanonicalizationError (
394
405
unvalidated_path. to_path_buf ( ) ,
406
+ origin,
395
407
io_err,
396
408
)
397
409
} ) ?;
398
410
system
399
411
. is_directory ( & canonicalized)
400
- . then_some ( Self ( canonicalized) )
412
+ . then_some ( Self {
413
+ inner : canonicalized,
414
+ origin,
415
+ } )
401
416
. ok_or_else ( || {
402
- SitePackagesDiscoveryError :: VenvDirIsNotADirectory ( unvalidated_path. to_path_buf ( ) )
417
+ SitePackagesDiscoveryError :: VenvDirIsNotADirectory (
418
+ unvalidated_path. to_path_buf ( ) ,
419
+ origin,
420
+ )
403
421
} )
404
422
}
405
423
@@ -408,9 +426,15 @@ impl SysPrefixPath {
408
426
// the parent of a canonicalised path that is known to exist
409
427
// is guaranteed to be a directory.
410
428
if cfg ! ( target_os = "windows" ) {
411
- Some ( Self ( path. to_path_buf ( ) ) )
429
+ Some ( Self {
430
+ inner : path. to_path_buf ( ) ,
431
+ origin : SysPrefixPathOrigin :: Derived ,
432
+ } )
412
433
} else {
413
- path. parent ( ) . map ( |path| Self ( path. to_path_buf ( ) ) )
434
+ path. parent ( ) . map ( |path| Self {
435
+ inner : path. to_path_buf ( ) ,
436
+ origin : SysPrefixPathOrigin :: Derived ,
437
+ } )
414
438
}
415
439
}
416
440
}
@@ -419,13 +443,31 @@ impl Deref for SysPrefixPath {
419
443
type Target = SystemPath ;
420
444
421
445
fn deref ( & self ) -> & Self :: Target {
422
- & self . 0
446
+ & self . inner
423
447
}
424
448
}
425
449
426
450
impl fmt:: Display for SysPrefixPath {
427
451
fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
428
- write ! ( f, "`sys.prefix` path `{}`" , self . 0 )
452
+ write ! ( f, "`sys.prefix` path `{}`" , self . inner)
453
+ }
454
+ }
455
+
456
+ #[ derive( Debug , PartialEq , Eq , Copy , Clone ) ]
457
+ #[ cfg_attr( feature = "serde" , derive( serde:: Serialize , serde:: Deserialize ) ) ]
458
+ pub enum SysPrefixPathOrigin {
459
+ PythonCliFlag ,
460
+ VirtualEnvVar ,
461
+ Derived ,
462
+ }
463
+
464
+ impl Display for SysPrefixPathOrigin {
465
+ fn fmt ( & self , f : & mut std:: fmt:: Formatter ) -> std:: fmt:: Result {
466
+ match self {
467
+ Self :: PythonCliFlag => f. write_str ( "`--python` argument" ) ,
468
+ Self :: VirtualEnvVar => f. write_str ( "`VIRTUAL_ENV` environment variable" ) ,
469
+ Self :: Derived => f. write_str ( "derived `sys.prefix` path" ) ,
470
+ }
429
471
}
430
472
}
431
473
@@ -584,11 +626,19 @@ mod tests {
584
626
585
627
fn test ( self ) {
586
628
let venv_path = self . build_mock_venv ( ) ;
587
- let venv = VirtualEnvironment :: new ( venv_path. clone ( ) , & self . system ) . unwrap ( ) ;
629
+ let venv = VirtualEnvironment :: new (
630
+ venv_path. clone ( ) ,
631
+ SysPrefixPathOrigin :: VirtualEnvVar ,
632
+ & self . system ,
633
+ )
634
+ . unwrap ( ) ;
588
635
589
636
assert_eq ! (
590
637
venv. venv_path,
591
- SysPrefixPath ( self . system. canonicalize_path( & venv_path) . unwrap( ) )
638
+ SysPrefixPath {
639
+ inner: self . system. canonicalize_path( & venv_path) . unwrap( ) ,
640
+ origin: SysPrefixPathOrigin :: VirtualEnvVar ,
641
+ }
592
642
) ;
593
643
assert_eq ! ( venv. include_system_site_packages, self . system_site_packages) ;
594
644
@@ -730,7 +780,7 @@ mod tests {
730
780
fn reject_venv_that_does_not_exist ( ) {
731
781
let system = TestSystem :: default ( ) ;
732
782
assert ! ( matches!(
733
- VirtualEnvironment :: new( "/.venv" , & system) ,
783
+ VirtualEnvironment :: new( "/.venv" , SysPrefixPathOrigin :: VirtualEnvVar , & system) ,
734
784
Err ( SitePackagesDiscoveryError :: VenvDirCanonicalizationError ( ..) )
735
785
) ) ;
736
786
}
@@ -743,7 +793,7 @@ mod tests {
743
793
. write_file_all ( "/.venv" , "" )
744
794
. unwrap ( ) ;
745
795
assert ! ( matches!(
746
- VirtualEnvironment :: new( "/.venv" , & system) ,
796
+ VirtualEnvironment :: new( "/.venv" , SysPrefixPathOrigin :: VirtualEnvVar , & system) ,
747
797
Err ( SitePackagesDiscoveryError :: VenvDirIsNotADirectory ( ..) )
748
798
) ) ;
749
799
}
@@ -756,8 +806,11 @@ mod tests {
756
806
. create_directory_all ( "/.venv" )
757
807
. unwrap ( ) ;
758
808
assert ! ( matches!(
759
- VirtualEnvironment :: new( "/.venv" , & system) ,
760
- Err ( SitePackagesDiscoveryError :: NoPyvenvCfgFile ( _) )
809
+ VirtualEnvironment :: new( "/.venv" , SysPrefixPathOrigin :: VirtualEnvVar , & system) ,
810
+ Err ( SitePackagesDiscoveryError :: NoPyvenvCfgFile (
811
+ SysPrefixPathOrigin :: VirtualEnvVar ,
812
+ _
813
+ ) )
761
814
) ) ;
762
815
}
763
816
@@ -769,7 +822,8 @@ mod tests {
769
822
memory_fs
770
823
. write_file_all ( & pyvenv_cfg_path, "home = bar = /.venv/bin" )
771
824
. unwrap ( ) ;
772
- let venv_result = VirtualEnvironment :: new ( "/.venv" , & system) ;
825
+ let venv_result =
826
+ VirtualEnvironment :: new ( "/.venv" , SysPrefixPathOrigin :: VirtualEnvVar , & system) ;
773
827
assert ! ( matches!(
774
828
venv_result,
775
829
Err ( SitePackagesDiscoveryError :: PyvenvCfgParseError (
@@ -788,7 +842,8 @@ mod tests {
788
842
memory_fs
789
843
. write_file_all ( & pyvenv_cfg_path, "home =" )
790
844
. unwrap ( ) ;
791
- let venv_result = VirtualEnvironment :: new ( "/.venv" , & system) ;
845
+ let venv_result =
846
+ VirtualEnvironment :: new ( "/.venv" , SysPrefixPathOrigin :: VirtualEnvVar , & system) ;
792
847
assert ! ( matches!(
793
848
venv_result,
794
849
Err ( SitePackagesDiscoveryError :: PyvenvCfgParseError (
@@ -807,7 +862,8 @@ mod tests {
807
862
memory_fs
808
863
. write_file_all ( & pyvenv_cfg_path, "= whatever" )
809
864
. unwrap ( ) ;
810
- let venv_result = VirtualEnvironment :: new ( "/.venv" , & system) ;
865
+ let venv_result =
866
+ VirtualEnvironment :: new ( "/.venv" , SysPrefixPathOrigin :: VirtualEnvVar , & system) ;
811
867
assert ! ( matches!(
812
868
venv_result,
813
869
Err ( SitePackagesDiscoveryError :: PyvenvCfgParseError (
@@ -824,7 +880,8 @@ mod tests {
824
880
let memory_fs = system. memory_file_system ( ) ;
825
881
let pyvenv_cfg_path = SystemPathBuf :: from ( "/.venv/pyvenv.cfg" ) ;
826
882
memory_fs. write_file_all ( & pyvenv_cfg_path, "" ) . unwrap ( ) ;
827
- let venv_result = VirtualEnvironment :: new ( "/.venv" , & system) ;
883
+ let venv_result =
884
+ VirtualEnvironment :: new ( "/.venv" , SysPrefixPathOrigin :: VirtualEnvVar , & system) ;
828
885
assert ! ( matches!(
829
886
venv_result,
830
887
Err ( SitePackagesDiscoveryError :: PyvenvCfgParseError (
@@ -843,7 +900,8 @@ mod tests {
843
900
memory_fs
844
901
. write_file_all ( & pyvenv_cfg_path, "home = foo" )
845
902
. unwrap ( ) ;
846
- let venv_result = VirtualEnvironment :: new ( "/.venv" , & system) ;
903
+ let venv_result =
904
+ VirtualEnvironment :: new ( "/.venv" , SysPrefixPathOrigin :: VirtualEnvVar , & system) ;
847
905
assert ! ( matches!(
848
906
venv_result,
849
907
Err ( SitePackagesDiscoveryError :: PyvenvCfgParseError (
0 commit comments