34
34
35
35
36
36
from io import open # needed for python 2
37
+ import functools
37
38
import os
38
39
from pep517 .envbuild import BuildEnvironment
39
40
from pep517 .wrappers import Pep517HookCaller
@@ -176,11 +177,23 @@ def _get_system_python_executable():
176
177
177
178
def python_binary_from_folder (path ):
178
179
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:
179
191
try :
180
192
filenotfounderror = FileNotFoundError
181
193
except NameError : # Python 2
182
194
filenotfounderror = OSError
183
195
try :
196
+ # Run it and see if version output works with no error:
184
197
subprocess .check_output ([
185
198
os .path .join (path , python_bin ), "--version"
186
199
], stderr = subprocess .STDOUT )
@@ -206,19 +219,26 @@ def binary_is_usable(python_bin):
206
219
bad_candidates = []
207
220
good_candidates = []
208
221
ever_had_nonvenv_path = False
222
+ ever_had_path_starting_with_prefix = False
209
223
for p in os .environ .get ("PATH" , "" ).split (":" ):
210
224
# Skip if not possibly the real system python:
211
225
if not os .path .normpath (p ).startswith (
212
226
os .path .normpath (search_prefix )
213
227
):
214
228
continue
215
229
230
+ ever_had_path_starting_with_prefix = True
231
+
216
232
# First folders might be virtualenv/venv we want to avoid:
217
233
if not ever_had_nonvenv_path :
218
234
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
+ ):
222
242
# Doesn't look like bog-standard system path.
223
243
if (p .endswith (os .path .sep + "bin" ) or
224
244
p .endswith (os .path .sep + "bin" + os .path .sep )):
@@ -230,14 +250,37 @@ def binary_is_usable(python_bin):
230
250
231
251
good_candidates .append (p )
232
252
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
+
233
273
# See if we can now actually find the system python:
234
274
for p in good_candidates + bad_candidates :
235
275
result = python_binary_from_folder (p )
236
276
if result is not None :
237
277
return result
238
278
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
+ )
241
284
242
285
243
286
def get_package_as_folder (dependency ):
0 commit comments