Skip to content

Commit 1660815

Browse files
committed
Make plugin compilation and invocation calls asynchronous
1 parent c4c4e4b commit 1660815

File tree

5 files changed

+209
-169
lines changed

5 files changed

+209
-169
lines changed

Sources/Commands/SwiftPackageTool.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -975,15 +975,18 @@ extension SwiftPackageTool {
975975
})
976976

977977
// Run the plugin.
978-
let result = try plugin.invoke(
978+
let buildEnvironment = try swiftTool.buildParameters().buildEnvironment
979+
let result = try tsc_await { plugin.invoke(
979980
action: .performCommand(targets: Array(targets.values), arguments: arguments),
980981
package: packageGraph.rootPackages[0], // FIXME: This should be the package that contains all the targets (and we should make sure all are in one)
981-
buildEnvironment: try swiftTool.buildParameters().buildEnvironment,
982+
buildEnvironment: buildEnvironment,
982983
scriptRunner: pluginScriptRunner,
983984
outputDirectory: outputDir,
984985
toolNamesToPaths: toolNamesToPaths,
986+
fileSystem: localFileSystem,
985987
observabilityScope: swiftTool.observabilityScope,
986-
fileSystem: localFileSystem)
988+
on: DispatchQueue(label: "plugin-invocation"),
989+
completion: $0) }
987990

988991
// Temporary: emit any output from the plugin.
989992
print(result.textOutput)

Sources/SPMBuildCore/PluginInvocation.swift

