Cross-compiling Rust Lambdas on macOS without Docker
The standard way of developing Lambda functions in Rust is to use a custom Lambda runtime provided by AWS and cross-compile everything – one binary per function – before deployment.
The runtime documentation now (finally!) contains a section called “Building for Amazon Linux 2” that explains a more recent way of cross-compiling Lambdas using Tier 1 targets without resorting to musl.
For example, if you prefer arm64 over x86_64 as I do, you typically run these commands to build Lambdas for that architecture:
# Add ARM64 target platform once
rustup target add aarch64-unknown-linux-gnu
# Build Lambda functions
cargo build --target aarch64-unknown-linux-gnu --release
Easy, right? Unfortunately, that’s not the end of the story, not on macOS anyway.
As it happens, rustup target add
only installs the Rust standard library for a given target. You still need a linker for the target platform and a cross compiler for crates that include C/C++ code.1
As luck would have it, you can get both for macOS by installing one of the cross compiler toolchains provided by this lovely project:
brew tap messense/macos-cross-toolchains
brew install aarch64-unknown-linux-gnu
Afterward, set these variables in your environment (e.g. in bashrc):
export CC_aarch64_unknown_linux_gnu=aarch64-unknown-linux-gnu-gcc
export CXX_aarch64_unknown_linux_gnu=aarch64-unknown-linux-gnu-g++
export AR_aarch64_unknown_linux_gnu=aarch64-unknown-linux-gnu-ar
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-unknown-linux-gnu-gcc
Now cargo build --target aarch64-unknown-linux-gnu
will work as expected on macOS and produce binaries ready to be deployed to AWS Lambda – all without Docker.
Update (Oct 2022): The same developer who has brought us the mentioned toolchains has another highly recommended project called cargo-zigbuild that makes cross-compiling even simpler.
One more tip before wrapping up: the easiest way to avoid any OpenSSL cross-compile issues under macOS is not using it in the first place. Use rustls instead. Most packages have a feature flag for it. Here’s an example from dilbert-feed:
# Cargo.toml
[dependencies]
aws-sdk-s3 = { version = "0.2", features = ["rustls"] }
reqwest = { version = "0.11", default-features = false, features = ["rustls-tls"] }
-
This might be surprising to Go programmers who are used to
GOOS=... GOARCH=... go build
working out of the box. ↩