diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..1de9478 --- /dev/null +++ b/build.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# build and pack a rust lambda library +# https://aws.amazon.com/blogs/opensource/rust-runtime-for-aws-lambda/ + +set -eo pipefail +mkdir -p target/lambda +export PROFILE=${PROFILE:-release} +export DEBUGINFO=${DEBUGINFO} +# cargo uses different names for target +# of its build profiles +if [[ "${PROFILE}" == "release" ]]; then + TARGET_PROFILE="${PROFILE}" +else + TARGET_PROFILE="debug" +fi +export CARGO_TARGET_DIR=$PWD/target/lambda +( + if [[ $# -gt 0 ]]; then + yum install -y "$@" + fi + # source cargo + . $HOME/.cargo/env + # cargo only supports --release flag for release + # profiles. dev is implicit + if [ "${PROFILE}" == "release" ]; then + cargo build ${CARGO_FLAGS:-} --${PROFILE} + else + cargo build ${CARGO_FLAGS:-} + fi +) 1>&2 + +function package() { + file="$1" + if [[ "${PROFILE}" == "release" ]] && [[ -z "${DEBUGINFO}" ]]; then + objcopy --only-keep-debug "$file" "$file.debug" + objcopy --strip-debug --strip-unneeded "$file" + objcopy --add-gnu-debuglink="$file.debug" "$file" + fi + rm "$file.zip" > 2&>/dev/null || true + # note: would use printf "@ $(basename $file)\n@=bootstrap" | zipnote -w "$file.zip" + # if not for https://bugs.launchpad.net/ubuntu/+source/zip/+bug/519611 + if [ "$file" != ./bootstrap ] && [ "$file" != bootstrap ]; then + mv "${file}" bootstrap + mv "${file}.debug" bootstrap.debug > 2&>/dev/null || true + fi + zip "$file.zip" bootstrap + rm bootstrap +} + +cd "${CARGO_TARGET_DIR}/${TARGET_PROFILE}" +( + . $HOME/.cargo/env + if [ -z "$BIN" ]; then + IFS=$'\n' + for executable in $(cargo metadata --no-deps --format-version=1 | jq -r '.packages[] | .targets[] | select(.kind[] | contains("bin")) | .name'); do + package "$executable" + done + else + package "$BIN" + fi + +) 1>&2 \ No newline at end of file diff --git a/index.js b/index.js index 28865a3..225a936 100644 --- a/index.js +++ b/index.js @@ -4,7 +4,7 @@ // https://serverless.com/framework/docs/providers/aws/guide/plugins/ // https://github.com/softprops/lambda-rust/ -const { spawnSync } = require("child_process"); +const { spawnSync, execSync } = require("child_process"); const { homedir } = require("os"); const path = require("path"); @@ -50,11 +50,49 @@ class RustPlugin { this.serverless.service.package.excludeDevDependencies = false; } - runDocker(funcArgs, cargoPackage, binary, profile) { + compileFunctionBinary({ + funcArgs, + cargoPackage, + binary, + profile, + dockerless + }) { + if (dockerless) { + return this.buildFromShell({ funcArgs, cargoPackage, binary, profile }); + } + return this.buildInDocker({ funcArgs, cargoPackage, binary, profile }); + } + + buildInDocker({ funcArgs, cargoPackage, binary, profile }) { + const dockerTag = (funcArgs || {}).dockerTag || this.custom.dockerTag; + return spawnSync( + "docker", + [ + ...this.getDockerArgs({ funcArgs, cargoPackage, binary, profile }), + `softprops/lambda-rust:${dockerTag}` + ], + NO_OUTPUT_CAPTURE + ); + } + + buildFromShell({ funcArgs, cargoPackage, binary, profile }) { + const buildCommand = [`BIN=${binary}`, `${__dirname}/build.sh`]; + const cargoFlags = this.getCargoFlags(funcArgs, cargoPackage); + if (cargoFlags) { + buildCommand.unshift(`CARGO_FLAGS="${cargoFlags}"`); + } + if (profile) { + buildCommand.unshift(`PROFILE=${profile}`); + } + + return execSync(`${buildCommand.join(" ")}`); + } + + getDockerArgs({ funcArgs, cargoPackage, binary, profile }) { const cargoHome = process.env.CARGO_HOME || path.join(homedir(), ".cargo"); const cargoRegistry = path.join(cargoHome, "registry"); const cargoDownloads = path.join(cargoHome, "git"); - const defaultArgs = [ + const args = [ "run", "--rm", "-t", @@ -67,13 +105,20 @@ class RustPlugin { `-v`, `${cargoDownloads}:/root/.cargo/git` ]; - const customArgs = []; - - let cargoFlags = (funcArgs || {}).cargoFlags || this.custom.cargoFlags; + const cargoFlags = this.getCargoFlags(funcArgs, cargoPackage); + if (cargoFlags) { + // --features awesome-feature, ect + args.push("-e", `CARGO_FLAGS=${cargoFlags}`); + } if (profile) { // release or dev - customArgs.push("-e", `PROFILE=${profile}`); + args.push("-e", `PROFILE=${profile}`); } + return args; + } + + getCargoFlags(funcArgs, cargoPackage) { + let cargoFlags = (funcArgs || {}).cargoFlags || this.custom.cargoFlags; if (cargoPackage != undefined) { if (cargoFlags) { cargoFlags = `${cargoFlags} -p ${cargoPackage}`; @@ -81,16 +126,8 @@ class RustPlugin { cargoFlags = ` -p ${cargoPackage}`; } } - if (cargoFlags) { - // --features awesome-feature, ect - customArgs.push("-e", `CARGO_FLAGS=${cargoFlags}`); - } - const dockerTag = (funcArgs || {}).dockerTag || this.custom.dockerTag; - return spawnSync( - "docker", - [...defaultArgs, ...customArgs, `softprops/lambda-rust:${dockerTag}`], - NO_OUTPUT_CAPTURE - ); + + return cargoFlags; } functions() { @@ -121,10 +158,16 @@ class RustPlugin { } this.serverless.cli.log(`Building native Rust ${func.handler} func...`); let profile = (func.rust || {}).profile || this.custom.profile; - const res = this.runDocker(func.rust, cargoPackage, binary, profile); + const res = this.compileFunctionBinary({ + rust: func.rust, + cargoPackage, + binary, + profile, + dockerless: this.custom.dockerless + }); if (res.error || res.status > 0) { this.serverless.cli.log( - `Dockerized Rust build encountered an error: ${res.error} ${res.status}.` + `Rust build encountered an error: ${res.error} ${res.status}.` ); throw new Error(res.error); } diff --git a/package.json b/package.json index 66d3097..816c0c4 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "files": [ "index.js", "package.json", + "build.sh", "README.md" ] } diff --git a/tests/test.sh b/tests/test.sh index fb455af..52019f1 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -10,7 +10,7 @@ export SILENT=1 source "${HERE}"/bashtest.sh -for project in test-func test-func-dev; do +for project in test-func test-func-dev test-func-dockerless; do cd "${HERE}"/"${project}" echo "👩‍🔬 Running tests for $project"