Skip to content

Commit 9334bb7

Browse files
authored
Merge pull request #1668 from ahoppen/connection-based-bsp
Use a `LocalConnection` to communicate the BSP messages between SourceKit-LSP and the build system
2 parents fe1363c + 7d11825 commit 9334bb7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+711
-559
lines changed

Package.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,10 +252,10 @@ let package = Package(
252252
.target(
253253
name: "SKSupport",
254254
dependencies: [
255-
"CAtomics",
256255
"LanguageServerProtocol",
257256
"LanguageServerProtocolJSONRPC",
258257
"SKLogging",
258+
"SourceKitD",
259259
"SwiftExtensions",
260260
.product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"),
261261
],
@@ -378,6 +378,7 @@ let package = Package(
378378

379379
.target(
380380
name: "SwiftExtensions",
381+
dependencies: ["CAtomics"],
381382
exclude: ["CMakeLists.txt"]
382383
),
383384

Sources/BuildServerProtocol/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ add_library(BuildServerProtocol STATIC
1111
RegisterForChangeNotifications.swift
1212
ShutdownBuild.swift
1313
SourceKitOptionsRequest.swift
14-
WaitForBuildSystemUpdates.swift)
14+
WaitForBuildSystemUpdates.swift
15+
WorkDoneProgress.swift)
1516
set_target_properties(BuildServerProtocol PROPERTIES
1617
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})
1718
target_link_libraries(BuildServerProtocol PRIVATE

Sources/SourceKitLSP/ResponseError+Init.swift renamed to Sources/BuildServerProtocol/WorkDoneProgress.swift

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -11,19 +11,6 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import LanguageServerProtocol
14-
import SourceKitD
1514

16-
extension ResponseError {
17-
package init(_ error: some Error) {
18-
switch error {
19-
case let error as ResponseError:
20-
self = error
21-
case let error as SKDError:
22-
self.init(error)
23-
case is CancellationError:
24-
self = .cancelled
25-
default:
26-
self = .unknown("Unknown error: \(error)")
27-
}
28-
}
29-
}
15+
public typealias CreateWorkDoneProgressRequest = LanguageServerProtocol.CreateWorkDoneProgressRequest
16+
public typealias WorkDoneProgress = LanguageServerProtocol.WorkDoneProgress

Sources/BuildSystemIntegration/BuildServerBuildSystem.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ package actor BuildServerBuildSystem: MessageHandler {
6767
package private(set) var indexDatabasePath: AbsolutePath?
6868
package private(set) var indexStorePath: AbsolutePath?
6969

70-
package weak var messageHandler: BuiltInBuildSystemMessageHandler?
70+
package let connectionToSourceKitLSP: any Connection
7171

7272
/// The build settings that have been received from the build server.
7373
private var buildSettings: [DocumentURI: SourceKitOptionsResponse] = [:]
@@ -76,7 +76,7 @@ package actor BuildServerBuildSystem: MessageHandler {
7676

7777
package init(
7878
projectRoot: AbsolutePath,
79-
messageHandler: BuiltInBuildSystemMessageHandler?,
79+
connectionToSourceKitLSP: any Connection,
8080
fileSystem: FileSystem = localFileSystem
8181
) async throws {
8282
let configPath = projectRoot.appending(component: "buildServer.json")
@@ -96,18 +96,18 @@ package actor BuildServerBuildSystem: MessageHandler {
9696
#endif
9797
self.projectRoot = projectRoot
9898
self.serverConfig = config
99-
self.messageHandler = messageHandler
99+
self.connectionToSourceKitLSP = connectionToSourceKitLSP
100100
try await self.initializeBuildServer()
101101
}
102102

103103
/// Creates a build system using the Build Server Protocol config.
104104
///
105105
/// - Returns: nil if `projectRoot` has no config or there is an error parsing it.
106-
package init?(projectRoot: AbsolutePath?, messageHandler: BuiltInBuildSystemMessageHandler?) async {
106+
package init?(projectRoot: AbsolutePath?, connectionToSourceKitLSP: any Connection) async {
107107
guard let projectRoot else { return nil }
108108

109109
do {
110-
try await self.init(projectRoot: projectRoot, messageHandler: messageHandler)
110+
try await self.init(projectRoot: projectRoot, connectionToSourceKitLSP: connectionToSourceKitLSP)
111111
} catch is FileSystemError {
112112
// config file was missing, no build server for this workspace
113113
return nil
@@ -218,8 +218,8 @@ package actor BuildServerBuildSystem: MessageHandler {
218218
reply(.failure(ResponseError.methodNotFound(R.method)))
219219
}
220220

221-
func handleBuildTargetsChanged(_ notification: DidChangeBuildTargetNotification) async {
222-
await self.messageHandler?.sendNotificationToSourceKitLSP(notification)
221+
func handleBuildTargetsChanged(_ notification: DidChangeBuildTargetNotification) {
222+
connectionToSourceKitLSP.send(notification)
223223
}
224224

225225
func handleFileOptionsChanged(_ notification: FileOptionsChangedNotification) async {
@@ -238,7 +238,7 @@ package actor BuildServerBuildSystem: MessageHandler {
238238
// FIXME: (BSP migration) When running in the legacy mode where teh BSP server pushes build settings to us, we could
239239
// consider having a separate target for each source file so that we can update individual targets instead of having
240240
// to send an update for all targets.
241-
await self.messageHandler?.sendNotificationToSourceKitLSP(DidChangeBuildTargetNotification(changes: nil))
241+
connectionToSourceKitLSP.send(DidChangeBuildTargetNotification(changes: nil))
242242
}
243243
}
244244

Sources/BuildSystemIntegration/BuildSystemDelegate.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,20 @@ package protocol BuildSystemManagerDelegate: AnyObject, Sendable {
3131
func buildTargetsChanged(_ changes: [BuildTargetEvent]?) async
3232

3333
/// Log the given message to the client's index log.
34+
// FIXME: (BSP Migration) Use `sendNotificationToClient`
3435
func logMessageToIndexLog(taskID: IndexTaskID, message: String)
3536

37+
/// Whether the client can handle `WorkDoneProgress` requests.
38+
var clientSupportsWorkDoneProgress: Bool { get async }
39+
40+
func sendNotificationToClient(_ notification: some NotificationType)
41+
42+
func sendRequestToClient<R: RequestType>(_ request: R) async throws -> R.Response
43+
44+
/// Wait until the connection to the client has been initialized, ie. wait until `SourceKitLSPServer` has replied
45+
/// to the `initialize` request.
46+
func waitUntilInitialized() async
47+
3648
/// Notify the delegate that the list of source files in the build system might have changed.
3749
func sourceFilesDidChange() async
3850
}

Sources/BuildSystemIntegration/BuildSystemManager.swift

Lines changed: 74 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import BuildServerProtocol
1414
import Dispatch
15+
import Foundation
1516
import LanguageServerProtocol
1617
import SKLogging
1718
import SKOptions
@@ -69,7 +70,9 @@ fileprivate class RequestCache<Request: RequestType & Hashable, Result: Sendable
6970
/// Since some `BuildSystem`s may require a bit of a time to compute their arguments asynchronously,
7071
/// this class has a configurable `buildSettings` timeout which denotes the amount of time to give
7172
/// the build system before applying the fallback arguments.
72-
package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
73+
package actor BuildSystemManager: QueueBasedMessageHandler {
74+
package static let signpostLoggingCategory: String = "build-system-manager-message-handling"
75+
7376
/// The files for which the delegate has requested change notifications, ie.
7477
/// the files for which the delegate wants to get `filesDependenciesUpdated`
7578
/// callbacks if the file's build settings.
@@ -141,25 +144,35 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
141144
}
142145
}
143146

147+
private let connectionFromBuildSystemToSourceKitLSP: LocalConnection
148+
private var connectionToBuildSystem: LocalConnection?
149+
144150
package init(
145151
buildSystemKind: BuildSystemKind?,
146152
toolchainRegistry: ToolchainRegistry,
147153
options: SourceKitLSPOptions,
148-
swiftpmTestHooks: SwiftPMTestHooks,
149-
reloadPackageStatusCallback: @Sendable @escaping (ReloadPackageStatus) async -> Void
154+
buildSystemTestHooks: BuildSystemTestHooks
150155
) async {
151156
self.fallbackBuildSystem = FallbackBuildSystem(options: options.fallbackBuildSystemOrDefault)
152157
self.toolchainRegistry = toolchainRegistry
153158
self.options = options
154159
self.projectRoot = buildSystemKind?.projectRoot
160+
connectionFromBuildSystemToSourceKitLSP = LocalConnection(receiverName: "BuildSystemManager")
161+
connectionFromBuildSystemToSourceKitLSP.start(handler: self)
155162
self.buildSystem = await BuiltInBuildSystemAdapter(
156163
buildSystemKind: buildSystemKind,
157164
toolchainRegistry: toolchainRegistry,
158165
options: options,
159-
swiftpmTestHooks: swiftpmTestHooks,
160-
reloadPackageStatusCallback: reloadPackageStatusCallback,
161-
messageHandler: self
166+
buildSystemTestHooks: buildSystemTestHooks,
167+
connectionToSourceKitLSP: connectionFromBuildSystemToSourceKitLSP
162168
)
169+
if let buildSystem {
170+
let connectionFromSourceKitLSPToBuildSystem = LocalConnection(receiverName: "\(type(of: buildSystem))")
171+
connectionFromSourceKitLSPToBuildSystem.start(handler: buildSystem)
172+
self.connectionToBuildSystem = connectionFromSourceKitLSPToBuildSystem
173+
} else {
174+
self.connectionToBuildSystem = nil
175+
}
163176
// The debounce duration of 500ms was chosen arbitrarily without any measurements.
164177
self.filesDependenciesUpdatedDebouncer = Debouncer(
165178
debounceDuration: .milliseconds(500),
@@ -179,15 +192,15 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
179192

180193
// FIXME: (BSP migration) Forward file watch patterns from this initialize request to the client
181194
initializeResult = Task { () -> InitializeBuildResponse? in
182-
guard let buildSystem else {
195+
guard let connectionToBuildSystem else {
183196
return nil
184197
}
185198
guard let buildSystemKind else {
186199
logger.fault("Created build system without a build system kind?")
187200
return nil
188201
}
189202
return await orLog("Initializing build system") {
190-
try await buildSystem.send(
203+
try await connectionToBuildSystem.send(
191204
InitializeBuildRequest(
192205
displayName: "SourceKit-LSP",
193206
version: "unknown",
@@ -200,8 +213,13 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
200213
}
201214
}
202215

216+
deinit {
217+
connectionFromBuildSystemToSourceKitLSP.close()
218+
connectionToBuildSystem?.close()
219+
}
220+
203221
package func filesDidChange(_ events: [FileEvent]) async {
204-
await self.buildSystem?.send(BuildServerProtocol.DidChangeWatchedFilesNotification(changes: events))
222+
connectionToBuildSystem?.send(BuildServerProtocol.DidChangeWatchedFilesNotification(changes: events))
205223

206224
var targetsWithUpdatedDependencies: Set<BuildTargetIdentifier> = []
207225
// If a Swift file within a target is updated, reload all the other files within the target since they might be
@@ -245,25 +263,51 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
245263
await self.filesDependenciesUpdatedDebouncer.scheduleCall(filesWithUpdatedDependencies)
246264
}
247265

248-
/// Implementation of `MessageHandler`, handling notifications from the build system.
249-
///
250-
/// - Important: Do not call directly.
251-
package func handle(_ notification: some LanguageServerProtocol.NotificationType) async {
266+
// FIXME: (BSP Migration) Can we use more fine-grained dependency tracking here?
267+
package let messageHandlingQueue = AsyncQueue<Serial>()
268+
269+
package func handleImpl(_ notification: some NotificationType) async {
252270
switch notification {
253271
case let notification as DidChangeBuildTargetNotification:
254272
await self.didChangeBuildTarget(notification: notification)
255273
case let notification as BuildServerProtocol.LogMessageNotification:
256274
await self.logMessage(notification: notification)
275+
case let notification as BuildServerProtocol.WorkDoneProgress:
276+
await self.workDoneProgress(notification: notification)
257277
default:
258278
logger.error("Ignoring unknown notification \(type(of: notification).method)")
259279
}
260280
}
261281

262-
/// Implementation of `MessageHandler`, handling requests from the build system.
263-
///
264-
/// - Important: Do not call directly.
265-
package nonisolated func handle<R: RequestType>(_ request: R) async throws -> R.Response {
266-
throw ResponseError.methodNotFound(R.method)
282+
package func handleImpl<Request: RequestType>(_ request: RequestAndReply<Request>) async {
283+
switch request {
284+
case let request as RequestAndReply<BuildServerProtocol.CreateWorkDoneProgressRequest>:
285+
await request.reply { try await self.createWorkDoneProgress(request: request.params) }
286+
default:
287+
await request.reply { throw ResponseError.methodNotFound(Request.method) }
288+
}
289+
}
290+
291+
private func createWorkDoneProgress(
292+
request: BuildServerProtocol.CreateWorkDoneProgressRequest
293+
) async throws -> BuildServerProtocol.CreateWorkDoneProgressRequest.Response {
294+
guard let delegate else {
295+
throw ResponseError.unknown("Connection to client closed")
296+
}
297+
guard await delegate.clientSupportsWorkDoneProgress else {
298+
throw ResponseError.unknown("Client does not support work done progress")
299+
}
300+
await delegate.waitUntilInitialized()
301+
return try await delegate.sendRequestToClient(request as LanguageServerProtocol.CreateWorkDoneProgressRequest)
302+
}
303+
304+
private func workDoneProgress(notification: BuildServerProtocol.WorkDoneProgress) async {
305+
guard let delegate else {
306+
logger.fault("Ignoring work done progress form build system because connection to client closed")
307+
return
308+
}
309+
await delegate.waitUntilInitialized()
310+
delegate.sendNotificationToClient(notification as LanguageServerProtocol.WorkDoneProgress)
267311
}
268312

269313
/// - Note: Needed so we can set the delegate from a different isolation context.
@@ -365,7 +409,7 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
365409

366410
/// Returns all the `ConfiguredTarget`s that the document is part of.
367411
package func targets(for document: DocumentURI) async -> [BuildTargetIdentifier] {
368-
guard let buildSystem else {
412+
guard let connectionToBuildSystem else {
369413
return []
370414
}
371415

@@ -374,7 +418,7 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
374418
let request = InverseSourcesRequest(textDocument: TextDocumentIdentifier(uri: document))
375419
do {
376420
let response = try await cachedTargetsForDocument.get(request) { document in
377-
return try await buildSystem.send(request)
421+
return try await connectionToBuildSystem.send(request)
378422
}
379423
return response.targets
380424
} catch {
@@ -430,7 +474,7 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
430474
in target: BuildTargetIdentifier?,
431475
language: Language
432476
) async throws -> FileBuildSettings? {
433-
guard let buildSystem, let target else {
477+
guard let connectionToBuildSystem, let target else {
434478
return nil
435479
}
436480
let request = SourceKitOptionsRequest(textDocument: TextDocumentIdentifier(uri: document), target: target)
@@ -441,7 +485,7 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
441485
// very quickly from `settings(for:language:)`.
442486
// https://github.com/apple/sourcekit-lsp/issues/1181
443487
let response = try await cachedSourceKitOptions.get(request) { request in
444-
try await buildSystem.send(request)
488+
try await connectionToBuildSystem.send(request)
445489
}
446490
guard let response else {
447491
return nil
@@ -514,8 +558,10 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
514558

515559
package func waitForUpToDateBuildGraph() async {
516560
await orLog("Waiting for build system updates") {
517-
let _: VoidResponse? = try await self.buildSystem?.send(WaitForBuildSystemUpdatesRequest())
561+
let _: VoidResponse? = try await connectionToBuildSystem?.send(WaitForBuildSystemUpdatesRequest())
518562
}
563+
// Handle any messages the build system might have sent us while updating.
564+
await self.messageHandlingQueue.async {}.valuePropagatingCancellation
519565
}
520566

521567
/// The root targets of the project have depth of 0 and all target dependencies have a greater depth than the target
@@ -574,7 +620,7 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
574620
targets: [BuildTargetIdentifier],
575621
logMessageToIndexLog: @escaping @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void
576622
) async throws {
577-
let _: VoidResponse? = try await buildSystem?.send(PrepareTargetsRequest(targets: targets))
623+
let _: VoidResponse? = try await connectionToBuildSystem?.send(PrepareTargetsRequest(targets: targets))
578624
await orLog("Calling fileDependenciesUpdated") {
579625
let filesInPreparedTargets = try await self.sourceFiles(in: targets).flatMap(\.sources).map(\.uri)
580626
await filesDependenciesUpdatedDebouncer.scheduleCall(Set(filesInPreparedTargets))
@@ -592,13 +638,13 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
592638
}
593639

594640
package func buildTargets() async throws -> [BuildTargetIdentifier: (target: BuildTarget, depth: Int)] {
595-
guard let buildSystem else {
641+
guard let connectionToBuildSystem else {
596642
return [:]
597643
}
598644

599645
let request = BuildTargetsRequest()
600646
let result = try await cachedBuildTargets.get(request) { request in
601-
let buildTargets = try await buildSystem.send(request).targets
647+
let buildTargets = try await connectionToBuildSystem.send(request).targets
602648
let depths = await self.targetDepths(for: buildTargets)
603649
var result: [BuildTargetIdentifier: (target: BuildTarget, depth: Int)] = [:]
604650
result.reserveCapacity(buildTargets.count)
@@ -622,7 +668,7 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
622668
}
623669

624670
package func sourceFiles(in targets: some Sequence<BuildTargetIdentifier>) async throws -> [SourcesItem] {
625-
guard let buildSystem else {
671+
guard let connectionToBuildSystem else {
626672
return []
627673
}
628674

@@ -632,7 +678,7 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
632678
let sortedTargets = targets.sorted { $0.uri.stringValue < $1.uri.stringValue }
633679
let request = BuildTargetSourcesRequest(targets: sortedTargets)
634680
let response = try await cachedTargetSources.get(request) { request in
635-
try await buildSystem.send(request)
681+
try await connectionToBuildSystem.send(request)
636682
}
637683
return response.items
638684
}

0 commit comments

Comments
 (0)