55import sys
66import re
77import subprocess
8+ import logging
89from email .parser import FeedParser
910
1011
11- from .compat import lambda_abi
1212from .compat import pip_import_string
1313from .compat import pip_no_compile_c_env_vars
1414from .compat import pip_no_compile_c_shim
1515from .utils import OSUtils
1616
17+ LOG = logging .getLogger (__name__ )
18+
1719
1820# TODO update the wording here
1921MISSING_DEPENDENCIES_TEMPLATE = r"""
@@ -56,10 +58,36 @@ class PackageDownloadError(PackagerError):
5658 pass
5759
5860
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+
5982class PythonPipDependencyBuilder (object ):
60- def __init__ (self , osutils = None , dependency_builder = None ):
83+ def __init__ (self , runtime , osutils = None , dependency_builder = None ):
6184 """Initialize a PythonPipDependencyBuilder.
6285
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+
6391 :type osutils: :class:`lambda_builders.utils.OSUtils`
6492 :param osutils: A class used for all interactions with the
6593 outside OS.
@@ -73,11 +101,11 @@ def __init__(self, osutils=None, dependency_builder=None):
73101 self .osutils = OSUtils ()
74102
75103 if dependency_builder is None :
76- dependency_builder = DependencyBuilder (self .osutils )
104+ dependency_builder = DependencyBuilder (self .osutils , runtime )
77105 self ._dependency_builder = dependency_builder
78106
79107 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 ):
81109 """Builds a python project's dependencies into an artifact directory.
82110
83111 :type artifacts_dir_path: str
@@ -90,11 +118,6 @@ def build_dependencies(self, artifacts_dir_path, scratch_dir_path,
90118 :param requirements_path: Path to a requirements.txt file to inspect
91119 for a list of dependencies.
92120
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-
98121 :type ui: :class:`lambda_builders.utils.UI` or None
99122 :param ui: A class that traps all progress information such as status
100123 and errors. If injected by the caller, it can be used to monitor
@@ -138,13 +161,16 @@ class DependencyBuilder(object):
138161 'sqlalchemy'
139162 }
140163
141- def __init__ (self , osutils , pip_runner = None ):
164+ def __init__ (self , osutils , runtime , pip_runner = None ):
142165 """Initialize a DependencyBuilder.
143166
144167 :type osutils: :class:`lambda_builders.utils.OSUtils`
145168 :param osutils: A class used for all interactions with the
146169 outside OS.
147170
171+ :type runtime: str
172+ :param runtime: AWS Lambda Python runtime to build for
173+
148174 :type pip_runner: :class:`PipRunner`
149175 :param pip_runner: This class is responsible for executing our pip
150176 on our behalf.
@@ -153,6 +179,7 @@ def __init__(self, osutils, pip_runner=None):
153179 if pip_runner is None :
154180 pip_runner = PipRunner (SubprocessPip (osutils ))
155181 self ._pip = pip_runner
182+ self .runtime = runtime
156183
157184 def build_site_packages (self , requirements_filepath ,
158185 target_directory ,
@@ -229,6 +256,9 @@ def _download_dependencies(self, directory, requirements_filename):
229256 else :
230257 incompatible_wheels .add (package )
231258
259+ LOG .debug ("initial compatible: %s" , compatible_wheels )
260+ LOG .debug ("initial incompatible: %s" , incompatible_wheels | sdists )
261+
232262 # Next we need to go through the downloaded packages and pick out any
233263 # dependencies that do not have a compatible wheel file downloaded.
234264 # For these packages we need to explicitly try to download a
@@ -242,6 +272,10 @@ def _download_dependencies(self, directory, requirements_filename):
242272 # file ourselves.
243273 compatible_wheels , incompatible_wheels = self ._categorize_wheel_files (
244274 directory )
275+ LOG .debug (
276+ "compatible wheels after second download pass: %s" ,
277+ compatible_wheels
278+ )
245279 missing_wheels = sdists - compatible_wheels
246280 self ._build_sdists (missing_wheels , directory , compile_c = True )
247281
@@ -255,6 +289,10 @@ def _download_dependencies(self, directory, requirements_filename):
255289 # compiler.
256290 compatible_wheels , incompatible_wheels = self ._categorize_wheel_files (
257291 directory )
292+ LOG .debug (
293+ "compatible after building wheels (no C compiling): %s" ,
294+ compatible_wheels
295+ )
258296 missing_wheels = sdists - compatible_wheels
259297 self ._build_sdists (missing_wheels , directory , compile_c = False )
260298
@@ -264,6 +302,10 @@ def _download_dependencies(self, directory, requirements_filename):
264302 # compatible version directly and building from source.
265303 compatible_wheels , incompatible_wheels = self ._categorize_wheel_files (
266304 directory )
305+ LOG .debug (
306+ "compatible after building wheels (C compiling): %s" ,
307+ compatible_wheels
308+ )
267309
268310 # Now there is still the case left over where the setup.py has been
269311 # made in such a way to be incompatible with python's setup tools,
@@ -273,6 +315,9 @@ def _download_dependencies(self, directory, requirements_filename):
273315 compatible_wheels , incompatible_wheels = self ._apply_wheel_whitelist (
274316 compatible_wheels , incompatible_wheels )
275317 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 )
276321
277322 return compatible_wheels , missing_wheels
278323
@@ -285,14 +330,19 @@ def _download_all_dependencies(self, requirements_filename, directory):
285330 self ._pip .download_all_dependencies (requirements_filename , directory )
286331 deps = {Package (directory , filename ) for filename
287332 in self ._osutils .get_directory_contents (directory )}
333+ LOG .debug ("Full dependency closure: %s" , deps )
288334 return deps
289335
290336 def _download_binary_wheels (self , packages , directory ):
291337 # 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 )
292340 self ._pip .download_manylinux_wheels (
293- [pkg .identifier for pkg in packages ], directory )
341+ [pkg .identifier for pkg in packages ], directory , lambda_abi )
294342
295343 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 )
296346 for sdist in sdists :
297347 path_to_sdist = self ._osutils .joinpath (directory , sdist .filename )
298348 self ._pip .build_wheel (path_to_sdist , directory , compile_c )
@@ -316,6 +366,9 @@ def _is_compatible_wheel_filename(self, filename):
316366 # Verify platform is compatible
317367 if platform not in self ._MANYLINUX_COMPATIBLE_PLATFORM :
318368 return False
369+
370+ lambda_runtime_abi = get_lambda_abi (self .runtime )
371+
319372 # Verify that the ABI is compatible with lambda. Either none or the
320373 # correct type for the python version cp27mu for py27 and cp36m for
321374 # py36.
@@ -326,7 +379,7 @@ def _is_compatible_wheel_filename(self, filename):
326379 # Deploying python 3 function which means we need cp36m abi
327380 # We can also accept abi3 which is the CPython 3 Stable ABI and
328381 # will work on any version of python 3.
329- return abi == 'cp36m' or abi == 'abi3'
382+ return abi == lambda_runtime_abi or abi == 'abi3'
330383 elif prefix_version == 'cp2' :
331384 # Deploying to python 2 function which means we need cp27mu abi
332385 return abi == 'cp27mu'
@@ -537,6 +590,7 @@ def __init__(self, pip, osutils=None):
537590 def _execute (self , command , args , env_vars = None , shim = None ):
538591 """Execute a pip command with the given arguments."""
539592 main_args = [command ] + args
593+ LOG .debug ("calling pip %s" , ' ' .join (main_args ))
540594 rc , out , err = self ._wrapped_pip .main (main_args , env_vars = env_vars ,
541595 shim = shim )
542596 return rc , out , err
@@ -589,7 +643,7 @@ def download_all_dependencies(self, requirements_filename, directory):
589643 # complain at deployment time.
590644 self .build_wheel (wheel_package_path , directory )
591645
592- def download_manylinux_wheels (self , packages , directory ):
646+ def download_manylinux_wheels (self , packages , directory , lambda_abi ):
593647 """Download wheel files for manylinux for all the given packages."""
594648 # If any one of these dependencies fails pip will bail out. Since we
595649 # are only interested in all the ones we can download, we need to feed
0 commit comments