5
5
import sys
6
6
import re
7
7
import subprocess
8
+ import logging
8
9
from email .parser import FeedParser
9
10
10
11
11
- from .compat import lambda_abi
12
12
from .compat import pip_import_string
13
13
from .compat import pip_no_compile_c_env_vars
14
14
from .compat import pip_no_compile_c_shim
15
15
from .utils import OSUtils
16
16
17
+ LOG = logging .getLogger (__name__ )
18
+
17
19
18
20
# TODO update the wording here
19
21
MISSING_DEPENDENCIES_TEMPLATE = r"""
@@ -56,10 +58,36 @@ class PackageDownloadError(PackagerError):
56
58
pass
57
59
58
60
61
+ class UnsupportedPythonVersion (PackagerError ):
62
+ """Generic networking error during a package download."""
63
+ def __init__ (self , version ):
64
+ super (UnsupportedPythonVersion , self ).__init__ (
65
+ "'%s' version of python is not supported" % version
66
+ )
67
+
68
+
69
+ def get_lambda_abi (runtime ):
70
+ supported = {
71
+ "python2.7" : "cp27mu" ,
72
+ "python3.6" : "cp36m" ,
73
+ "python3.7" : "cp37m"
74
+ }
75
+
76
+ if runtime not in supported :
77
+ raise UnsupportedPythonVersion (runtime )
78
+
79
+ return supported [runtime ]
80
+
81
+
59
82
class PythonPipDependencyBuilder (object ):
60
- def __init__ (self , osutils = None , dependency_builder = None ):
83
+ def __init__ (self , runtime , osutils = None , dependency_builder = None ):
61
84
"""Initialize a PythonPipDependencyBuilder.
62
85
86
+ :type runtime: str
87
+ :param runtime: Python version to build dependencies for. This can
88
+ either be python2.7, python3.6 or python3.7. These are currently the
89
+ only supported values.
90
+
63
91
:type osutils: :class:`lambda_builders.utils.OSUtils`
64
92
:param osutils: A class used for all interactions with the
65
93
outside OS.
@@ -73,11 +101,11 @@ def __init__(self, osutils=None, dependency_builder=None):
73
101
self .osutils = OSUtils ()
74
102
75
103
if dependency_builder is None :
76
- dependency_builder = DependencyBuilder (self .osutils )
104
+ dependency_builder = DependencyBuilder (self .osutils , runtime )
77
105
self ._dependency_builder = dependency_builder
78
106
79
107
def build_dependencies (self , artifacts_dir_path , scratch_dir_path ,
80
- requirements_path , runtime , ui = None , config = None ):
108
+ requirements_path , ui = None , config = None ):
81
109
"""Builds a python project's dependencies into an artifact directory.
82
110
83
111
:type artifacts_dir_path: str
@@ -90,11 +118,6 @@ def build_dependencies(self, artifacts_dir_path, scratch_dir_path,
90
118
:param requirements_path: Path to a requirements.txt file to inspect
91
119
for a list of dependencies.
92
120
93
- :type runtime: str
94
- :param runtime: Python version to build dependencies for. This can
95
- either be python2.7 or python3.6. These are currently the only
96
- supported values.
97
-
98
121
:type ui: :class:`lambda_builders.utils.UI` or None
99
122
:param ui: A class that traps all progress information such as status
100
123
and errors. If injected by the caller, it can be used to monitor
@@ -138,13 +161,16 @@ class DependencyBuilder(object):
138
161
'sqlalchemy'
139
162
}
140
163
141
- def __init__ (self , osutils , pip_runner = None ):
164
+ def __init__ (self , osutils , runtime , pip_runner = None ):
142
165
"""Initialize a DependencyBuilder.
143
166
144
167
:type osutils: :class:`lambda_builders.utils.OSUtils`
145
168
:param osutils: A class used for all interactions with the
146
169
outside OS.
147
170
171
+ :type runtime: str
172
+ :param runtime: AWS Lambda Python runtime to build for
173
+
148
174
:type pip_runner: :class:`PipRunner`
149
175
:param pip_runner: This class is responsible for executing our pip
150
176
on our behalf.
@@ -153,6 +179,7 @@ def __init__(self, osutils, pip_runner=None):
153
179
if pip_runner is None :
154
180
pip_runner = PipRunner (SubprocessPip (osutils ))
155
181
self ._pip = pip_runner
182
+ self .runtime = runtime
156
183
157
184
def build_site_packages (self , requirements_filepath ,
158
185
target_directory ,
@@ -229,6 +256,9 @@ def _download_dependencies(self, directory, requirements_filename):
229
256
else :
230
257
incompatible_wheels .add (package )
231
258
259
+ LOG .debug ("initial compatible: %s" , compatible_wheels )
260
+ LOG .debug ("initial incompatible: %s" , incompatible_wheels | sdists )
261
+
232
262
# Next we need to go through the downloaded packages and pick out any
233
263
# dependencies that do not have a compatible wheel file downloaded.
234
264
# For these packages we need to explicitly try to download a
@@ -242,6 +272,10 @@ def _download_dependencies(self, directory, requirements_filename):
242
272
# file ourselves.
243
273
compatible_wheels , incompatible_wheels = self ._categorize_wheel_files (
244
274
directory )
275
+ LOG .debug (
276
+ "compatible wheels after second download pass: %s" ,
277
+ compatible_wheels
278
+ )
245
279
missing_wheels = sdists - compatible_wheels
246
280
self ._build_sdists (missing_wheels , directory , compile_c = True )
247
281
@@ -255,6 +289,10 @@ def _download_dependencies(self, directory, requirements_filename):
255
289
# compiler.
256
290
compatible_wheels , incompatible_wheels = self ._categorize_wheel_files (
257
291
directory )
292
+ LOG .debug (
293
+ "compatible after building wheels (no C compiling): %s" ,
294
+ compatible_wheels
295
+ )
258
296
missing_wheels = sdists - compatible_wheels
259
297
self ._build_sdists (missing_wheels , directory , compile_c = False )
260
298
@@ -264,6 +302,10 @@ def _download_dependencies(self, directory, requirements_filename):
264
302
# compatible version directly and building from source.
265
303
compatible_wheels , incompatible_wheels = self ._categorize_wheel_files (
266
304
directory )
305
+ LOG .debug (
306
+ "compatible after building wheels (C compiling): %s" ,
307
+ compatible_wheels
308
+ )
267
309
268
310
# Now there is still the case left over where the setup.py has been
269
311
# made in such a way to be incompatible with python's setup tools,
@@ -273,6 +315,9 @@ def _download_dependencies(self, directory, requirements_filename):
273
315
compatible_wheels , incompatible_wheels = self ._apply_wheel_whitelist (
274
316
compatible_wheels , incompatible_wheels )
275
317
missing_wheels = deps - compatible_wheels
318
+ LOG .debug ("Final compatible: %s" , compatible_wheels )
319
+ LOG .debug ("Final incompatible: %s" , incompatible_wheels )
320
+ LOG .debug ("Final missing wheels: %s" , missing_wheels )
276
321
277
322
return compatible_wheels , missing_wheels
278
323
@@ -285,14 +330,19 @@ def _download_all_dependencies(self, requirements_filename, directory):
285
330
self ._pip .download_all_dependencies (requirements_filename , directory )
286
331
deps = {Package (directory , filename ) for filename
287
332
in self ._osutils .get_directory_contents (directory )}
333
+ LOG .debug ("Full dependency closure: %s" , deps )
288
334
return deps
289
335
290
336
def _download_binary_wheels (self , packages , directory ):
291
337
# Try to get binary wheels for each package that isn't compatible.
338
+ LOG .debug ("Downloading missing wheels: %s" , packages )
339
+ lambda_abi = get_lambda_abi (self .runtime )
292
340
self ._pip .download_manylinux_wheels (
293
- [pkg .identifier for pkg in packages ], directory )
341
+ [pkg .identifier for pkg in packages ], directory , lambda_abi )
294
342
295
343
def _build_sdists (self , sdists , directory , compile_c = True ):
344
+ LOG .debug ("Build missing wheels from sdists "
345
+ "(C compiling %s): %s" , compile_c , sdists )
296
346
for sdist in sdists :
297
347
path_to_sdist = self ._osutils .joinpath (directory , sdist .filename )
298
348
self ._pip .build_wheel (path_to_sdist , directory , compile_c )
@@ -316,6 +366,9 @@ def _is_compatible_wheel_filename(self, filename):
316
366
# Verify platform is compatible
317
367
if platform not in self ._MANYLINUX_COMPATIBLE_PLATFORM :
318
368
return False
369
+
370
+ lambda_runtime_abi = get_lambda_abi (self .runtime )
371
+
319
372
# Verify that the ABI is compatible with lambda. Either none or the
320
373
# correct type for the python version cp27mu for py27 and cp36m for
321
374
# py36.
@@ -326,7 +379,7 @@ def _is_compatible_wheel_filename(self, filename):
326
379
# Deploying python 3 function which means we need cp36m abi
327
380
# We can also accept abi3 which is the CPython 3 Stable ABI and
328
381
# will work on any version of python 3.
329
- return abi == 'cp36m' or abi == 'abi3'
382
+ return abi == lambda_runtime_abi or abi == 'abi3'
330
383
elif prefix_version == 'cp2' :
331
384
# Deploying to python 2 function which means we need cp27mu abi
332
385
return abi == 'cp27mu'
@@ -537,6 +590,7 @@ def __init__(self, pip, osutils=None):
537
590
def _execute (self , command , args , env_vars = None , shim = None ):
538
591
"""Execute a pip command with the given arguments."""
539
592
main_args = [command ] + args
593
+ LOG .debug ("calling pip %s" , ' ' .join (main_args ))
540
594
rc , out , err = self ._wrapped_pip .main (main_args , env_vars = env_vars ,
541
595
shim = shim )
542
596
return rc , out , err
@@ -589,7 +643,7 @@ def download_all_dependencies(self, requirements_filename, directory):
589
643
# complain at deployment time.
590
644
self .build_wheel (wheel_package_path , directory )
591
645
592
- def download_manylinux_wheels (self , packages , directory ):
646
+ def download_manylinux_wheels (self , packages , directory , lambda_abi ):
593
647
"""Download wheel files for manylinux for all the given packages."""
594
648
# If any one of these dependencies fails pip will bail out. Since we
595
649
# are only interested in all the ones we can download, we need to feed
0 commit comments