Skip to content

Commit e663bbc

Browse files
committed
Make all methods on BuildSystem async
This is the prerequisite for making the build systems actors.
1 parent 5e37b32 commit e663bbc

14 files changed

+208
-93
lines changed

Sources/SKCore/BuildServerBuildSystem.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ public final class BuildServerBuildSystem: MessageHandler {
6565
/// Delegate to handle any build system events.
6666
public weak var delegate: BuildSystemDelegate?
6767

68+
/// - Note: Needed to set the delegate from a different actor isolation context
69+
public func setDelegate(_ delegate: BuildSystemDelegate?) async {
70+
self.delegate = delegate
71+
}
72+
6873
/// The build settings that have been received from the build server.
6974
private var buildSettings: [DocumentURI: FileBuildSettings] = [:]
7075

Sources/SKCore/BuildSystem.swift

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,23 @@ public enum FileHandlingCapability: Comparable {
3939
public protocol BuildSystem: AnyObject {
4040

4141
/// The path to the raw index store data, if any.
42-
var indexStorePath: AbsolutePath? { get }
42+
var indexStorePath: AbsolutePath? { get async }
4343

4444
/// The path to put the index database, if any.
45-
var indexDatabasePath: AbsolutePath? { get }
45+
var indexDatabasePath: AbsolutePath? { get async }
4646

4747
/// Path remappings for remapping index data for local use.
48-
var indexPrefixMappings: [PathPrefixMapping] { get }
48+
var indexPrefixMappings: [PathPrefixMapping] { get async }
4949

5050
/// Delegate to handle any build system events such as file build settings
5151
/// initial reports as well as changes.
52-
var delegate: BuildSystemDelegate? { get set }
52+
var delegate: BuildSystemDelegate? { get async }
53+
54+
/// Set the build system's delegate.
55+
///
56+
/// - Note: Needed so we can set the delegate from a different actor isolation
57+
/// context.
58+
func setDelegate(_ delegate: BuildSystemDelegate?) async
5359

5460
/// Retrieve build settings for the given document with the given source
5561
/// language.
@@ -64,16 +70,16 @@ public protocol BuildSystem: AnyObject {
6470
/// IMPORTANT: When first receiving a register request, the `BuildSystem` MUST asynchronously
6571
/// inform its delegate of any initial settings for the given file via the
6672
/// `fileBuildSettingsChanged` method, even if unavailable.
67-
func registerForChangeNotifications(for: DocumentURI, language: Language)
73+
func registerForChangeNotifications(for: DocumentURI, language: Language) async
6874

6975
/// Unregister the given file for build-system level change notifications,
7076
/// such as command line flag changes, dependency changes, etc.
71-
func unregisterForChangeNotifications(for: DocumentURI)
77+
func unregisterForChangeNotifications(for: DocumentURI) async
7278

7379
/// Called when files in the project change.
74-
func filesDidChange(_ events: [FileEvent])
80+
func filesDidChange(_ events: [FileEvent]) async
7581

76-
func fileHandlingCapability(for uri: DocumentURI) -> FileHandlingCapability
82+
func fileHandlingCapability(for uri: DocumentURI) async -> FileHandlingCapability
7783
}
7884

7985
public let buildTargetsNotSupported =

Sources/SKCore/BuildSystemManager.swift

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -111,17 +111,18 @@ public actor BuildSystemManager {
111111
/// Create a BuildSystemManager that wraps the given build system. The new
112112
/// manager will modify the delegate of the underlying build system.
113113
public init(buildSystem: BuildSystem?, fallbackBuildSystem: FallbackBuildSystem?,
114-
mainFilesProvider: MainFilesProvider?, fallbackSettingsTimeout: DispatchTimeInterval = .seconds(3)) {
115-
precondition(buildSystem?.delegate == nil)
114+
mainFilesProvider: MainFilesProvider?, fallbackSettingsTimeout: DispatchTimeInterval = .seconds(3)) async {
115+
let buildSystemHasDelegate = await buildSystem?.delegate != nil
116+
precondition(!buildSystemHasDelegate)
116117
self.buildSystem = buildSystem
117118
self.fallbackBuildSystem = fallbackBuildSystem
118119
self._mainFilesProvider = mainFilesProvider
119120
self.fallbackSettingsTimeout = fallbackSettingsTimeout
120-
self.buildSystem?.delegate = self
121+
await self.buildSystem?.setDelegate(self)
121122
}
122123

123-
public func filesDidChange(_ events: [FileEvent]) {
124-
self.buildSystem?.filesDidChange(events)
124+
public func filesDidChange(_ events: [FileEvent]) async {
125+
await self.buildSystem?.filesDidChange(events)
125126
self.fallbackBuildSystem?.filesDidChange(events)
126127
}
127128
}
@@ -182,7 +183,7 @@ extension BuildSystemManager {
182183
}
183184
}
184185

185-
public func registerForChangeNotifications(for uri: DocumentURI, language: Language) {
186+
public func registerForChangeNotifications(for uri: DocumentURI, language: Language) async {
186187
log("registerForChangeNotifications(\(uri.pseudoPath))")
187188
let mainFile: DocumentURI
188189

@@ -194,7 +195,7 @@ extension BuildSystemManager {
194195
self.watchedFiles[uri] = (mainFile, language)
195196
}
196197

197-
let newStatus = self.cachedStatusOrRegisterForSettings(for: mainFile, language: language)
198+
let newStatus = await self.cachedStatusOrRegisterForSettings(for: mainFile, language: language)
198199

199200
if let mainChange = newStatus.buildSettingsChange,
200201
let delegate = self._delegate {
@@ -206,7 +207,7 @@ extension BuildSystemManager {
206207
/// Return settings for `file` based on the `change` settings for `mainFile`.
207208
///
208209
/// This is used when inferring arguments for header files (e.g. main file is a `.m` while file is a` .h`).
209-
func convert(
210+
nonisolated func convert(
210211
change: FileBuildSettingsChange,
211212
ofMainFile mainFile: DocumentURI,
212213
to file: DocumentURI
@@ -227,7 +228,7 @@ extension BuildSystemManager {
227228
func cachedStatusOrRegisterForSettings(
228229
for mainFile: DocumentURI,
229230
language: Language
230-
) -> MainFileStatus {
231+
) async -> MainFileStatus {
231232
// If we already have a status for the main file, use that.
232233
// Don't update any existing timeout.
233234
if let status = self.mainFileStatuses[mainFile] {
@@ -248,7 +249,7 @@ extension BuildSystemManager {
248249
// Intentionally register with the `BuildSystem` after setting the fallback to allow for
249250
// testing of the fallback system triggering before the `BuildSystem` can reply (e.g. if a
250251
// fallback time of 0 is specified).
251-
buildSystem.registerForChangeNotifications(for: mainFile, language: language)
252+
await buildSystem.registerForChangeNotifications(for: mainFile, language: language)
252253

253254

254255
newStatus = .waiting
@@ -263,6 +264,16 @@ extension BuildSystemManager {
263264
} else { // Don't have any build systems.
264265
newStatus = .unsupported
265266
}
267+
268+
if let status = self.mainFileStatuses[mainFile] {
269+
// Since we await above, another call to `cachedStatusOrRegisterForSettings`
270+
// might have set the main file status of `mainFile`. If this race happened,
271+
// return the value set by the concurrently executing function. This is safe
272+
// since all calls from this function are either side-effect free or
273+
// idempotent.
274+
return status
275+
}
276+
266277
self.mainFileStatuses[mainFile] = newStatus
267278
return newStatus
268279
}
@@ -321,27 +332,30 @@ extension BuildSystemManager {
321332
}
322333
}
323334

324-
public func unregisterForChangeNotifications(for uri: DocumentURI) {
335+
public func unregisterForChangeNotifications(for uri: DocumentURI) async {
325336
guard let mainFile = self.watchedFiles[uri]?.mainFile else {
326337
log("Unbalanced calls for registerForChangeNotifications and unregisterForChangeNotifications", level: .warning)
327338
return
328339
}
329340
self.watchedFiles[uri] = nil
330-
self.checkUnreferencedMainFile(mainFile)
341+
await self.checkUnreferencedMainFile(mainFile)
331342
}
332343

333344
/// If the given main file is no longer referenced by any watched files,
334345
/// remove it and unregister it at the underlying build system.
335-
func checkUnreferencedMainFile(_ mainFile: DocumentURI) {
346+
func checkUnreferencedMainFile(_ mainFile: DocumentURI) async {
336347
if !self.watchedFiles.values.lazy.map({ $0.mainFile }).contains(mainFile) {
337348
// This was the last reference to the main file. Remove it.
338-
self.buildSystem?.unregisterForChangeNotifications(for: mainFile)
349+
await self.buildSystem?.unregisterForChangeNotifications(for: mainFile)
339350
self.mainFileStatuses[mainFile] = nil
340351
}
341352
}
342353

343-
public nonisolated func fileHandlingCapability(for uri: DocumentURI) -> FileHandlingCapability {
344-
return max(buildSystem?.fileHandlingCapability(for: uri) ?? .unhandled, fallbackBuildSystem?.fileHandlingCapability(for: uri) ?? .unhandled)
354+
public func fileHandlingCapability(for uri: DocumentURI) async -> FileHandlingCapability {
355+
return max(
356+
await buildSystem?.fileHandlingCapability(for: uri) ?? .unhandled,
357+
fallbackBuildSystem?.fileHandlingCapability(for: uri) ?? .unhandled
358+
)
345359
}
346360
}
347361

@@ -444,7 +458,7 @@ extension BuildSystemManager: MainFilesDelegate {
444458
}
445459

446460
// FIXME: Consider debouncing/limiting this, seems to trigger often during a build.
447-
public func mainFilesChangedImpl() {
461+
public func mainFilesChangedImpl() async {
448462
let origWatched = self.watchedFiles
449463
self.watchedFiles = [:]
450464
var buildSettingsChanges = [DocumentURI: FileBuildSettingsChange]()
@@ -458,9 +472,9 @@ extension BuildSystemManager: MainFilesDelegate {
458472

459473
if state.mainFile != newMainFile {
460474
log("main file for '\(uri)' changed old: '\(state.mainFile)' -> new: '\(newMainFile)'", level: .info)
461-
self.checkUnreferencedMainFile(state.mainFile)
475+
await self.checkUnreferencedMainFile(state.mainFile)
462476

463-
let newStatus = self.cachedStatusOrRegisterForSettings(
477+
let newStatus = await self.cachedStatusOrRegisterForSettings(
464478
for: newMainFile, language: language)
465479
if let change = newStatus.buildSettingsChange {
466480
let newChange = self.convert(change: change, ofMainFile: newMainFile, to: uri)

Sources/SKCore/CompilationDatabaseBuildSystem.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ public final class CompilationDatabaseBuildSystem {
4646
/// Delegate to handle any build system events.
4747
public weak var delegate: BuildSystemDelegate? = nil
4848

49+
public func setDelegate(_ delegate: BuildSystemDelegate?) async {
50+
self.delegate = delegate
51+
}
52+
4953
let projectRoot: AbsolutePath?
5054

5155
let fileSystem: FileSystem

Sources/SKCore/FallbackBuildSystem.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ public final class FallbackBuildSystem: BuildSystem {
3838
/// Delegate to handle any build system events.
3939
public weak var delegate: BuildSystemDelegate? = nil
4040

41+
public func setDelegate(_ delegate: BuildSystemDelegate?) async {
42+
self.delegate = delegate
43+
}
44+
4145
public var indexStorePath: AbsolutePath? { return nil }
4246

4347
public var indexDatabasePath: AbsolutePath? { return nil }

Sources/SKSwiftPMWorkspace/SwiftPMWorkspace.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ public final class SwiftPMWorkspace {
6262
/// Delegate to handle any build system events.
6363
public weak var delegate: SKCore.BuildSystemDelegate? = nil
6464

65+
public func setDelegate(_ delegate: SKCore.BuildSystemDelegate?) async {
66+
self.delegate = delegate
67+
}
68+
6569
let workspacePath: TSCAbsolutePath
6670
let packageRoot: TSCAbsolutePath
6771
/// *Public for testing*

Sources/SKTestSupport/SKSwiftPMTestWorkspace.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public final class SKSwiftPMTestWorkspace {
104104
listenToUnitEvents: false)
105105

106106
let server = self.testServer.server!
107-
let workspace = Workspace(
107+
let workspace = await Workspace(
108108
documentManager: DocumentManager(),
109109
rootUri: DocumentURI(sources.rootDirectory),
110110
capabilityRegistry: CapabilityRegistry(clientCapabilities: ClientCapabilities()),

Sources/SKTestSupport/SKTibsTestWorkspace.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public final class SKTibsTestWorkspace {
8787
let indexDelegate = SourceKitIndexDelegate()
8888
tibsWorkspace.delegate = indexDelegate
8989

90-
let workspace = Workspace(
90+
let workspace = await Workspace(
9191
documentManager: DocumentManager(),
9292
rootUri: DocumentURI(sources.rootDirectory),
9393
capabilityRegistry: CapabilityRegistry(clientCapabilities: clientCapabilities),

Sources/SourceKitLSP/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ add_library(SourceKitLSP STATIC
55
DocumentTokens.swift
66
IndexStoreDB+MainFilesProvider.swift
77
RangeAdjuster.swift
8+
Sequence+AsyncMap.swift
89
SourceKitIndexDelegate.swift
910
SourceKitLSPCommandMetadata.swift
1011
SourceKitServer+Options.swift
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
extension Sequence {
14+
/// Just like `Sequence.map` but allows an `async` transform function.
15+
func asyncMap<T>(
16+
_ transform: (Element) async throws -> T
17+
) async rethrows -> [T] {
18+
var result: [T] = []
19+
result.reserveCapacity(self.underestimatedCount)
20+
21+
for element in self {
22+
try await result.append(transform(element))
23+
}
24+
25+
return result
26+
}
27+
28+
/// Just like `Sequence.compactMap` but allows an `async` transform function.
29+
func asyncCompactMap<T>(
30+
_ transform: (Element) async throws -> T?
31+
) async rethrows -> [T] {
32+
var result: [T] = []
33+
34+
for element in self {
35+
if let transformed = try await transform(element) {
36+
result.append(transformed)
37+
}
38+
}
39+
40+
return result
41+
}
42+
}
43+

0 commit comments

Comments
 (0)