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
@@ -172,11 +173,23 @@ def _get_system_python_executable():
172173
173174 def python_binary_from_folder (path ):
174175 def binary_is_usable (python_bin ):
176+ """ Helper function to see if a given binary name refers
177+ to a usable python interpreter binary
178+ """
179+
180+ # Abort if path isn't present at all or a directory:
181+ if not os .path .exists (
182+ os .path .join (path , python_bin )
183+ ) or os .path .isdir (os .path .join (path , python_bin )):
184+ return
185+ # We should check file not found anyway trying to run it,
186+ # since it might be a dead symlink:
175187 try :
176188 filenotfounderror = FileNotFoundError
177189 except NameError : # Python 2
178190 filenotfounderror = OSError
179191 try :
192+ # Run it and see if version output works with no error:
180193 subprocess .check_output ([
181194 os .path .join (path , python_bin ), "--version"
182195 ], stderr = subprocess .STDOUT )
@@ -202,19 +215,26 @@ def binary_is_usable(python_bin):
202215 bad_candidates = []
203216 good_candidates = []
204217 ever_had_nonvenv_path = False
218+ ever_had_path_starting_with_prefix = False
205219 for p in os .environ .get ("PATH" , "" ).split (":" ):
206220 # Skip if not possibly the real system python:
207221 if not os .path .normpath (p ).startswith (
208222 os .path .normpath (search_prefix )
209223 ):
210224 continue
211225
226+ ever_had_path_starting_with_prefix = True
227+
212228 # First folders might be virtualenv/venv we want to avoid:
213229 if not ever_had_nonvenv_path :
214230 sep = os .path .sep
215- if ("system32" not in p .lower () and "usr" not in p ) or \
216- {"home" , ".tox" }.intersection (set (p .split (sep ))) or \
217- "users" in p .lower ():
231+ if (
232+ ("system32" not in p .lower () and
233+ "usr" not in p and
234+ not p .startswith ("/opt/python" )) or
235+ {"home" , ".tox" }.intersection (set (p .split (sep ))) or
236+ "users" in p .lower ()
237+ ):
218238 # Doesn't look like bog-standard system path.
219239 if (p .endswith (os .path .sep + "bin" ) or
220240 p .endswith (os .path .sep + "bin" + os .path .sep )):
@@ -226,14 +246,37 @@ def binary_is_usable(python_bin):
226246
227247 good_candidates .append (p )
228248
249+ # If we have a bad env with PATH not containing any reference to our
250+ # real python (travis, why would you do that to me?) then just guess
251+ # based from the search prefix location itself:
252+ if not ever_had_path_starting_with_prefix :
253+ # ... and yes we're scanning all the folders for that, it's dumb
254+ # but i'm not aware of a better way: (@JonasT)
255+ for root , dirs , files in os .walk (search_prefix , topdown = True ):
256+ for name in dirs :
257+ bad_candidates .append (os .path .join (root , name ))
258+
259+ # Sort candidates by length (to prefer shorter ones):
260+ def candidate_cmp (a , b ):
261+ return len (a ) - len (b )
262+ good_candidates = sorted (
263+ good_candidates , key = functools .cmp_to_key (candidate_cmp )
264+ )
265+ bad_candidates = sorted (
266+ bad_candidates , key = functools .cmp_to_key (candidate_cmp )
267+ )
268+
229269 # See if we can now actually find the system python:
230270 for p in good_candidates + bad_candidates :
231271 result = python_binary_from_folder (p )
232272 if result is not None :
233273 return result
234274
235- raise RuntimeError ("failed to locate system python in: " +
236- sys .real_prefix )
275+ raise RuntimeError (
276+ "failed to locate system python in: {}"
277+ " - checked candidates were: {}, {}"
278+ .format (sys .real_prefix , good_candidates , bad_candidates )
279+ )
237280
238281
239282def get_package_as_folder (dependency ):
0 commit comments