diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8da4ec9ef3..29c3860499 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: - run: | pip install virtualenv - make dist + make aws-lambda-layer-build - uses: actions/upload-artifact@v2 with: diff --git a/Makefile b/Makefile index 29c2886671..4fac8eca5a 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ help: @echo "make test: Run basic tests (not testing most integrations)" @echo "make test-all: Run ALL tests (slow, closest to CI)" @echo "make format: Run code formatters (destructive)" + @echo "make aws-lambda-layer-build: Build serverless ZIP dist package" @echo @echo "Also make sure to read ./CONTRIBUTING.md" @false @@ -58,3 +59,7 @@ apidocs-hotfix: apidocs @$(VENV_PATH)/bin/pip install ghp-import @$(VENV_PATH)/bin/ghp-import -pf docs/_build .PHONY: apidocs-hotfix + +aws-lambda-layer-build: dist + $(VENV_PATH)/bin/python -m scripts.build-awslambda-layer +.PHONY: aws-lambda-layer-build diff --git a/scripts/build-awslambda-layer.py b/scripts/build-awslambda-layer.py new file mode 100644 index 0000000000..7cbfb1cb5f --- /dev/null +++ b/scripts/build-awslambda-layer.py @@ -0,0 +1,71 @@ +import os +import subprocess +import tempfile +import shutil +from sentry_sdk.consts import VERSION as SDK_VERSION + + +DIST_DIRNAME = "dist" +DIST_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", DIST_DIRNAME)) +DEST_ZIP_FILENAME = f"sentry-python-serverless-{SDK_VERSION}.zip" +WHEELS_FILEPATH = os.path.join( + DIST_DIRNAME, f"sentry_sdk-{SDK_VERSION}-py2.py3-none-any.whl" +) + +# Top directory in the ZIP file. Placing the Sentry package in `/python` avoids +# creating a directory for a specific version. For more information, see +# https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html#configuration-layers-path +PACKAGE_PARENT_DIRECTORY = "python" + + +class PackageBuilder: + def __init__(self, base_dir) -> None: + self.base_dir = base_dir + self.packages_dir = self.get_relative_path_of(PACKAGE_PARENT_DIRECTORY) + + def make_directories(self): + os.makedirs(self.packages_dir) + + def install_python_binaries(self): + subprocess.run( + [ + "pip", + "install", + "--no-cache-dir", # Disables the cache -> always accesses PyPI + "-q", # Quiet + WHEELS_FILEPATH, # Copied to the target directory before installation + "-t", # Target directory flag + self.packages_dir, + ], + check=True, + ) + + def zip(self, filename): + subprocess.run( + [ + "zip", + "-q", # Quiet + "-x", # Exclude files + "**/__pycache__/*", # Files to be excluded + "-r", # Recurse paths + filename, # Output filename + PACKAGE_PARENT_DIRECTORY, # Files to be zipped + ], + cwd=self.base_dir, + check=True, # Raises CalledProcessError if exit status is non-zero + ) + + def get_relative_path_of(self, subfile): + return os.path.join(self.base_dir, subfile) + + +def build_packaged_zip(): + with tempfile.TemporaryDirectory() as tmp_dir: + package_builder = PackageBuilder(tmp_dir) + package_builder.make_directories() + package_builder.install_python_binaries() + package_builder.zip(DEST_ZIP_FILENAME) + shutil.copy(package_builder.get_relative_path_of(DEST_ZIP_FILENAME), DIST_DIR) + + +build_packaged_zip()