Skip to content

Commit 50328cc

Browse files
committed
Use buildTarget/inverseSources from BSP to get targets of a source file
1 parent 4e5b3a1 commit 50328cc

16 files changed

+248
-95
lines changed

Sources/BuildServerProtocol/BuildTargets.swift

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -211,44 +211,3 @@ public struct OutputsItem: Codable, Hashable, Sendable {
211211
/// The output paths for sources that belong to this build target.
212212
public var outputPaths: [URI]
213213
}
214-
215-
/// The build target changed notification is sent from the server to the client
216-
/// to signal a change in a build target. The server communicates during the
217-
/// initialize handshake whether this method is supported or not.
218-
public struct BuildTargetsChangedNotification: NotificationType {
219-
public static let method: String = "buildTarget/didChange"
220-
221-
public var changes: [BuildTargetEvent]
222-
223-
public init(changes: [BuildTargetEvent]) {
224-
self.changes = changes
225-
}
226-
}
227-
228-
public struct BuildTargetEvent: Codable, Hashable, Sendable {
229-
/// The identifier for the changed build target.
230-
public var target: BuildTargetIdentifier
231-
232-
/// The kind of change for this build target.
233-
public var kind: BuildTargetEventKind?
234-
235-
/// Any additional metadata about what information changed.
236-
public var data: LSPAny?
237-
238-
public init(target: BuildTargetIdentifier, kind: BuildTargetEventKind?, data: LSPAny?) {
239-
self.target = target
240-
self.kind = kind
241-
self.data = data
242-
}
243-
}
244-
245-
public enum BuildTargetEventKind: Int, Codable, Hashable, Sendable {
246-
/// The build target is new.
247-
case created = 1
248-
249-
/// The build target has changed.
250-
case changed = 2
251-
252-
/// The build target has been deleted.
253-
case deleted = 3
254-
}

