Skip to content

Commit 2bdd645

Browse files
committed
WIP : migrate archive plugin to new lambda-build
1 parent 90517ad commit 2bdd645

File tree

16 files changed

+1031
-828
lines changed

16 files changed

+1031
-828
lines changed

Package.swift

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,32 +12,44 @@ let package = Package(
1212
name: "swift-aws-lambda-runtime",
1313
platforms: platforms,
1414
products: [
15+
16+
/*
17+
The runtime library targets
18+
*/
19+
1520
// this library exports `AWSLambdaRuntimeCore` and adds Foundation convenience methods
1621
.library(name: "AWSLambdaRuntime", targets: ["AWSLambdaRuntime"]),
1722

1823
// this has all the main functionality for lambda and it does not link Foundation
1924
.library(name: "AWSLambdaRuntimeCore", targets: ["AWSLambdaRuntimeCore"]),
2025

26+
/*
27+
The plugins
28+
'lambda-init' creates a new Lambda function
29+
'lambda-build' packages the Lambda function
30+
'lambda-deploy' deploys the Lambda function
31+
32+
Plugins requires Linux or at least macOS v15
33+
34+
*/
2135
// plugin to create a new Lambda function, based on a template
2236
.plugin(name: "AWSLambdaInitializer", targets: ["AWSLambdaInitializer"]),
2337

2438
// plugin to package the lambda, creating an archive that can be uploaded to AWS
25-
// requires Linux or at least macOS v15
26-
.plugin(name: "AWSLambdaPackager", targets: ["AWSLambdaPackager"]),
39+
.plugin(name: "AWSLambdaBuilder", targets: ["AWSLambdaBuilder"]),
2740

2841
// plugin to deploy a Lambda function
2942
.plugin(name: "AWSLambdaDeployer", targets: ["AWSLambdaDeployer"]),
3043

31-
// an executable that implements the business logic for the plugins
32-
.executable(name: "AWSLambdaPluginHelper", targets: ["AWSLambdaPluginHelper"]),
33-
44+
/*
45+
Testing targets
46+
*/
3447
// for testing only
3548
.library(name: "AWSLambdaTesting", targets: ["AWSLambdaTesting"]),
3649
],
3750
dependencies: [
3851
.package(url: "https://github.com/apple/swift-nio.git", from: "2.76.0"),
3952
.package(url: "https://github.com/apple/swift-log.git", from: "1.5.4"),
40-
// .package(url: "https://github.com/apple/swift-crypto.git", from: "3.9.1"),
4153
],
4254
targets: [
4355
.target(
@@ -69,10 +81,32 @@ let package = Package(
6981
permissions: [
7082
.writeToPackageDirectory(reason: "Create a file with an HelloWorld Lambda function.")
7183
]
72-
)
84+
),
85+
dependencies: [
86+
.target(name: "AWSLambdaPluginHelper")
87+
]
7388
),
89+
// keep this one (with "archive") to not break workflows
90+
// This will be deprecated at some point in the future
91+
// .plugin(
92+
// name: "AWSLambdaPackager",
93+
// capability: .command(
94+
// intent: .custom(
95+
// verb: "archive",
96+
// description:
97+
// "Archive the Lambda binary and prepare it for uploading to AWS. Requires docker on macOS or non Amazonlinux 2 distributions."
98+
// ),
99+
// permissions: [
100+
// .allowNetworkConnections(
101+
// scope: .docker,
102+
// reason: "This plugin uses Docker to create the AWS Lambda ZIP package."
103+
// )
104+
// ]
105+
// ),
106+
// path: "Plugins/AWSLambdaBuilder" // same sources as the new "lambda-build" plugin
107+
// ),
74108
.plugin(
75-
name: "AWSLambdaPackager",
109+
name: "AWSLambdaBuilder",
76110
capability: .command(
77111
intent: .custom(
78112
verb: "lambda-build",
@@ -85,13 +119,16 @@ let package = Package(
85119
reason: "This plugin uses Docker to create the AWS Lambda ZIP package."
86120
)
87121
]
88-
)
122+
),
123+
dependencies: [
124+
.target(name: "AWSLambdaPluginHelper")
125+
]
89126
),
90127
.plugin(
91128
name: "AWSLambdaDeployer",
92129
capability: .command(
93130
intent: .custom(
94-
verb: "deploy",
131+
verb: "lambda-deploy",
95132
description:
96133
"Deploy the Lambda function. You must have an AWS account and know an access key and secret access key."
97134
),
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftAWSLambdaRuntime open source project
4+
//
5+
// Copyright (c) 2022 Apple Inc. and the SwiftAWSLambdaRuntime project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import PackagePlugin
16+
17+
#if canImport(FoundationEssentials)
18+
import FoundationEssentials
19+
#else
20+
import Foundation
21+
#endif
22+
23+
@main
24+
struct AWSLambdaPackager: CommandPlugin {
25+
26+
func performCommand(context: PackagePlugin.PluginContext, arguments: [String]) async throws {
27+
28+
// values to pass to the AWSLambdaPluginHelper
29+
let outputDirectory: URL
30+
let products: [Product]
31+
let buildConfiguration: PackageManager.BuildConfiguration
32+
let packageID: String = context.package.id
33+
let packageDisplayName = context.package.displayName
34+
let packageDirectory = context.package.directoryURL
35+
let dockerToolPath = try context.tool(named: "docker").url
36+
let zipToolPath = try context.tool(named: "zip").url
37+
38+
// extract arguments that require PluginContext to fully resolve
39+
// resolve them here and pass them to the AWSLambdaPluginHelper as arguments
40+
var argumentExtractor = ArgumentExtractor(arguments)
41+
42+
let outputPathArgument = argumentExtractor.extractOption(named: "output-path")
43+
let productsArgument = argumentExtractor.extractOption(named: "products")
44+
let configurationArgument = argumentExtractor.extractOption(named: "configuration")
45+
46+
if let outputPath = outputPathArgument.first {
47+
#if os(Linux)
48+
var isDirectory: Bool = false
49+
#else
50+
var isDirectory: ObjCBool = false
51+
#endif
52+
guard FileManager.default.fileExists(atPath: outputPath, isDirectory: &isDirectory)
53+
else {
54+
throw BuilderErrors.invalidArgument("invalid output directory '\(outputPath)'")
55+
}
56+
outputDirectory = URL(string: outputPath)!
57+
} else {
58+
outputDirectory = context.pluginWorkDirectoryURL.appending(path: "\(AWSLambdaPackager.self)")
59+
}
60+
61+
let explicitProducts = !productsArgument.isEmpty
62+
if explicitProducts {
63+
let _products = try context.package.products(named: productsArgument)
64+
for product in _products {
65+
guard product is ExecutableProduct else {
66+
throw BuilderErrors.invalidArgument("product named '\(product.name)' is not an executable product")
67+
}
68+
}
69+
products = _products
70+
71+
} else {
72+
products = context.package.products.filter { $0 is ExecutableProduct }
73+
}
74+
75+
if let _buildConfigurationName = configurationArgument.first {
76+
guard let _buildConfiguration = PackageManager.BuildConfiguration(rawValue: _buildConfigurationName) else {
77+
throw BuilderErrors.invalidArgument("invalid build configuration named '\(_buildConfigurationName)'")
78+
}
79+
buildConfiguration = _buildConfiguration
80+
} else {
81+
buildConfiguration = .release
82+
}
83+
84+
// TODO: When running on Amazon Linux 2, we have to build directly from the plugin
85+
// launch the build, then call the helper just for the ZIP part
86+
87+
let tool = try context.tool(named: "AWSLambdaPluginHelper")
88+
let args = [
89+
"build",
90+
"--output-path", outputDirectory.path(),
91+
"--products", products.map { $0.name }.joined(separator: ","),
92+
"--configuration", buildConfiguration.rawValue,
93+
"--package-id", packageID,
94+
"--package-display-name", packageDisplayName,
95+
"--package-directory", packageDirectory.path(),
96+
"--docker-tool-path", dockerToolPath.path,
97+
"--zip-tool-path", zipToolPath.path
98+
] + arguments
99+
100+
// Invoke the plugin helper on the target directory, passing a configuration
101+
// file from the package directory.
102+
let process = try Process.run(tool.url, arguments: args)
103+
process.waitUntilExit()
104+
105+
// Check whether the subprocess invocation was successful.
106+
if !(process.terminationReason == .exit && process.terminationStatus == 0) {
107+
let problem = "\(process.terminationReason):\(process.terminationStatus)"
108+
Diagnostics.error("AWSLambdaPluginHelper invocation failed: \(problem)")
109+
}
110+
}
111+
112+
// TODO: When running on Amazon Linux 2, we have to build directly from the plugin
113+
// private func build(
114+
// packageIdentity: Package.ID,
115+
// products: [Product],
116+
// buildConfiguration: PackageManager.BuildConfiguration,
117+
// verboseLogging: Bool
118+
// ) throws -> [LambdaProduct: URL] {
119+
// print("-------------------------------------------------------------------------")
120+
// print("building \"\(packageIdentity)\"")
121+
// print("-------------------------------------------------------------------------")
122+
//
123+
// var results = [LambdaProduct: URL]()
124+
// for product in products {
125+
// print("building \"\(product.name)\"")
126+
// var parameters = PackageManager.BuildParameters()
127+
// parameters.configuration = buildConfiguration
128+
// parameters.otherSwiftcFlags = ["-static-stdlib"]
129+
// parameters.logging = verboseLogging ? .verbose : .concise
130+
//
131+
// let result = try packageManager.build(
132+
// .product(product.name),
133+
// parameters: parameters
134+
// )
135+
// guard let artifact = result.executableArtifact(for: product) else {
136+
// throw Errors.productExecutableNotFound(product.name)
137+
// }
138+
// results[.init(product)] = artifact.url
139+
// }
140+
// return results
141+
// }
142+
143+
// private func isAmazonLinux2() -> Bool {
144+
// if let data = FileManager.default.contents(atPath: "/etc/system-release"),
145+
// let release = String(data: data, encoding: .utf8)
146+
// {
147+
// return release.hasPrefix("Amazon Linux release 2")
148+
// } else {
149+
// return false
150+
// }
151+
// }
152+
}
153+
154+
private enum BuilderErrors: Error, CustomStringConvertible {
155+
case invalidArgument(String)
156+
157+
var description: String {
158+
switch self {
159+
case .invalidArgument(let description):
160+
return description
161+
}
162+
}
163+
}
164+
165+
extension PackageManager.BuildResult {
166+
// find the executable produced by the build
167+
func executableArtifact(for product: Product) -> PackageManager.BuildResult.BuiltArtifact? {
168+
let executables = self.builtArtifacts.filter {
169+
$0.kind == .executable && $0.url.lastPathComponent == product.name
170+
}
171+
guard !executables.isEmpty else {
172+
return nil
173+
}
174+
guard executables.count == 1, let executable = executables.first else {
175+
return nil
176+
}
177+
return executable
178+
}
179+
}

Plugins/AWSLambdaDeployer/Plugin.swift

Lines changed: 17 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -12,67 +12,33 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15-
import Foundation
1615
import PackagePlugin
1716

17+
#if canImport(FoundationEssentials)
18+
import FoundationEssentials
19+
#else
20+
import Foundation
21+
#endif
22+
1823
@main
19-
@available(macOS 15.0, *)
2024
struct AWSLambdaDeployer: CommandPlugin {
2125

2226
func performCommand(context: PackagePlugin.PluginContext, arguments: [String]) async throws {
23-
let configuration = try Configuration(context: context, arguments: arguments)
24-
25-
if configuration.help {
26-
self.displayHelpMessage()
27-
return
28-
}
2927

30-
let tool = try context.tool(named: "AWSLambdaDeployerHelper")
31-
try Utils.execute(executable: tool.url, arguments: [], logLevel: .debug)
32-
}
28+
let tool = try context.tool(named: "AWSLambdaPluginHelper")
3329

34-
private func displayHelpMessage() {
35-
print(
36-
"""
37-
OVERVIEW: A SwiftPM plugin to deploy a Lambda function.
30+
let args = ["deploy"] + arguments
3831

39-
USAGE: swift package lambda-deploy
40-
[--with-url]
41-
[--help] [--verbose]
32+
// Invoke the plugin helper on the target directory, passing a configuration
33+
// file from the package directory.
34+
let process = try Process.run(tool.url, arguments: args)
35+
process.waitUntilExit()
4236

43-
OPTIONS:
44-
--with-url Add an URL to access the Lambda function
45-
--verbose Produce verbose output for debugging.
46-
--help Show help information.
47-
"""
48-
)
49-
}
50-
}
51-
52-
private struct Configuration: CustomStringConvertible {
53-
public let help: Bool
54-
public let verboseLogging: Bool
55-
56-
public init(
57-
context: PluginContext,
58-
arguments: [String]
59-
) throws {
60-
var argumentExtractor = ArgumentExtractor(arguments)
61-
let verboseArgument = argumentExtractor.extractFlag(named: "verbose") > 0
62-
let helpArgument = argumentExtractor.extractFlag(named: "help") > 0
63-
64-
// help required ?
65-
self.help = helpArgument
66-
67-
// verbose logging required ?
68-
self.verboseLogging = verboseArgument
69-
}
70-
71-
var description: String {
72-
"""
73-
{
74-
verboseLogging: \(self.verboseLogging)
37+
// Check whether the subprocess invocation was successful.
38+
if !(process.terminationReason == .exit && process.terminationStatus == 0) {
39+
let problem = "\(process.terminationReason):\(process.terminationStatus)"
40+
Diagnostics.error("AWSLambdaPluginHelper invocation failed: \(problem)")
7541
}
76-
"""
7742
}
43+
7844
}

0 commit comments

Comments
 (0)