Skip to content

Commit 1606e73

Browse files
authored
Merge pull request #836 from ahoppen/ahoppen/build-system-actor
Migrate `BuildSystemManager` and all the build systems to be actors
2 parents 0d38b7c + 60961ce commit 1606e73

21 files changed

+854
-739
lines changed

Sources/LSPTestSupport/Assertions.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,27 @@ public func assertNoThrow<T>(
2222
XCTAssertNoThrow(try expression(), message(), file: file, line: line)
2323
}
2424

25+
/// Same as `XCTAssertThrows` but executes the trailing closure.
26+
public func assertThrowsError<T>(
27+
_ expression: @autoclosure () async throws -> T,
28+
_ message: @autoclosure () -> String = "",
29+
file: StaticString = #filePath,
30+
line: UInt = #line,
31+
_ errorHandler: (_ error: Error) -> Void = { _ in }
32+
) async {
33+
let didThrow: Bool
34+
do {
35+
_ = try await expression()
36+
didThrow = false
37+
} catch {
38+
errorHandler(error)
39+
didThrow = true
40+
}
41+
if !didThrow {
42+
XCTFail("Expression was expected to throw but did not throw", file: file, line: line)
43+
}
44+
}
45+
2546
/// Same as `XCTAssertEqual` but doesn't take autoclosures and thus `expression1`
2647
/// and `expression2` can contain `await`.
2748
public func assertEqual<T: Equatable>(
@@ -34,6 +55,28 @@ public func assertEqual<T: Equatable>(
3455
XCTAssertEqual(expression1, expression2, message(), file: file, line: line)
3556
}
3657

58+
/// Same as `XCTAssertNil` but doesn't take autoclosures and thus `expression`
59+
/// can contain `await`.
60+
public func assertNil<T: Equatable>(
61+
_ expression: T?,
62+
_ message: @autoclosure () -> String = "",
63+
file: StaticString = #filePath,
64+
line: UInt = #line
65+
) {
66+
XCTAssertNil(expression, message(), file: file, line: line)
67+
}
68+
69+
/// Same as `XCTAssertNotNil` but doesn't take autoclosures and thus `expression`
70+
/// can contain `await`.
71+
public func assertNotNil<T: Equatable>(
72+
_ expression: T?,
73+
_ message: @autoclosure () -> String = "",
74+
file: StaticString = #filePath,
75+
line: UInt = #line
76+
) {
77+
XCTAssertNotNil(expression, message(), file: file, line: line)
78+
}
79+
3780
extension XCTestCase {
3881
private struct ExpectationNotFulfilledError: Error, CustomStringConvertible {
3982
var expecatations: [XCTestExpectation]

Sources/SKCore/BuildServerBuildSystem.swift

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,7 @@ func executable(_ name: String) -> String {
4343
///
4444
/// Provides build settings from a build server launched based on a
4545
/// `buildServer.json` configuration file provided in the repo root.
46-
public final class BuildServerBuildSystem: MessageHandler {
47-
/// The handler's request queue. `delegate` will always be called on this queue.
48-
public let queue: DispatchQueue = DispatchQueue(label: "language-server-queue", qos: .userInitiated)
49-
46+
public actor BuildServerBuildSystem: MessageHandler {
5047
let projectRoot: AbsolutePath
5148
let buildFolder: AbsolutePath?
5249
let serverConfig: BuildServerConfig
@@ -65,10 +62,15 @@ public final class BuildServerBuildSystem: MessageHandler {
6562
/// Delegate to handle any build system events.
6663
public weak var delegate: BuildSystemDelegate?
6764

65+
/// - Note: Needed to set the delegate from a different actor isolation context
66+
public func setDelegate(_ delegate: BuildSystemDelegate?) async {
67+
self.delegate = delegate
68+
}
69+
6870
/// The build settings that have been received from the build server.
6971
private var buildSettings: [DocumentURI: FileBuildSettings] = [:]
7072

71-
public init(projectRoot: AbsolutePath, buildFolder: AbsolutePath?, fileSystem: FileSystem = localFileSystem) throws {
73+
public init(projectRoot: AbsolutePath, buildFolder: AbsolutePath?, fileSystem: FileSystem = localFileSystem) async throws {
7274
let configPath = projectRoot.appending(component: "buildServer.json")
7375
let config = try loadBuildServerConfig(path: configPath, fileSystem: fileSystem)
7476
#if os(Windows)
@@ -90,12 +92,11 @@ public final class BuildServerBuildSystem: MessageHandler {
9092
/// Creates a build system using the Build Server Protocol config.
9193
///
9294
/// - Returns: nil if `projectRoot` has no config or there is an error parsing it.
93-
public convenience init?(projectRoot: AbsolutePath?, buildSetup: BuildSetup)
94-
{
95+
public init?(projectRoot: AbsolutePath?, buildSetup: BuildSetup) async {
9596
if projectRoot == nil { return nil }
9697

9798
do {
98-
try self.init(projectRoot: projectRoot!, buildFolder: buildSetup.path)
99+
try await self.init(projectRoot: projectRoot!, buildFolder: buildSetup.path)
99100
} catch _ as FileSystemError {
100101
// config file was missing, no build server for this workspace
101102
return nil
@@ -166,13 +167,11 @@ public final class BuildServerBuildSystem: MessageHandler {
166167
/// the build server has sent us a notification.
167168
///
168169
/// We need to notify the delegate about any updated build settings.
169-
public func handle(_ params: some NotificationType, from clientID: ObjectIdentifier) {
170-
queue.async {
171-
if let params = params as? BuildTargetsChangedNotification {
172-
self.handleBuildTargetsChanged(Notification(params, clientID: clientID))
173-
} else if let params = params as? FileOptionsChangedNotification {
174-
self.handleFileOptionsChanged(Notification(params, clientID: clientID))
175-
}
170+
public func handle(_ params: some NotificationType, from clientID: ObjectIdentifier) async {
171+
if let params = params as? BuildTargetsChangedNotification {
172+
await self.handleBuildTargetsChanged(Notification(params, clientID: clientID))
173+
} else if let params = params as? FileOptionsChangedNotification {
174+
await self.handleFileOptionsChanged(Notification(params, clientID: clientID))
176175
}
177176
}
178177

@@ -185,30 +184,28 @@ public final class BuildServerBuildSystem: MessageHandler {
185184
from clientID: ObjectIdentifier,
186185
reply: @escaping (LSPResult<R.Response>) -> Void
187186
) {
188-
queue.async {
189-
reply(.failure(ResponseError.methodNotFound(R.method)))
190-
}
187+
reply(.failure(ResponseError.methodNotFound(R.method)))
191188
}
192189

193-
func handleBuildTargetsChanged(_ notification: LanguageServerProtocol.Notification<BuildTargetsChangedNotification>) {
194-
self.delegate?.buildTargetsChanged(notification.params.changes)
190+
func handleBuildTargetsChanged(_ notification: LanguageServerProtocol.Notification<BuildTargetsChangedNotification>) async {
191+
await self.delegate?.buildTargetsChanged(notification.params.changes)
195192
}
196193

197-
func handleFileOptionsChanged(_ notification: LanguageServerProtocol.Notification<FileOptionsChangedNotification>) {
194+
func handleFileOptionsChanged(_ notification: LanguageServerProtocol.Notification<FileOptionsChangedNotification>) async {
198195
let result = notification.params.updatedOptions
199196
let settings = FileBuildSettings(
200197
compilerArguments: result.options, workingDirectory: result.workingDirectory)
201-
self.buildSettingsChanged(for: notification.params.uri, settings: settings)
198+
await self.buildSettingsChanged(for: notification.params.uri, settings: settings)
202199
}
203200

204201
/// Record the new build settings for the given document and inform the delegate
205202
/// about the changed build settings.
206-
private func buildSettingsChanged(for document: DocumentURI, settings: FileBuildSettings?) {
203+
private func buildSettingsChanged(for document: DocumentURI, settings: FileBuildSettings?) async {
207204
buildSettings[document] = settings
208205
if let settings {
209-
self.delegate?.fileBuildSettingsChanged([document: .modified(settings)])
206+
await self.delegate?.fileBuildSettingsChanged([document: .modified(settings)])
210207
} else {
211-
self.delegate?.fileBuildSettingsChanged([document: .removedOrUnavailable])
208+
await self.delegate?.fileBuildSettingsChanged([document: .removedOrUnavailable])
212209
}
213210
}
214211
}
@@ -234,12 +231,14 @@ extension BuildServerBuildSystem: BuildSystem {
234231
public func registerForChangeNotifications(for uri: DocumentURI, language: Language) {
235232
let request = RegisterForChanges(uri: uri, action: .register)
236233
_ = self.buildServer?.send(request, queue: requestQueue, reply: { result in
237-
if let error = result.failure {
238-
log("error registering \(uri): \(error)", level: .error)
239-
240-
// BuildServer registration failed, so tell our delegate that no build
241-
// settings are available.
242-
self.buildSettingsChanged(for: uri, settings: nil)
234+
Task {
235+
if let error = result.failure {
236+
log("error registering \(uri): \(error)", level: .error)
237+
238+
// BuildServer registration failed, so tell our delegate that no build
239+
// settings are available.
240+
await self.buildSettingsChanged(for: uri, settings: nil)
241+
}
243242
}
244243
})
245244
}

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/BuildSystemDelegate.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,25 @@ public protocol BuildSystemDelegate: AnyObject {
1818
///
1919
/// The callee should request new sources and outputs for the build targets of
2020
/// interest.
21-
func buildTargetsChanged(_ changes: [BuildTargetEvent])
21+
func buildTargetsChanged(_ changes: [BuildTargetEvent]) async
2222

2323
/// Notify the delegate that the given files' build settings have changed.
2424
///
2525
/// The delegate should cache the new build settings for any of the given
2626
/// files that they are interested in.
2727
func fileBuildSettingsChanged(
28-
_ changedFiles: [DocumentURI: FileBuildSettingsChange])
28+
_ changedFiles: [DocumentURI: FileBuildSettingsChange]) async
2929

3030
/// Notify the delegate that the dependencies of the given files have changed
3131
/// and that ASTs may need to be refreshed. If the given set is empty, assume
3232
/// that all watched files are affected.
3333
///
3434
/// The callee should refresh ASTs unless it is able to determine that a
3535
/// refresh is not necessary.
36-
func filesDependenciesUpdated(_ changedFiles: Set<DocumentURI>)
36+
func filesDependenciesUpdated(_ changedFiles: Set<DocumentURI>) async
3737

3838
/// Notify the delegate that the file handling capability of this build system
3939
/// for some file has changed. The delegate should discard any cached file
4040
/// handling capability.
41-
func fileHandlingCapabilityChanged()
41+
func fileHandlingCapabilityChanged() async
4242
}

0 commit comments

Comments
 (0)