1
1
// Copyright (c) Microsoft Corporation.
2
2
// Licensed under the MIT License.
3
3
4
- use std:: fs;
4
+ use std:: {
5
+ collections:: HashMap ,
6
+ fs,
7
+ path:: { Path , PathBuf } ,
8
+ sync:: { Arc , Mutex } ,
9
+ thread,
10
+ } ;
5
11
6
- use log:: warn;
7
12
use pet_core:: {
8
13
python_environment:: { PythonEnvironment , PythonEnvironmentBuilder , PythonEnvironmentCategory } ,
9
14
reporter:: Reporter ,
10
15
Locator ,
11
16
} ;
12
17
use pet_fs:: path:: resolve_symlink;
13
- use pet_python_utils:: { env:: PythonEnv , executable:: find_executables} ;
18
+ use pet_python_utils:: {
19
+ env:: { PythonEnv , ResolvedPythonEnv } ,
20
+ executable:: find_executables,
21
+ } ;
14
22
use pet_virtualenv:: is_virtualenv;
15
23
16
- pub struct LinuxGlobalPython { }
24
+ pub struct LinuxGlobalPython {
25
+ reported_executables : Arc < Mutex < HashMap < PathBuf , PythonEnvironment > > > ,
26
+ }
17
27
18
28
impl LinuxGlobalPython {
19
29
pub fn new ( ) -> LinuxGlobalPython {
20
- LinuxGlobalPython { }
30
+ LinuxGlobalPython {
31
+ reported_executables : Arc :: new (
32
+ Mutex :: new ( HashMap :: < PathBuf , PythonEnvironment > :: new ( ) ) ,
33
+ ) ,
34
+ }
35
+ }
36
+
37
+ fn find_cached ( & self , reporter : Option < & dyn Reporter > ) {
38
+ if std:: env:: consts:: OS == "macos" || std:: env:: consts:: OS == "windows" {
39
+ return ;
40
+ }
41
+ // Look through the /bin, /usr/bin, /usr/local/bin directories
42
+ thread:: scope ( |s| {
43
+ for bin in [ "/bin" , "/usr/bin" , "/usr/local/bin" ] {
44
+ s. spawn ( move || {
45
+ find_and_report_global_pythons_in ( bin, reporter, & self . reported_executables ) ;
46
+ } ) ;
47
+ }
48
+ } ) ;
21
49
}
22
50
}
23
51
impl Default for LinuxGlobalPython {
@@ -47,41 +75,62 @@ impl Locator for LinuxGlobalPython {
47
75
// If we do not have a version, then we cannot use this method.
48
76
// Without version means we have not spawned the Python exe, thus do not have the real info.
49
77
env. version . clone ( ) ?;
50
- let prefix = env. prefix . clone ( ) ?;
51
78
let executable = env. executable . clone ( ) ;
52
79
53
- // If prefix or version is not available, then we cannot use this method.
54
- // 1. For files in /bin or /usr/bin, the prefix is always /usr
55
- // 2. For files in /usr/local/ bin, the prefix is always /usr/local
80
+ self . find_cached ( None ) ;
81
+
82
+ // We only support python environments in /bin, / usr/bin, /usr/local/bin
56
83
if !executable. starts_with ( "/bin" )
57
84
&& !executable. starts_with ( "/usr/bin" )
58
85
&& !executable. starts_with ( "/usr/local/bin" )
59
- && !prefix. starts_with ( "/usr" )
60
- && !prefix. starts_with ( "/usr/local" )
61
86
{
62
87
return None ;
63
88
}
64
89
65
- // All known global linux are always installed in `/bin` or `/usr/bin` or `/usr/local/bin`
66
- if executable. starts_with ( "/bin" )
67
- || executable. starts_with ( "/usr/bin" )
68
- || executable. starts_with ( "/usr/local/bin" )
69
- {
70
- get_python_in_bin ( env)
71
- } else {
72
- warn ! (
73
- "Unknown Python exe ({:?}), not in any of the known locations /bin, /usr/bin, /usr/local/bin" ,
74
- executable
75
- ) ;
76
- None
90
+ self . reported_executables
91
+ . lock ( )
92
+ . unwrap ( )
93
+ . get ( & executable)
94
+ . cloned ( )
95
+ }
96
+
97
+ fn find ( & self , reporter : & dyn Reporter ) {
98
+ if std:: env:: consts:: OS == "macos" || std:: env:: consts:: OS == "windows" {
99
+ return ;
77
100
}
101
+ self . reported_executables . lock ( ) . unwrap ( ) . clear ( ) ;
102
+ self . find_cached ( Some ( reporter) )
78
103
}
104
+ }
79
105
80
- fn find ( & self , _reporter : & dyn Reporter ) {
81
- // No point looking in /usr/bin or /bin folder.
82
- // We will end up looking in these global locations and spawning them in other parts.
83
- // Here we cannot assume that anything in /usr/bin is a global Python, it could be a symlink or other.
84
- // Safer approach is to just spawn it which we need to do to get the `sys.prefix`
106
+ fn find_and_report_global_pythons_in (
107
+ bin : & str ,
108
+ reporter : Option < & dyn Reporter > ,
109
+ reported_executables : & Arc < Mutex < HashMap < PathBuf , PythonEnvironment > > > ,
110
+ ) {
111
+ let python_executables = find_executables ( Path :: new ( bin) ) ;
112
+
113
+ for exe in python_executables. clone ( ) . iter ( ) {
114
+ if reported_executables. lock ( ) . unwrap ( ) . contains_key ( exe) {
115
+ continue ;
116
+ }
117
+ if let Some ( resolved) = ResolvedPythonEnv :: from ( exe) {
118
+ if let Some ( env) = get_python_in_bin ( & resolved. to_python_env ( ) ) {
119
+ let mut reported_executables = reported_executables. lock ( ) . unwrap ( ) ;
120
+ // env.symlinks = Some([symlinks, env.symlinks.clone().unwrap_or_default()].concat());
121
+ if let Some ( symlinks) = & env. symlinks {
122
+ for symlink in symlinks {
123
+ reported_executables. insert ( symlink. clone ( ) , env. clone ( ) ) ;
124
+ }
125
+ }
126
+ if let Some ( exe) = env. executable . clone ( ) {
127
+ reported_executables. insert ( exe, env. clone ( ) ) ;
128
+ }
129
+ if let Some ( reporter) = reporter {
130
+ reporter. report_environment ( & env) ;
131
+ }
132
+ }
133
+ }
85
134
}
86
135
}
87
136
@@ -100,6 +149,7 @@ fn get_python_in_bin(env: &PythonEnv) -> Option<PythonEnvironment> {
100
149
// Keep track of what the exe resolves to.
101
150
// Will have a value only if the exe is in another dir
102
151
// E.g. /bin/python3 might be a symlink to /usr/bin/python3.12
152
+ // Similarly /usr/local/python/current/bin/python might point to something like /usr/local/python/3.10.13/bin/python3.10
103
153
// However due to legacy reasons we'll be treating these two as separate exes.
104
154
// Hence they will be separate Python environments.
105
155
let mut resolved_exe_is_from_another_dir = None ;
@@ -119,6 +169,14 @@ fn get_python_in_bin(env: &PythonEnv) -> Option<PythonEnvironment> {
119
169
resolved_exe_is_from_another_dir = Some ( symlink) ;
120
170
}
121
171
}
172
+ if let Ok ( symlink) = fs:: canonicalize ( & executable) {
173
+ // Ensure this is a symlink in the bin or usr/bin directory.
174
+ if symlink. starts_with ( bin) {
175
+ symlinks. push ( symlink) ;
176
+ } else {
177
+ resolved_exe_is_from_another_dir = Some ( symlink) ;
178
+ }
179
+ }
122
180
123
181
// Look for other symlinks in the same folder
124
182
// We know that on linux there are sym links in the same folder as the exe.
0 commit comments