Skip to content

Commit 140fd87

Browse files
authored
Merge pull request #41 from awslabs/develop
Release v0.0.3
2 parents aa0acbc + 3744cea commit 140fd87

File tree

18 files changed

+143
-64
lines changed

18 files changed

+143
-64
lines changed

.appveyor.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ environment:
77

88
- PYTHON: "C:\\Python27-x64"
99
- PYTHON: "C:\\Python36-x64"
10-
# - PYTHON: "C:\\Python37-x64"
10+
- PYTHON: "C:\\Python37-x64"
1111

1212

1313
build: off

.travis.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ python:
55
- "2.7"
66
- "3.6"
77
# Enable 3.7 without globally enabling sudo and dist: xenial for other build jobs
8-
#matrix:
9-
# include:
10-
# - python: 3.7
11-
# dist: xenial
12-
# sudo: true
8+
matrix:
9+
include:
10+
- python: 3.7
11+
dist: xenial
12+
sudo: true
1313
install:
1414
# Install the code requirements
1515
- make init

MANIFEST.in

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
include LICENSE
2-
include requirements/base.txt
3-
include requirements/dev.txt
2+
include requirements/*
43
recursive-include aws_lambda_builders/workflows *
54
prune tests
65

aws_lambda_builders/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
22
AWS Lambda Builder Library
33
"""
4-
__version__ = '0.0.2'
4+
__version__ = '0.0.3'
55
RPC_PROTOCOL_VERSION = "0.1"

aws_lambda_builders/exceptions.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ class UnsupportedManifestError(LambdaBuilderError):
1616

1717

1818
class MisMatchRuntimeError(LambdaBuilderError):
19-
MESSAGE = "A runtime version mismatch was found for the given language " \
20-
"'{language}', required runtime '{required_runtime}'"
19+
MESSAGE = "{language} executable found in your path does not " \
20+
"match runtime. " \
21+
"\n Expected version: {required_runtime}, Found version: {found_runtime}. " \
22+
"\n Possibly related: https://github.com/awslabs/aws-lambda-builders/issues/30"
2123

2224

2325
class WorkflowNotFoundError(LambdaBuilderError):

aws_lambda_builders/validate.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ def validate_python_cmd(required_language, required_runtime_version):
1717
"python",
1818
"-c",
1919
"import sys; "
20+
"sys.stdout.write('python' + str(sys.version_info.major) + '.' + str(sys.version_info.minor)); "
2021
"assert sys.version_info.major == {major} "
2122
"and sys.version_info.minor == {minor}".format(
2223
major=major,
@@ -32,7 +33,8 @@ def validate_python_cmd(required_language, required_runtime_version):
3233
class RuntimeValidator(object):
3334
SUPPORTED_RUNTIMES = [
3435
"python2.7",
35-
"python3.6"
36+
"python3.6",
37+
"python3.7",
3638
]
3739

3840
@classmethod
@@ -62,10 +64,11 @@ def validate_runtime(cls, required_language, required_runtime):
6264
p = subprocess.Popen(cmd,
6365
cwd=os.getcwd(),
6466
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
65-
p.communicate()
67+
found_runtime, _ = p.communicate()
6668
if p.returncode != 0:
6769
raise MisMatchRuntimeError(language=required_language,
68-
required_runtime=required_runtime)
70+
required_runtime=required_runtime,
71+
found_runtime=str(found_runtime.decode('utf-8')))
6972
else:
7073
LOG.warning("'%s' runtime has not "
7174
"been validated!", required_language)

aws_lambda_builders/workflows/python_pip/DESIGN.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
This package is an effort to port the Chalice packager to a library that can
66
be used to handle the dependency resilution portion of packaging Python code
77
for use in AWS Lambda. The scope for this builder is to take an existing
8-
directory containing customer code, and a top-levle `requirements.txt` file
8+
directory containing customer code, and a top-level `requirements.txt` file
99
specifying third party depedencies. The builder will examine the dependencies
1010
and use pip to build and include the dependencies in the customer code bundle
1111
in a way that makes them importable.
@@ -146,4 +146,4 @@ bundle has an `__init__.py` and is on the `PYTHONPATH`.
146146

147147
The dependencies should now be succesfully installed in the target directory.
148148
All the temporary/intermediate files can now be deleting including all the
149-
wheel files and sdists.
149+
wheel files and sdists.

aws_lambda_builders/workflows/python_pip/actions.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,14 @@ def __init__(self, artifacts_dir, manifest_path, scratch_dir, runtime):
1717
self.manifest_path = manifest_path
1818
self.scratch_dir = scratch_dir
1919
self.runtime = runtime
20-
self.package_builder = PythonPipDependencyBuilder()
20+
self.package_builder = PythonPipDependencyBuilder(runtime=runtime)
2121

2222
def execute(self):
2323
try:
2424
self.package_builder.build_dependencies(
2525
self.artifacts_dir,
2626
self.manifest_path,
27-
self.scratch_dir,
28-
self.runtime,
27+
self.scratch_dir
2928
)
3029
except PackagerError as ex:
3130
raise ActionFailedError(str(ex))

aws_lambda_builders/workflows/python_pip/compat.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import os
2-
import six
32

43

54
def pip_import_string():
@@ -101,9 +100,3 @@ def raise_compile_error(*args, **kwargs):
101100
pip_no_compile_c_env_vars = {
102101
'CC': '/var/false'
103102
}
104-
105-
106-
if six.PY3:
107-
lambda_abi = 'cp36m'
108-
else:
109-
lambda_abi = 'cp27mu'

aws_lambda_builders/workflows/python_pip/packager.py

Lines changed: 67 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@
55
import sys
66
import re
77
import subprocess
8+
import logging
89
from email.parser import FeedParser
910

1011

11-
from .compat import lambda_abi
1212
from .compat import pip_import_string
1313
from .compat import pip_no_compile_c_env_vars
1414
from .compat import pip_no_compile_c_shim
1515
from .utils import OSUtils
1616

17+
LOG = logging.getLogger(__name__)
18+
1719

1820
# TODO update the wording here
1921
MISSING_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+
5982
class 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

requirements/dev.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ coverage==4.3.4
22
flake8==3.3.0
33
tox==2.2.1
44
pytest-cov==2.4.0
5+
# astroid > 2.0.4 is not compatible with pylint1.7
6+
astroid>=1.5.8,<2.1.0
57
pylint==1.7.2
68

79
# Test requirements

requirements/python_pip.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
# Following packages are required by `python_pip` workflow to run.
3+
# TODO: Consider moving this dependency directly into the `python_pip` workflow module
4+
setuptools
5+
wheel

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def read_version():
5555
'{}=aws_lambda_builders.__main__:main'.format(cmd_name)
5656
]
5757
},
58-
install_requires=read_requirements('base.txt'),
58+
install_requires=read_requirements('base.txt') + read_requirements("python_pip.txt"),
5959
extras_require={
6060
'dev': read_requirements('dev.txt')
6161
},

0 commit comments

Comments
 (0)