3434
3535
3636from io import open # needed for python 2
37+ import functools
3738import os
3839from pep517 .envbuild import BuildEnvironment
3940from pep517 .wrappers import Pep517HookCaller
@@ -176,11 +177,23 @@ def _get_system_python_executable():
176177
177178 def python_binary_from_folder (path ):
178179 def binary_is_usable (python_bin ):
180+ """ Helper function to see if a given binary name refers
181+ to a usable python interpreter binary
182+ """
183+
184+ # Abort if path isn't present at all or a directory:
185+ if not os .path .exists (
186+ os .path .join (path , python_bin )
187+ ) or os .path .isdir (os .path .join (path , python_bin )):
188+ return
189+ # We should check file not found anyway trying to run it,
190+ # since it might be a dead symlink:
179191 try :
180192 filenotfounderror = FileNotFoundError
181193 except NameError : # Python 2
182194 filenotfounderror = OSError
183195 try :
196+ # Run it and see if version output works with no error:
184197 subprocess .check_output ([
185198 os .path .join (path , python_bin ), "--version"
186199 ], stderr = subprocess .STDOUT )
@@ -206,19 +219,26 @@ def binary_is_usable(python_bin):
206219 bad_candidates = []
207220 good_candidates = []
208221 ever_had_nonvenv_path = False
222+ ever_had_path_starting_with_prefix = False
209223 for p in os .environ .get ("PATH" , "" ).split (":" ):
210224 # Skip if not possibly the real system python:
211225 if not os .path .normpath (p ).startswith (
212226 os .path .normpath (search_prefix )
213227 ):
214228 continue
215229
230+ ever_had_path_starting_with_prefix = True
231+
216232 # First folders might be virtualenv/venv we want to avoid:
217233 if not ever_had_nonvenv_path :
218234 sep = os .path .sep
219- if ("system32" not in p .lower () and "usr" not in p ) or \
220- {"home" , ".tox" }.intersection (set (p .split (sep ))) or \
221- "users" in p .lower ():
235+ if (
236+ ("system32" not in p .lower () and
237+ "usr" not in p and
238+ not p .startswith ("/opt/python" )) or
239+ {"home" , ".tox" }.intersection (set (p .split (sep ))) or
240+ "users" in p .lower ()
241+ ):
222242 # Doesn't look like bog-standard system path.
223243 if (p .endswith (os .path .sep + "bin" ) or
224244 p .endswith (os .path .sep + "bin" + os .path .sep )):
@@ -230,14 +250,37 @@ def binary_is_usable(python_bin):
230250
231251 good_candidates .append (p )
232252
253+ # If we have a bad env with PATH not containing any reference to our
254+ # real python (travis, why would you do that to me?) then just guess
255+ # based from the search prefix location itself:
256+ if not ever_had_path_starting_with_prefix :
257+ # ... and yes we're scanning all the folders for that, it's dumb
258+ # but i'm not aware of a better way: (@JonasT)
259+ for root , dirs , files in os .walk (search_prefix , topdown = True ):
260+ for name in dirs :
261+ bad_candidates .append (os .path .join (root , name ))
262+
263+ # Sort candidates by length (to prefer shorter ones):
264+ def candidate_cmp (a , b ):
265+ return len (a ) - len (b )
266+ good_candidates = sorted (
267+ good_candidates , key = functools .cmp_to_key (candidate_cmp )
268+ )
269+ bad_candidates = sorted (
270+ bad_candidates , key = functools .cmp_to_key (candidate_cmp )
271+ )
272+
233273 # See if we can now actually find the system python:
234274 for p in good_candidates + bad_candidates :
235275 result = python_binary_from_folder (p )
236276 if result is not None :
237277 return result
238278
239- raise RuntimeError ("failed to locate system python in: " +
240- sys .real_prefix )
279+ raise RuntimeError (
280+ "failed to locate system python in: {}"
281+ " - checked candidates were: {}, {}"
282+ .format (sys .real_prefix , good_candidates , bad_candidates )
283+ )
241284
242285
243286def get_package_as_folder (dependency ):
0 commit comments