Sources/BuildServerProtocol/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
add_library(BuildServerProtocol STATIC
22
BuildTargets.swift
3+
DidChangeBuildTargetNotification.swift
34
DidChangeWatchedFilesNotification.swift
45
FileOptions.swift
56
InitializeBuild.swift
7+
InverseSourcesRequest.swift
68
Messages.swift
79
RegisterForChangeNotifications.swift
810
ShutdownBuild.swift)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 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 build target changed notification is sent from the server to the client
16+
/// to signal a change in a build target. The server communicates during the
17+
/// initialize handshake whether this method is supported or not.
18+
public struct DidChangeBuildTargetNotification: NotificationType, Equatable {
19+
public static let method: String = "buildTarget/didChange"
20+
21+
/// **(BSP Extension)**
22+
/// `changes` can be `nil` to indicate that all targets might have changed.
23+
public var changes: [BuildTargetEvent]?
24+
25+
public init(changes: [BuildTargetEvent]?) {
26+
self.changes = changes
27+
}
28+
}
29+
30+
public struct BuildTargetEvent: Codable, Hashable, Sendable {
31+
/// The identifier for the changed build target.
32+
public var target: BuildTargetIdentifier
33+
34+
/// The kind of change for this build target.
35+
public var kind: BuildTargetEventKind?
36+
37+
/// Kind of data to expect in the `data` field. If this field is not set, the kind of data is not specified.
38+
public var dataKind: String?
39+
40+
/// Any additional metadata about what information changed.
41+
public var data: LSPAny?
42+
43+
public init(target: BuildTargetIdentifier, kind: BuildTargetEventKind?, dataKind: String?, data: LSPAny?) {
44+
self.target = target
45+
self.kind = kind
46+
self.dataKind = dataKind
47+
self.data = data
48+
}
49+
}
50+
51+
public enum BuildTargetEventKind: Int, Codable, Hashable, Sendable {
52+
/// The build target is new.
53+
case created = 1
54+
55+
/// The build target has changed.
56+
case changed = 2
57+
58+
/// The build target has been deleted.
59+
case deleted = 3
60+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 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+
public struct TextDocumentIdentifier: Codable, Sendable, Hashable {
16+
/// The text document's URI.
17+
public var uri: URI
18+
19+
public init(uri: URI) {
20+
self.uri = uri
21+
}
22+
}
23+
24+
/// The inverse sources request is sent from the client to the server to query for the list of build targets containing
25+
/// a text document. The server communicates during the initialize handshake whether this method is supported or not.
26+
/// This request can be viewed as the inverse of buildTarget/sources, except it only works for text documents and not
27+
/// directories.
28+
public struct InverseSourcesRequest: RequestType, Hashable {
29+
public static let method: String = "buildTarget/inverseSources"
30+
public typealias Response = InverseSourcesResponse
31+
32+
public var textDocument: TextDocumentIdentifier
33+
34+
public init(textDocument: TextDocumentIdentifier) {
35+
self.textDocument = textDocument
36+
}
37+
}
38+
39+
public struct InverseSourcesResponse: ResponseType, Hashable {
40+
public var targets: [BuildTargetIdentifier]
41+
42+
public init(targets: [BuildTargetIdentifier]) {
43+
self.targets = targets
44+
}
45+
}

Sources/BuildServerProtocol/Messages.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,18 @@
1212
import LanguageServerProtocol
1313

1414
fileprivate let requestTypes: [_RequestType.Type] = [
15-
BuildTargets.self,
1615
BuildTargetOutputPaths.self,
16+
BuildTargets.self,
1717
BuildTargetSources.self,
1818
InitializeBuild.self,
19+
InverseSourcesRequest.self,
1920
RegisterForChanges.self,
2021
ShutdownBuild.self,
2122
SourceKitOptions.self,
2223
]
2324

2425
fileprivate let notificationTypes: [NotificationType.Type] = [
25-
BuildTargetsChangedNotification.self,
26+
DidChangeBuildTargetNotification.self,
2627
ExitBuildNotification.self,
2728
FileOptionsChangedNotification.self,
2829
InitializedBuildNotification.self,

Sources/BuildSystemIntegration/BuildServerBuildSystem.swift

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ package actor BuildServerBuildSystem: MessageHandler {
200200
"""
201201
)
202202
bspMessageHandlingQueue.async {
203-
if let params = params as? BuildTargetsChangedNotification {
203+
if let params = params as? DidChangeBuildTargetNotification {
204204
await self.handleBuildTargetsChanged(params)
205205
} else if let params = params as? FileOptionsChangedNotification {
206206
await self.handleFileOptionsChanged(params)
@@ -225,10 +225,8 @@ package actor BuildServerBuildSystem: MessageHandler {
225225
reply(.failure(ResponseError.methodNotFound(R.method)))
226226
}
227227

228-
func handleBuildTargetsChanged(
229-
_ notification: BuildTargetsChangedNotification
230-
) async {
231-
await self.delegate?.buildTargetsChanged(notification.changes)
228+
func handleBuildTargetsChanged(_ notification: DidChangeBuildTargetNotification) async {
229+
await self.messageHandler?.sendNotificationToSourceKitLSP(notification)
232230
}
233231

234232
func handleFileOptionsChanged(
@@ -283,8 +281,8 @@ extension BuildServerBuildSystem: BuiltInBuildSystem {
283281
return nil
284282
}
285283

286-
package func targets(for document: DocumentURI) async -> [BuildTargetIdentifier] {
287-
return [BuildTargetIdentifier.dummy]
284+
package func inverseSources(_ request: InverseSourcesRequest) -> InverseSourcesResponse {
285+
return InverseSourcesResponse(targets: [BuildTargetIdentifier.dummy])
288286
}
289287

290288
package func generateBuildGraph() {}

Sources/BuildSystemIntegration/BuildSystem.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@ package protocol BuiltInBuildSystem: AnyObject, Sendable {
110110
language: Language
111111
) async throws -> FileBuildSettings?
112112

113-
/// Return the list of targets and run destinations that the given document can be built for.
114-
func targets(for document: DocumentURI) async -> [BuildTargetIdentifier]
113+
/// Return the list of targets that the given document can be built for.
114+
func inverseSources(_ request: InverseSourcesRequest) async -> InverseSourcesResponse
115115

116116
/// Re-generate the build graph.
117117
func generateBuildGraph() async throws

Sources/BuildSystemIntegration/BuildSystemDelegate.swift

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,27 @@ import BuildServerProtocol
1313
import LanguageServerProtocol
1414

1515
/// Handles build system events, such as file build settings changes.
16+
// FIXME: (BSP migration) The build system should exclusively communicate back to SourceKit-LSP using BSP and this protocol should be deleted.
1617
package protocol BuildSystemDelegate: AnyObject, Sendable {
17-
/// Notify the delegate that the build targets have changed.
18+
/// Notify the delegate that the given files' build settings have changed.
19+
func fileBuildSettingsChanged(_ changedFiles: Set<DocumentURI>) async
20+
21+
/// Notify the delegate that the dependencies of the given files have changed
22+
/// and that ASTs may need to be refreshed. If the given set is empty, assume
23+
/// that all watched files are affected.
1824
///
19-
/// The callee should request new sources and outputs for the build targets of
20-
/// interest.
21-
func buildTargetsChanged(_ changes: [BuildTargetEvent]) async
25+
/// The callee should refresh ASTs unless it is able to determine that a
26+
/// refresh is not necessary.
27+
func filesDependenciesUpdated(_ changedFiles: Set<DocumentURI>) async
28+
29+
/// Notify the delegate that the file handling capability of this build system
30+
/// for some file has changed. The delegate should discard any cached file
31+
/// handling capability.
32+
func fileHandlingCapabilityChanged() async
33+
}
2234

35+
/// Handles build system events, such as file build settings changes.
36+
package protocol BuildSystemManagerDelegate: AnyObject, Sendable {
2337
/// Notify the delegate that the given files' build settings have changed.
2438
func fileBuildSettingsChanged(_ changedFiles: Set<DocumentURI>) async
2539

Sources/BuildSystemIntegration/BuildSystemManager.swift

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,39 @@ import struct TSCBasic.AbsolutePath
2323
import os
2424
#endif
2525

26+
fileprivate class RequestCache<Request: RequestType & Hashable> {
27+
private var storage: [Request: Task<Request.Response, Error>] = [:]
28+
29+
func get(
30+
_ key: Request,
31+
isolation: isolated any Actor = #isolation,
32+
compute: @Sendable @escaping (Request) async throws(Error) -> Request.Response
33+
) async throws(Error) -> Request.Response {
34+
let task: Task<Request.Response, Error>
35+
if let cached = storage[key] {
36+
task = cached
37+
} else {
38+
task = Task {
39+
try await compute(key)
40+
}
41+
storage[key] = task
42+
}
43+
return try await task.value
44+
}
45+
46+
func clear(where condition: (Request) -> Bool, isolation: isolated any Actor = #isolation) {
47+
for key in storage.keys {
48+
if condition(key) {
49+
storage[key] = nil
50+
}
51+
}
52+
}
53+
54+
func clearAll(isolation: isolated any Actor = #isolation) {
55+
storage.removeAll()
56+
}
57+
}
58+
2659
/// `BuildSystem` that integrates client-side information such as main-file lookup as well as providing
2760
/// common functionality such as caching.
2861
///
@@ -56,13 +89,15 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
5689
var mainFilesProvider: MainFilesProvider?
5790

5891
/// Build system delegate that will receive notifications about setting changes, etc.
59-
var delegate: BuildSystemDelegate?
92+
var delegate: BuildSystemManagerDelegate?
6093

6194
/// The list of toolchains that are available.
6295
///
6396
/// Used to determine which toolchain to use for a given document.
6497
private let toolchainRegistry: ToolchainRegistry
6598

99+
private var cachedTargetsForDocument = RequestCache<InverseSourcesRequest>()
100+
66101
/// The root of the project that this build system manages. For example, for SwiftPM packages, this is the folder
67102
/// containing Package.swift. For compilation databases it is the root folder based on which the compilation database
68103
/// was found.
@@ -108,7 +143,12 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
108143
///
109144
/// - Important: Do not call directly.
110145
package func handle(_ notification: some LanguageServerProtocol.NotificationType) {
111-
logger.error("Ignoring unknown notification \(type(of: notification).method) from build system")
146+
switch notification {
147+
case let notification as DidChangeBuildTargetNotification:
148+
self.didChangeTextDocumentTargets(notification: notification)
149+
default:
150+
logger.error("Ignoring unknown notification \(type(of: notification).method)")
151+
}
112152
}
113153

114154
/// Implementation of `MessageHandler`, handling requests from the build system.
@@ -119,7 +159,7 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
119159
}
120160

121161
/// - Note: Needed so we can set the delegate from a different isolation context.
122-
package func setDelegate(_ delegate: BuildSystemDelegate?) {
162+
package func setDelegate(_ delegate: BuildSystemManagerDelegate?) {
123163
self.delegate = delegate
124164
}
125165

@@ -160,9 +200,23 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
160200
}
161201
}
162202

163-
/// Returns all the `BuildTargetIdentifier`s that the document is part of.
203+
/// Returns all the `ConfiguredTarget`s that the document is part of.
164204
package func targets(for document: DocumentURI) async -> [BuildTargetIdentifier] {
165-
return await buildSystem?.underlyingBuildSystem.targets(for: document) ?? []
205+
guard let buildSystem else {
206+
return []
207+
}
208+
209+
// FIXME: (BSP migration) Only use `InverseSourcesRequest` if the BSP server declared it can handle it in the
210+
// capabilities
211+
let request = InverseSourcesRequest(textDocument: TextDocumentIdentifier(uri: document))
212+
do {
213+
let response = try await cachedTargetsForDocument.get(request) { document in
214+
return try await buildSystem.send(request)
215+
}
216+
return response.targets
217+
} catch {
218+
return []
219+
}
166220
}
167221

168222
/// Returns the `BuildTargetIdentifier` that should be used for semantic functionality of the given document.
@@ -390,17 +444,17 @@ extension BuildSystemManager: BuildSystemDelegate {
390444
}
391445
}
392446

393-
package func buildTargetsChanged(_ changes: [BuildTargetEvent]) async {
394-
if let delegate = self.delegate {
395-
await delegate.buildTargetsChanged(changes)
396-
}
397-
}
398-
399447
package func fileHandlingCapabilityChanged() async {
400448
if let delegate = self.delegate {
401449
await delegate.fileHandlingCapabilityChanged()
402450
}
403451
}
452+
453+
private func didChangeTextDocumentTargets(notification: DidChangeBuildTargetNotification) {
454+
// Every `DidChangeBuildTargetNotification` notification needs to invalidate the cache since the changed target
455+
// might gained a source file.
456+
self.cachedTargetsForDocument.clearAll()
457+
}
404458
}
405459

406460
extension BuildSystemManager {

0 commit comments

Comments
 (0)