Skip to content

Commit 1fd700b

Browse files
committed
Expose all BSP messages used by SourceKit-LSP to BSP servers
The interaction to an out-of-process BSP server still went through the `BuildServerBuildSystem`, which doesn’t forward all messages to the build system and uses the old push-based model for build settings. If we discover that the BSP server supports the new pull-based build settings model, we now forward all methods to it directly, without going through `BuiltInBuildSystemAdapter`, which has been renamed to `LegacyBuildServerBuildSystem`. rdar://136106323 rdar://127606323 rdar://126493405 Fixes #1226 Fixes #1173
1 parent e75ee23 commit 1fd700b

File tree

26 files changed

+808
-742
lines changed

26 files changed

+808
-742
lines changed

Contributor Documentation/BSP Extensions.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ export interface SourceKitInitializeBuildResponseData {
1616
indexDatabasePath?: string;
1717

1818
/** Whether the build server supports the `buildTarget/prepare` request */
19-
supportsPreparation?: bool;
19+
prepareProvider?: bool;
20+
21+
/** Whether the server implements the `textDocument/sourceKitOptions` request. */
22+
sourceKitOptionsProvider?: bool;
2023
}
2124
```
2225

Package.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ let package = Package(
178178
dependencies: [
179179
"LanguageServerProtocol",
180180
"SKLogging",
181+
"SwiftExtensions",
181182
],
182183
exclude: ["CMakeLists.txt"]
183184
),

Sources/BuildServerProtocol/Messages/InitializeBuildRequest.swift

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -270,12 +270,21 @@ public struct SourceKitInitializeBuildResponseData: LSPAnyCodable, Codable, Send
270270
public var indexStorePath: String?
271271

272272
/// Whether the build server supports the `buildTarget/prepare` request.
273-
public var supportsPreparation: Bool?
273+
public var prepareProvider: Bool?
274274

275-
public init(indexDatabasePath: String?, indexStorePath: String?, supportsPreparation: Bool?) {
275+
/// Whether the server implements the `textDocument/sourceKitOptions` request.
276+
public var sourceKitOptionsProvider: Bool?
277+
278+
public init(
279+
indexDatabasePath: String?,
280+
indexStorePath: String?,
281+
prepareProvider: Bool?,
282+
sourceKitOptionsProvider: Bool?
283+
) {
276284
self.indexDatabasePath = indexDatabasePath
277285
self.indexStorePath = indexStorePath
278-
self.supportsPreparation = supportsPreparation
286+
self.prepareProvider = prepareProvider
287+
self.sourceKitOptionsProvider = sourceKitOptionsProvider
279288
}
280289

281290
public init?(fromLSPDictionary dictionary: [String: LanguageServerProtocol.LSPAny]) {
@@ -285,8 +294,11 @@ public struct SourceKitInitializeBuildResponseData: LSPAnyCodable, Codable, Send
285294
if case .string(let indexStorePath) = dictionary[CodingKeys.indexStorePath.stringValue] {
286295
self.indexStorePath = indexStorePath
287296
}
288-
if case .bool(let supportsPreparation) = dictionary[CodingKeys.supportsPreparation.stringValue] {
289-
self.supportsPreparation = supportsPreparation
297+
if case .bool(let prepareProvider) = dictionary[CodingKeys.prepareProvider.stringValue] {
298+
self.prepareProvider = prepareProvider
299+
}
300+
if case .bool(let sourceKitOptionsProvider) = dictionary[CodingKeys.sourceKitOptionsProvider.stringValue] {
301+
self.sourceKitOptionsProvider = sourceKitOptionsProvider
290302
}
291303
}
292304

@@ -298,8 +310,11 @@ public struct SourceKitInitializeBuildResponseData: LSPAnyCodable, Codable, Send
298310
if let indexStorePath {
299311
result[CodingKeys.indexStorePath.stringValue] = .string(indexStorePath)
300312
}
301-
if let supportsPreparation {
302-
result[CodingKeys.supportsPreparation.stringValue] = .bool(supportsPreparation)
313+
if let prepareProvider {
314+
result[CodingKeys.prepareProvider.stringValue] = .bool(prepareProvider)
315+
}
316+
if let sourceKitOptionsProvider {
317+
result[CodingKeys.sourceKitOptionsProvider.stringValue] = .bool(sourceKitOptionsProvider)
303318
}
304319
return .dictionary(result)
305320
}

Sources/BuildSystemIntegration/BuildSystemManager.swift

Lines changed: 142 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import SwiftExtensions
2121
import ToolchainRegistry
2222

2323
import struct TSCBasic.AbsolutePath
24+
import struct TSCBasic.RelativePath
2425
import func TSCBasic.resolveSymlinks
2526

2627
fileprivate typealias RequestCache<Request: RequestType & Hashable> = Cache<Request, Request.Response>
@@ -91,6 +92,112 @@ fileprivate extension DocumentURI {
9192
}
9293
}
9394

95+
fileprivate extension InitializeBuildResponse {
96+
var sourceKitData: SourceKitInitializeBuildResponseData? {
97+
guard dataKind == nil || dataKind == .sourceKit else {
98+
return nil
99+
}
100+
guard case .dictionary(let data) = data else {
101+
return nil
102+
}
103+
return SourceKitInitializeBuildResponseData(fromLSPDictionary: data)
104+
}
105+
}
106+
107+
/// A build system adapter is responsible for receiving messages from the `BuildSystemManager` and forwarding them to
108+
/// the build system. For built-in build systems, this means that we need to translate the BSP messages to methods in
109+
/// the `BuiltInBuildSystem` protocol. For external (aka. out-of-process, aka. BSP servers) build systems, this means
110+
/// that we need to manage the external build system's lifetime.
111+
private enum BuildSystemAdapter {
112+
case builtIn(BuiltInBuildSystemAdapter)
113+
case external(ExternalBuildSystemAdapter)
114+
}
115+
116+
private extension BuildSystemKind {
117+
private static func createBuiltInBuildSystemAdapter(
118+
projectRoot: AbsolutePath,
119+
messagesToSourceKitLSPHandler: any MessageHandler,
120+
_ createBuildSystem: @Sendable (_ connectionToSourceKitLSP: any Connection) async throws -> BuiltInBuildSystem?
121+
) async -> (buildSystemAdapter: BuildSystemAdapter, connectionToBuildSystem: any Connection)? {
122+
let connectionToSourceKitLSP = LocalConnection(receiverName: "BuildSystemManager")
123+
connectionToSourceKitLSP.start(handler: messagesToSourceKitLSPHandler)
124+
125+
let buildSystem = await orLog("Creating build system") {
126+
try await createBuildSystem(connectionToSourceKitLSP)
127+
}
128+
guard let buildSystem else {
129+
logger.log("Failed to create build system at \(projectRoot.pathString)")
130+
return nil
131+
}
132+
logger.log("Created \(type(of: buildSystem), privacy: .public) at \(projectRoot.pathString)")
133+
let buildSystemAdapter = BuiltInBuildSystemAdapter(
134+
underlyingBuildSystem: buildSystem,
135+
connectionToSourceKitLSP: connectionToSourceKitLSP
136+
)
137+
let connectionToBuildSystem = LocalConnection(receiverName: "Build system")
138+
connectionToBuildSystem.start(handler: buildSystemAdapter)
139+
return (.builtIn(buildSystemAdapter), connectionToBuildSystem)
140+
}
141+
142+
/// Create a `BuildSystemAdapter` that manages a build system of this kind and return a connection that can be used
143+
/// to send messages to the build system.
144+
func createBuildSystemAdapter(
145+
toolchainRegistry: ToolchainRegistry,
146+
options: SourceKitLSPOptions,
147+
buildSystemTestHooks testHooks: BuildSystemTestHooks,
148+
messagesToSourceKitLSPHandler: any MessageHandler
149+
) async -> (buildSystemAdapter: BuildSystemAdapter, connectionToBuildSystem: any Connection)? {
150+
switch self {
151+
case .buildServer(projectRoot: let projectRoot):
152+
let buildSystem = await orLog("Creating external build system") {
153+
try await ExternalBuildSystemAdapter(
154+
projectRoot: projectRoot,
155+
messagesToSourceKitLSPHandler: messagesToSourceKitLSPHandler
156+
)
157+
}
158+
guard let buildSystem else {
159+
logger.log("Failed to create external build system at \(projectRoot.pathString)")
160+
return nil
161+
}
162+
logger.log("Created external build server at \(projectRoot.pathString)")
163+
return (.external(buildSystem), buildSystem.connectionToBuildServer)
164+
case .compilationDatabase(projectRoot: let projectRoot):
165+
return await Self.createBuiltInBuildSystemAdapter(
166+
projectRoot: projectRoot,
167+
messagesToSourceKitLSPHandler: messagesToSourceKitLSPHandler
168+
) { connectionToSourceKitLSP in
169+
CompilationDatabaseBuildSystem(
170+
projectRoot: projectRoot,
171+
searchPaths: (options.compilationDatabaseOrDefault.searchPaths ?? []).compactMap {
172+
try? RelativePath(validating: $0)
173+
},
174+
connectionToSourceKitLSP: connectionToSourceKitLSP
175+
)
176+
}
177+
case .swiftPM(projectRoot: let projectRoot):
178+
return await Self.createBuiltInBuildSystemAdapter(
179+
projectRoot: projectRoot,
180+
messagesToSourceKitLSPHandler: messagesToSourceKitLSPHandler
181+
) { connectionToSourceKitLSP in
182+
try await SwiftPMBuildSystem(
183+
projectRoot: projectRoot,
184+
toolchainRegistry: toolchainRegistry,
185+
options: options,
186+
connectionToSourceKitLSP: connectionToSourceKitLSP,
187+
testHooks: testHooks.swiftPMTestHooks
188+
)
189+
}
190+
case .testBuildSystem(projectRoot: let projectRoot):
191+
return await Self.createBuiltInBuildSystemAdapter(
192+
projectRoot: projectRoot,
193+
messagesToSourceKitLSPHandler: messagesToSourceKitLSPHandler
194+
) { connectionToSourceKitLSP in
195+
TestBuildSystem(projectRoot: projectRoot, connectionToSourceKitLSP: connectionToSourceKitLSP)
196+
}
197+
}
198+
}
199+
}
200+
94201
/// Entry point for all build system queries.
95202
package actor BuildSystemManager: QueueBasedMessageHandler {
96203
package static let signpostLoggingCategory: String = "build-system-manager-message-handling"
@@ -110,11 +217,6 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
110217
/// get `fileBuildSettingsChanged` and `filesDependenciesUpdated` callbacks.
111218
private var watchedFiles: [DocumentURI: (mainFile: DocumentURI, language: Language)] = [:]
112219

113-
/// The underlying primary build system.
114-
///
115-
/// - Important: The only time this should be modified is in the initializer. Afterwards, it must be constant.
116-
private var buildSystem: BuiltInBuildSystemAdapter?
117-
118220
/// The connection through which the `BuildSystemManager` can send requests to the build system.
119221
///
120222
/// Access to this property should generally go through the non-underscored version, which waits until the build
@@ -131,12 +233,19 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
131233
}
132234
}
133235

236+
/// The build system adapter that is used to answer build system queries.
237+
private var buildSystemAdapter: BuildSystemAdapter?
238+
134239
/// If the underlying build system is a `TestBuildSystem`, return it. Otherwise, `nil`
135240
///
136241
/// - Important: For testing purposes only.
137242
package var testBuildSystem: TestBuildSystem? {
138243
get async {
139-
return await buildSystem?.testBuildSystem
244+
switch buildSystemAdapter {
245+
case .builtIn(let builtInBuildSystemAdapter): return await builtInBuildSystemAdapter.testBuildSystem
246+
case .external: return nil
247+
case nil: return nil
248+
}
140249
}
141250
}
142251

@@ -205,16 +314,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
205314
/// The `SourceKitInitializeBuildResponseData` received from the `build/initialize` request, if any.
206315
package var initializationData: SourceKitInitializeBuildResponseData? {
207316
get async {
208-
guard let initializeResult = await initializeResult.value else {
209-
return nil
210-
}
211-
guard initializeResult.dataKind == nil || initializeResult.dataKind == .sourceKit else {
212-
return nil
213-
}
214-
guard case .dictionary(let data) = initializeResult.data else {
215-
return nil
216-
}
217-
return SourceKitInitializeBuildResponseData(fromLSPDictionary: data)
317+
return await initializeResult.value?.sourceKitData
218318
}
219319
}
220320

@@ -227,20 +327,15 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
227327
self.toolchainRegistry = toolchainRegistry
228328
self.options = options
229329
self.projectRoot = buildSystemKind?.projectRoot
230-
self.buildSystem = await BuiltInBuildSystemAdapter(
231-
buildSystemKind: buildSystemKind,
330+
let buildSystemAdapterAndConnection = await buildSystemKind?.createBuildSystemAdapter(
232331
toolchainRegistry: toolchainRegistry,
233332
options: options,
234333
buildSystemTestHooks: buildSystemTestHooks,
235334
messagesToSourceKitLSPHandler: WeakMessageHandler(self)
236335
)
237-
if let buildSystem {
238-
let connectionToBuildSystem = LocalConnection(receiverName: "Build system")
239-
connectionToBuildSystem.start(handler: buildSystem)
240-
self._connectionToBuildSystem = connectionToBuildSystem
241-
} else {
242-
self._connectionToBuildSystem = nil
243-
}
336+
self.buildSystemAdapter = buildSystemAdapterAndConnection?.buildSystemAdapter
337+
self._connectionToBuildSystem = buildSystemAdapterAndConnection?.connectionToBuildSystem
338+
244339
// The debounce duration of 500ms was chosen arbitrarily without any measurements.
245340
self.filesDependenciesUpdatedDebouncer = Debouncer(
246341
debounceDuration: .milliseconds(500),
@@ -278,6 +373,27 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
278373
)
279374
)
280375
}
376+
if let initializeResponse, !(initializeResponse.sourceKitData?.sourceKitOptionsProvider ?? false),
377+
case .external(let externalBuildSystemAdapter) = buildSystemAdapter
378+
{
379+
// The BSP server does not support the pull-based settings model. Inject a `LegacyBuildServerBuildSystem` that
380+
// offers the pull-based model to `BuildSystemManager` and uses the push-based model to get build settings from
381+
// the build server.
382+
logger.log("Launched a legacy BSP server. Using push-based build settings model.")
383+
let legacyBuildServer = await LegacyBuildServerBuildSystem(
384+
projectRoot: buildSystemKind.projectRoot,
385+
initializationData: initializeResponse,
386+
externalBuildSystemAdapter
387+
)
388+
let adapter = BuiltInBuildSystemAdapter(
389+
underlyingBuildSystem: legacyBuildServer,
390+
connectionToSourceKitLSP: legacyBuildServer.connectionToSourceKitLSP
391+
)
392+
self.buildSystemAdapter = .builtIn(adapter)
393+
let connectionToBuildSystem = LocalConnection(receiverName: "Legacy BSP server")
394+
connectionToBuildSystem.start(handler: adapter)
395+
self._connectionToBuildSystem = connectionToBuildSystem
396+
}
281397
_connectionToBuildSystem.send(OnBuildInitializedNotification())
282398
return initializeResponse
283399
}
@@ -782,7 +898,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
782898
}
783899

784900
package func sourceFiles(in targets: Set<BuildTargetIdentifier>) async throws -> [SourcesItem] {
785-
guard let connectionToBuildSystem = await connectionToBuildSystem else {
901+
guard let connectionToBuildSystem = await connectionToBuildSystem, !targets.isEmpty else {
786902
return []
787903
}
788904

Sources/BuildSystemIntegration/BuiltInBuildSystem.swift

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,6 @@ package struct PrepareNotSupportedError: Error, CustomStringConvertible {
2828

2929
/// Provider of FileBuildSettings and other build-related information.
3030
package protocol BuiltInBuildSystem: AnyObject, Sendable {
31-
/// When opening an LSP workspace at `workspaceFolder`, determine the directory in which a project of this build system
32-
/// starts. For example, a user might open the `Sources` folder of a SwiftPM project, then the project root is the
33-
/// directory containing `Package.swift`.
34-
///
35-
/// Returns `nil` if the build system can't handle the given workspace folder
36-
static func projectRoot(for workspaceFolder: AbsolutePath, options: SourceKitLSPOptions) -> AbsolutePath?
37-
3831
/// The root of the project that this build system manages. For example, for SwiftPM packages, this is the folder
3932
/// containing Package.swift. For compilation databases it is the root folder based on which the compilation database
4033
/// was found.

0 commit comments

Comments
 (0)