Skip to content

Commit 7da0f96

Browse files
Generate all requested modes in parallel (#280)
### Motivation The generator itself is synchronous, but it can be run in one of more modes: `types`, `client`, `server`, and these runs can be parallelised. This is likely to have value to most adopters since most will use at least two modes. This is especially useful for large APIs. ### Modifications Run all the requested modes of the generator in a task group. ### Result When running with multiple modes, generation is faster. Concretely, when using the Github API and generating types and client, it cut the overall generation time by 40%. ### Test Plan CI. ### Resolves Resolves #227. Signed-off-by: Si Beaumont <[email protected]>
1 parent a07ebd2 commit 7da0f96

File tree

7 files changed

+41
-23
lines changed

7 files changed

+41
-23
lines changed

Sources/_OpenAPIGeneratorCore/Config.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
/// A single generator pipeline run produces exactly one file, so for
1919
/// generating multiple files, create multiple configuration values, each with
2020
/// a different generator mode.
21-
public struct Config {
21+
public struct Config: Sendable {
2222

2323
/// The generator mode to use.
2424
public var mode: GeneratorMode

Sources/_OpenAPIGeneratorCore/Diagnostics.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import Foundation
1515
import OpenAPIKit
1616

1717
/// A message emitted by the generator.
18-
public struct Diagnostic: Error, Codable {
18+
public struct Diagnostic: Error, Codable, Sendable {
1919

2020
/// Describes the severity of a diagnostic.
2121
public enum Severity: String, Codable, Sendable {
@@ -327,8 +327,7 @@ struct PrintingDiagnosticCollector: DiagnosticCollector {
327327
}
328328

329329
/// A diagnostic collector that prints diagnostics to standard error.
330-
public struct StdErrPrintingDiagnosticCollector: DiagnosticCollector {
331-
330+
public struct StdErrPrintingDiagnosticCollector: DiagnosticCollector, Sendable {
332331
/// Creates a new collector.
333332
public init() {}
334333

Sources/_OpenAPIGeneratorCore/FeatureFlags.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
/// enabled unconditionally on main and the feature flag removed, and version
2626
/// 0.2 is tagged. (This is for pre-1.0 versioning, would be 1.0 and 2.0 after
2727
/// 1.0 is released.)
28-
public enum FeatureFlag: String, Hashable, Codable, CaseIterable {
28+
public enum FeatureFlag: String, Hashable, Codable, CaseIterable, Sendable {
2929

3030
/// Support for `nullable` schemas.
3131
///

Sources/swift-openapi-generator/GenerateCommand.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ struct _GenerateCommand: AsyncParsableCommand {
5555
var isDryRun: Bool = false
5656

5757
func run() async throws {
58-
try generate.runGenerator(
58+
try await generate.runGenerator(
5959
outputDirectory: outputDirectory,
6060
pluginSource: pluginSource,
6161
isDryRun: isDryRun

Sources/swift-openapi-generator/GenerateOptions+runGenerator.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ extension _GenerateOptions {
2929
outputDirectory: URL,
3030
pluginSource: PluginSource?,
3131
isDryRun: Bool
32-
) throws {
32+
) async throws {
3333
let config = try loadedConfig()
3434
let sortedModes = try resolvedModes(config)
3535
let resolvedAdditionalImports = resolvedAdditionalImports(config)
@@ -41,7 +41,7 @@ extension _GenerateOptions {
4141
featureFlags: resolvedFeatureFlags
4242
)
4343
}
44-
let diagnostics: any DiagnosticCollector
44+
let diagnostics: any DiagnosticCollector & Sendable
4545
let finalizeDiagnostics: () throws -> Void
4646
if let diagnosticsOutputPath {
4747
let _diagnostics = _YamlFileDiagnosticsCollector(url: diagnosticsOutputPath)
@@ -70,7 +70,7 @@ extension _GenerateOptions {
7070
"""
7171
)
7272
do {
73-
try _Tool.runGenerator(
73+
try await _Tool.runGenerator(
7474
doc: doc,
7575
configs: configs,
7676
pluginSource: pluginSource,

Sources/swift-openapi-generator/YamlFileDiagnosticsCollector.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ struct _DiagnosticsYamlFileContent: Encodable {
2121
}
2222

2323
/// A collector that writes diagnostics to a YAML file.
24-
class _YamlFileDiagnosticsCollector: DiagnosticCollector {
24+
final class _YamlFileDiagnosticsCollector: DiagnosticCollector, @unchecked Sendable {
25+
/// Protects `diagnostics`.
26+
private let lock = NSLock()
2527

2628
/// A list of collected diagnostics.
2729
private var diagnostics: [Diagnostic] = []
@@ -36,12 +38,16 @@ class _YamlFileDiagnosticsCollector: DiagnosticCollector {
3638
}
3739

3840
func emit(_ diagnostic: Diagnostic) {
41+
lock.lock()
42+
defer { lock.unlock() }
3943
diagnostics.append(diagnostic)
4044
}
4145

4246
/// Finishes writing to the collector by persisting the accumulated
4347
/// diagnostics to a YAML file.
4448
func finalize() throws {
49+
lock.lock()
50+
defer { lock.unlock() }
4551
let uniqueMessages = Set(diagnostics.map(\.message)).sorted()
4652
let encoder = YAMLEncoder()
4753
encoder.options.sortKeys = true

Sources/swift-openapi-generator/runGenerator.swift

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,14 @@
1111
// SPDX-License-Identifier: Apache-2.0
1212
//
1313
//===----------------------------------------------------------------------===//
14-
import Foundation
14+
#if os(Linux)
15+
@preconcurrency import struct Foundation.URL
16+
@preconcurrency import struct Foundation.Data
17+
#else
18+
import struct Foundation.URL
19+
import struct Foundation.Data
20+
#endif
21+
import class Foundation.FileManager
1522
import ArgumentParser
1623
import _OpenAPIGeneratorCore
1724

@@ -32,24 +39,30 @@ extension _Tool {
3239
pluginSource: PluginSource?,
3340
outputDirectory: URL,
3441
isDryRun: Bool,
35-
diagnostics: any DiagnosticCollector
36-
) throws {
42+
diagnostics: any DiagnosticCollector & Sendable
43+
) async throws {
3744
let docData: Data
3845
do {
3946
docData = try Data(contentsOf: doc)
4047
} catch {
4148
throw ValidationError("Failed to load the OpenAPI document at path \(doc.path), error: \(error)")
4249
}
43-
for config in configs {
44-
try runGenerator(
45-
doc: doc,
46-
docData: docData,
47-
config: config,
48-
outputDirectory: outputDirectory,
49-
outputFileName: config.mode.outputFileName,
50-
isDryRun: isDryRun,
51-
diagnostics: diagnostics
52-
)
50+
51+
try await withThrowingTaskGroup(of: Void.self) { group in
52+
for config in configs {
53+
group.addTask {
54+
try runGenerator(
55+
doc: doc,
56+
docData: docData,
57+
config: config,
58+
outputDirectory: outputDirectory,
59+
outputFileName: config.mode.outputFileName,
60+
isDryRun: isDryRun,
61+
diagnostics: diagnostics
62+
)
63+
}
64+
}
65+
try await group.waitForAll()
5366
}
5467

5568
// If from a BuildTool plugin, the generator will have to emit all 3 files

0 commit comments

Comments
 (0)