11// Copyright (c) Microsoft Corporation.
22// Licensed under the MIT License.
33
4- use std:: { fs , path :: PathBuf } ;
4+ use std:: fs ;
55
6- use log:: error ;
6+ use log:: warn ;
77use pet_core:: {
88 python_environment:: { PythonEnvironment , PythonEnvironmentBuilder , PythonEnvironmentCategory } ,
99 reporter:: Reporter ,
@@ -44,34 +44,35 @@ impl Locator for LinuxGlobalPython {
4444 return None ;
4545 }
4646
47- if let ( Some ( prefix) , Some ( _) ) = ( env. prefix . clone ( ) , env. version . clone ( ) ) {
48- let executable = env. executable . clone ( ) ;
49-
50- // If prefix or version is not available, then we cannot use this method.
51- // 1. For files in /bin or /usr/bin, the prefix is always /usr
52- // 2. For files in /usr/local/bin, the prefix is always /usr/local
53- if !executable. starts_with ( "/bin" )
54- && !executable. starts_with ( "/usr/bin" )
55- && !executable. starts_with ( "/usr/local/bin" )
56- && !prefix. starts_with ( "/usr" )
57- && !prefix. starts_with ( "/usr/local" )
58- {
59- return None ;
60- }
47+ // If we do not have a version, then we cannot use this method.
48+ // Without version means we have not spawned the Python exe, thus do not have the real info.
49+ env. version . clone ( ) ?;
50+ let prefix = env. prefix . clone ( ) ?;
51+ let executable = env. executable . clone ( ) ;
52+
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
56+ if !executable. starts_with ( "/bin" )
57+ && !executable. starts_with ( "/usr/bin" )
58+ && !executable. starts_with ( "/usr/local/bin" )
59+ && !prefix. starts_with ( "/usr" )
60+ && !prefix. starts_with ( "/usr/local" )
61+ {
62+ return None ;
63+ }
6164
62- // All known global linux are always installed in `/bin` or `/usr/bin`
63- if executable. starts_with ( "/bin" ) || executable. starts_with ( "/usr/bin" ) {
64- get_python_in_usr_bin ( env)
65- } else if executable. starts_with ( "/usr/local/bin" ) {
66- get_python_in_usr_local_bin ( env)
67- } else {
68- error ! (
69- "Invalid state, ex ({:?}) is not in any of /bin, /usr/bin, /usr/local/bin" ,
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" ,
7074 executable
7175 ) ;
72- None
73- }
74- } else {
7576 None
7677 }
7778 }
@@ -84,7 +85,7 @@ impl Locator for LinuxGlobalPython {
8485 }
8586}
8687
87- fn get_python_in_usr_bin ( env : & PythonEnv ) -> Option < PythonEnvironment > {
88+ fn get_python_in_bin ( env : & PythonEnv ) -> Option < PythonEnvironment > {
8889 // If we do not have the prefix, then do not try
8990 // This method will be called with resolved Python where prefix & version is available.
9091 if env. version . clone ( ) . is_none ( ) || env. prefix . clone ( ) . is_none ( ) {
@@ -94,86 +95,54 @@ fn get_python_in_usr_bin(env: &PythonEnv) -> Option<PythonEnvironment> {
9495 let mut symlinks = env. symlinks . clone ( ) . unwrap_or_default ( ) ;
9596 symlinks. push ( executable. clone ( ) ) ;
9697
97- let bin = PathBuf :: from ( "/bin" ) ;
98- let usr_bin = PathBuf :: from ( "/usr/bin" ) ;
98+ let bin = executable. parent ( ) ?;
99+
100+ // Keep track of what the exe resolves to.
101+ // Will have a value only if the exe is in another dir
102+ // E.g. /bin/python3 might be a symlink to /usr/bin/python3.12
103+ // However due to legacy reasons we'll be treating these two as separate exes.
104+ // Hence they will be separate Python environments.
105+ let mut resolved_exe_is_from_another_dir = None ;
99106
100107 // Possible this exe is a symlink to another file in the same directory.
101- // E.g. /usr/bin/python3 is a symlink to /usr/bin/python3.12
108+ // E.g. Generally /usr/bin/python3 is a symlink to /usr/bin/python3.12
109+ // E.g. Generally /usr/local/bin/python3 is a symlink to /usr/local/bin/python3.12
110+ // E.g. Generally /bin/python3 is a symlink to /bin/python3.12
102111 // let bin = executable.parent()?;
103112 // We use canonicalize to get the real path of the symlink.
104113 // Only used in this case, see notes for resolve_symlink.
105114 if let Some ( symlink) = resolve_symlink ( & executable) . or ( fs:: canonicalize ( & executable) . ok ( ) ) {
106115 // Ensure this is a symlink in the bin or usr/bin directory.
107- if symlink. starts_with ( & bin) || symlink . starts_with ( & usr_bin ) {
116+ if symlink. starts_with ( bin) {
108117 symlinks. push ( symlink) ;
118+ } else {
119+ resolved_exe_is_from_another_dir = Some ( symlink) ;
109120 }
110121 }
111122
112- // Look for other symlinks in /usr/bin and /bin folder
113- // https://stackoverflow.com/questions/68728225/what-is-the-difference-between-usr-bin-python3-and-bin-python3
114- // We know that on linux there are symlinks in both places.
123+ // Look for other symlinks in the same folder
124+ // We know that on linux there are sym links in the same folder as the exe.
115125 // & they all point to one exe and have the same version and same prefix.
116- for possible_symlink in [ find_executables ( & bin) , find_executables ( & usr_bin ) ] . concat ( ) {
117- if let Some ( symlink) =
118- resolve_symlink ( & possible_symlink) . or ( fs:: canonicalize ( & possible_symlink) . ok ( ) )
126+ for possible_symlink in find_executables ( bin) . iter ( ) {
127+ if let Some ( ref symlink) =
128+ resolve_symlink ( & possible_symlink) . or ( fs:: canonicalize ( possible_symlink) . ok ( ) )
119129 {
120- // the file /bin/python3 is a symlink to /usr/bin/python3.12
121- // the file /bin/python3.12 is a symlink to /usr/bin/python3.12
122- // the file /usr/bin/python3 is a symlink to /usr/bin/python3.12
123- // Thus we have 3 symlinks pointing to the same exe /usr/bin/python3.12
124- if symlinks. contains ( & symlink) {
125- symlinks. push ( possible_symlink) ;
130+ // Generally the file /bin/python3 is a symlink to /usr/bin/python3.12
131+ // Generally the file /bin/python3.12 is a symlink to /usr/bin/python3.12
132+ // Generally the file /usr/bin/python3 is a symlink to /usr/bin/python3.12
133+ // HOWEVER, we will be treating the files in /bin and /usr/bin as different.
134+ // Hence check whether the resolve symlink is in the same directory.
135+ if symlink. starts_with ( bin) & symlinks. contains ( symlink) {
136+ symlinks. push ( possible_symlink. to_owned ( ) ) ;
126137 }
127- }
128- }
129- symlinks. sort ( ) ;
130- symlinks. dedup ( ) ;
131-
132- Some (
133- PythonEnvironmentBuilder :: new ( PythonEnvironmentCategory :: LinuxGlobal )
134- . executable ( Some ( executable) )
135- . version ( env. version . clone ( ) )
136- . prefix ( env. prefix . clone ( ) )
137- . symlinks ( Some ( symlinks) )
138- . build ( ) ,
139- )
140- }
141-
142- fn get_python_in_usr_local_bin ( env : & PythonEnv ) -> Option < PythonEnvironment > {
143- // If we do not have the prefix, then do not try
144- // This method will be called with resolved Python where prefix & version is available.
145- if env. version . clone ( ) . is_none ( ) || env. prefix . clone ( ) . is_none ( ) {
146- return None ;
147- }
148- let executable = env. executable . clone ( ) ;
149- let mut symlinks = env. symlinks . clone ( ) . unwrap_or_default ( ) ;
150- symlinks. push ( executable. clone ( ) ) ;
151138
152- let usr_local_bin = PathBuf :: from ( "/usr/local/bin" ) ;
153-
154- // Possible this exe is a symlink to another file in the same directory.
155- // E.g. /usr/local/bin/python3 could be a symlink to /usr/local/bin/python3.12
156- // let bin = executable.parent()?;
157- // We use canonicalize to get the real path of the symlink.
158- // Only used in this case, see notes for resolve_symlink.
159- if let Some ( symlink) = resolve_symlink ( & executable) . or ( fs:: canonicalize ( & executable) . ok ( ) ) {
160- // Ensure this is a symlink in the bin or usr/local/bin directory.
161- if symlink. starts_with ( & usr_local_bin) {
162- symlinks. push ( symlink) ;
163- }
164- }
165-
166- // Look for other symlinks in this same folder
167- for possible_symlink in find_executables ( & usr_local_bin) {
168- if let Some ( symlink) =
169- resolve_symlink ( & possible_symlink) . or ( fs:: canonicalize ( & possible_symlink) . ok ( ) )
170- {
171- // the file /bin/python3 is a symlink to /usr/bin/python3.12
172- // the file /bin/python3.12 is a symlink to /usr/bin/python3.12
173- // the file /usr/bin/python3 is a symlink to /usr/bin/python3.12
174- // Thus we have 3 symlinks pointing to the same exe /usr/bin/python3.12
175- if symlinks. contains ( & symlink) {
176- symlinks. push ( possible_symlink) ;
139+ // Possible the env.executable = /bin/python3
140+ // And the possible_symlink = /bin/python3.12
141+ // & possible that both of the above are symlinks and point to /usr/bin/python3.12
142+ // In this case /bin/python3 === /bin/python.3.12
143+ // However as mentioned earlier we will not be treating these the same as /usr/bin/python3.12
144+ if resolved_exe_is_from_another_dir == Some ( symlink. to_owned ( ) ) {
145+ symlinks. push ( possible_symlink. to_owned ( ) ) ;
177146 }
178147 }
179148 }
0 commit comments