Skip to content

Commit 6004015

Browse files
committed
Introduce a BuiltInBuildSystemAdapter that can be used to transition BuildSystem to a type that implements BSP
1 parent 7352e31 commit 6004015

File tree

15 files changed

+221
-56
lines changed

15 files changed

+221
-56
lines changed

Package.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ let package = Package(
254254
dependencies: [
255255
"CAtomics",
256256
"LanguageServerProtocol",
257+
"LanguageServerProtocolJSONRPC",
257258
"SKLogging",
258259
"SwiftExtensions",
259260
.product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"),

Sources/BuildSystemIntegration/BuildServerBuildSystem.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ package actor BuildServerBuildSystem: MessageHandler {
7474
self.delegate = delegate
7575
}
7676

77+
package weak var messageHandler: BuiltInBuildSystemMessageHandler?
78+
79+
package func setMessageHandler(_ messageHandler: any BuiltInBuildSystemMessageHandler) {
80+
self.messageHandler = messageHandler
81+
}
82+
7783
/// The build settings that have been received from the build server.
7884
private var buildSettings: [DocumentURI: FileBuildSettings] = [:]
7985

Sources/BuildSystemIntegration/BuildSystem.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ package protocol BuiltInBuildSystem: AnyObject, Sendable {
119119
/// context.
120120
func setDelegate(_ delegate: BuildSystemDelegate?) async
121121

122+
/// Set the message handler that is used to send messages from the build system to SourceKit-LSP.
123+
// FIXME: (BSP Migration) This should be set in the initializer but can't right now because BuiltInBuildSystemAdapter is not
124+
// responsible for creating the build system.
125+
func setMessageHandler(_ messageHandler: BuiltInBuildSystemMessageHandler) async
126+
122127
/// Whether the build system is capable of preparing a target for indexing, ie. if the `prepare` methods has been
123128
/// implemented.
124129
var supportsPreparation: Bool { get }

Sources/BuildSystemIntegration/BuildSystemManager.swift

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,16 @@ import os
3434
/// Since some `BuildSystem`s may require a bit of a time to compute their arguments asynchronously,
3535
/// this class has a configurable `buildSettings` timeout which denotes the amount of time to give
3636
/// the build system before applying the fallback arguments.
37-
package actor BuildSystemManager {
37+
package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
3838
/// The files for which the delegate has requested change notifications, ie.
3939
/// the files for which the delegate wants to get `filesDependenciesUpdated`
4040
/// callbacks if the file's build settings.
4141
var watchedFiles: [DocumentURI: (mainFile: DocumentURI, language: Language)] = [:]
4242

4343
/// The underlying primary build system.
44-
let buildSystem: BuiltInBuildSystem?
44+
///
45+
/// - Important: The only time this should be modified is in the initializer. Afterwards, it must be constant.
46+
private(set) var buildSystem: BuiltInBuildSystemAdapter?
4547

4648
/// Timeout before fallback build settings are used.
4749
let fallbackSettingsTimeout: DispatchTimeInterval
@@ -66,12 +68,12 @@ package actor BuildSystemManager {
6668
/// was found.
6769
package var projectRoot: AbsolutePath? {
6870
get async {
69-
return await buildSystem?.projectRoot
71+
return await buildSystem?.underlyingBuildSystem.projectRoot
7072
}
7173
}
7274

7375
package var supportsPreparation: Bool {
74-
return buildSystem?.supportsPreparation ?? false
76+
return buildSystem?.underlyingBuildSystem.supportsPreparation ?? false
7577
}
7678

7779
/// Create a BuildSystemManager that wraps the given build system. The new
@@ -85,28 +87,45 @@ package actor BuildSystemManager {
8587
) async {
8688
let buildSystemHasDelegate = await buildSystem?.delegate != nil
8789
precondition(!buildSystemHasDelegate)
88-
self.buildSystem = buildSystem
8990
self.fallbackBuildSystem = fallbackBuildSystem
9091
self.mainFilesProvider = mainFilesProvider
9192
self.toolchainRegistry = toolchainRegistry
9293
self.fallbackSettingsTimeout = fallbackSettingsTimeout
93-
await self.buildSystem?.setDelegate(self)
94+
self.buildSystem =
95+
if let buildSystem {
96+
await BuiltInBuildSystemAdapter(buildSystem: buildSystem, messageHandler: self)
97+
} else {
98+
nil
99+
}
100+
await self.buildSystem?.underlyingBuildSystem.setDelegate(self)
94101
}
95102

96103
package func filesDidChange(_ events: [FileEvent]) async {
97-
await self.buildSystem?.filesDidChange(events)
104+
await self.buildSystem?.underlyingBuildSystem.filesDidChange(events)
105+
}
106+
107+
/// Implementation of `MessageHandler`, handling notifications from the build system.
108+
///
109+
/// - Important: Do not call directly.
110+
package func handle(_ notification: some LanguageServerProtocol.NotificationType) {
111+
logger.error("Ignoring unknown notification \(type(of: notification).method) from build system")
112+
}
113+
114+
/// Implementation of `MessageHandler`, handling requests from the build system.
115+
///
116+
/// - Important: Do not call directly.
117+
package nonisolated func handle<R: RequestType>(_ request: R) async throws -> R.Response {
118+
throw ResponseError.methodNotFound(R.method)
98119
}
99-
}
100120

101-
extension BuildSystemManager {
102121
/// - Note: Needed so we can set the delegate from a different isolation context.
103122
package func setDelegate(_ delegate: BuildSystemDelegate?) {
104123
self.delegate = delegate
105124
}
106125

107126
/// Returns the toolchain that should be used to process the given document.
108127
package func toolchain(for uri: DocumentURI, _ language: Language) async -> Toolchain? {
109-
if let toolchain = await buildSystem?.toolchain(for: uri, language) {
128+
if let toolchain = await buildSystem?.underlyingBuildSystem.toolchain(for: uri, language) {
110129
return toolchain
111130
}
112131

@@ -128,7 +147,7 @@ extension BuildSystemManager {
128147
/// Returns the language that a document should be interpreted in for background tasks where the editor doesn't
129148
/// specify the document's language.
130149
package func defaultLanguage(for document: DocumentURI) async -> Language? {
131-
if let defaultLanguage = await buildSystem?.defaultLanguage(for: document) {
150+
if let defaultLanguage = await buildSystem?.underlyingBuildSystem.defaultLanguage(for: document) {
132151
return defaultLanguage
133152
}
134153
switch document.fileURL?.pathExtension {
@@ -143,7 +162,7 @@ extension BuildSystemManager {
143162

144163
/// Returns all the `ConfiguredTarget`s that the document is part of.
145164
package func configuredTargets(for document: DocumentURI) async -> [ConfiguredTarget] {
146-
return await buildSystem?.configuredTargets(for: document) ?? []
165+
return await buildSystem?.underlyingBuildSystem.configuredTargets(for: document) ?? []
147166
}
148167

149168
/// Returns the `ConfiguredTarget` that should be used for semantic functionality of the given document.
@@ -203,7 +222,7 @@ extension BuildSystemManager {
203222
// For now, this should be fine because all build systems return
204223
// very quickly from `settings(for:language:)`.
205224
// https://github.com/apple/sourcekit-lsp/issues/1181
206-
return try await buildSystem.buildSettings(for: document, in: target, language: language)
225+
return try await buildSystem.underlyingBuildSystem.buildSettings(for: document, in: target, language: language)
207226
}
208227

209228
/// Returns the build settings for the given file in the given target.
@@ -266,26 +285,26 @@ extension BuildSystemManager {
266285
}
267286

268287
package func generateBuildGraph() async throws {
269-
try await self.buildSystem?.generateBuildGraph()
288+
try await self.buildSystem?.underlyingBuildSystem.generateBuildGraph()
270289
}
271290

272291
package func waitForUpToDateBuildGraph() async {
273-
await self.buildSystem?.waitForUpToDateBuildGraph()
292+
await self.buildSystem?.underlyingBuildSystem.waitForUpToDateBuildGraph()
274293
}
275294

276295
package func topologicalSort(of targets: [ConfiguredTarget]) async throws -> [ConfiguredTarget]? {
277-
return await buildSystem?.topologicalSort(of: targets)
296+
return await buildSystem?.underlyingBuildSystem.topologicalSort(of: targets)
278297
}
279298

280299
package func targets(dependingOn targets: [ConfiguredTarget]) async -> [ConfiguredTarget]? {
281-
return await buildSystem?.targets(dependingOn: targets)
300+
return await buildSystem?.underlyingBuildSystem.targets(dependingOn: targets)
282301
}
283302

284303
package func prepare(
285304
targets: [ConfiguredTarget],
286305
logMessageToIndexLog: @escaping @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void
287306
) async throws {
288-
try await buildSystem?.prepare(targets: targets, logMessageToIndexLog: logMessageToIndexLog)
307+
try await buildSystem?.underlyingBuildSystem.prepare(targets: targets, logMessageToIndexLog: logMessageToIndexLog)
289308
}
290309

291310
package func registerForChangeNotifications(for uri: DocumentURI, language: Language) async {
@@ -297,7 +316,7 @@ extension BuildSystemManager {
297316
// system. That way, iff the main file changes, we will also notify the
298317
// delegate about build setting changes of all header files that are based
299318
// on that main file.
300-
await buildSystem?.registerForChangeNotifications(for: mainFile)
319+
await buildSystem?.underlyingBuildSystem.registerForChangeNotifications(for: mainFile)
301320
}
302321

303322
package func unregisterForChangeNotifications(for uri: DocumentURI) async {
@@ -310,19 +329,19 @@ extension BuildSystemManager {
310329
if watchedFilesReferencing(mainFiles: [mainFile]).isEmpty {
311330
// Nobody is interested in this main file anymore.
312331
// We are no longer interested in change notifications for it.
313-
await self.buildSystem?.unregisterForChangeNotifications(for: mainFile)
332+
await self.buildSystem?.underlyingBuildSystem.unregisterForChangeNotifications(for: mainFile)
314333
}
315334
}
316335

317336
package func fileHandlingCapability(for uri: DocumentURI) async -> FileHandlingCapability {
318337
return max(
319-
await buildSystem?.fileHandlingCapability(for: uri) ?? .unhandled,
338+
await buildSystem?.underlyingBuildSystem.fileHandlingCapability(for: uri) ?? .unhandled,
320339
fallbackBuildSystem != nil ? .fallback : .unhandled
321340
)
322341
}
323342

324343
package func sourceFiles() async -> [SourceFileInfo] {
325-
return await buildSystem?.sourceFiles() ?? []
344+
return await buildSystem?.underlyingBuildSystem.sourceFiles() ?? []
326345
}
327346

328347
package func testFiles() async -> [DocumentURI] {
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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+
import LanguageServerProtocol
14+
import SKLogging
15+
import SKSupport
16+
17+
// FIXME: (BSP Migration) This should be a MessageHandler once we have migrated all build system queries to BSP and can use
18+
// LocalConnection for the communication.
19+
protocol BuiltInBuildSystemAdapterDelegate: Sendable {
20+
func handle(_ notification: some NotificationType) async
21+
func handle<R: RequestType>(_ request: R) async throws -> R.Response
22+
}
23+
24+
// FIXME: (BSP Migration) This should be a MessageHandler once we have migrated all build system queries to BSP and can use
25+
// LocalConnection for the communication.
26+
package protocol BuiltInBuildSystemMessageHandler: AnyObject, Sendable {
27+
func sendNotificationToSourceKitLSP(_ notification: some NotificationType) async
28+
func sendRequestToSourceKitLSP<R: RequestType>(_ request: R) async throws -> R.Response
29+
}
30+
31+
/// A type that outwardly acts as a build server conforming to the Build System Integration Protocol and internally uses
32+
/// a `BuiltInBuildSystem` to satisfy the requests.
33+
actor BuiltInBuildSystemAdapter: BuiltInBuildSystemMessageHandler {
34+
/// The underlying build system
35+
// FIXME: (BSP Migration) This should be private, all messages should go through BSP. Only accessible from the outside for transition
36+
// purposes.
37+
package let underlyingBuildSystem: BuiltInBuildSystem
38+
private let messageHandler: any BuiltInBuildSystemAdapterDelegate
39+
40+
init(
41+
buildSystem: BuiltInBuildSystem,
42+
messageHandler: any BuiltInBuildSystemAdapterDelegate
43+
) async {
44+
self.underlyingBuildSystem = buildSystem
45+
self.messageHandler = messageHandler
46+
await buildSystem.setMessageHandler(self)
47+
}
48+
49+
package func send<R: RequestType>(_ request: R) async throws -> R.Response {
50+
logger.info(
51+
"""
52+
Received request to build system
53+
\(request.forLogging)
54+
"""
55+
)
56+
/// Executes `body` and casts the result type to `R.Response`, statically checking that the return type of `body` is
57+
/// the response type of `request`.
58+
func handle<HandledRequestType: RequestType>(
59+
_ request: HandledRequestType,
60+
_ body: (HandledRequestType) async throws -> HandledRequestType.Response
61+
) async throws -> R.Response {
62+
return try await body(request) as! R.Response
63+
}
64+
65+
switch request {
66+
default:
67+
throw ResponseError.methodNotFound(R.method)
68+
}
69+
}
70+
71+
package func send(_ notification: some NotificationType) async {
72+
logger.info(
73+
"""
74+
Sending notification to build system
75+
\(notification.forLogging)
76+
"""
77+
)
78+
// FIXME: (BSP Migration) These messages should be handled using a LocalConnection, which also gives us logging for the messages
79+
// sent. We can only do this once all requests to the build system have been migrated and we can implement proper
80+
// dependency management between the BSP messages
81+
switch notification {
82+
default:
83+
logger.error("Ignoring unknown notification \(type(of: notification).method) from SourceKit-LSP")
84+
}
85+
}
86+
87+
func sendNotificationToSourceKitLSP(_ notification: some LanguageServerProtocol.NotificationType) async {
88+
logger.info(
89+
"""
90+
Received notification from build system
91+
\(notification.forLogging)
92+
"""
93+
)
94+
await messageHandler.handle(notification)
95+
}
96+
97+
func sendRequestToSourceKitLSP<R: RequestType>(_ request: R) async throws -> R.Response {
98+
logger.info(
99+
"""
100+
Received request from build system
101+
\(request.forLogging)
102+
"""
103+
)
104+
return try await messageHandler.handle(request)
105+
}
106+
107+
}

Sources/BuildSystemIntegration/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ add_library(BuildSystemIntegration STATIC
44
BuildSystem.swift
55
BuildSystemDelegate.swift
66
BuildSystemManager.swift
7+
BuiltInBuildSystemAdapter.swift
78
CompilationDatabase.swift
89
CompilationDatabaseBuildSystem.swift
910
FallbackBuildSystem.swift

Sources/BuildSystemIntegration/CompilationDatabaseBuildSystem.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ package actor CompilationDatabaseBuildSystem {
4747
self.delegate = delegate
4848
}
4949

50+
package weak var messageHandler: BuiltInBuildSystemMessageHandler?
51+
52+
package func setMessageHandler(_ messageHandler: any BuiltInBuildSystemMessageHandler) {
53+
self.messageHandler = messageHandler
54+
}
55+
5056
package let projectRoot: AbsolutePath
5157

5258
let searchPaths: [RelativePath]

Sources/BuildSystemIntegration/SwiftPMBuildSystem.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,12 @@ package actor SwiftPMBuildSystem {
130130
self.delegate = delegate
131131
}
132132

133+
package weak var messageHandler: BuiltInBuildSystemMessageHandler?
134+
135+
package func setMessageHandler(_ messageHandler: any BuiltInBuildSystemMessageHandler) {
136+
self.messageHandler = messageHandler
137+
}
138+
133139
/// This callback is informed when `reloadPackage` starts and ends executing.
134140
private var reloadPackageStatusCallback: (ReloadPackageStatus) async -> Void
135141

Sources/InProcessClient/CMakeLists.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
add_library(InProcessClient STATIC
2-
InProcessSourceKitLSPClient.swift
3-
LocalConnection.swift)
2+
InProcessSourceKitLSPClient.swift)
43

54
set_target_properties(InProcessClient PROPERTIES
65
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})

Sources/LanguageServerProtocol/Connection.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,35 @@ public protocol MessageHandler: AnyObject, Sendable {
4545
reply: @Sendable @escaping (LSPResult<Request.Response>) -> Void
4646
)
4747
}
48+
49+
// MARK: - WeakMessageHelper
50+
51+
/// Wrapper around a weak `MessageHandler`.
52+
///
53+
/// This allows us to eg. set the ``TestSourceKitLSPClient`` as the message handler of
54+
/// `SourceKitLSPServer` without retaining it.
55+
public final class WeakMessageHandler: MessageHandler, Sendable {
56+
// `nonisolated(unsafe)` is fine because `handler` is never modified, only if the weak reference is deallocated, which
57+
// is atomic.
58+
private nonisolated(unsafe) weak var handler: (any MessageHandler)?
59+
60+
public init(_ handler: any MessageHandler) {
61+
self.handler = handler
62+
}
63+
64+
public func handle(_ params: some LanguageServerProtocol.NotificationType) {
65+
handler?.handle(params)
66+
}
67+
68+
public func handle<Request: RequestType>(
69+
_ params: Request,
70+
id: LanguageServerProtocol.RequestID,
71+
reply: @Sendable @escaping (LanguageServerProtocol.LSPResult<Request.Response>) -> Void
72+
) {
73+
guard let handler = handler else {
74+
reply(.failure(.unknown("Handler has been deallocated")))
75+
return
76+
}
77+
handler.handle(params, id: id, reply: reply)
78+
}
79+
}

0 commit comments

Comments
 (0)