Lines changed: 90 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -52,95 +52,107 @@ extension PluginTarget {
5252
scriptRunner: PluginScriptRunner,
5353
outputDirectory: AbsolutePath,
5454
toolNamesToPaths: [String: AbsolutePath],
55+
fileSystem: FileSystem,
5556
observabilityScope: ObservabilityScope,
56-
fileSystem: FileSystem
57-
) throws -> PluginInvocationResult {
57+
on queue: DispatchQueue,
58+
completion: @escaping (Result<PluginInvocationResult, Error>) -> Void
59+
) {
5860
// Create the plugin working directory if needed (but don't do anything with it if it already exists).
5961
do {
6062
try fileSystem.createDirectory(outputDirectory, recursive: true)
6163
}
6264
catch {
63-
throw PluginEvaluationError.outputDirectoryCouldNotBeCreated(path: outputDirectory, underlyingError: error)
65+
return completion(.failure(PluginEvaluationError.couldNotCreateOuputDirectory(path: outputDirectory, underlyingError: error)))
6466
}
6567

6668
// Create the input context to send to the plugin.
6769
// TODO: Some of this could probably be cached.
6870
var serializer = PluginScriptRunnerInputSerializer(buildEnvironment: buildEnvironment)
69-
let inputStruct = try serializer.makePluginScriptRunnerInput(
70-
rootPackage: package,
71-
pluginWorkDir: outputDirectory,
72-
builtProductsDir: outputDirectory, // FIXME — what is this parameter needed for?
73-
toolNamesToPaths: toolNamesToPaths,
74-
pluginAction: action)
71+
let inputStruct: PluginScriptRunnerInput
72+
do {
73+
inputStruct = try serializer.makePluginScriptRunnerInput(
74+
rootPackage: package,
75+
pluginWorkDir: outputDirectory,
76+
builtProductsDir: outputDirectory, // FIXME — what is this parameter needed for?
77+
toolNamesToPaths: toolNamesToPaths,
78+
pluginAction: action)
79+
}
80+
catch {
81+
return completion(.failure(PluginEvaluationError.couldNotSerializePluginInput(underlyingError: error)))
82+
}
7583

7684
// Call the plugin script runner to actually invoke the plugin.
77-
// TODO: This should be asynchronous.
7885
var outputText = Data()
79-
let outputStruct = try scriptRunner.runPluginScript(
86+
scriptRunner.runPluginScript(
8087
sources: sources,
8188
input: inputStruct,
8289
toolsVersion: self.apiVersion,
8390
writableDirectories: [outputDirectory],
91+
fileSystem: fileSystem,
8492
observabilityScope: observabilityScope,
85-
textOutputHandler: { data in
93+
on: DispatchQueue(label: "plugin-invocation"),
94+
outputHandler: { data in
8695
outputText.append(contentsOf: data)
87-
},
88-
handlerQueue: DispatchQueue(label: "plugin-invocation"),
89-
fileSystem: fileSystem)
90-
91-
// Generate emittable Diagnostics from the plugin output.
92-
let diagnostics: [Diagnostic] = try outputStruct.diagnostics.map { diag in
93-
let metadata: ObservabilityMetadata? = try diag.file.map {
94-
var metadata = ObservabilityMetadata()
95-
metadata.fileLocation = try .init(.init(validating: $0), line: diag.line)
96-
return metadata
97-
}
98-
99-
switch diag.severity {
100-
case .error:
101-
return .error(diag.message, metadata: metadata)
102-
case .warning:
103-
return .warning(diag.message, metadata: metadata)
104-
case .remark:
105-
return .info(diag.message, metadata: metadata)
106-
}
107-
}
96+
}) { result in
97+
switch result {
98+
case .success(let output):
99+
// Generate emittable Diagnostics from the plugin output.
100+
let diagnostics: [Diagnostic] = output.diagnostics.map { diag in
101+
let metadata: ObservabilityMetadata? = diag.file.map {
102+
var metadata = ObservabilityMetadata()
103+
metadata.fileLocation = try? .init(.init(validating: $0), line: diag.line)
104+
return metadata
105+
}
106+
107+
switch diag.severity {
108+
case .error:
109+
return .error(diag.message, metadata: metadata)
110+
case .warning:
111+
return .warning(diag.message, metadata: metadata)
112+
case .remark:
113+
return .info(diag.message, metadata: metadata)
114+
}
115+
}
108116

109-
// FIXME: Validate the plugin output structure here, e.g. paths, etc.
110-
111-
// Generate commands from the plugin output. This is where we translate from the transport JSON to our
112-
// internal form. We deal with BuildCommands and PrebuildCommands separately.
113-
// FIXME: This feels a bit too specific to have here.
114-
// FIXME: Also there is too much repetition here, need to unify it.
115-
let buildCommands = outputStruct.buildCommands.map { cmd in
116-
PluginInvocationResult.BuildCommand(
117-
configuration: .init(
118-
displayName: cmd.displayName,
119-
executable: cmd.executable,
120-
arguments: cmd.arguments,
121-
environment: cmd.environment,
122-
workingDirectory: cmd.workingDirectory.map{ AbsolutePath($0) }),
123-
inputFiles: cmd.inputFiles.map{ AbsolutePath($0) },
124-
outputFiles: cmd.outputFiles.map{ AbsolutePath($0) })
125-
}
126-
let prebuildCommands = outputStruct.prebuildCommands.map { cmd in
127-
PluginInvocationResult.PrebuildCommand(
128-
configuration: .init(
129-
displayName: cmd.displayName,
130-
executable: cmd.executable,
131-
arguments: cmd.arguments,
132-
environment: cmd.environment,
133-
workingDirectory: cmd.workingDirectory.map{ AbsolutePath($0) }),
134-
outputFilesDirectory: AbsolutePath(cmd.outputFilesDirectory))
135-
}
117+
// FIXME: Validate the plugin output structure here, e.g. paths, etc.
118+
119+
// Generate commands from the plugin output. This is where we translate from the transport JSON to our
120+
// internal form. We deal with BuildCommands and PrebuildCommands separately.
121+
// FIXME: This feels a bit too specific to have here.
122+
// FIXME: Also there is too much repetition here, need to unify it.
123+
let buildCommands = output.buildCommands.map { cmd in
124+
PluginInvocationResult.BuildCommand(
125+
configuration: .init(
126+
displayName: cmd.displayName,
127+
executable: cmd.executable,
128+
arguments: cmd.arguments,
129+
environment: cmd.environment,
130+
workingDirectory: cmd.workingDirectory.map{ AbsolutePath($0) }),
131+
inputFiles: cmd.inputFiles.map{ AbsolutePath($0) },
132+
outputFiles: cmd.outputFiles.map{ AbsolutePath($0) })
133+
}
134+
let prebuildCommands = output.prebuildCommands.map { cmd in
135+
PluginInvocationResult.PrebuildCommand(
136+
configuration: .init(
137+
displayName: cmd.displayName,
138+
executable: cmd.executable,
139+
arguments: cmd.arguments,
140+
environment: cmd.environment,
141+
workingDirectory: cmd.workingDirectory.map{ AbsolutePath($0) }),
142+
outputFilesDirectory: AbsolutePath(cmd.outputFilesDirectory))
143+
}
136144

137-
// Create and return an evaluation result for the invocation.
138-
return PluginInvocationResult(
139-
plugin: self,
140-
diagnostics: diagnostics,
141-
textOutput: String(decoding: outputText, as: UTF8.self),
142-
buildCommands: buildCommands,
143-
prebuildCommands: prebuildCommands)
145+
// Create and return an evaluation result for the invocation.
146+
completion(.success(PluginInvocationResult(
147+
plugin: self,
148+
diagnostics: diagnostics,
149+
textOutput: String(decoding: outputText, as: UTF8.self),
150+
buildCommands: buildCommands,
151+
prebuildCommands: prebuildCommands)))
152+
case .failure(let error):
153+
completion(.failure(error))
154+
}
155+
}
144156
}
145157
}
146158

