Skip to content

Commit b452c5a

Browse files
committed
manually merge previous changes on execute() function
1 parent a79defc commit b452c5a

File tree

1 file changed

+36
-27
lines changed

1 file changed

+36
-27
lines changed

Plugins/AWSLambdaPackager/PluginUtils.swift

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,70 +14,77 @@
1414

1515
import Dispatch
1616
import PackagePlugin
17-
18-
#if canImport(FoundationEssentials)
19-
import FoundationEssentials
20-
#else
21-
import struct Foundation.URL
22-
import struct Foundation.Data
23-
import struct Foundation.CharacterSet
24-
import class Foundation.Process
25-
import class Foundation.Pipe
26-
#endif
17+
import Synchronization
18+
import Foundation
2719

2820
struct Utils {
2921
@discardableResult
3022
static func execute(
3123
executable: URL,
3224
arguments: [String],
33-
customWorkingDirectory: URL? = nil,
25+
customWorkingDirectory: URL? = .none,
3426
logLevel: ProcessLogLevel
3527
) throws -> String {
3628
if logLevel >= .debug {
3729
print("\(executable.absoluteString) \(arguments.joined(separator: " "))")
3830
}
3931

40-
// this shared global variable is safe because we're mutating it in a dispatch group
41-
// https://developer.apple.com/documentation/foundation/process/1408746-terminationhandler
42-
nonisolated(unsafe) var output = ""
32+
let fd = dup(1)
33+
let stdout = fdopen(fd, "rw")
34+
defer { fclose(stdout) }
35+
36+
// We need to use an unsafe transfer here to get the fd into our Sendable closure.
37+
// This transfer is fine, because we guarantee that the code in the outputHandler
38+
// is run before we continue the functions execution, where the fd is used again.
39+
// See `process.waitUntilExit()` and the following `outputSync.wait()`
40+
struct UnsafeTransfer<Value>: @unchecked Sendable {
41+
let value: Value
42+
}
43+
44+
let outputMutex = Mutex("")
4345
let outputSync = DispatchGroup()
44-
let outputQueue = DispatchQueue(label: "AWSLambdaPlugin.output")
46+
let outputQueue = DispatchQueue(label: "AWSLambdaPackager.output")
47+
let unsafeTransfer = UnsafeTransfer(value: stdout)
4548
let outputHandler = { @Sendable (data: Data?) in
4649
dispatchPrecondition(condition: .onQueue(outputQueue))
4750

4851
outputSync.enter()
4952
defer { outputSync.leave() }
5053

51-
guard let _output = data.flatMap({ String(decoding: $0, as: UTF8.self).trimmingCharacters(in: CharacterSet(["\n"])) }), !_output.isEmpty else {
54+
guard
55+
let _output = data.flatMap({
56+
String(data: $0, encoding: .utf8)?.trimmingCharacters(in: CharacterSet(["\n"]))
57+
}), !_output.isEmpty
58+
else {
5259
return
5360
}
5461

55-
output += _output + "\n"
62+
outputMutex.withLock { output in
63+
output += _output + "\n"
64+
}
5665

5766
switch logLevel {
5867
case .silent:
5968
break
6069
case .debug(let outputIndent), .output(let outputIndent):
6170
print(String(repeating: " ", count: outputIndent), terminator: "")
6271
print(_output)
63-
fflush(stdout)
72+
fflush(unsafeTransfer.value)
6473
}
6574
}
6675

6776
let pipe = Pipe()
6877
pipe.fileHandleForReading.readabilityHandler = { fileHandle in
69-
outputQueue.async {
70-
outputHandler(fileHandle.availableData)
71-
}
78+
outputQueue.async { outputHandler(fileHandle.availableData) }
7279
}
7380

7481
let process = Process()
7582
process.standardOutput = pipe
7683
process.standardError = pipe
77-
process.executableURL = executable
84+
process.executableURL = URL(fileURLWithPath: executable.description)
7885
process.arguments = arguments
79-
if let customWorkingDirectory {
80-
process.currentDirectoryURL = customWorkingDirectory
86+
if let workingDirectory = customWorkingDirectory {
87+
process.currentDirectoryURL = URL(fileURLWithPath: workingDirectory.path())
8188
}
8289
process.terminationHandler = { _ in
8390
outputQueue.async {
@@ -91,24 +98,26 @@ struct Utils {
9198
// wait for output to be full processed
9299
outputSync.wait()
93100

101+
let output = outputMutex.withLock { $0 }
102+
94103
if process.terminationStatus != 0 {
95104
// print output on failure and if not already printed
96105
if logLevel < .output {
97106
print(output)
98107
fflush(stdout)
99108
}
100-
throw ProcessError.processFailed([executable.absoluteString] + arguments, process.terminationStatus, output)
109+
throw ProcessError.processFailed([executable.path()] + arguments, process.terminationStatus)
101110
}
102111

103112
return output
104113
}
105114

106115
enum ProcessError: Error, CustomStringConvertible {
107-
case processFailed([String], Int32, String)
116+
case processFailed([String], Int32)
108117

109118
var description: String {
110119
switch self {
111-
case .processFailed(let arguments, let code, _):
120+
case .processFailed(let arguments, let code):
112121
return "\(arguments.joined(separator: " ")) failed with code \(code)"
113122
}
114123
}

0 commit comments

Comments
 (0)