Skip to content

feat: Rust cargo lambda workflow #350

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 93 commits into from
Feb 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
44a524f
feat: support rust via cargo
softprops May 26, 2020
4099290
make black reformat
softprops May 26, 2020
1d4377b
use os.path.join in path assertion tests to address windows paths
softprops May 27, 2020
ee7ca39
address pylint ci errors
softprops May 28, 2020
cc91826
fix reformatted list comprehensions
softprops May 28, 2020
3a97629
try passing rust-lld linker on windows
softprops May 28, 2020
3be9ba2
update rust cargo design doc. windows support was added and tested
softprops May 28, 2020
5d0e9d2
add test for cargo workspaces project
softprops May 28, 2020
4adb733
reformat test sources
softprops May 28, 2020
ccb7923
build with musl on linux because glibc may differ on lambda
softprops Jun 1, 2020
4a1d43f
update linux copy and bin paths
softprops Jun 2, 2020
c2ea1c4
update make test again, use the version appveyor is complaining about
softprops Jun 26, 2020
7fdab80
update integration tests for rust cargo with latest aws rust runtime …
softprops Jul 1, 2020
692f45b
align TestCustomMakeWorkflow integ test assumptions with appveyor rea…
softprops Jul 2, 2020
1f9864d
make x86_64-unknown-linux-musl a const for the rust cargo workflow
softprops Jul 10, 2020
7566af6
add integ test for failing cargo rust build
softprops Jul 15, 2020
f3cb498
Update Rust workflow to use cargo-lambda
calavera Mar 31, 2022
f100e78
Fix deprecation warnings.
calavera Mar 31, 2022
2966fb6
Install Zig on Windows manually
calavera Apr 16, 2022
28a1a6b
Print Zig version on Windows
calavera Apr 16, 2022
2c28d2a
Update version of cargo-lambda
calavera Apr 19, 2022
8618cfe
Run windows tests in powershell
calavera Apr 22, 2022
9c968d1
Fix powershell env notation
calavera Apr 22, 2022
3f60f46
Print clang version on windows
calavera Apr 22, 2022
e513d57
Fix env variable name
calavera Apr 22, 2022
bab95cb
Try new version of zigbuild that fixes some linker issues on Windows.
calavera May 5, 2022
cc7447c
Update releases URL.
calavera May 5, 2022
3c518d5
Upgrade LLVM and clang
calavera May 17, 2022
1f63ada
Update visual studio image
calavera May 17, 2022
5848c00
Change the visual studio image everywhere.
calavera May 17, 2022
d6c1e85
Revert upgrade changes
calavera May 17, 2022
52ae803
Print environment
calavera Jun 24, 2022
fbe1b93
Update to Visual Studio 2022
calavera Jul 7, 2022
158195d
Fix python variable
calavera Jul 7, 2022
9630fad
Fix package urls
calavera Jul 7, 2022
22a84c8
Update missing vs 2019 reference.
calavera Jul 7, 2022
4d2dd05
Change windows package suffix
calavera Jul 7, 2022
b02dedd
Update cargo-lambda to version 0.9.0
calavera Jul 10, 2022
12acb4c
Go back to the original VS version.
calavera Jul 10, 2022
a91f56e
Cleanup options
calavera Jul 12, 2022
0dfde75
Add integration test with cargo_lambda_flags
calavera Jul 12, 2022
2c58e29
Detect binaries when the project only includes one function.
calavera Jul 12, 2022
ecca29b
Bring back handler as an optional argument.
calavera Jul 12, 2022
f48f1bf
Ignore errors if directory doesn't exist.
calavera Jul 12, 2022
abe6c3a
Add debug logs
calavera Jul 13, 2022
05a6cd9
Log the artifact destination path.
calavera Jul 13, 2022
f363a9c
Add missing comma
calavera Jul 13, 2022
88c6728
Print out and err in the log when debug is enabled
calavera Jul 13, 2022
61342f9
Only set RUST_LOG when it's not already set
calavera Jul 14, 2022
bcb6ff8
Add experimentalCargoLambda feature flag.
calavera Sep 3, 2022
b624579
Add integration test for multi-function projects.
calavera Sep 3, 2022
ab39fa5
Remove type hints
calavera Sep 3, 2022
52caab0
Add new CI steps for GHA
calavera Jan 13, 2023
b408412
Add build_in_source_support
calavera Jan 13, 2023
8f540c9
Test rust logger
calavera Jan 13, 2023
97563d3
Fix assertion
calavera Jan 13, 2023
44b3742
Don't fail fast
calavera Jan 13, 2023
b442bd7
fix: Fix failing esbuild integration tests (#423)
mildaniel Jan 14, 2023
943c9c7
Remove default shell
calavera Jan 19, 2023
b262ccd
Revert "Remove default shell"
calavera Jan 19, 2023
9df26cb
Add check to ensure that Cargo Lambda is installed.
calavera Jan 19, 2023
27513a5
feat: Add support for mjs files with esbuild (#427)
mildaniel Jan 17, 2023
7b8aecd
Test Cargo Lambda check
calavera Jan 19, 2023
e327c27
Don't redefine which
calavera Jan 19, 2023
0266ddb
Organize code in more modules
calavera Jan 20, 2023
07a2487
Add more documentation
calavera Jan 20, 2023
c553d5e
Format code
calavera Jan 20, 2023
da5de28
Capture exception
calavera Jan 20, 2023
2d6ab07
chore: Remove type/bug label for Bug Issue Template (#425)
jfuss Jan 20, 2023
3dbf9d7
Clean design doc and tests
calavera Jan 20, 2023
39da946
chore: Version bump to 1.25.0 (#429)
mildaniel Jan 25, 2023
5e642a8
refactor: assign each workflow a default build directory (#428)
torresxb1 Jan 27, 2023
e119447
fix: remove unused symlinking (#432)
torresxb1 Jan 28, 2023
3539bbb
chore: Move to ruff from pylint (#435)
jfuss Jan 31, 2023
07edae9
chore: Enable pylint within ruff (#436)
jfuss Feb 1, 2023
42f315b
refactor: esbuild refactor for readability (#433)
torresxb1 Feb 2, 2023
65efcd8
feat: use build_dir in esbuild workflow to support building in source…
torresxb1 Feb 3, 2023
9b87138
Fix formatting
calavera Feb 6, 2023
c1ae732
Update build in source settings
calavera Feb 6, 2023
111ea79
fix: remove python3.6 support (#434)
mndeveci Feb 6, 2023
2b5d728
chore: bump version to 1.26.0 (#441)
mndeveci Feb 6, 2023
53a36a7
Remove default value for subprocess_cargo_lambda
calavera Feb 7, 2023
def9544
feat: Pin ruff version, add dependabot config (#442)
mildaniel Feb 7, 2023
7d697ca
feat: Add sources content flag to supported esbuild options (#439)
mildaniel Feb 7, 2023
036bd44
Better process management
calavera Feb 8, 2023
bcbe142
Make release mode the default
calavera Feb 8, 2023
c947f6c
Remove already default None
calavera Feb 8, 2023
64cc813
Turn debug message into warning
calavera Feb 8, 2023
e015be0
Update documentation format
calavera Feb 8, 2023
2237d76
Fix formatting
calavera Feb 8, 2023
e591484
Merge branch 'develop' into rust-cargo-lambda-workflow
calavera Feb 8, 2023
0accf64
Preserve binary permissions
calavera Feb 8, 2023
a5c720a
Merge branch 'develop' into rust-cargo-lambda-workflow
calavera Feb 9, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 57 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
- python-integration
- ruby-integration
- dotnet-integration
- rust-cargo-lambda-integration
steps:
- name: report-failure
if: |
Expand All @@ -33,7 +34,8 @@ jobs:
needs.custom-make-integration.result != 'success' ||
needs.python-integration.result != 'success' ||
needs.ruby-integration.result != 'success' ||
needs.dotnet-integration.result != 'success'
needs.dotnet-integration.result != 'success' ||
needs.rust-cargo-lambda-integration.result != 'success'
run: exit 1
- name: report-success
run: exit 0
Expand Down Expand Up @@ -296,3 +298,57 @@ jobs:
python-version: ${{ matrix.python }}
- run: make init
- run: pytest -vv tests/integration/workflows/dotnet_clipackage

rust-cargo-lambda-integration:
name: ${{ matrix.os }} / ${{ matrix.python }} / rust-cargo-lambda
if: github.repository_owner == 'aws'
runs-on: ${{ matrix.os }}
env:
CARGO_LAMBDA_VERSION: 0.15.0
defaults:
run:
shell: bash
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
python:
- "3.9"
- "3.8"
- "3.7"
rust:
- stable
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}

# Install and configure Rust
- name: Install rustup
run: |
: install rustup if needed
if ! command -v rustup &> /dev/null ; then
curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused -fsSL "https://sh.rustup.rs" | sh -s -- --default-toolchain none -y
echo "${CARGO_HOME:-$HOME/.cargo}/bin" >> $GITHUB_PATH
fi
if: ${{ matrix.os }} == 'ubuntu-latest'
- name: rustup toolchain install ${{ matrix.rust }}
run: rustup toolchain install ${{ matrix.rust }} --profile minimal --no-self-update
- run: rustup default ${{ matrix.rust }}
- run: |
: disable incremental compilation
echo CARGO_INCREMENTAL=0 >> $GITHUB_ENV
- run: |
: enable colors in Cargo output
echo CARGO_TERM_COLOR=always >> $GITHUB_ENV

# Install and configure Cargo Lambda
- name: Install Cargo Lambda
run: pip install cargo-lambda==$CARGO_LAMBDA_VERSION
- run: echo "$HOME/.local/bin" >> $GITHUB_PATH

- run: make init
- run: pytest -vv tests/integration/workflows/rust_cargo
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ init:
LAMBDA_BUILDERS_DEV=1 pip install -e '.[dev]'

test:
# Run unit tests
# Run unit and functional tests
# Fail if coverage falls below 94%
LAMBDA_BUILDERS_DEV=1 pytest -vv --cov aws_lambda_builders --cov-report term-missing --cov-fail-under 94 tests/unit tests/functional

unit-test:
LAMBDA_BUILDERS_DEV=1 pytest tests/unit

func-test:
LAMBDA_BUILDERS_DEV=1 pytest tests/functional

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Lambda Builders currently contains the following workflows
* Typescript with esbuild
* Ruby with Bundler
* Go with Mod
* Rust with Cargo

In Addition to above workflows, AWS Lambda Builders also supports *Custom Workflows* through a Makefile.

Expand Down
1 change: 1 addition & 0 deletions aws_lambda_builders/workflows/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
import aws_lambda_builders.workflows.dotnet_clipackage
import aws_lambda_builders.workflows.custom_make
import aws_lambda_builders.workflows.nodejs_npm_esbuild
import aws_lambda_builders.workflows.rust_cargo
28 changes: 28 additions & 0 deletions aws_lambda_builders/workflows/rust_cargo/DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Rust Cargo Builder

## Scope

This package enables the creation of a Lambda deployment package for Rust projects managed using the [cargo](https://doc.rust-lang.org/cargo/) build tool targeting Lambda's "provided" runtime. Rust support for the provided runtime is bundled as a compilation dependency of these projects, provided by the [lambda](https://github.com/awslabs/aws-lambda-rust-runtime) crate.

## Implementation

This package uses [Cargo Lambda](https://www.cargo-lambda.info) to do all the heavy lifting for cross compilation, target validation, and other executable optimizations.

It supports X86-64 architectures with the target `x86_64-unknown-linux-gnu` by default. It also supports ARM architectures with the target option `aarch64-unknown-linux-gnu`. Those are the only two valid targets. The target is automatically configured based on the `architecture` option in the `RustCargoLambdaWorkflow`.

The general algorithm for preparing a rust executable for use on AWS Lambda is as follows.

### Build

It builds a binary in the standard cargo target directory. The binary's name is always `bootstrap`, and it's always located under `target/lambda/HANDLER_NAME/bootstrap`.

### Copy and Rename executable

It then copies the executable to the target directory honoring the provided runtime's [expectation on executable names](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html).

## Notes

Like the go builders, the workflow argument `options.artifact_executable_name`
interface can used to provide a handler name that resolves to an executable. This
enables sam support for cargo workspaces allowing for one rust project to have multiple lambdas. Cargo workspaces have a notion of a `package` and `bin`. A `package` can have
multiple bins but typically `packages` have a 1-to-1 relationship with a default `bin`: `main.rs`. The handler names must be uniques across a Rust project, regardless of how many packages and binaries that project includes.
5 changes: 5 additions & 0 deletions aws_lambda_builders/workflows/rust_cargo/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
Builds Rust Lambda functions using Cargo Lambda
"""

from .workflow import RustCargoLambdaWorkflow
146 changes: 146 additions & 0 deletions aws_lambda_builders/workflows/rust_cargo/actions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
"""
Rust Cargo build actions
"""

import logging
import os

from aws_lambda_builders.workflow import BuildMode
from aws_lambda_builders.actions import ActionFailedError, BaseAction, Purpose
from aws_lambda_builders.architecture import X86_64, ARM64
from .exceptions import CargoLambdaExecutionException
from .utils import OSUtils


LOG = logging.getLogger(__name__)


class RustCargoLambdaBuildAction(BaseAction):
NAME = "CargoLambdaBuild"
DESCRIPTION = "Building the project using Cargo Lambda"
PURPOSE = Purpose.COMPILE_SOURCE

def __init__(
self,
source_dir,
binaries,
mode,
subprocess_cargo_lambda,
architecture=X86_64,
handler=None,
flags=None,
):
"""
Build the a Rust executable

:type source_dir: str
:param source_dir:
Path to a folder containing the source code

:type binaries: dict
:param binaries:
Resolved path dependencies

:type mode: str
:param mode:
Mode the build should produce

:type architecture: str, optional
:param architecture:
Target architecture to build the binary, either arm64 or x86_64

:type handler: str, optional
:param handler:
Handler name in `bin_name` format

:type flags: list, optional
:param flags:
Extra list of flags to pass to `cargo lambda build`

:type subprocess_cargo_lambda: aws_lambda_builders.workflows.rust_cargo.cargo_lambda.SubprocessCargoLambda
:param subprocess_cargo_lambda: An instance of the Cargo Lambda process wrapper
"""

self._source_dir = source_dir
self._mode = mode
self._binaries = binaries
self._handler = handler
self._flags = flags
self._architecture = architecture
self._subprocess_cargo_lambda = subprocess_cargo_lambda

def build_command(self):
cmd = [self._binaries["cargo"].binary_path, "lambda", "build"]
if self._mode != BuildMode.DEBUG:
cmd.append("--release")
if self._architecture == ARM64:
cmd.append("--arm64")
if self._handler:
cmd.extend(["--bin", self._handler])
if self._flags:
cmd.extend(self._flags)

return cmd

def execute(self):
try:
return self._subprocess_cargo_lambda.run(command=self.build_command(), cwd=self._source_dir)
except CargoLambdaExecutionException as ex:
raise ActionFailedError(str(ex))


class RustCopyAndRenameAction(BaseAction):
NAME = "RustCopyAndRename"
DESCRIPTION = "Copy Rust executable, renaming if needed"
PURPOSE = Purpose.COPY_SOURCE

def __init__(self, source_dir, artifacts_dir, handler=None, osutils=OSUtils()):
"""
Copy and rename Rust executable

Parameters
----------
source_dir : str
Path to a folder containing the source code

artifacts_dir : str
Path to a folder containing the deployable artifacts

handler : str, optional
Handler name in `package.bin_name` or `bin_name` format

osutils : aws_lambda_builders.workflows.rust_cargo.utils.OSUtils, optional
Optional, External IO utils
"""
self._source_dir = source_dir
self._handler = handler
self._artifacts_dir = artifacts_dir
self._osutils = osutils

def base_path(self):
return os.path.join(self._source_dir, "target", "lambda")

def binary_path(self):
base = self.base_path()
if self._handler:
binary_path = os.path.join(base, self._handler, "bootstrap")
LOG.debug("copying function binary from %s", binary_path)
return binary_path

output = os.listdir(base)
if len(output) == 1:
binary_path = os.path.join(base, output[0], "bootstrap")
LOG.debug("copying function binary from %s", binary_path)
return binary_path

LOG.warning("unexpected list of binary directories: [%s]", ", ".join(output))
raise CargoLambdaExecutionException(
message="unable to find function binary, use the option `artifact_executable_name` to specify the name"
)

def execute(self):
self._osutils.makedirs(self._artifacts_dir)
binary_path = self.binary_path()
destination_path = os.path.join(self._artifacts_dir, "bootstrap")
LOG.debug("copying function binary from %s to %s", binary_path, destination_path)
self._osutils.copyfile(binary_path, destination_path)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did some integration tests and found that this doesn't copy the permission bits of the executable. We should use copy2 instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching that @hawflau ! I've updated the code to use copy2.

Loading