@@ -223,15 +235,17 @@ extension PackageGraph {
223235
let pluginOutputDir = outputDir.appending(components: package.identity.description, target.name, pluginTarget.name)
224236

225237
// Invoke the plugin.
226-
let result = try pluginTarget.invoke(
238+
let result = try tsc_await { pluginTarget.invoke(
227239
action: .createBuildToolCommands(target: target),
228240
package: package,
229241
buildEnvironment: buildEnvironment,
230242
scriptRunner: pluginScriptRunner,
231243
outputDirectory: pluginOutputDir,
232244
toolNamesToPaths: toolNamesToPaths,
245+
fileSystem: fileSystem,
233246
observabilityScope: observabilityScope,
234-
fileSystem: fileSystem)
247+
on: DispatchQueue(label: "plugin-invocation"),
248+
completion: $0) }
235249
pluginResults.append(result)
236250
}
237251

@@ -342,7 +356,8 @@ public struct PluginInvocationResult {
342356

343357
/// An error in plugin evaluation.
344358
public enum PluginEvaluationError: Swift.Error {
345-
case outputDirectoryCouldNotBeCreated(path: AbsolutePath, underlyingError: Error)
359+
case couldNotCreateOuputDirectory(path: AbsolutePath, underlyingError: Error)
360+
case couldNotSerializePluginInput(underlyingError: Error)
346361
case runningPluginFailed(underlyingError: Error)
347362
case decodingPluginOutputFailed(json: Data, underlyingError: Error)
348363
}
@@ -366,11 +381,12 @@ public protocol PluginScriptRunner {
366381
input: PluginScriptRunnerInput,
367382
toolsVersion: ToolsVersion,
368383
writableDirectories: [AbsolutePath],
384+
fileSystem: FileSystem,
369385
observabilityScope: ObservabilityScope,
370-
textOutputHandler: @escaping (Data) -> Void,
371-
handlerQueue: DispatchQueue,
372-
fileSystem: FileSystem
373-
) throws -> PluginScriptRunnerOutput
386+
on queue: DispatchQueue,
387+
outputHandler: @escaping (Data) -> Void,
388+
completion: @escaping (Result<PluginScriptRunnerOutput, Error>) -> Void
389+
)
374390

375391
/// Returns the Triple that represents the host for which plugin script tools should be built, or for which binary
376392
/// tools should be selected.

0 commit comments

Comments
 (0)