Skip to content

Commit d5807c7

Browse files
committed
Define commands/actions as asynchronous
1 parent 9ad3f2b commit d5807c7

33 files changed

+305
-363
lines changed

Sources/SwiftDocC/Infrastructure/DocumentationConverter.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public struct DocumentationConverter: DocumentationConverterProtocol {
5050
private var dataProvider: DocumentationWorkspaceDataProvider
5151

5252
/// An optional closure that sets up a context before the conversion begins.
53+
@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released")
5354
public var setupContext: ((inout DocumentationContext) -> Void)?
5455

5556
/// Conversion batches should be big enough to keep all cores busy but small enough not to keep
@@ -189,9 +190,6 @@ public struct DocumentationConverter: DocumentationConverterProtocol {
189190
if let dataProvider = self.currentDataProvider {
190191
try workspace.unregisterProvider(dataProvider)
191192
}
192-
193-
// Do additional context setup.
194-
setupContext?(&context)
195193

196194
/*
197195
Asynchronously cancel registration if necessary.
Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
This source file is part of the Swift.org open source project
33

4-
Copyright (c) 2021 Apple Inc. and the Swift project authors
4+
Copyright (c) 2021-2024 Apple Inc. and the Swift project authors
55
Licensed under Apache License v2.0 with Runtime Library Exception
66

77
See https://swift.org/LICENSE.txt for license information
@@ -13,18 +13,16 @@ import SwiftDocC
1313

1414
/// An independent unit of work in the command-line workflow.
1515
///
16-
/// An `Action` represents a discrete documentation task; it takes options and inputs,
17-
/// performs its work, reports any problems it encounters, and outputs it generates.
18-
public protocol Action {
16+
/// An action represents a discrete documentation task; it takes options and inputs, performs its work, reports any problems it encounters, and outputs it generates.
17+
package protocol AsyncAction {
1918
/// Performs the action and returns an ``ActionResult``.
20-
mutating func perform(logHandle: LogHandle) throws -> ActionResult
19+
mutating func perform(logHandle: inout LogHandle) async throws -> ActionResult
2120
}
2221

23-
/// An action for which you can optionally customize the documentation context.
24-
public protocol RecreatingContext: Action {
25-
/// A closure that an action calls with the action's context for built documentation,
26-
/// before the action performs work.
27-
///
28-
/// Use this closure to set the action's context to a certain state before the action runs.
29-
var setupContext: ((inout DocumentationContext) -> Void)? { get set }
22+
package extension AsyncAction {
23+
mutating func perform(logHandle: LogHandle) async throws -> ActionResult {
24+
var logHandle = logHandle
25+
return try await perform(logHandle: &logHandle)
26+
}
3027
}
28+

Sources/SwiftDocCUtilities/Action/Actions/Action+MoveOutput.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import Foundation
1212
import SwiftDocC
1313

14-
extension Action {
14+
extension AsyncAction {
1515

1616
/// Creates a new unique directory, with an optional template, inside of specified container.
1717
/// - Parameters:

Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertAction.swift

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import Foundation
1414
import SwiftDocC
1515

1616
/// An action that converts a source bundle into compiled documentation.
17-
public struct ConvertAction: Action, RecreatingContext {
17+
public struct ConvertAction: AsyncAction {
1818
enum Error: DescribedError {
1919
case doesNotContainBundle(url: URL)
2020
case cancelPending
@@ -59,12 +59,6 @@ public struct ConvertAction: Action, RecreatingContext {
5959
private var fileManager: FileManagerProtocol
6060
private let temporaryDirectory: URL
6161

62-
public var setupContext: ((inout DocumentationContext) -> Void)? {
63-
didSet {
64-
converter.setupContext = setupContext
65-
}
66-
}
67-
6862
var converter: DocumentationConverter
6963

7064
private var durationMetric: Benchmark.Duration?
@@ -284,7 +278,7 @@ public struct ConvertAction: Action, RecreatingContext {
284278

285279
/// Converts each eligible file from the source documentation bundle,
286280
/// saves the results in the given output alongside the template files.
287-
mutating public func perform(logHandle: LogHandle) throws -> ActionResult {
281+
public mutating func perform(logHandle: inout LogHandle) async throws -> ActionResult {
288282
// Add the default diagnostic console writer now that we know what log handle it should write to.
289283
if !diagnosticEngine.hasConsumer(matching: { $0 is DiagnosticConsoleWriter }) {
290284
diagnosticEngine.add(
@@ -451,7 +445,7 @@ public struct ConvertAction: Action, RecreatingContext {
451445
benchmark(end: totalTimeMetric)
452446

453447
if !didEncounterError {
454-
let coverageResults = try coverageAction.perform(logHandle: logHandle)
448+
let coverageResults = try await coverageAction.perform(logHandle: &logHandle)
455449
postConversionProblems.append(contentsOf: coverageResults.problems)
456450
}
457451

Sources/SwiftDocCUtilities/Action/Actions/CoverageAction.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,24 @@ import Foundation
1212
import SwiftDocC
1313

1414
/// An action that creates documentation coverage info for a documentation bundle.
15-
public struct CoverageAction: Action {
16-
internal init(
15+
public struct CoverageAction: AsyncAction {
16+
init(
1717
documentationCoverageOptions: DocumentationCoverageOptions,
1818
workingDirectory: URL,
19-
fileManager: FileManagerProtocol) {
19+
fileManager: FileManagerProtocol
20+
) {
2021
self.documentationCoverageOptions = documentationCoverageOptions
2122
self.workingDirectory = workingDirectory
2223
self.fileManager = fileManager
2324
}
2425

2526
public let documentationCoverageOptions: DocumentationCoverageOptions
26-
internal let workingDirectory: URL
27+
let workingDirectory: URL
2728
private let fileManager: FileManagerProtocol
2829

29-
public mutating func perform(logHandle: LogHandle) throws -> ActionResult {
30+
public mutating func perform(logHandle: inout LogHandle) async throws -> ActionResult {
3031
switch documentationCoverageOptions.level {
3132
case .brief, .detailed:
32-
var logHandle = logHandle
3333
print(" --- Experimental coverage output enabled. ---", to: &logHandle)
3434

3535
let summaryString = try CoverageDataEntry.generateSummary(

Sources/SwiftDocCUtilities/Action/Actions/EmitGeneratedCurationAction.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import Foundation
1212
import SwiftDocC
1313

1414
/// An action that emits documentation extension files that reflect the auto-generated curation.
15-
struct EmitGeneratedCurationAction: Action {
15+
struct EmitGeneratedCurationAction: AsyncAction {
1616
let catalogURL: URL?
1717
let additionalSymbolGraphDirectory: URL?
1818
let outputURL: URL
@@ -41,7 +41,7 @@ struct EmitGeneratedCurationAction: Action {
4141
self.fileManager = fileManager
4242
}
4343

44-
mutating func perform(logHandle: LogHandle) throws -> ActionResult {
44+
mutating func perform(logHandle: inout LogHandle) async throws -> ActionResult {
4545
let workspace = DocumentationWorkspace()
4646
let context = try DocumentationContext(dataProvider: workspace)
4747

Sources/SwiftDocCUtilities/Action/Actions/IndexAction.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import Foundation
1212
import SwiftDocC
1313

1414
/// An action that creates an index of a documentation bundle.
15-
public struct IndexAction: Action {
15+
public struct IndexAction: AsyncAction {
1616
let rootURL: URL
1717
let outputURL: URL
1818
let bundleIdentifier: String
@@ -22,8 +22,7 @@ public struct IndexAction: Action {
2222
private var dataProvider: LocalFileSystemDataProvider!
2323

2424
/// Initializes the action with the given validated options, creates or uses the given action workspace & context.
25-
public init(documentationBundleURL: URL, outputURL: URL, bundleIdentifier: String, diagnosticEngine: DiagnosticEngine = .init()) throws
26-
{
25+
public init(documentationBundleURL: URL, outputURL: URL, bundleIdentifier: String, diagnosticEngine: DiagnosticEngine = .init()) throws {
2726
// Initialize the action context.
2827
self.rootURL = documentationBundleURL
2928
self.outputURL = outputURL
@@ -35,7 +34,7 @@ public struct IndexAction: Action {
3534

3635
/// Converts each eligible file from the source documentation bundle,
3736
/// saves the results in the given output alongside the template files.
38-
mutating public func perform(logHandle: LogHandle) throws -> ActionResult {
37+
mutating public func perform(logHandle: inout LogHandle) async throws -> ActionResult {
3938
let problems = try buildIndex()
4039
diagnosticEngine.emit(problems)
4140

Sources/SwiftDocCUtilities/Action/Actions/Init/InitAction.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import Foundation
1212
import SwiftDocC
1313

1414
/// An action that generates a documentation catalog from a template seed.
15-
public struct InitAction: Action {
16-
15+
public struct InitAction: AsyncAction {
16+
1717
enum Error: DescribedError {
1818
case catalogAlreadyExists
1919
var errorDescription: String {
@@ -72,8 +72,7 @@ public struct InitAction: Action {
7272
/// Generates a documentation catalog from a catalog template.
7373
///
7474
/// - Parameter logHandle: The file handle that the convert and preview actions will print debug messages to.
75-
public mutating func perform(logHandle: SwiftDocC.LogHandle) throws -> ActionResult {
76-
75+
public mutating func perform(logHandle: inout LogHandle) async throws -> ActionResult {
7776
let diagnosticEngine: DiagnosticEngine = DiagnosticEngine(treatWarningsAsErrors: false)
7877
diagnosticEngine.filterLevel = .warning
7978
diagnosticEngine.add(DiagnosticConsoleWriter(formattingOptions: []))

Sources/SwiftDocCUtilities/Action/Actions/Merge/MergeAction.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import SwiftDocC
1313
import Markdown
1414

1515
/// An action that merges a list of documentation archives into a combined archive.
16-
struct MergeAction: Action {
16+
struct MergeAction: AsyncAction {
1717
var archives: [URL]
1818
var landingPageInfo: LandingPageInfo
1919
var outputURL: URL
@@ -33,7 +33,7 @@ struct MergeAction: Action {
3333
}
3434
}
3535

36-
mutating func perform(logHandle: LogHandle) throws -> ActionResult {
36+
mutating func perform(logHandle: inout LogHandle) async throws -> ActionResult {
3737
guard let firstArchive = archives.first else {
3838
// A validation warning should have already been raised in `Docc/Merge/InputAndOutputOptions/validate()`.
3939
return ActionResult(didEncounterError: true, outputs: [])

Sources/SwiftDocCUtilities/Action/Actions/PreviewAction.swift

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ fileprivate func trapSignals() {
3333
}
3434

3535
/// An action that monitors a documentation bundle for changes and runs a live web-preview.
36-
public final class PreviewAction: Action, RecreatingContext {
36+
public final class PreviewAction: AsyncAction {
3737
/// A test configuration allowing running multiple previews for concurrent testing.
3838
static var allowConcurrentPreviews = false
3939

@@ -46,8 +46,7 @@ public final class PreviewAction: Action, RecreatingContext {
4646
let port: Int
4747

4848
var convertAction: ConvertAction
49-
50-
public var setupContext: ((inout DocumentationContext) -> Void)?
49+
5150
private var previewPaths: [String] = []
5251

5352
// Use for testing to override binding to a system port
@@ -96,7 +95,7 @@ public final class PreviewAction: Action, RecreatingContext {
9695
/// > Important: On macOS, the bundle will be converted each time the source is modified.
9796
///
9897
/// - Parameter logHandle: The file handle that the convert and preview actions will print debug messages to.
99-
public func perform(logHandle: LogHandle) throws -> ActionResult {
98+
public func perform(logHandle: inout LogHandle) async throws -> ActionResult {
10099
self.logHandle = logHandle
101100

102101
if let rootURL = convertAction.rootURL {
@@ -110,7 +109,7 @@ public final class PreviewAction: Action, RecreatingContext {
110109
print("Template: \(htmlTemplateDirectory.path)", to: &self.logHandle)
111110
}
112111

113-
let previewResult = try preview()
112+
let previewResult = try await preview()
114113
return ActionResult(didEncounterError: previewResult.didEncounterError, outputs: [convertAction.targetDirectory])
115114
}
116115

@@ -120,9 +119,9 @@ public final class PreviewAction: Action, RecreatingContext {
120119
servers.removeValue(forKey: serverIdentifier)
121120
}
122121

123-
func preview() throws -> ActionResult {
122+
func preview() async throws -> ActionResult {
124123
// Convert the documentation source for previewing.
125-
let result = try convert()
124+
let result = try await convert()
126125
guard !result.didEncounterError else {
127126
return result
128127
}
@@ -163,14 +162,13 @@ public final class PreviewAction: Action, RecreatingContext {
163162
return previewResult
164163
}
165164

166-
func convert() throws -> ActionResult {
165+
func convert() async throws -> ActionResult {
167166
// `cancel()` will throw `cancelPending` if there is already queued conversion.
168167
try convertAction.cancel()
169168

170169
convertAction = try createConvertAction()
171-
convertAction.setupContext = setupContext
172170

173-
let result = try convertAction.perform(logHandle: logHandle)
171+
let result = try await convertAction.perform(logHandle: &logHandle)
174172
previewPaths = try convertAction.context.previewPaths()
175173
return result
176174
}
@@ -198,6 +196,9 @@ extension PreviewAction {
198196
guard let rootURL = convertAction.rootURL else {
199197
return
200198
}
199+
200+
var convertTask: Task<Void, any Error>?
201+
201202
monitor = try DirectoryMonitor(root: rootURL) { _, folderURL in
202203
defer {
203204
// Reload the directory contents and start to monitor for changes.
@@ -209,23 +210,26 @@ extension PreviewAction {
209210
print(error.localizedDescription, to: &self.logHandle)
210211
}
211212
}
212-
213-
do {
214-
print("Source bundle was modified, converting... ", terminator: "", to: &self.logHandle)
215-
let result = try self.convert()
216-
if result.didEncounterError {
217-
throw ErrorsEncountered()
213+
214+
print("Source bundle was modified, converting... ", terminator: "", to: &self.logHandle)
215+
convertTask?.cancel()
216+
convertTask = Task {
217+
do {
218+
let result = try await self.convert()
219+
if result.didEncounterError {
220+
throw ErrorsEncountered()
221+
}
222+
print("Done.", to: &self.logHandle)
223+
} catch ConvertAction.Error.cancelPending {
224+
// `monitor.restart()` is already queueing a new convert action which will start when the previous one completes.
225+
// We can safely ignore the current action and just log to the console.
226+
print("\nConversion already in progress...", to: &self.logHandle)
227+
} catch DocumentationContext.ContextError.registrationDisabled {
228+
// The context cancelled loading the bundles and threw to yield execution early.
229+
print("\nConversion cancelled...", to: &self.logHandle)
230+
} catch {
231+
print("\n\(error.localizedDescription)\nCompilation failed", to: &self.logHandle)
218232
}
219-
print("Done.", to: &self.logHandle)
220-
} catch ConvertAction.Error.cancelPending {
221-
// `monitor.restart()` is already queueing a new convert action which will start when the previous one completes.
222-
// We can safely ignore the current action and just log to the console.
223-
print("\nConversion already in progress...", to: &self.logHandle)
224-
} catch DocumentationContext.ContextError.registrationDisabled {
225-
// The context cancelled loading the bundles and threw to yield execution early.
226-
print("\nConversion cancelled...", to: &self.logHandle)
227-
} catch {
228-
print("\n\(error.localizedDescription)\nCompilation failed", to: &self.logHandle)
229233
}
230234
}
231235
try monitor.start()

0 commit comments

Comments
 (0)