11// Copyright (c) Microsoft Corporation.
22// Licensed under the MIT License.
33
4- use std:: fs;
4+ use std:: {
5+ collections:: HashMap ,
6+ fs,
7+ path:: { Path , PathBuf } ,
8+ sync:: { Arc , Mutex } ,
9+ thread,
10+ } ;
511
6- use log:: warn;
712use pet_core:: {
813 python_environment:: { PythonEnvironment , PythonEnvironmentBuilder , PythonEnvironmentCategory } ,
914 reporter:: Reporter ,
1015 Locator ,
1116} ;
1217use 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+ } ;
1422use pet_virtualenv:: is_virtualenv;
1523
16- pub struct LinuxGlobalPython { }
24+ pub struct LinuxGlobalPython {
25+ reported_executables : Arc < Mutex < HashMap < PathBuf , PythonEnvironment > > > ,
26+ }
1727
1828impl LinuxGlobalPython {
1929 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+ } ) ;
2149 }
2250}
2351impl Default for LinuxGlobalPython {
@@ -47,41 +75,62 @@ impl Locator for LinuxGlobalPython {
4775 // If we do not have a version, then we cannot use this method.
4876 // Without version means we have not spawned the Python exe, thus do not have the real info.
4977 env. version . clone ( ) ?;
50- let prefix = env. prefix . clone ( ) ?;
5178 let executable = env. executable . clone ( ) ;
5279
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
5683 if !executable. starts_with ( "/bin" )
5784 && !executable. starts_with ( "/usr/bin" )
5885 && !executable. starts_with ( "/usr/local/bin" )
59- && !prefix. starts_with ( "/usr" )
60- && !prefix. starts_with ( "/usr/local" )
6186 {
6287 return None ;
6388 }
6489
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 ;
77100 }
101+ self . reported_executables . lock ( ) . unwrap ( ) . clear ( ) ;
102+ self . find_cached ( Some ( reporter) )
78103 }
104+ }
79105
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+ }
85134 }
86135}
87136
@@ -100,6 +149,7 @@ fn get_python_in_bin(env: &PythonEnv) -> Option<PythonEnvironment> {
100149 // Keep track of what the exe resolves to.
101150 // Will have a value only if the exe is in another dir
102151 // 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
103153 // However due to legacy reasons we'll be treating these two as separate exes.
104154 // Hence they will be separate Python environments.
105155 let mut resolved_exe_is_from_another_dir = None ;
@@ -119,6 +169,14 @@ fn get_python_in_bin(env: &PythonEnv) -> Option<PythonEnvironment> {
119169 resolved_exe_is_from_another_dir = Some ( symlink) ;
120170 }
121171 }
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+ }
122180
123181 // Look for other symlinks in the same folder
124182 // We know that on linux there are sym links in the same folder as the exe.
0 commit comments