Skip to content

Commit a882987

Browse files
committed
check for amazonlinux, zip correctly, drop 5.2 and 5.3
1 parent 907da4e commit a882987

6 files changed

+122
-136
lines changed

Package.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ let package = Package(
99
.library(name: "AWSLambdaRuntime", targets: ["AWSLambdaRuntime"]),
1010
// this has all the main functionality for lambda and it does not link Foundation
1111
.library(name: "AWSLambdaRuntimeCore", targets: ["AWSLambdaRuntimeCore"]),
12-
// plugin to package the lambda, preparing an archive that can be uploaded to AWS. requires docker.
12+
// plugin to package the lambda, creating an archive that can be uploaded to AWS
1313
.plugin(name: "AWSLambdaPackager", targets: ["AWSLambdaPackager"]),
1414
// for testing only
1515
.library(name: "AWSLambdaTesting", targets: ["AWSLambdaTesting"]),
@@ -35,7 +35,7 @@ let package = Package(
3535
]),
3636
.plugin(
3737
name: "AWSLambdaPackager",
38-
capability: .command(intent: .custom(verb: "archive", description: "Archive Lambda binary and prepare it for uploading to AWS"))
38+
capability: .command(intent: .custom(verb: "archive", description: "Archive the Lambda binary and prepare it for uploading to AWS. Requires docker on macOS."))
3939
),
4040
.testTarget(name: "AWSLambdaRuntimeCoreTests", dependencies: [
4141
.byName(name: "AWSLambdaRuntimeCore"),

Package@swift-5.2.swift

-55
This file was deleted.

Package@swift-5.3.swift

-1
This file was deleted.

Package@swift-5.4.swift

-1
This file was deleted.

Package@swift-5.4.swift

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// swift-tools-version:5.4
2+
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "swift-aws-lambda-runtime",
7+
products: [
8+
// this library exports `AWSLambdaRuntimeCore` and adds Foundation convenience methods
9+
.library(name: "AWSLambdaRuntime", targets: ["AWSLambdaRuntime"]),
10+
// this has all the main functionality for lambda and it does not link Foundation
11+
.library(name: "AWSLambdaRuntimeCore", targets: ["AWSLambdaRuntimeCore"]),
12+
// for testing only
13+
.library(name: "AWSLambdaTesting", targets: ["AWSLambdaTesting"]),
14+
],
15+
dependencies: [
16+
.package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.33.0")),
17+
.package(url: "https://github.com/apple/swift-log.git", .upToNextMajor(from: "1.4.2")),
18+
.package(url: "https://github.com/swift-server/swift-backtrace.git", .upToNextMajor(from: "1.2.3")),
19+
],
20+
targets: [
21+
.target(name: "AWSLambdaRuntime", dependencies: [
22+
.byName(name: "AWSLambdaRuntimeCore"),
23+
.product(name: "NIOCore", package: "swift-nio"),
24+
.product(name: "NIOFoundationCompat", package: "swift-nio"),
25+
]),
26+
.target(name: "AWSLambdaRuntimeCore", dependencies: [
27+
.product(name: "Logging", package: "swift-log"),
28+
.product(name: "Backtrace", package: "swift-backtrace"),
29+
.product(name: "NIOHTTP1", package: "swift-nio"),
30+
.product(name: "NIOCore", package: "swift-nio"),
31+
.product(name: "NIOConcurrencyHelpers", package: "swift-nio"),
32+
.product(name: "NIOPosix", package: "swift-nio"),
33+
]),
34+
.testTarget(name: "AWSLambdaRuntimeCoreTests", dependencies: [
35+
.byName(name: "AWSLambdaRuntimeCore"),
36+
.product(name: "NIOTestUtils", package: "swift-nio"),
37+
.product(name: "NIOFoundationCompat", package: "swift-nio"),
38+
]),
39+
.testTarget(name: "AWSLambdaRuntimeTests", dependencies: [
40+
.byName(name: "AWSLambdaRuntimeCore"),
41+
.byName(name: "AWSLambdaRuntime"),
42+
]),
43+
// testing helper
44+
.target(name: "AWSLambdaTesting", dependencies: [
45+
.byName(name: "AWSLambdaRuntime"),
46+
.product(name: "NIO", package: "swift-nio"),
47+
]),
48+
.testTarget(name: "AWSLambdaTestingTests", dependencies: ["AWSLambdaTesting"]),
49+
// for perf testing
50+
.target(name: "MockServer", dependencies: [
51+
.product(name: "NIOHTTP1", package: "swift-nio"),
52+
.product(name: "NIO", package: "swift-nio"),
53+
]),
54+
]
55+
)

Package@swift-5.5.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Package@swift-5.2.swift
1+
Package@swift-5.4.swift

Plugins/AWSLambdaPackager/Plugin.swift

+64-76
Original file line numberDiff line numberDiff line change
@@ -28,41 +28,39 @@ struct AWSLambdaPackager: CommandPlugin {
2828
throw Errors.unknownProduct("no appropriate products found to package")
2929
}
3030

31-
#if os(macOS)
32-
let builtProducts = try self.buildInDocker(
33-
packageIdentity: context.package.id,
34-
packageDirectory: context.package.directory,
35-
products: configuration.products,
36-
toolsProvider: { name in try context.tool(named: name).path },
37-
outputDirectory: configuration.outputDirectory,
38-
baseImage: configuration.baseImage,
39-
buildConfiguration: configuration.buildConfiguration,
40-
verboseLogging: configuration.verboseLogging
41-
)
42-
#elseif os(Linux)
43-
let builtProducts = try self.build(
44-
products: configuration.products,
45-
buildConfiguration: configuration.buildConfiguration,
46-
verboseLogging: configuration.verboseLogging
47-
)
48-
#else
49-
throw Errors.unsupportedPlatform("only macOS and Linux are supported")
50-
#endif
31+
let builtProducts: [LambdaProduct: Path]
32+
if self.isAmazonLinux2() {
33+
// build directly on the machine
34+
builtProducts = try self.build(
35+
products: configuration.products,
36+
buildConfiguration: configuration.buildConfiguration,
37+
verboseLogging: configuration.verboseLogging
38+
)
39+
} else {
40+
// build with docker
41+
builtProducts = try self.buildInDocker(
42+
packageIdentity: context.package.id,
43+
packageDirectory: context.package.directory,
44+
products: configuration.products,
45+
toolsProvider: { name in try context.tool(named: name).path },
46+
outputDirectory: configuration.outputDirectory,
47+
baseImage: configuration.baseImage,
48+
buildConfiguration: configuration.buildConfiguration,
49+
verboseLogging: configuration.verboseLogging
50+
)
51+
}
5152

53+
// create the archive
5254
let archives = try self.package(
5355
products: builtProducts,
5456
toolsProvider: { name in try context.tool(named: name).path },
5557
outputDirectory: configuration.outputDirectory,
5658
verboseLogging: configuration.verboseLogging
5759
)
5860

59-
if !archives.isEmpty {
60-
print("\(archives.count) archives created:")
61-
for (product, archivePath) in archives {
62-
print(" * \(product.name) at \(archivePath.string)")
63-
}
64-
} else {
65-
print("no archives created")
61+
print("\(archives.count > 0 ? archives.count.description : "no") archive\(archives.count != 1 ? "s" : "") created")
62+
for (product, archivePath) in archives {
63+
print(" * \(product.name) at \(archivePath.string)")
6664
}
6765
}
6866

@@ -115,7 +113,7 @@ struct AWSLambdaPackager: CommandPlugin {
115113
arguments: ["run", "--rm", "-v", "\(packageDirectory.string):/workspace", "-w", "/workspace", builderImageName, "bash", "-cl", buildCommand],
116114
verboseLogging: verboseLogging
117115
)
118-
#warning("this knows too much about the underlying implementation")
116+
// TODO: this knows too much about the underlying implementation
119117
builtProducts[.init(product)] = packageDirectory.appending([".build", buildConfiguration.rawValue, product.name])
120118
}
121119
return builtProducts
@@ -142,10 +140,7 @@ struct AWSLambdaPackager: CommandPlugin {
142140
.product(product.name),
143141
parameters: parameters
144142
)
145-
guard result.builtArtifacts.count <= 1 else {
146-
throw Errors.unknownExecutable("too many executable artifacts found for \(product.name)")
147-
}
148-
guard let artifact = result.builtArtifacts.first else {
143+
guard let artifact = result.executableArtifact(for: product) else {
149144
throw Errors.unknownExecutable("no executable artifacts found for \(product.name)")
150145
}
151146
results[.init(product)] = artifact.path
@@ -170,14 +165,21 @@ struct AWSLambdaPackager: CommandPlugin {
170165
}
171166

172167
// prep zipfile location
173-
let zipfilePath = outputDirectory.appending(product.name, "\(product.name).zip")
174-
if FileManager.default.fileExists(atPath: zipfilePath.string) {
175-
try FileManager.default.removeItem(atPath: zipfilePath.string)
168+
let workingDirectory = outputDirectory.appending(product.name)
169+
let zipfilePath = workingDirectory.appending("\(product.name).zip")
170+
if FileManager.default.fileExists(atPath: workingDirectory.string) {
171+
try FileManager.default.removeItem(atPath: workingDirectory.string)
176172
}
177-
try FileManager.default.createDirectory(atPath: zipfilePath.removingLastComponent().string, withIntermediateDirectories: true)
173+
try FileManager.default.createDirectory(atPath: workingDirectory.string, withIntermediateDirectories: true)
174+
175+
// rename artifact to "bootstrap"
176+
let relocatedArtifactPath = workingDirectory.appending(artifactPath.lastComponent)
177+
let symbolicLinkPath = workingDirectory.appending("bootstrap")
178+
try FileManager.default.copyItem(atPath: artifactPath.string, toPath: relocatedArtifactPath.string)
179+
try FileManager.default.createSymbolicLink(atPath: symbolicLinkPath.string, withDestinationPath: relocatedArtifactPath.lastComponent)
178180

179181
#if os(macOS) || os(Linux)
180-
let arguments = ["--junk-paths", zipfilePath.string, artifactPath.string]
182+
let arguments = ["--junk-paths", "--symlinks", zipfilePath.string, relocatedArtifactPath.string, symbolicLinkPath.string]
181183
#else
182184
throw Error.unsupportedPlatform("can't or don't know how to create a zipfile on this platform")
183185
#endif
@@ -190,22 +192,6 @@ struct AWSLambdaPackager: CommandPlugin {
190192
)
191193

192194
archives[product] = zipfilePath
193-
194-
/*
195-
196-
target=".build/lambda/$executable"
197-
rm -rf "$target"
198-
mkdir -p "$target"
199-
cp ".build/release/$executable" "$target/"
200-
# add the target deps based on ldd
201-
ldd ".build/release/$executable" | grep swift | awk '{print $3}' | xargs cp -Lv -t "$target"
202-
cd "$target"
203-
ln -s "$executable" "bootstrap"
204-
zip --symlinks lambda.zip *
205-
206-
*/
207-
// docker run --rm -v "$workspace":/workspace -w /workspace/Examples/Deployment builder \
208-
// bash -cl "./scripts/package.sh $executable"
209195
}
210196
return archives
211197
}
@@ -223,25 +209,25 @@ struct AWSLambdaPackager: CommandPlugin {
223209

224210
let sync = DispatchGroup()
225211
var output = ""
226-
let outputLock = NSLock()
227-
let outputHandler = { (fileHandle: FileHandle) in
212+
let outputQueue = DispatchQueue(label: "AWSLambdaPackager.output")
213+
let outputHandler = { (data: Data?) in
214+
dispatchPrecondition(condition: .onQueue(outputQueue))
215+
guard let _output = data.flatMap({ String(data: $0, encoding: .utf8)?.trimmingCharacters(in: CharacterSet(["\n"])) }), !_output.isEmpty else {
216+
return
217+
}
228218
sync.enter()
229219
defer { sync.leave() }
230-
if !fileHandle.availableData.isEmpty, let _output = String(data: fileHandle.availableData, encoding: .utf8)?.trimmingCharacters(in: CharacterSet(["\n"])) {
231-
if verboseLogging {
232-
print(_output)
233-
fflush(stdout)
234-
}
235-
outputLock.lock()
236-
output += _output
237-
outputLock.unlock()
220+
if verboseLogging {
221+
print(_output)
222+
fflush(stdout)
238223
}
224+
output += _output
239225
}
240226

241227
let stdoutPipe = Pipe()
242-
stdoutPipe.fileHandleForReading.readabilityHandler = outputHandler
228+
stdoutPipe.fileHandleForReading.readabilityHandler = { fileHandle in outputQueue.async { outputHandler(fileHandle.availableData) } }
243229
let stderrPipe = Pipe()
244-
stderrPipe.fileHandleForReading.readabilityHandler = outputHandler
230+
stderrPipe.fileHandleForReading.readabilityHandler = { fileHandle in outputQueue.async { outputHandler(fileHandle.availableData) } }
245231

246232
let process = Process()
247233
process.standardOutput = stdoutPipe
@@ -252,10 +238,10 @@ struct AWSLambdaPackager: CommandPlugin {
252238
process.currentDirectoryURL = URL(fileURLWithPath: workingDirectory.string)
253239
}
254240
process.terminationHandler = { _ in
255-
// Read and pass on any remaining free-form text output from the plugin.
256-
stderrPipe.fileHandleForReading.readabilityHandler?(stderrPipe.fileHandleForReading)
257-
// Read and pass on any remaining messages from the plugin.
258-
stdoutPipe.fileHandleForReading.readabilityHandler?(stdoutPipe.fileHandleForReading)
241+
outputQueue.async {
242+
outputHandler(try? stdoutPipe.fileHandleForReading.readToEnd())
243+
outputHandler(try? stderrPipe.fileHandleForReading.readToEnd())
244+
}
259245
}
260246

261247
try process.run()
@@ -270,13 +256,20 @@ struct AWSLambdaPackager: CommandPlugin {
270256

271257
return output
272258
}
259+
260+
private func isAmazonLinux2() -> Bool {
261+
if let data = FileManager.default.contents(atPath: "/etc/system-release"), let release = String(data: data, encoding: .utf8) {
262+
return release.hasPrefix("Amazon Linux release 2")
263+
} else {
264+
return false
265+
}
266+
}
273267
}
274268

275269
private struct Configuration {
276270
public let outputDirectory: Path
277271
public let products: [Product]
278272
public let buildConfiguration: PackageManager.BuildConfiguration
279-
public let staticallyLinkRuntime: Bool
280273
public let verboseLogging: Bool
281274
public let baseImage: String
282275
public let version: String
@@ -286,11 +279,6 @@ private struct Configuration {
286279
self.outputDirectory = context.pluginWorkDirectory.appending(subpath: "\(AWSLambdaPackager.self)") // FIXME: read argument
287280
self.products = context.package.products.filter { $0 is ExecutableProduct } // FIXME: read argument, filter is ugly
288281
self.buildConfiguration = .release // FIXME: read argument
289-
#if os(Linux)
290-
self.staticallyLinkRuntime = true // FIXME: read argument
291-
#else
292-
self.staticallyLinkRuntime = false // FIXME: read argument, warn if set to true
293-
#endif
294282
self.verboseLogging = true // FIXME: read argument
295283
let swiftVersion = "5.6" // FIXME: read dynamically current version
296284
self.baseImage = "swift:\(swiftVersion)-amazonlinux2" // FIXME: read argument
@@ -311,7 +299,7 @@ private enum Errors: Error {
311299

312300
extension PackageManager.BuildResult {
313301
// find the executable produced by the build
314-
func executableArtifact(for product: Product) throws -> PackageManager.BuildResult.BuiltArtifact? {
302+
func executableArtifact(for product: Product) -> PackageManager.BuildResult.BuiltArtifact? {
315303
let executables = self.builtArtifacts.filter { $0.kind == .executable && $0.path.lastComponent == product.name }
316304
guard !executables.isEmpty else {
317305
return nil

0 commit comments

Comments
 (0)