Skip to content

Commit e083124

Browse files
authored
Merge pull request #1655 from ahoppen/bsp-settings
Use BSP requests to get build settings of a source file
2 parents a4edcea + f9e468f commit e083124

22 files changed

+465
-583
lines changed

Sources/BuildServerProtocol/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ add_library(BuildServerProtocol STATIC
22
BuildTargets.swift
33
DidChangeBuildTargetNotification.swift
44
DidChangeWatchedFilesNotification.swift
5-
FileOptions.swift
65
InitializeBuild.swift
76
InverseSourcesRequest.swift
87
Messages.swift
98
RegisterForChangeNotifications.swift
10-
ShutdownBuild.swift)
9+
ShutdownBuild.swift
10+
SourceKitOptionsRequest.swift)
1111
set_target_properties(BuildServerProtocol PROPERTIES
1212
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})
1313
target_link_libraries(BuildServerProtocol PRIVATE

Sources/BuildServerProtocol/FileOptions.swift

Lines changed: 0 additions & 34 deletions
This file was deleted.

Sources/BuildServerProtocol/Messages.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ fileprivate let requestTypes: [_RequestType.Type] = [
1919
InverseSourcesRequest.self,
2020
RegisterForChanges.self,
2121
ShutdownBuild.self,
22-
SourceKitOptions.self,
22+
SourceKitOptionsRequest.self,
2323
]
2424

2525
fileprivate let notificationTypes: [NotificationType.Type] = [

Sources/BuildServerProtocol/RegisterForChangeNotifications.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,19 @@ public enum RegisterAction: String, Hashable, Codable, Sendable {
4040
/// build server to the language server when it detects
4141
/// changes to a registered files build settings.
4242
public struct FileOptionsChangedNotification: NotificationType {
43+
public struct Options: ResponseType, Hashable {
44+
/// The compiler options required for the requested file.
45+
public var options: [String]
46+
47+
/// The working directory for the compile command.
48+
public var workingDirectory: String?
49+
}
50+
4351
public static let method: String = "build/sourceKitOptionsChanged"
4452

4553
/// The URI of the document that has changed settings.
4654
public var uri: URI
4755

4856
/// The updated options for the registered file.
49-
public var updatedOptions: SourceKitOptionsResult
57+
public var updatedOptions: Options
5058
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 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+
15+
/// The SourceKitOptions request is sent from the client to the server to query for the list of compiler options
16+
/// necessary to compile this file in the given target.
17+
///
18+
/// The build settings are considered up-to-date and can be cached by SourceKit-LSP until a
19+
/// `DidChangeBuildTargetNotification` is sent for the requested target.
20+
///
21+
/// The request may return `nil` if it doesn't have any build settings for this file in the given target.
22+
public struct SourceKitOptionsRequest: RequestType, Hashable {
23+
public static let method: String = "textDocument/sourceKitOptions"
24+
public typealias Response = SourceKitOptionsResponse?
25+
26+
/// The URI of the document to get options for
27+
public var textDocument: TextDocumentIdentifier
28+
29+
/// The target for which the build setting should be returned.
30+
///
31+
/// A source file might be part of multiple targets and might have different compiler arguments in those two targets,
32+
/// thus the target is necessary in this request.
33+
public var target: BuildTargetIdentifier
34+
35+
public init(textDocument: TextDocumentIdentifier, target: BuildTargetIdentifier) {
36+
self.textDocument = textDocument
37+
self.target = target
38+
}
39+
}
40+
41+
public struct SourceKitOptionsResponse: ResponseType, Hashable {
42+
/// The compiler options required for the requested file.
43+
public var compilerArguments: [String]
44+
45+
/// The working directory for the compile command.
46+
public var workingDirectory: String?
47+
48+
public init(compilerArguments: [String], workingDirectory: String? = nil) {
49+
self.compilerArguments = compilerArguments
50+
self.workingDirectory = workingDirectory
51+
}
52+
}

Sources/BuildSystemIntegration/BuildServerBuildSystem.swift

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ package actor BuildServerBuildSystem: MessageHandler {
7878
package weak var messageHandler: BuiltInBuildSystemMessageHandler?
7979

8080
/// The build settings that have been received from the build server.
81-
private var buildSettings: [DocumentURI: FileBuildSettings] = [:]
81+
private var buildSettings: [DocumentURI: SourceKitOptionsResponse] = [:]
8282

8383
package init(
8484
projectRoot: AbsolutePath,
@@ -178,10 +178,10 @@ package actor BuildServerBuildSystem: MessageHandler {
178178
logger.log("Initialized build server \(response.displayName)")
179179

180180
// see if index store was set as part of the server metadata
181-
if let indexDbPath = readReponseDataKey(data: response.data, key: "indexDatabasePath") {
181+
if let indexDbPath = readResponseDataKey(data: response.data, key: "indexDatabasePath") {
182182
self.indexDatabasePath = try AbsolutePath(validating: indexDbPath, relativeTo: self.projectRoot)
183183
}
184-
if let indexStorePath = readReponseDataKey(data: response.data, key: "indexStorePath") {
184+
if let indexStorePath = readResponseDataKey(data: response.data, key: "indexStorePath") {
185185
self.indexStorePath = try AbsolutePath(validating: indexStorePath, relativeTo: self.projectRoot)
186186
}
187187
self.buildServer = buildServer
@@ -228,11 +228,9 @@ package actor BuildServerBuildSystem: MessageHandler {
228228
await self.messageHandler?.sendNotificationToSourceKitLSP(notification)
229229
}
230230

231-
func handleFileOptionsChanged(
232-
_ notification: FileOptionsChangedNotification
233-
) async {
231+
func handleFileOptionsChanged(_ notification: FileOptionsChangedNotification) async {
234232
let result = notification.updatedOptions
235-
let settings = FileBuildSettings(
233+
let settings = SourceKitOptionsResponse(
236234
compilerArguments: result.options,
237235
workingDirectory: result.workingDirectory
238236
)
@@ -241,13 +239,16 @@ package actor BuildServerBuildSystem: MessageHandler {
241239

242240
/// Record the new build settings for the given document and inform the delegate
243241
/// about the changed build settings.
244-
private func buildSettingsChanged(for document: DocumentURI, settings: FileBuildSettings?) async {
242+
private func buildSettingsChanged(for document: DocumentURI, settings: SourceKitOptionsResponse?) async {
245243
buildSettings[document] = settings
246-
await self.delegate?.fileBuildSettingsChanged([document])
244+
// FIXME: (BSP migration) When running in the legacy mode where teh BSP server pushes build settings to us, we could
245+
// consider having a separate target for each source file so that we can update individual targets instead of having
246+
// to send an update for all targets.
247+
await self.messageHandler?.sendNotificationToSourceKitLSP(DidChangeBuildTargetNotification(changes: nil))
247248
}
248249
}
249250

250-
private func readReponseDataKey(data: LSPAny?, key: String) -> String? {
251+
private func readResponseDataKey(data: LSPAny?, key: String) -> String? {
251252
if case .dictionary(let dataDict)? = data,
252253
case .string(let stringVal)? = dataDict[key]
253254
{
@@ -267,16 +268,8 @@ extension BuildServerBuildSystem: BuiltInBuildSystem {
267268

268269
package nonisolated var supportsPreparation: Bool { false }
269270

270-
/// The build settings for the given file.
271-
///
272-
/// Returns `nil` if no build settings have been received from the build
273-
/// server yet or if no build settings are available for this file.
274-
package func buildSettings(
275-
for document: DocumentURI,
276-
in target: BuildTargetIdentifier,
277-
language: Language
278-
) async -> FileBuildSettings? {
279-
return buildSettings[document]
271+
package func sourceKitOptions(request: SourceKitOptionsRequest) async throws -> SourceKitOptionsResponse? {
272+
return buildSettings[request.textDocument.uri]
280273
}
281274

282275
package func defaultLanguage(for document: DocumentURI) async -> Language? {
@@ -287,7 +280,7 @@ extension BuildServerBuildSystem: BuiltInBuildSystem {
287280
return nil
288281
}
289282

290-
package func inverseSources(_ request: InverseSourcesRequest) -> InverseSourcesResponse {
283+
package func inverseSources(request: InverseSourcesRequest) -> InverseSourcesResponse {
291284
return InverseSourcesResponse(targets: [BuildTargetIdentifier.dummy])
292285
}
293286

Sources/BuildSystemIntegration/BuildSystemDelegate.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,6 @@ import LanguageServerProtocol
1515
/// Handles build system events, such as file build settings changes.
1616
// FIXME: (BSP migration) The build system should exclusively communicate back to SourceKit-LSP using BSP and this protocol should be deleted.
1717
package protocol BuildSystemDelegate: AnyObject, Sendable {
18-
/// Notify the delegate that the given files' build settings have changed.
19-
func fileBuildSettingsChanged(_ changedFiles: Set<DocumentURI>) async
20-
2118
/// Notify the delegate that the dependencies of the given files have changed
2219
/// and that ASTs may need to be refreshed. If the given set is empty, assume
2320
/// that all watched files are affected.

Sources/BuildSystemIntegration/BuildSystemManager.swift

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
9696

9797
private var cachedTargetsForDocument = RequestCache<InverseSourcesRequest>()
9898

99+
private var cachedSourceKitOptions = RequestCache<SourceKitOptionsRequest>()
100+
99101
/// The root of the project that this build system manages. For example, for SwiftPM packages, this is the folder
100102
/// containing Package.swift. For compilation databases it is the root folder based on which the compilation database
101103
/// was found.
@@ -112,7 +114,7 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
112114
}
113115

114116
package init(
115-
buildSystemKind: (WorkspaceType, projectRoot: AbsolutePath)?,
117+
buildSystemKind: BuildSystemKind?,
116118
toolchainRegistry: ToolchainRegistry,
117119
options: SourceKitLSPOptions,
118120
swiftpmTestHooks: SwiftPMTestHooks,
@@ -131,41 +133,17 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
131133
await self.buildSystem?.underlyingBuildSystem.setDelegate(self)
132134
}
133135

134-
/// Create a BuildSystemManager that wraps the given build system.
135-
/// The new manager will modify the delegate of the underlying build system.
136-
///
137-
/// - Important: For testing purposes only
138-
package init(
139-
testBuildSystem: BuiltInBuildSystem?,
140-
fallbackBuildSystem: FallbackBuildSystem?,
141-
mainFilesProvider: MainFilesProvider?,
142-
toolchainRegistry: ToolchainRegistry
143-
) async {
144-
let buildSystemHasDelegate = await testBuildSystem?.delegate != nil
145-
precondition(!buildSystemHasDelegate)
146-
self.fallbackBuildSystem = fallbackBuildSystem
147-
self.mainFilesProvider = mainFilesProvider
148-
self.toolchainRegistry = toolchainRegistry
149-
self.buildSystem =
150-
if let testBuildSystem {
151-
await BuiltInBuildSystemAdapter(testBuildSystem: testBuildSystem, messageHandler: self)
152-
} else {
153-
nil
154-
}
155-
await self.buildSystem?.underlyingBuildSystem.setDelegate(self)
156-
}
157-
158136
package func filesDidChange(_ events: [FileEvent]) async {
159137
await self.buildSystem?.send(BuildServerProtocol.DidChangeWatchedFilesNotification(changes: events))
160138
}
161139

162140
/// Implementation of `MessageHandler`, handling notifications from the build system.
163141
///
164142
/// - Important: Do not call directly.
165-
package func handle(_ notification: some LanguageServerProtocol.NotificationType) {
143+
package func handle(_ notification: some LanguageServerProtocol.NotificationType) async {
166144
switch notification {
167145
case let notification as DidChangeBuildTargetNotification:
168-
self.didChangeTextDocumentTargets(notification: notification)
146+
await self.didChangeBuildTarget(notification: notification)
169147
default:
170148
logger.error("Ignoring unknown notification \(type(of: notification).method)")
171149
}
@@ -290,12 +268,24 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
290268
guard let buildSystem, let target else {
291269
return nil
292270
}
271+
let request = SourceKitOptionsRequest(textDocument: TextDocumentIdentifier(uri: document), target: target)
272+
293273
// TODO: We should only wait `fallbackSettingsTimeout` for build settings
294274
// and return fallback afterwards.
295275
// For now, this should be fine because all build systems return
296276
// very quickly from `settings(for:language:)`.
297277
// https://github.com/apple/sourcekit-lsp/issues/1181
298-
return try await buildSystem.underlyingBuildSystem.buildSettings(for: document, in: target, language: language)
278+
let response = try await cachedSourceKitOptions.get(request) { request in
279+
try await buildSystem.send(request)
280+
}
281+
guard let response else {
282+
return nil
283+
}
284+
return FileBuildSettings(
285+
compilerArguments: response.compilerArguments,
286+
workingDirectory: response.workingDirectory,
287+
isFallback: false
288+
)
299289
}
300290

301291
/// Returns the build settings for the given file in the given target.
@@ -440,14 +430,6 @@ extension BuildSystemManager: BuildSystemDelegate {
440430
)
441431
}
442432

443-
package func fileBuildSettingsChanged(_ changedFiles: Set<DocumentURI>) async {
444-
let changedWatchedFiles = watchedFilesReferencing(mainFiles: changedFiles)
445-
446-
if !changedWatchedFiles.isEmpty, let delegate = self.delegate {
447-
await delegate.fileBuildSettingsChanged(changedWatchedFiles)
448-
}
449-
}
450-
451433
package func filesDependenciesUpdated(_ changedFiles: Set<DocumentURI>) async {
452434
// Empty changes --> assume everything has changed.
453435
guard !changedFiles.isEmpty else {
@@ -470,10 +452,28 @@ extension BuildSystemManager: BuildSystemDelegate {
470452
}
471453
}
472454

473-
private func didChangeTextDocumentTargets(notification: DidChangeBuildTargetNotification) {
455+
private func didChangeBuildTarget(notification: DidChangeBuildTargetNotification) async {
474456
// Every `DidChangeBuildTargetNotification` notification needs to invalidate the cache since the changed target
475457
// might gained a source file.
476458
self.cachedTargetsForDocument.clearAll()
459+
460+
let updatedTargets: Set<BuildTargetIdentifier>? =
461+
if let changes = notification.changes {
462+
Set(changes.map(\.target))
463+
} else {
464+
nil
465+
}
466+
self.cachedSourceKitOptions.clear { cacheKey in
467+
guard let updatedTargets else {
468+
// All targets might have changed
469+
return true
470+
}
471+
return updatedTargets.contains(cacheKey.target)
472+
}
473+
474+
// FIXME: (BSP Migration) Communicate that the build target has changed to the `BuildSystemManagerDelegate` and make
475+
// it responsible for figuring out which files are affected.
476+
await delegate?.fileBuildSettingsChanged(Set(watchedFiles.keys))
477477
}
478478
}
479479

Sources/BuildSystemIntegration/BuildSystem.swift renamed to Sources/BuildSystemIntegration/BuiltInBuildSystem.swift

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,10 @@ package protocol BuiltInBuildSystem: AnyObject, Sendable {
108108
///
109109
/// Returns `nil` if the build system can't provide build settings for this
110110
/// file or if it hasn't computed build settings for the file yet.
111-
func buildSettings(
112-
for document: DocumentURI,
113-
in target: BuildTargetIdentifier,
114-
language: Language
115-
) async throws -> FileBuildSettings?
111+
func sourceKitOptions(request: SourceKitOptionsRequest) async throws -> SourceKitOptionsResponse?
116112

117113
/// Return the list of targets that the given document can be built for.
118-
func inverseSources(_ request: InverseSourcesRequest) async -> InverseSourcesResponse
114+
func inverseSources(request: InverseSourcesRequest) async throws -> InverseSourcesResponse
119115

120116
/// Schedule a task that re-generates the build graph. The function may return before the build graph has finished
121117
/// being generated. If clients need to wait for an up-to-date build graph, they should call

0 commit comments

Comments
 (0)