diff --git a/Plugins/AWSLambdaBuilder/Plugin.swift b/Plugins/AWSLambdaBuilder/Plugin.swift index 9e90c8b3..d64c6ed3 100644 --- a/Plugins/AWSLambdaBuilder/Plugin.swift +++ b/Plugins/AWSLambdaBuilder/Plugin.swift @@ -21,43 +21,42 @@ struct AWSLambdaBuilder: CommandPlugin { func performCommand(context: PackagePlugin.PluginContext, arguments: [String]) async throws { - // values to pass to the AWSLambdaPluginHelper - let outputDirectory: URL - let products: [Product] - let buildConfiguration: PackageManager.BuildConfiguration - let packageID: String = context.package.id - let packageDisplayName = context.package.displayName - let packageDirectory = context.package.directoryURL - let zipToolPath = try context.tool(named: "zip").url - - // extract arguments that require PluginContext to fully resolve - // resolve them here and pass them to the AWSLambdaPluginHelper as arguments + // This plugin is a thin layer over the AWSLambdaPluginHelper executable. It resolves only + // the values that require the PackagePlugin sandbox or the package graph, injects them as + // canonical arguments, then forwards every argument it did not consume to the helper. The + // helper owns the remaining argument parsing and defaulting. var argumentExtractor = ArgumentExtractor(arguments) + // Options the plugin resolves itself. These are consumed (extracted) here so they are not + // also forwarded via remainingArguments, which would pass them to the helper twice. let outputPathArgument = argumentExtractor.extractOption(named: "output-path") let productsArgument = argumentExtractor.extractOption(named: "products") + // The helper requires --configuration; the plugin supplies the default. Validation of the + // value itself is left to the helper. let configurationArgument = argumentExtractor.extractOption(named: "configuration") - - // Resolve the container CLI that matches the requested cross-compilation method. - // The plugin sandbox can only run tools it resolves up front, so we must pick the right - // binary here — `container` for `--cross-compile container`, `docker` otherwise. Extracting - // these options only peeks them for the plugin; the original `arguments` (which the helper - // re-parses) is still forwarded unchanged below. // `--container-cli` is a deprecated alias for `--cross-compile`. let crossCompileArgument = argumentExtractor.extractOption(named: "cross-compile") let containerCliArgument = argumentExtractor.extractOption(named: "container-cli") + + // Resolve the container CLI that matches the requested cross-compilation method. The plugin + // sandbox can only run tools it resolves up front, so we must pick the right binary here: + // `container` for `--cross-compile container`, `docker` otherwise. let crossCompileMethod = (crossCompileArgument.first ?? containerCliArgument.first)?.lowercased() let containerCLIToolName = crossCompileMethod == "container" ? "container" : "docker" let containerToolPath = try context.tool(named: containerCLIToolName).url + let zipToolPath = try context.tool(named: "zip").url + // Resolve the output directory. The default lives under the plugin's work directory, whose + // location is only known to the plugin. This path is part of the plugin's public contract + // (documented and consumed by lambda-deploy), so it must stay stable. + let outputDirectory: URL if let outputPath = outputPathArgument.first { #if os(Linux) var isDirectory: Bool = false #else var isDirectory: ObjCBool = false #endif - guard FileManager.default.fileExists(atPath: outputPath, isDirectory: &isDirectory) - else { + guard FileManager.default.fileExists(atPath: outputPath, isDirectory: &isDirectory) else { throw BuilderErrors.invalidArgument("invalid output directory '\(outputPath)'") } outputDirectory = URL(fileURLWithPath: outputPath) @@ -65,42 +64,35 @@ struct AWSLambdaBuilder: CommandPlugin { outputDirectory = context.pluginWorkDirectoryURL.appending(path: "\(AWSLambdaBuilder.self)") } - let explicitProducts = !productsArgument.isEmpty - if explicitProducts { - let _products = try context.package.products(named: productsArgument) - for product in _products { - guard product is ExecutableProduct else { - throw BuilderErrors.invalidArgument("product named '\(product.name)' is not an executable product") - } - } - products = _products - - } else { + // Resolve and validate the products against the package graph. + let products: [Product] + if productsArgument.isEmpty { products = context.package.products.filter { $0 is ExecutableProduct } - } - - if let _buildConfigurationName = configurationArgument.first { - guard let _buildConfiguration = PackageManager.BuildConfiguration(rawValue: _buildConfigurationName) else { - throw BuilderErrors.invalidArgument("invalid build configuration named '\(_buildConfigurationName)'") - } - buildConfiguration = _buildConfiguration } else { - buildConfiguration = .release + products = try context.package.products(named: productsArgument) + for product in products where !(product is ExecutableProduct) { + throw BuilderErrors.invalidArgument("product named '\(product.name)' is not an executable product") + } } let tool = try context.tool(named: "AWSLambdaPluginHelper") - let args = - [ - "build", - "--output-path", outputDirectory.path(), - "--products", products.map { $0.name }.joined(separator: ","), - "--configuration", buildConfiguration.rawValue, - "--package-id", packageID, - "--package-display-name", packageDisplayName, - "--package-directory", packageDirectory.path(), - "--docker-tool-path", containerToolPath.path, - "--zip-tool-path", zipToolPath.path, - ] + arguments + var args = [ + "build", + "--output-path", outputDirectory.path(), + "--products", products.map { $0.name }.joined(separator: ","), + "--package-id", context.package.id, + "--package-display-name", context.package.displayName, + "--package-directory", context.package.directoryURL.path(), + "--configuration", configurationArgument.first ?? "release", + "--cross-compile-tool-path", containerToolPath.path, + "--zip-tool-path", zipToolPath.path, + ] + // Re-inject the cross-compilation method (normalised to --cross-compile) so the helper can + // select the build method, then forward everything the plugin did not consume. + if let crossCompileMethod { + args += ["--cross-compile", crossCompileMethod] + } + args += argumentExtractor.remainingArguments // Invoke the plugin helper, passing the current environment so that // AWS credentials and HOME are available to the subprocess. diff --git a/Plugins/AWSLambdaDeployer/Plugin.swift b/Plugins/AWSLambdaDeployer/Plugin.swift index d0237b36..234992d2 100644 --- a/Plugins/AWSLambdaDeployer/Plugin.swift +++ b/Plugins/AWSLambdaDeployer/Plugin.swift @@ -23,7 +23,9 @@ struct AWSLambdaDeployer: CommandPlugin { let tool = try context.tool(named: "AWSLambdaPluginHelper") - // Resolve products: use --products if provided, otherwise default to all executable targets + // Resolve products: use --products if provided, otherwise default to all executable targets. + // --products is consumed (extracted) here so it is not also forwarded via remainingArguments, + // which would pass it to the helper twice. var argumentExtractor = ArgumentExtractor(arguments) let productsArgument = argumentExtractor.extractOption(named: "products") @@ -36,7 +38,7 @@ struct AWSLambdaDeployer: CommandPlugin { let productNames = products.map { $0.name }.joined(separator: ",") - let args = ["deploy", "--products", productNames] + arguments + let args = ["deploy", "--products", productNames] + argumentExtractor.remainingArguments // Invoke the plugin helper, passing the current environment so that // AWS credentials (env vars, HOME for ~/.aws/credentials) are available. diff --git a/Plugins/AWSLambdaPackager/Plugin@swift-6.4.swift b/Plugins/AWSLambdaPackager/Plugin@swift-6.4.swift index b9b68991..af50c2ab 100644 --- a/Plugins/AWSLambdaPackager/Plugin@swift-6.4.swift +++ b/Plugins/AWSLambdaPackager/Plugin@swift-6.4.swift @@ -32,34 +32,38 @@ struct AWSLambdaPackager: CommandPlugin { "'archive' is deprecated. Please use 'swift package lambda-build' instead." ) - // Resolve context-dependent values (same as AWSLambdaBuilder) - let outputDirectory: URL - let products: [Product] - let buildConfiguration: PackageManager.BuildConfiguration - let packageID: String = context.package.id - let packageDisplayName = context.package.displayName - let packageDirectory = context.package.directoryURL - let zipToolPath = try context.tool(named: "zip").url - + // This deprecated command is a thin layer over the AWSLambdaPluginHelper executable. It + // resolves only the values that require the PackagePlugin sandbox or the package graph, + // injects them as canonical arguments, then forwards every argument it did not consume to + // the helper. The helper owns the remaining argument parsing and defaulting. + // It mirrors AWSLambdaBuilder, delegating to the same `build` helper subcommand. var argumentExtractor = ArgumentExtractor(arguments) + // Options the plugin resolves itself. These are consumed (extracted) here so they are not + // also forwarded via remainingArguments, which would pass them to the helper twice. let outputPathArgument = argumentExtractor.extractOption(named: "output-path") + // `--output-directory` is a deprecated alias for `--output-path`. let outputDirectoryArgument = argumentExtractor.extractOption(named: "output-directory") let productsArgument = argumentExtractor.extractOption(named: "products") + // The helper requires --configuration; the plugin supplies the default. Validation of the + // value itself is left to the helper. let configurationArgument = argumentExtractor.extractOption(named: "configuration") - - // Resolve the container CLI that matches the requested cross-compilation method. - // The plugin sandbox can only run tools it resolves up front, so we must pick the right - // binary here — `container` for `container`, `docker` otherwise. Extracting these options - // only peeks them for the plugin; the original `arguments` (which the helper re-parses) is - // still forwarded unchanged below. `--container-cli` is the legacy alias for `--cross-compile`. + // `--container-cli` is a deprecated alias for `--cross-compile`. let crossCompileArgument = argumentExtractor.extractOption(named: "cross-compile") let containerCliArgument = argumentExtractor.extractOption(named: "container-cli") + + // Resolve the container CLI that matches the requested cross-compilation method. The plugin + // sandbox can only run tools it resolves up front, so we must pick the right binary here: + // `container` for `--cross-compile container`, `docker` otherwise. let crossCompileMethod = (crossCompileArgument.first ?? containerCliArgument.first)?.lowercased() let containerCLIToolName = crossCompileMethod == "container" ? "container" : "docker" let containerToolPath = try context.tool(named: containerCLIToolName).url + let zipToolPath = try context.tool(named: "zip").url - // output directory + // Resolve the output directory. The default lives under the plugin's work directory, whose + // location is only known to the plugin. This path is part of the plugin's public contract + // (documented and consumed by lambda-deploy), so it must stay stable. + let outputDirectory: URL if let outputPath = outputPathArgument.first ?? outputDirectoryArgument.first { #if os(Linux) var isDirectory: Bool = false @@ -75,44 +79,36 @@ struct AWSLambdaPackager: CommandPlugin { outputDirectory = context.pluginWorkDirectoryURL.appending(path: "\(AWSLambdaPackager.self)") } - // products - let explicitProducts = !productsArgument.isEmpty - if explicitProducts { - let _products = try context.package.products(named: productsArgument) - for product in _products { - guard product is ExecutableProduct else { - throw PackagerErrors.invalidArgument("product named '\(product.name)' is not an executable product") - } - } - products = _products - } else { + // Resolve and validate the products against the package graph. + let products: [Product] + if productsArgument.isEmpty { products = context.package.products.filter { $0 is ExecutableProduct } - } - - // build configuration - if let buildConfigurationName = configurationArgument.first { - guard let _buildConfiguration = PackageManager.BuildConfiguration(rawValue: buildConfigurationName) else { - throw PackagerErrors.invalidArgument("invalid build configuration named '\(buildConfigurationName)'") - } - buildConfiguration = _buildConfiguration } else { - buildConfiguration = .release + products = try context.package.products(named: productsArgument) + for product in products where !(product is ExecutableProduct) { + throw PackagerErrors.invalidArgument("product named '\(product.name)' is not an executable product") + } } // Build the resolved arguments for the helper let tool = try context.tool(named: "AWSLambdaPluginHelper") - let args = - [ - "build", - "--output-path", outputDirectory.path(), - "--products", products.map { $0.name }.joined(separator: ","), - "--configuration", buildConfiguration.rawValue, - "--package-id", packageID, - "--package-display-name", packageDisplayName, - "--package-directory", packageDirectory.path(), - "--docker-tool-path", containerToolPath.path, - "--zip-tool-path", zipToolPath.path, - ] + arguments + var args = [ + "build", + "--output-path", outputDirectory.path(), + "--products", products.map { $0.name }.joined(separator: ","), + "--package-id", context.package.id, + "--package-display-name", context.package.displayName, + "--package-directory", context.package.directoryURL.path(), + "--configuration", configurationArgument.first ?? "release", + "--cross-compile-tool-path", containerToolPath.path, + "--zip-tool-path", zipToolPath.path, + ] + // Re-inject the cross-compilation method (normalised to --cross-compile) so the helper can + // select the build method, then forward everything the plugin did not consume. + if let crossCompileMethod { + args += ["--cross-compile", crossCompileMethod] + } + args += argumentExtractor.remainingArguments // Invoke the plugin helper, passing the current environment let process = Process() diff --git a/Sources/AWSLambdaPluginHelper/lambda-build/Backends/AppleContainerCLI.swift b/Sources/AWSLambdaPluginHelper/lambda-build/Backends/AppleContainerCLI.swift new file mode 100644 index 00000000..37e4aff2 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/lambda-build/Backends/AppleContainerCLI.swift @@ -0,0 +1,50 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright SwiftAWSLambdaRuntime project authors +// Copyright (c) Amazon.com, Inc. or its affiliates. +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +/// The Apple `container` CLI argument flavor (https://github.com/apple/container). +/// +/// This type builds its complete argument vector on its own and shares no helper with other +/// ``ContainerCLI`` implementations — see the note on ``ContainerCLI``. Its argument layout +/// deliberately differs from Docker's where the CLIs differ: the image subcommand is +/// `image pull` rather than `pull`, and the runtime needs an explicit `--memory` reservation. +@available(LambdaSwift 2.0, *) +struct AppleContainerCLI: ContainerCLI { + let executableName = "container" + + func pullArguments(image: String) -> [String] { + ["image", "pull", image] + } + + func runArguments( + baseImage: String, + workingDirectory: String, + mounts: [String], + env: [String: String]?, + command: String + ) -> [String] { + // container's runtime needs a bit more memory than the default + var args: [String] = ["run", "--memory", "4G", "--rm"] + for mount in mounts { + args += ["-v", mount] + } + if let env { + for (key, value) in env.sorted(by: { $0.key < $1.key }) { + args += ["-e", "\(key)=\(value)"] + } + } + args += ["-w", workingDirectory, baseImage, "bash", "-cl", command] + return args + } +} diff --git a/Sources/AWSLambdaPluginHelper/lambda-build/Backends/BuildBackend.swift b/Sources/AWSLambdaPluginHelper/lambda-build/Backends/BuildBackend.swift new file mode 100644 index 00000000..64b740fd --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/lambda-build/Backends/BuildBackend.swift @@ -0,0 +1,53 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright SwiftAWSLambdaRuntime project authors +// Copyright (c) Amazon.com, Inc. or its affiliates. +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif + +/// A strategy for building Lambda product executables for the Amazon Linux target. +/// +/// A backend owns *how* a build is performed — natively on an Amazon Linux host, inside a +/// container (docker, Apple's `container`, …), or via a Swift cross-compilation SDK. Backend +/// selection lives in ``Builder`` and ``CrossCompileMethod/makeBackend(configuration:)``; the +/// configuration each backend needs (tool paths, base image, …) is injected through its +/// initializer rather than this protocol, so the protocol stays stable as new backends are added. +@available(LambdaSwift 2.0, *) +protocol BuildBackend { + /// A human-readable name used in log output (e.g. "docker", "container", "native"). + var name: String { get } + + /// Build the requested products and return a map of product name to the built binary's + /// location on the host filesystem. + /// + /// - Parameters: + /// - packageIdentity: The package identity, used for log output. + /// - packageDirectory: The root directory of the package being built. + /// - products: The executable product names to build. + /// - buildConfiguration: `debug` or `release`. + /// - noStrip: When `true`, debug symbols are not stripped from the binary. + /// - verboseLogging: When `true`, emit verbose output for debugging. + /// - Returns: A map of product name to the built executable's URL on the host. + func build( + packageIdentity: String, + packageDirectory: URL, + products: [String], + buildConfiguration: BuildConfiguration, + noStrip: Bool, + verboseLogging: Bool + ) throws -> [String: URL] +} diff --git a/Sources/AWSLambdaPluginHelper/lambda-build/Backends/ContainerBuildBackend.swift b/Sources/AWSLambdaPluginHelper/lambda-build/Backends/ContainerBuildBackend.swift new file mode 100644 index 00000000..a05e0d86 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/lambda-build/Backends/ContainerBuildBackend.swift @@ -0,0 +1,136 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright SwiftAWSLambdaRuntime project authors +// Copyright (c) Amazon.com, Inc. or its affiliates. +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif + +/// Builds products inside a container, cross-compiling for Amazon Linux. +/// +/// The build *flow* (pull the image, resolve the build output path, run `swift build` per product) +/// is identical across container runtimes; only the argument spelling differs, which is delegated +/// to the injected ``ContainerCLI``. This single backend therefore serves docker, Apple's +/// `container`, and any future runtime with its own ``ContainerCLI``. +@available(LambdaSwift 2.0, *) +struct ContainerBuildBackend: BuildBackend { + let cli: ContainerCLI + let toolPath: URL + let baseImage: String + let disableImageUpdate: Bool + + /// The cross-compile method that selected this backend, retained for error reporting. + let method: CrossCompileMethod + + var name: String { self.method.rawValue } + + func build( + packageIdentity: String, + packageDirectory: URL, + products: [String], + buildConfiguration: BuildConfiguration, + noStrip: Bool, + verboseLogging: Bool + ) throws -> [String: URL] { + + // verify the container CLI binary exists at the resolved path + guard FileManager.default.fileExists(atPath: self.toolPath.path()) else { + throw BuilderErrors.containerCLINotFound(self.method) + } + + print("-------------------------------------------------------------------------") + print("building \"\(packageIdentity)\" in \(self.name)") + print("-------------------------------------------------------------------------") + + if !self.disableImageUpdate { + // update the underlying image, if necessary + print("updating \"\(self.baseImage)\" image") + try Utils.execute( + executable: self.toolPath, + arguments: self.cli.pullArguments(image: self.baseImage), + logLevel: verboseLogging ? .debug : .output + ) + } + + // get the build output path + let buildOutputPathCommand = "swift build -c \(buildConfiguration.rawValue) --show-bin-path" + let dockerBuildOutputPath = try Utils.execute( + executable: self.toolPath, + arguments: self.cli.runArguments( + baseImage: self.baseImage, + workingDirectory: "/workspace", + mounts: ["\(packageDirectory.path()):/workspace"], + env: nil, + command: buildOutputPathCommand + ), + logLevel: verboseLogging ? .debug : .silent + ) + guard let buildPathOutput = dockerBuildOutputPath.split(separator: "\n").last else { + throw BuilderErrors.failedParsingDockerOutput(dockerBuildOutputPath) + } + let buildOutputPath = URL( + string: buildPathOutput.replacingOccurrences(of: "/workspace/", with: packageDirectory.description) + )! + + // build the products + var builtProducts = [String: URL]() + for product in products { + print("building \"\(product)\"") + var buildCommand = + "swift build -c \(buildConfiguration.rawValue) --product \(product) --static-swift-stdlib" + if !noStrip { + buildCommand += " -Xlinker -s" + } + if let localPath = ProcessInfo.processInfo.environment["LAMBDA_USE_LOCAL_DEPS"] { + // when developing locally, we must have the full swift-aws-lambda-runtime project in the container + // because Examples' Package.swift have a dependency on ../.. + // just like Package.swift's examples assume ../.., we assume we are two levels below the root project + let slice = packageDirectory.pathComponents.suffix(2) + try Utils.execute( + executable: self.toolPath, + arguments: self.cli.runArguments( + baseImage: self.baseImage, + workingDirectory: "/workspace/\(slice.joined(separator: "/"))", + mounts: ["\(packageDirectory.path())../..:/workspace"], + env: ["LAMBDA_USE_LOCAL_DEPS": localPath], + command: buildCommand + ), + logLevel: verboseLogging ? .debug : .output + ) + } else { + try Utils.execute( + executable: self.toolPath, + arguments: self.cli.runArguments( + baseImage: self.baseImage, + workingDirectory: "/workspace", + mounts: ["\(packageDirectory.path()):/workspace"], + env: nil, + command: buildCommand + ), + logLevel: verboseLogging ? .debug : .output + ) + } + let productPath = buildOutputPath.appending(path: product) + + guard FileManager.default.fileExists(atPath: productPath.path()) else { + print("expected '\(product)' binary at \"\(productPath.path())\"") + throw BuilderErrors.productExecutableNotFound(product) + } + builtProducts[product] = productPath + } + return builtProducts + } +} diff --git a/Sources/AWSLambdaPluginHelper/lambda-build/Backends/ContainerCLI.swift b/Sources/AWSLambdaPluginHelper/lambda-build/Backends/ContainerCLI.swift new file mode 100644 index 00000000..152bc678 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/lambda-build/Backends/ContainerCLI.swift @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright SwiftAWSLambdaRuntime project authors +// Copyright (c) Amazon.com, Inc. or its affiliates. +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +/// The command-line argument "flavor" of a container runtime CLI (docker, Apple's `container`, +/// and future runtimes such as podman, finch, or colima). +/// +/// This abstracts only *how arguments are spelled* for a given CLI — the build flow that uses +/// them lives in ``ContainerBuildBackend``. Each conforming type builds its complete argument +/// vector independently: there is intentionally **no** shared helper or default implementation of +/// these methods. Two runtimes that happen to share an argument layout today may diverge in a +/// future release, and future runtimes may not be compatible at all, so each owns its own argv in +/// full and is covered by its own tests. +@available(LambdaSwift 2.0, *) +protocol ContainerCLI { + /// The name of the executable to resolve and run (e.g. "docker", "container"). + var executableName: String { get } + + /// The arguments to pull (update) the given base image. + func pullArguments(image: String) -> [String] + + /// The arguments to run a command inside a container created from `baseImage`. + /// + /// - Parameters: + /// - baseImage: The container image to run. + /// - workingDirectory: The working directory inside the container. + /// - mounts: Volume mounts, each in the CLI's `host:container` form. + /// - env: Environment variables to set inside the container, or `nil`. + /// - command: The shell command to execute inside the container. + func runArguments( + baseImage: String, + workingDirectory: String, + mounts: [String], + env: [String: String]?, + command: String + ) -> [String] +} diff --git a/Sources/AWSLambdaPluginHelper/lambda-build/Backends/DockerCLI.swift b/Sources/AWSLambdaPluginHelper/lambda-build/Backends/DockerCLI.swift new file mode 100644 index 00000000..e470148c --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/lambda-build/Backends/DockerCLI.swift @@ -0,0 +1,47 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright SwiftAWSLambdaRuntime project authors +// Copyright (c) Amazon.com, Inc. or its affiliates. +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +/// The Docker CLI argument flavor. +/// +/// This type builds its complete argument vector on its own and shares no helper with other +/// ``ContainerCLI`` implementations — see the note on ``ContainerCLI``. +@available(LambdaSwift 2.0, *) +struct DockerCLI: ContainerCLI { + let executableName = "docker" + + func pullArguments(image: String) -> [String] { + ["pull", image] + } + + func runArguments( + baseImage: String, + workingDirectory: String, + mounts: [String], + env: [String: String]?, + command: String + ) -> [String] { + var args: [String] = ["run", "--rm"] + for mount in mounts { + args += ["-v", mount] + } + if let env { + for (key, value) in env.sorted(by: { $0.key < $1.key }) { + args += ["-e", "\(key)=\(value)"] + } + } + args += ["-w", workingDirectory, baseImage, "bash", "-cl", command] + return args + } +} diff --git a/Sources/AWSLambdaPluginHelper/lambda-build/Backends/NativeBuildBackend.swift b/Sources/AWSLambdaPluginHelper/lambda-build/Backends/NativeBuildBackend.swift new file mode 100644 index 00000000..be83ec4a --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/lambda-build/Backends/NativeBuildBackend.swift @@ -0,0 +1,75 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright SwiftAWSLambdaRuntime project authors +// Copyright (c) Amazon.com, Inc. or its affiliates. +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif + +/// Builds products natively on the host by invoking `/usr/bin/swift` directly. +/// +/// Used when the build already runs on an Amazon Linux host, so no cross-compilation is needed. +@available(LambdaSwift 2.0, *) +struct NativeBuildBackend: BuildBackend { + let name = "native" + + func build( + packageIdentity: String, + packageDirectory: URL, + products: [String], + buildConfiguration: BuildConfiguration, + noStrip: Bool, + verboseLogging: Bool + ) throws -> [String: URL] { + print("-------------------------------------------------------------------------") + print("building \"\(packageIdentity)\"") + print("-------------------------------------------------------------------------") + + var results = [String: URL]() + for product in products { + print("building \"\(product)\"") + var buildArguments = [ + "build", "-c", buildConfiguration.rawValue, + "--product", product, + "--static-swift-stdlib", + ] + if !noStrip { + buildArguments += ["-Xlinker", "-s"] + } + try Utils.execute( + executable: URL(fileURLWithPath: "/usr/bin/swift"), + arguments: buildArguments, + logLevel: verboseLogging ? .debug : .output + ) + + // get the build output path + let showBinPathArguments = ["build", "-c", buildConfiguration.rawValue, "--show-bin-path"] + let binPath = try Utils.execute( + executable: URL(fileURLWithPath: "/usr/bin/swift"), + arguments: showBinPathArguments, + logLevel: .silent + ).trimmingCharacters(in: .whitespacesAndNewlines) + + let productPath = URL(fileURLWithPath: binPath).appending(path: product) + guard FileManager.default.fileExists(atPath: productPath.path()) else { + print("expected '\(product)' binary at \"\(productPath.path())\"") + throw BuilderErrors.productExecutableNotFound(product) + } + results[product] = productPath + } + return results + } +} diff --git a/Sources/AWSLambdaPluginHelper/lambda-build/Builder.swift b/Sources/AWSLambdaPluginHelper/lambda-build/Builder.swift index d7ba3320..ce5a334f 100644 --- a/Sources/AWSLambdaPluginHelper/lambda-build/Builder.swift +++ b/Sources/AWSLambdaPluginHelper/lambda-build/Builder.swift @@ -34,34 +34,24 @@ struct Builder { self.displayAL2Warning() } - let builtProducts: [String: URL] - + // Select the build backend: build natively when already on an Amazon Linux host, + // otherwise cross-compile using the backend chosen by --cross-compile. + let backend: BuildBackend if self.isAmazonLinux(.al2) || self.isAmazonLinux(.al2023) { - // native build on Amazon Linux - builtProducts = try self.buildNative( - packageIdentity: configuration.packageID, - products: configuration.products, - buildConfiguration: configuration.buildConfiguration, - noStrip: configuration.noStrip, - verboseLogging: configuration.verboseLogging - ) + backend = NativeBuildBackend() } else { - // build with docker/container - builtProducts = try self.buildInDocker( - packageIdentity: configuration.packageID, - packageDirectory: configuration.packageDirectory, - products: configuration.products, - containerCLIPath: configuration.dockerToolPath, - containerCLI: configuration.crossCompileMethod, - outputDirectory: configuration.outputDirectory, - baseImage: configuration.baseDockerImage, - disableDockerImageUpdate: configuration.disableDockerImageUpdate, - buildConfiguration: configuration.buildConfiguration, - noStrip: configuration.noStrip, - verboseLogging: configuration.verboseLogging - ) + backend = try configuration.crossCompileMethod.makeBackend(configuration: configuration) } + let builtProducts = try backend.build( + packageIdentity: configuration.packageID, + packageDirectory: configuration.packageDirectory, + products: configuration.products, + buildConfiguration: configuration.buildConfiguration, + noStrip: configuration.noStrip, + verboseLogging: configuration.verboseLogging + ) + // create the archive let archives = try self.package( packageName: configuration.packageDisplayName, @@ -79,154 +69,6 @@ struct Builder { } } - private func buildNative( - packageIdentity: String, - products: [String], - buildConfiguration: BuildConfiguration, - noStrip: Bool, - verboseLogging: Bool - ) throws -> [String: URL] { - print("-------------------------------------------------------------------------") - print("building \"\(packageIdentity)\"") - print("-------------------------------------------------------------------------") - - var results = [String: URL]() - for product in products { - print("building \"\(product)\"") - var buildArguments = [ - "build", "-c", buildConfiguration.rawValue, - "--product", product, - "--static-swift-stdlib", - ] - if !noStrip { - buildArguments += ["-Xlinker", "-s"] - } - try Utils.execute( - executable: URL(fileURLWithPath: "/usr/bin/swift"), - arguments: buildArguments, - logLevel: verboseLogging ? .debug : .output - ) - - // get the build output path - let showBinPathArguments = ["build", "-c", buildConfiguration.rawValue, "--show-bin-path"] - let binPath = try Utils.execute( - executable: URL(fileURLWithPath: "/usr/bin/swift"), - arguments: showBinPathArguments, - logLevel: .silent - ).trimmingCharacters(in: .whitespacesAndNewlines) - - let productPath = URL(fileURLWithPath: binPath).appending(path: product) - guard FileManager.default.fileExists(atPath: productPath.path()) else { - print("expected '\(product)' binary at \"\(productPath.path())\"") - throw BuilderErrors.productExecutableNotFound(product) - } - results[product] = productPath - } - return results - } - - private func buildInDocker( - packageIdentity: String, - packageDirectory: URL, - products: [String], - containerCLIPath: URL, - containerCLI: CrossCompileMethod, - outputDirectory: URL, - baseImage: String, - disableDockerImageUpdate: Bool, - buildConfiguration: BuildConfiguration, - noStrip: Bool, - verboseLogging: Bool - ) throws -> [String: URL] { - - // verify the container CLI binary exists at the resolved path - guard FileManager.default.fileExists(atPath: containerCLIPath.path()) else { - throw BuilderErrors.containerCLINotFound(containerCLI) - } - - print("-------------------------------------------------------------------------") - print("building \"\(packageIdentity)\" in \(containerCLI)") - print("-------------------------------------------------------------------------") - - if !disableDockerImageUpdate { - // update the underlying image, if necessary - print("updating \"\(baseImage)\" image") - try Utils.execute( - executable: containerCLIPath, - arguments: containerCLI.pullArguments(image: baseImage), - logLevel: verboseLogging ? .debug : .output - ) - } - - // get the build output path - let buildOutputPathCommand = "swift build -c \(buildConfiguration.rawValue) --show-bin-path" - let dockerBuildOutputPath = try Utils.execute( - executable: containerCLIPath, - arguments: containerCLI.runArguments( - baseImage: baseImage, - workingDirectory: "/workspace", - mounts: ["\(packageDirectory.path()):/workspace"], - env: nil, - command: buildOutputPathCommand - ), - logLevel: verboseLogging ? .debug : .silent - ) - guard let buildPathOutput = dockerBuildOutputPath.split(separator: "\n").last else { - throw BuilderErrors.failedParsingDockerOutput(dockerBuildOutputPath) - } - let buildOutputPath = URL( - string: buildPathOutput.replacingOccurrences(of: "/workspace/", with: packageDirectory.description) - )! - - // build the products - var builtProducts = [String: URL]() - for product in products { - print("building \"\(product)\"") - var buildCommand = - "swift build -c \(buildConfiguration.rawValue) --product \(product) --static-swift-stdlib" - if !noStrip { - buildCommand += " -Xlinker -s" - } - if let localPath = ProcessInfo.processInfo.environment["LAMBDA_USE_LOCAL_DEPS"] { - // when developing locally, we must have the full swift-aws-lambda-runtime project in the container - // because Examples' Package.swift have a dependency on ../.. - // just like Package.swift's examples assume ../.., we assume we are two levels below the root project - let slice = packageDirectory.pathComponents.suffix(2) - try Utils.execute( - executable: containerCLIPath, - arguments: containerCLI.runArguments( - baseImage: baseImage, - workingDirectory: "/workspace/\(slice.joined(separator: "/"))", - mounts: ["\(packageDirectory.path())../..:/workspace"], - env: ["LAMBDA_USE_LOCAL_DEPS": localPath], - command: buildCommand - ), - logLevel: verboseLogging ? .debug : .output - ) - } else { - try Utils.execute( - executable: containerCLIPath, - arguments: containerCLI.runArguments( - baseImage: baseImage, - workingDirectory: "/workspace", - mounts: ["\(packageDirectory.path()):/workspace"], - env: nil, - command: buildCommand - ), - logLevel: verboseLogging ? .debug : .output - ) - } - let productPath = buildOutputPath.appending(path: product) - - guard FileManager.default.fileExists(atPath: productPath.path()) else { - print("expected '\(product)' binary at \"\(productPath.path())\"") - throw BuilderErrors.productExecutableNotFound(product) - } - builtProducts[product] = productPath - } - return builtProducts - } - // TODO: explore using ziplib or similar instead of shelling out private func package( packageName: String, @@ -398,97 +240,6 @@ struct Builder { } } -@available(LambdaSwift 2.0, *) -enum CrossCompileMethod: String, CustomStringConvertible { - case docker - case container - case swiftStaticSdk = "swift-static-sdk" - case customSdk = "custom-sdk" - - var isSupported: Bool { - switch self { - case .docker, .container: return true - case .swiftStaticSdk, .customSdk: return false - } - } - - static func parse(_ value: String?) throws -> Self { - guard let value else { - return .docker - } - - guard let method = CrossCompileMethod(rawValue: value.lowercased()) else { - throw BuilderErrors.invalidArgument( - "invalid cross-compile method '\(value)'. Use 'docker', 'container', 'swift-static-sdk', or 'custom-sdk'." - ) - } - - guard method.isSupported else { - throw BuilderErrors.unsupportedCrossCompileMethod(method) - } - - return method - } - - /// Returns the container CLI pull arguments for the given image. - func pullArguments(image: String) -> [String] { - switch self { - case .docker: - return ["pull", image] - case .container: - return ["image", "pull", image] - case .swiftStaticSdk, .customSdk: - fatalError("pullArguments should not be called for unsupported cross-compile methods") - } - } - - /// Returns the container CLI run arguments for the given configuration. - func runArguments( - baseImage: String, - workingDirectory: String, - mounts: [String], - env: [String: String]?, - command: String - ) -> [String] { - func genericArgs() -> [String] { - var args: [String] = ["run", "--rm"] - for mount in mounts { - args += ["-v", mount] - } - if let env { - for (key, value) in env.sorted(by: { $0.key < $1.key }) { - args += ["-e", "\(key)=\(value)"] - } - } - args += ["-w", workingDirectory, baseImage, "bash", "-cl", command] - return args - } - switch self { - - case .docker: - return genericArgs() - - case .container: - var args = genericArgs() - - // container's runtime needs a bit more memory - if self == .container { - args.insert("--memory", at: 1) - args.insert("4G", at: 2) - } - - return args - - case .swiftStaticSdk, .customSdk: - fatalError("runArguments should not be called for unsupported cross-compile methods") - } - } - - var description: String { - self.rawValue - } -} - @available(LambdaSwift 2.0, *) struct BuilderConfiguration: CustomStringConvertible { @@ -508,7 +259,7 @@ struct BuilderConfiguration: CustomStringConvertible { public let packageID: String public let packageDisplayName: String public let packageDirectory: URL - public let dockerToolPath: URL + public let crossCompileToolPath: URL public let zipToolPath: URL public init(arguments: [String]) throws { @@ -520,7 +271,7 @@ struct BuilderConfiguration: CustomStringConvertible { let packageIDArgument = argumentExtractor.extractOption(named: "package-id") let packageDisplayNameArgument = argumentExtractor.extractOption(named: "package-display-name") let packageDirectoryArgument = argumentExtractor.extractOption(named: "package-directory") - let dockerToolPathArgument = argumentExtractor.extractOption(named: "docker-tool-path") + let crossCompileToolPathArgument = argumentExtractor.extractOption(named: "cross-compile-tool-path") let zipToolPathArgument = argumentExtractor.extractOption(named: "zip-tool-path") let productsArgument = argumentExtractor.extractOption(named: "products") let configurationArgument = argumentExtractor.extractOption(named: "configuration") @@ -556,11 +307,11 @@ struct BuilderConfiguration: CustomStringConvertible { } self.packageDirectory = URL(fileURLWithPath: packageDirectoryArgument.first!) - // docker tool path - guard !dockerToolPathArgument.isEmpty else { - throw BuilderErrors.invalidArgument("--docker-tool-path argument is required") + // cross-compile tool path + guard !crossCompileToolPathArgument.isEmpty else { + throw BuilderErrors.invalidArgument("--cross-compile-tool-path argument is required") } - self.dockerToolPath = URL(fileURLWithPath: dockerToolPathArgument.first!) + self.crossCompileToolPath = URL(fileURLWithPath: crossCompileToolPathArgument.first!) // zip tool path guard !zipToolPathArgument.isEmpty else { @@ -634,7 +385,7 @@ struct BuilderConfiguration: CustomStringConvertible { outputDirectory: \(self.outputDirectory) products: \(self.products) buildConfiguration: \(self.buildConfiguration) - dockerToolPath: \(self.dockerToolPath) + crossCompileToolPath: \(self.crossCompileToolPath) baseDockerImage: \(self.baseDockerImage) disableDockerImageUpdate: \(self.disableDockerImageUpdate) crossCompileMethod: \(self.crossCompileMethod) diff --git a/Sources/AWSLambdaPluginHelper/lambda-build/CrossCompileMethod.swift b/Sources/AWSLambdaPluginHelper/lambda-build/CrossCompileMethod.swift new file mode 100644 index 00000000..d3224059 --- /dev/null +++ b/Sources/AWSLambdaPluginHelper/lambda-build/CrossCompileMethod.swift @@ -0,0 +1,85 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright SwiftAWSLambdaRuntime project authors +// Copyright (c) Amazon.com, Inc. or its affiliates. +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif + +/// The cross-compilation method requested via `--cross-compile`. +/// +/// This enum is the parsed user choice and a factory for the matching ``BuildBackend``. It holds +/// no execution logic itself: container argument spelling lives in the ``ContainerCLI`` types and +/// the build flow lives in the ``BuildBackend`` types. +@available(LambdaSwift 2.0, *) +enum CrossCompileMethod: String, CustomStringConvertible { + case docker + case container + case swiftStaticSdk = "swift-static-sdk" + case customSdk = "custom-sdk" + + var isSupported: Bool { + switch self { + case .docker, .container: return true + case .swiftStaticSdk, .customSdk: return false + } + } + + static func parse(_ value: String?) throws -> Self { + guard let value else { + return .docker + } + + guard let method = CrossCompileMethod(rawValue: value.lowercased()) else { + throw BuilderErrors.invalidArgument( + "invalid cross-compile method '\(value)'. Use 'docker', 'container', 'swift-static-sdk', or 'custom-sdk'." + ) + } + + guard method.isSupported else { + throw BuilderErrors.unsupportedCrossCompileMethod(method) + } + + return method + } + + /// Creates the ``BuildBackend`` that performs a cross-compiled build for this method. + /// + /// Used when the host is not already an Amazon Linux machine. The configuration supplies the + /// resolved tool path, base image, and image-update preference. + func makeBackend(configuration: BuilderConfiguration) throws -> BuildBackend { + let cli: ContainerCLI + switch self { + case .docker: + cli = DockerCLI() + case .container: + cli = AppleContainerCLI() + case .swiftStaticSdk, .customSdk: + throw BuilderErrors.unsupportedCrossCompileMethod(self) + } + return ContainerBuildBackend( + cli: cli, + toolPath: configuration.crossCompileToolPath, + baseImage: configuration.baseDockerImage, + disableImageUpdate: configuration.disableDockerImageUpdate, + method: self + ) + } + + var description: String { + self.rawValue + } +} diff --git a/Tests/AWSLambdaPluginHelperTests/BuildBackendTests.swift b/Tests/AWSLambdaPluginHelperTests/BuildBackendTests.swift new file mode 100644 index 00000000..d8ed3cd0 --- /dev/null +++ b/Tests/AWSLambdaPluginHelperTests/BuildBackendTests.swift @@ -0,0 +1,198 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright SwiftAWSLambdaRuntime project authors +// Copyright (c) Amazon.com, Inc. or its affiliates. +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Testing + +@testable import AWSLambdaPluginHelper + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif + +// MARK: - DockerCLI argv + +/// Golden tests pinning the exact argument vector emitted for the Docker CLI. These are kept +/// independent from the Apple `container` tests on purpose: the two CLIs may diverge, and a shared +/// fixture would let a regression in one hide behind the other. +@Suite("DockerCLI argument vectors") +struct DockerCLIArgumentTests { + + @available(LambdaSwift 2.0, *) + @Test("pull arguments") + func pullArguments() { + let cli = DockerCLI() + #expect(cli.executableName == "docker") + #expect(cli.pullArguments(image: "swift:amazonlinux2023") == ["pull", "swift:amazonlinux2023"]) + } + + @available(LambdaSwift 2.0, *) + @Test("run arguments without env") + func runArgumentsNoEnv() { + let cli = DockerCLI() + let args = cli.runArguments( + baseImage: "swift:amazonlinux2023", + workingDirectory: "/workspace", + mounts: ["/host/pkg:/workspace"], + env: nil, + command: "swift build" + ) + #expect( + args == [ + "run", "--rm", + "-v", "/host/pkg:/workspace", + "-w", "/workspace", + "swift:amazonlinux2023", + "bash", "-cl", "swift build", + ] + ) + } + + @available(LambdaSwift 2.0, *) + @Test("run arguments with sorted env") + func runArgumentsWithEnv() { + let cli = DockerCLI() + let args = cli.runArguments( + baseImage: "img", + workingDirectory: "/w", + mounts: ["/a:/b", "/c:/d"], + env: ["ZED": "1", "ABLE": "2"], + command: "cmd" + ) + #expect( + args == [ + "run", "--rm", + "-v", "/a:/b", + "-v", "/c:/d", + "-e", "ABLE=2", + "-e", "ZED=1", + "-w", "/w", + "img", + "bash", "-cl", "cmd", + ] + ) + } +} + +// MARK: - AppleContainerCLI argv + +/// Golden tests pinning the exact argument vector emitted for Apple's `container` CLI. Independent +/// from the Docker tests by design (see ``DockerCLIArgumentTests``). Note the `image pull` verb and +/// the `--memory 4G` reservation that distinguish this CLI from Docker. +@Suite("AppleContainerCLI argument vectors") +struct AppleContainerCLIArgumentTests { + + @available(LambdaSwift 2.0, *) + @Test("pull arguments use the image subcommand") + func pullArguments() { + let cli = AppleContainerCLI() + #expect(cli.executableName == "container") + #expect(cli.pullArguments(image: "swift:amazonlinux2023") == ["image", "pull", "swift:amazonlinux2023"]) + } + + @available(LambdaSwift 2.0, *) + @Test("run arguments reserve memory and come before --rm") + func runArgumentsNoEnv() { + let cli = AppleContainerCLI() + let args = cli.runArguments( + baseImage: "swift:amazonlinux2023", + workingDirectory: "/workspace", + mounts: ["/host/pkg:/workspace"], + env: nil, + command: "swift build" + ) + #expect( + args == [ + "run", "--memory", "4G", "--rm", + "-v", "/host/pkg:/workspace", + "-w", "/workspace", + "swift:amazonlinux2023", + "bash", "-cl", "swift build", + ] + ) + } + + @available(LambdaSwift 2.0, *) + @Test("run arguments with sorted env") + func runArgumentsWithEnv() { + let cli = AppleContainerCLI() + let args = cli.runArguments( + baseImage: "img", + workingDirectory: "/w", + mounts: ["/a:/b"], + env: ["ZED": "1", "ABLE": "2"], + command: "cmd" + ) + #expect( + args == [ + "run", "--memory", "4G", "--rm", + "-v", "/a:/b", + "-e", "ABLE=2", + "-e", "ZED=1", + "-w", "/w", + "img", + "bash", "-cl", "cmd", + ] + ) + } +} + +// MARK: - CrossCompileMethod.makeBackend + +@Suite("CrossCompileMethod backend selection") +struct CrossCompileMethodBackendTests { + + @available(LambdaSwift 2.0, *) + static func makeConfiguration() throws -> BuilderConfiguration { + try BuilderConfiguration(arguments: [ + "--package-id", "test", + "--package-display-name", "Test", + "--package-directory", "/tmp/pkg", + "--cross-compile-tool-path", "/usr/local/bin/docker", + "--zip-tool-path", "/usr/bin/zip", + "--output-path", "/tmp", + "--products", "MyLambda", + "--configuration", "release", + ]) + } + + @available(LambdaSwift 2.0, *) + @Test("docker selects a container backend running the docker CLI") + func dockerBackend() throws { + let backend = try CrossCompileMethod.docker.makeBackend(configuration: Self.makeConfiguration()) + let container = try #require(backend as? ContainerBuildBackend) + #expect(container.name == "docker") + #expect(container.cli is DockerCLI) + } + + @available(LambdaSwift 2.0, *) + @Test("container selects a container backend running the apple container CLI") + func containerBackend() throws { + let backend = try CrossCompileMethod.container.makeBackend(configuration: Self.makeConfiguration()) + let container = try #require(backend as? ContainerBuildBackend) + #expect(container.name == "container") + #expect(container.cli is AppleContainerCLI) + } + + @available(LambdaSwift 2.0, *) + @Test("unsupported methods throw", arguments: [CrossCompileMethod.swiftStaticSdk, .customSdk]) + func unsupportedThrows(method: CrossCompileMethod) throws { + let config = try Self.makeConfiguration() + #expect(throws: BuilderErrors.self) { + _ = try method.makeBackend(configuration: config) + } + } +} diff --git a/Tests/AWSLambdaPluginHelperTests/BuilderConfigurationTests.swift b/Tests/AWSLambdaPluginHelperTests/BuilderConfigurationTests.swift index 9e54887f..62a95091 100644 --- a/Tests/AWSLambdaPluginHelperTests/BuilderConfigurationTests.swift +++ b/Tests/AWSLambdaPluginHelperTests/BuilderConfigurationTests.swift @@ -32,7 +32,7 @@ struct BuilderConfigurationTests { "--package-id", "my-package", "--package-display-name", "MyPackage", "--package-directory", "/tmp/project", - "--docker-tool-path", "/usr/local/bin/docker", + "--cross-compile-tool-path", "/usr/local/bin/docker", "--zip-tool-path", "/usr/bin/zip", "--output-path", outputPath, "--products", products, @@ -120,7 +120,7 @@ struct BuilderConfigurationTests { "--package-id", "my-package", "--package-display-name", "MyPackage", "--package-directory", "/tmp/project", - "--docker-tool-path", "/usr/local/bin/docker", + "--cross-compile-tool-path", "/usr/local/bin/docker", "--zip-tool-path", "/usr/bin/zip", "--output-directory", "/custom/output/path", "--products", "MyLambda", @@ -137,7 +137,7 @@ struct BuilderConfigurationTests { "--package-id", "my-package", "--package-display-name", "MyPackage", "--package-directory", "/tmp/project", - "--docker-tool-path", "/usr/local/bin/docker", + "--cross-compile-tool-path", "/usr/local/bin/docker", "--zip-tool-path", "/usr/bin/zip", "--output-path", "/primary/path", "--output-directory", "/deprecated/path", diff --git a/Tests/AWSLambdaPluginHelperTests/PropertyTests.swift b/Tests/AWSLambdaPluginHelperTests/PropertyTests.swift index 5b5e5b80..4cca4235 100644 --- a/Tests/AWSLambdaPluginHelperTests/PropertyTests.swift +++ b/Tests/AWSLambdaPluginHelperTests/PropertyTests.swift @@ -145,7 +145,7 @@ struct DeprecatedAliasEquivalencePropertyTests { "--package-id", "my-package", "--package-display-name", "MyPackage", "--package-directory", "/tmp/project", - "--docker-tool-path", "/usr/local/bin/docker", + "--cross-compile-tool-path", "/usr/local/bin/docker", "--zip-tool-path", "/usr/bin/zip", "--products", "MyLambda", "--configuration", "release", @@ -242,7 +242,7 @@ struct MutualExclusionPropertyTests { "--package-id", "my-package", "--package-display-name", "MyPackage", "--package-directory", "/tmp/project", - "--docker-tool-path", "/usr/local/bin/docker", + "--cross-compile-tool-path", "/usr/local/bin/docker", "--zip-tool-path", "/usr/bin/zip", "--output-path", "/tmp/output", "--products", "MyLambda", @@ -531,7 +531,7 @@ struct AL2WarningLogicPropertyTests { "--package-id", "my-package", "--package-display-name", "MyPackage", "--package-directory", "/tmp/project", - "--docker-tool-path", "/usr/local/bin/docker", + "--cross-compile-tool-path", "/usr/local/bin/docker", "--zip-tool-path", "/usr/bin/zip", "--output-path", "/tmp/output", "--products", "MyLambda", @@ -608,7 +608,7 @@ struct UnsupportedCrossCompileMethodsPropertyTests { "--package-id", "my-package", "--package-display-name", "MyPackage", "--package-directory", "/tmp/project", - "--docker-tool-path", "/usr/local/bin/docker", + "--cross-compile-tool-path", "/usr/local/bin/docker", "--zip-tool-path", "/usr/bin/zip", "--output-path", "/tmp/output", "--products", "MyLambda",