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
@@ -172,11 +173,23 @@ def _get_system_python_executable():
172
173
173
174
def python_binary_from_folder (path ):
174
175
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:
175
187
try :
176
188
filenotfounderror = FileNotFoundError
177
189
except NameError : # Python 2
178
190
filenotfounderror = OSError
179
191
try :
192
+ # Run it and see if version output works with no error:
180
193
subprocess .check_output ([
181
194
os .path .join (path , python_bin ), "--version"
182
195
], stderr = subprocess .STDOUT )
@@ -202,19 +215,26 @@ def binary_is_usable(python_bin):
202
215
bad_candidates = []
203
216
good_candidates = []
204
217
ever_had_nonvenv_path = False
218
+ ever_had_path_starting_with_prefix = False
205
219
for p in os .environ .get ("PATH" , "" ).split (":" ):
206
220
# Skip if not possibly the real system python:
207
221
if not os .path .normpath (p ).startswith (
208
222
os .path .normpath (search_prefix )
209
223
):
210
224
continue
211
225
226
+ ever_had_path_starting_with_prefix = True
227
+
212
228
# First folders might be virtualenv/venv we want to avoid:
213
229
if not ever_had_nonvenv_path :
214
230
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
+ ):
218
238
# Doesn't look like bog-standard system path.
219
239
if (p .endswith (os .path .sep + "bin" ) or
220
240
p .endswith (os .path .sep + "bin" + os .path .sep )):
@@ -226,14 +246,37 @@ def binary_is_usable(python_bin):
226
246
227
247
good_candidates .append (p )
228
248
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
+
229
269
# See if we can now actually find the system python:
230
270
for p in good_candidates + bad_candidates :
231
271
result = python_binary_from_folder (p )
232
272
if result is not None :
233
273
return result
234
274
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
+ )
237
280
238
281
239
282
def get_package_as_folder (dependency ):
0 commit comments