diff --git a/index.js b/index.js index 58f330b..23ac35e 100644 --- a/index.js +++ b/index.js @@ -47,9 +47,10 @@ class RustPlugin { dockerTag: DEFAULT_DOCKER_TAG, dockerImage: DEFAULT_DOCKER_IMAGE, dockerless: false, + strictMode: true, }, (this.serverless.service.custom && this.serverless.service.custom.rust) || - {} + {}, ); // Docker can't access resources outside of the current build directory. @@ -74,15 +75,13 @@ class RustPlugin { "" ).split(/\s+/); + let target = (funcArgs || {}).target || this.custom.target; - let target = (funcArgs || {}).target || this.custom.target - - const targetArgs = - target ? - ['--target', target] - : MUSL_PLATFORMS.includes(platform) - ? ["--target", "x86_64-unknown-linux-musl"] - : []; + const targetArgs = target + ? ["--target", target] + : MUSL_PLATFORMS.includes(platform) + ? ["--target", "x86_64-unknown-linux-musl"] + : []; return [ ...defaultArgs, ...profileArgs, @@ -94,30 +93,29 @@ class RustPlugin { localBuildEnv(funcArgs, env, platform) { const defaultEnv = { ...env }; - let target = (funcArgs || {}).target || this.custom.target - let linker = (funcArgs || {}).linker || this.custom.linker + let target = (funcArgs || {}).target || this.custom.target; + let linker = (funcArgs || {}).linker || this.custom.linker; - const platformEnv = - linker ? - { + const platformEnv = linker + ? { RUSTFLAGS: (env["RUSTFLAGS"] || "") + ` -Clinker=${linker}`, TARGET_CC: linker, - [`CC_${target || 'x86_64_unknown_linux_musl'}`]: linker, + [`CC_${target || "x86_64_unknown_linux_musl"}`]: linker, } - : "win32" === platform + : "win32" === platform + ? { + RUSTFLAGS: (env["RUSTFLAGS"] || "") + " -Clinker=rust-lld", + TARGET_CC: "rust-lld", + CC_x86_64_unknown_linux_musl: "rust-lld", + } + : "darwin" === platform ? { - RUSTFLAGS: (env["RUSTFLAGS"] || "") + " -Clinker=rust-lld", - TARGET_CC: "rust-lld", - CC_x86_64_unknown_linux_musl: "rust-lld", + RUSTFLAGS: + (env["RUSTFLAGS"] || "") + " -Clinker=x86_64-linux-musl-gcc", + TARGET_CC: "x86_64-linux-musl-gcc", + CC_x86_64_unknown_linux_musl: "x86_64-linux-musl-gcc", } - : "darwin" === platform - ? { - RUSTFLAGS: - (env["RUSTFLAGS"] || "") + " -Clinker=x86_64-linux-musl-gcc", - TARGET_CC: "x86_64-linux-musl-gcc", - CC_x86_64_unknown_linux_musl: "x86_64-linux-musl-gcc", - } - : {}; + : {}; return { ...defaultEnv, ...platformEnv, @@ -127,8 +125,11 @@ class RustPlugin { localSourceDir(funcArgs, profile, platform) { let executable = "target"; if (MUSL_PLATFORMS.includes(platform)) { - let target = (funcArgs || {}).target || this.custom.target - executable = path.join(executable, target ? target : "x86_64-unknown-linux-musl"); + let target = (funcArgs || {}).target || this.custom.target; + executable = path.join( + executable, + target ? target : "x86_64-unknown-linux-musl", + ); } return path.join(executable, profile !== "dev" ? "release" : "debug"); } @@ -137,7 +138,7 @@ class RustPlugin { return path.join( "target", "lambda", - profile !== "dev" ? "release" : "debug" + profile !== "dev" ? "release" : "debug", ); } @@ -147,7 +148,7 @@ class RustPlugin { cargoPackage, binary, profile, - platform() + platform(), ); const env = this.localBuildEnv(funcArgs, process.env, platform()); @@ -169,7 +170,7 @@ class RustPlugin { "bootstrap", readFileSync(path.join(sourceDir, binary)), "", - 0o755 + 0o755, ); const targetDir = this.localArtifactDir(profile); try { @@ -195,7 +196,7 @@ class RustPlugin { srcPath, cargoRegistry, cargoDownloads, - env + env, ) { const defaultArgs = [ "run", @@ -251,7 +252,7 @@ class RustPlugin { this.srcPath, cargoRegistry, cargoDownloads, - process.env + process.env, ); this.serverless.cli.log("Running containerized build"); @@ -281,6 +282,7 @@ class RustPlugin { /** the entry point for building functions */ build() { + const strictMode = this.custom.strictMode !== false; const service = this.serverless.service; if (service.provider.name != "aws") { return; @@ -289,55 +291,65 @@ class RustPlugin { this.functions().forEach((funcName) => { const func = service.getFunction(funcName); const runtime = func.runtime || service.provider.runtime; - if (runtime != RUST_RUNTIME) { + + func.tags = func.tags || {}; + if (!(runtime === RUST_RUNTIME || func.tags.language === "rust")) { // skip functions which don't apply to rust return; } rustFunctionsFound = true; - const { cargoPackage, binary } = this.cargoBinary(func); - this.serverless.cli.log(`Building Rust ${func.handler} func...`); - let profile = (func.rust || {}).profile || this.custom.profile; - - const res = this.buildLocally(func) - ? this.localBuild(func.rust, cargoPackage, binary, profile) - : this.dockerBuild(func.rust, cargoPackage, binary, profile); - if (res.error || res.status > 0) { + func.package = func.package || {}; + if (func.package.artifact && func.package.artifact !== "") { this.serverless.cli.log( - `Rust build encountered an error: ${res.error} ${res.status}.` + `Artifact defined for ${func.handler}, skipping build...`, ); - throw new Error(res.error); - } - // If all went well, we should now have find a packaged compiled binary under `target/lambda/release`. - // - // The AWS "provided" lambda runtime requires executables to be named - // "bootstrap" -- https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html - // - // To avoid artifact naming conflicts when we potentially have more than one function - // we leverage the ability to declare a package artifact directly - // see https://serverless.com/framework/docs/providers/aws/guide/packaging/ - // for more information - const artifactPath = path.join( - this.srcPath, - `target/lambda/${"dev" === profile ? "debug" : "release"}`, - `${binary}.zip` - ); - func.package = func.package || {}; - func.package.artifact = artifactPath; + } else { + const { cargoPackage, binary } = this.cargoBinary(func); + + this.serverless.cli.log(`Building Rust ${func.handler} func...`); + let profile = (func.rust || {}).profile || this.custom.profile; + + const res = this.buildLocally(func) + ? this.localBuild(func.rust, cargoPackage, binary, profile) + : this.dockerBuild(func.rust, cargoPackage, binary, profile); + if (res.error || res.status > 0) { + this.serverless.cli.log( + `Rust build encountered an error: ${res.error} ${res.status}.`, + ); + throw new Error(res.error); + } + // If all went well, we should now have find a packaged compiled binary under `target/lambda/release`. + // + // The AWS "provided" lambda runtime requires executables to be named + // "bootstrap" -- https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html + // + // To avoid artifact naming conflicts when we potentially have more than one function + // we leverage the ability to declare a package artifact directly + // see https://serverless.com/framework/docs/providers/aws/guide/packaging/ + // for more information + const artifactPath = path.join( + this.srcPath, + `target/lambda/${"dev" === profile ? "debug" : "release"}`, + `${binary}.zip`, + ); + func.package = func.package || {}; + func.package.artifact = artifactPath; - // Ensure the runtime is set to a sane value for other plugins - if (func.runtime == RUST_RUNTIME) { - func.runtime = BASE_RUNTIME; + // Ensure the runtime is set to a sane value for other plugins + if (func.runtime == RUST_RUNTIME) { + func.runtime = BASE_RUNTIME; + } } }); if (service.provider.runtime === RUST_RUNTIME) { service.provider.runtime = BASE_RUNTIME; } - if (!rustFunctionsFound) { + if (!rustFunctionsFound && strictMode) { throw new Error( `Error: no Rust functions found. ` + `Use 'runtime: ${RUST_RUNTIME}' in global or ` + - `function configuration to use this plugin.` + `function configuration to use this plugin.`, ); } } diff --git a/tests/unit/index.test.js b/tests/unit/index.test.js index 28714cd..a3ff5b7 100644 --- a/tests/unit/index.test.js +++ b/tests/unit/index.test.js @@ -13,13 +13,14 @@ describe("RustPlugin", () => { dockerImage: "notsoftprops/lambda-rust", dockerTag: "latest", dockerless: true, + strictMode: true, }, }, package: {}, }, config: {}, }, - {} + {}, ); it("registers expected lifecycle hooks", () => { @@ -33,13 +34,14 @@ describe("RustPlugin", () => { it("sets sensible defaults", () => { const unconfigured = new RustPlugin( { version: "1.71.3", service: { package: {} }, config: {} }, - {} + {}, ); assert.deepEqual(unconfigured.custom, { cargoFlags: "", dockerImage: "softprops/lambda-rust", dockerTag: "latest", dockerless: false, + strictMode: true, }); }); @@ -54,19 +56,21 @@ describe("RustPlugin", () => { dockerImage: "notsoftprops/lambda-rust", dockerTag: "custom-tag", dockerless: true, + strictMode: false, }, }, package: {}, }, config: {}, }, - {} + {}, ); assert.deepEqual(configured.custom, { cargoFlags: "--features foo", dockerImage: "notsoftprops/lambda-rust", dockerTag: "custom-tag", dockerless: true, + strictMode: false, }); }); @@ -95,7 +99,7 @@ describe("RustPlugin", () => { "--features", "foo", ], - "failed on linux" + "failed on linux", ); assert.deepEqual( plugin.localBuildArgs({}, "foo", "bar", "release", "darwin"), @@ -109,7 +113,7 @@ describe("RustPlugin", () => { "--features", "foo", ], - "failed on osx" + "failed on osx", ); assert.deepEqual( plugin.localBuildArgs({}, "foo", "bar", "release", "win32"), @@ -123,63 +127,67 @@ describe("RustPlugin", () => { "--features", "foo", ], - "failed on windows" + "failed on windows", ); }); it("configures expected localBuildEnv", () => { - assert.deepEqual(plugin.localBuildEnv({}, "linux"), {}, "failed on linux"); assert.deepEqual( - plugin.localBuildEnv({}, "darwin"), + plugin.localBuildEnv({}, {}, "linux"), + {}, + "failed on linux", + ); + assert.deepEqual( + plugin.localBuildEnv({}, {}, "darwin"), { CC_x86_64_unknown_linux_musl: "x86_64-linux-musl-gcc", RUSTFLAGS: " -Clinker=x86_64-linux-musl-gcc", TARGET_CC: "x86_64-linux-musl-gcc", }, - "failed on osx" + "failed on osx", ); assert.deepEqual( - plugin.localBuildEnv({}, "win32"), + plugin.localBuildEnv({}, {}, "win32"), { CC_x86_64_unknown_linux_musl: "rust-lld", RUSTFLAGS: " -Clinker=rust-lld", TARGET_CC: "rust-lld", }, - "failed on windows" + "failed on windows", ); }); it("configures expected localSourceDir", () => { assert.equal( - plugin.localSourceDir("dev", "linux"), + plugin.localSourceDir({}, "dev", "linux"), path.join("target", "x86_64-unknown-linux-musl", "debug"), - "failed on linux" + "failed on linux", ); assert.equal( - plugin.localSourceDir("release", "linux"), + plugin.localSourceDir({}, "release", "linux"), path.join("target", "x86_64-unknown-linux-musl", "release"), - "failed on linux" + "failed on linux", ); assert.equal( - plugin.localSourceDir("dev", "darwin"), + plugin.localSourceDir({}, "dev", "darwin"), path.join("target", "x86_64-unknown-linux-musl", "debug"), - "failed on osx" + "failed on osx", ); assert.equal( - plugin.localSourceDir("release", "darwin"), + plugin.localSourceDir({}, "release", "darwin"), path.join("target", "x86_64-unknown-linux-musl", "release"), - "failed on osx" + "failed on osx", ); assert.equal( - plugin.localSourceDir("dev", "win32"), + plugin.localSourceDir({}, "dev", "win32"), path.join("target", "x86_64-unknown-linux-musl", "debug"), - "failed on windows" + "failed on windows", ); assert.equal( - plugin.localSourceDir("release", "win32"), + plugin.localSourceDir({}, "release", "win32"), path.join("target", "x86_64-unknown-linux-musl", "release"), - "failed on windows" + "failed on windows", ); }); @@ -187,12 +195,12 @@ describe("RustPlugin", () => { assert.equal( plugin.localArtifactDir("dev"), path.join("target", "lambda", "debug"), - "failed on linux" + "failed on linux", ); assert.equal( plugin.localArtifactDir("release"), path.join("target", "lambda", "release"), - "failed on linux" + "failed on linux", ); }); @@ -213,7 +221,7 @@ describe("RustPlugin", () => { }, config: {}, }, - {} + {}, ); assert(dockerless.buildLocally({})); @@ -230,7 +238,7 @@ describe("RustPlugin", () => { "source_path", "cargo_registry", "cargo_downloads", - {} + {}, ), [ "run", @@ -249,7 +257,7 @@ describe("RustPlugin", () => { "-e", "CARGO_FLAGS=--features foo -p foo", "notsoftprops/lambda-rust:latest", - ] + ], ); }); });