Skip to content

Commit cf47f2c

Browse files
authored
Merge pull request #1659 from ahoppen/more-bsp
Migrate `fileHandlingCapability` and `prepare` to BSP and remove `registerForChangeNotifications`
2 parents bf8ff8b + 3a11898 commit cf47f2c

18 files changed

+267
-237
lines changed

Sources/BuildServerProtocol/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ add_library(BuildServerProtocol STATIC
44
DidChangeWatchedFilesNotification.swift
55
InitializeBuild.swift
66
InverseSourcesRequest.swift
7+
LogMessageNotification.swift
78
Messages.swift
9+
PrepareTargetsRequest.swift
810
RegisterForChangeNotifications.swift
911
ShutdownBuild.swift
1012
SourceKitOptionsRequest.swift)
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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 enum LogMessageType: Int, Sendable, Codable {
16+
/// An error message.
17+
case error = 1
18+
19+
/// A warning message.
20+
case warning = 2
21+
22+
/// An information message.
23+
case info = 3
24+
25+
/// A log message.
26+
case log = 4
27+
}
28+
29+
public typealias TaskIdentifier = String
30+
31+
public struct TaskId: Sendable, Codable {
32+
/// A unique identifier
33+
public var id: TaskIdentifier
34+
35+
/// The parent task ids, if any. A non-empty parents field means
36+
/// this task is a sub-task of every parent task id. The child-parent
37+
/// relationship of tasks makes it possible to render tasks in
38+
/// a tree-like user interface or inspect what caused a certain task
39+
/// execution.
40+
/// OriginId should not be included in the parents field, there is a separate
41+
/// field for that.
42+
public var parents: [TaskIdentifier]?
43+
44+
public init(id: TaskIdentifier, parents: [TaskIdentifier]? = nil) {
45+
self.id = id
46+
self.parents = parents
47+
}
48+
}
49+
50+
/// The log message notification is sent from a server to a client to ask the client to log a particular message in its console.
51+
///
52+
/// A `build/logMessage`` notification is similar to LSP's `window/logMessage``, except for a few additions like id and originId.
53+
public struct LogMessageNotification: NotificationType {
54+
public static let method: String = "build/logMessage"
55+
56+
/// The message type.
57+
public var type: LogMessageType
58+
59+
/// The task id if any.
60+
public var task: TaskId?
61+
62+
/// The actual message.
63+
public var message: String
64+
65+
public init(type: LogMessageType, task: TaskId? = nil, message: String) {
66+
self.type = type
67+
self.task = task
68+
self.message = message
69+
}
70+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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 typealias OriginId = String
16+
17+
public struct PrepareTargetsRequest: RequestType, Hashable {
18+
public static let method: String = "buildTarget/prepare"
19+
public typealias Response = VoidResponse
20+
21+
/// A list of build targets to prepare.
22+
public var targets: [BuildTargetIdentifier]
23+
24+
public var originId: OriginId?
25+
26+
public init(targets: [BuildTargetIdentifier], originId: OriginId? = nil) {
27+
self.targets = targets
28+
self.originId = originId
29+
}
30+
}

Sources/BuildServerProtocol/RegisterForChangeNotifications.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
1010
//
1111
//===----------------------------------------------------------------------===//
12+
1213
import LanguageServerProtocol
1314

1415
/// The register for changes request is sent from the language

Sources/BuildSystemIntegration/BuildServerBuildSystem.swift

Lines changed: 35 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ package actor BuildServerBuildSystem: MessageHandler {
8080
/// The build settings that have been received from the build server.
8181
private var buildSettings: [DocumentURI: SourceKitOptionsResponse] = [:]
8282

83+
private var urisRegisteredForChanges: Set<URI> = []
84+
8385
package init(
8486
projectRoot: AbsolutePath,
8587
messageHandler: BuiltInBuildSystemMessageHandler?,
@@ -269,7 +271,38 @@ extension BuildServerBuildSystem: BuiltInBuildSystem {
269271
package nonisolated var supportsPreparation: Bool { false }
270272

271273
package func sourceKitOptions(request: SourceKitOptionsRequest) async throws -> SourceKitOptionsResponse? {
272-
return buildSettings[request.textDocument.uri]
274+
// FIXME: (BSP Migration) If the BSP server supports it, send the `SourceKitOptions` request to it. Only do the
275+
// `RegisterForChanges` dance if we are in the legacy mode.
276+
277+
// Support the pre Swift 6.1 build settings workflow where SourceKit-LSP registers for changes for a file and then
278+
// expects updates to those build settings to get pushed to SourceKit-LSP with `FileOptionsChangedNotification`.
279+
// We do so by registering for changes when requesting build settings for a document for the first time. We never
280+
// unregister for changes. The expectation is that all BSP servers migrate to the `SourceKitOptionsRequest` soon,
281+
// which renders this code path dead.
282+
let uri = request.textDocument.uri
283+
if !urisRegisteredForChanges.contains(uri) {
284+
let request = RegisterForChanges(uri: uri, action: .register)
285+
_ = self.buildServer?.send(request) { result in
286+
if let error = result.failure {
287+
logger.error("Error registering \(request.uri): \(error.forLogging)")
288+
289+
Task {
290+
// BuildServer registration failed, so tell our delegate that no build
291+
// settings are available.
292+
await self.buildSettingsChanged(for: request.uri, settings: nil)
293+
}
294+
}
295+
}
296+
}
297+
298+
guard let buildSettings = buildSettings[uri] else {
299+
return nil
300+
}
301+
302+
return SourceKitOptionsResponse(
303+
compilerArguments: buildSettings.compilerArguments,
304+
workingDirectory: buildSettings.workingDirectory
305+
)
273306
}
274307

275308
package func defaultLanguage(for document: DocumentURI) async -> Language? {
@@ -296,64 +329,12 @@ extension BuildServerBuildSystem: BuiltInBuildSystem {
296329
return nil
297330
}
298331

299-
package func prepare(
300-
targets: [BuildTargetIdentifier],
301-
logMessageToIndexLog: @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void
302-
) async throws {
332+
package func prepare(request: PrepareTargetsRequest) async throws -> VoidResponse {
303333
throw PrepareNotSupportedError()
304334
}
305335

306-
package func registerForChangeNotifications(for uri: DocumentURI) {
307-
let request = RegisterForChanges(uri: uri, action: .register)
308-
_ = self.buildServer?.send(request) { result in
309-
if let error = result.failure {
310-
logger.error("Error registering \(uri): \(error.forLogging)")
311-
312-
Task {
313-
// BuildServer registration failed, so tell our delegate that no build
314-
// settings are available.
315-
await self.buildSettingsChanged(for: uri, settings: nil)
316-
}
317-
}
318-
}
319-
}
320-
321-
/// Unregister the given file for build-system level change notifications, such as command
322-
/// line flag changes, dependency changes, etc.
323-
package func unregisterForChangeNotifications(for uri: DocumentURI) {
324-
let request = RegisterForChanges(uri: uri, action: .unregister)
325-
_ = self.buildServer?.send(request) { result in
326-
if let error = result.failure {
327-
logger.error("Error unregistering \(uri.forLogging): \(error.forLogging)")
328-
}
329-
}
330-
}
331-
332336
package func didChangeWatchedFiles(notification: BuildServerProtocol.DidChangeWatchedFilesNotification) {}
333337

334-
package func fileHandlingCapability(for uri: DocumentURI) -> FileHandlingCapability {
335-
guard
336-
let fileUrl = uri.fileURL,
337-
let path = try? AbsolutePath(validating: fileUrl.path)
338-
else {
339-
return .unhandled
340-
}
341-
342-
// TODO: We should not make any assumptions about which files the build server can handle.
343-
// Instead we should query the build server which files it can handle
344-
// (https://github.com/swiftlang/sourcekit-lsp/issues/492).
345-
346-
if projectRoot.isAncestorOfOrEqual(to: path) {
347-
return .handled
348-
}
349-
350-
if let realpath = try? resolveSymlinks(path), realpath != path, projectRoot.isAncestorOfOrEqual(to: realpath) {
351-
return .handled
352-
}
353-
354-
return .unhandled
355-
}
356-
357338
package func sourceFiles() async -> [SourceFileInfo] {
358339
// BuildServerBuildSystem does not support syntactic test discovery or background indexing.
359340
// (https://github.com/swiftlang/sourcekit-lsp/issues/1173).

Sources/BuildSystemIntegration/BuildSystemDelegate.swift

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,6 @@ package protocol BuildSystemDelegate: AnyObject, Sendable {
2222
/// The callee should refresh ASTs unless it is able to determine that a
2323
/// refresh is not necessary.
2424
func filesDependenciesUpdated(_ changedFiles: Set<DocumentURI>) async
25-
26-
/// Notify the delegate that the file handling capability of this build system
27-
/// for some file has changed. The delegate should discard any cached file
28-
/// handling capability.
29-
func fileHandlingCapabilityChanged() async
3025
}
3126

3227
/// Handles build system events, such as file build settings changes.
@@ -42,8 +37,10 @@ package protocol BuildSystemManagerDelegate: AnyObject, Sendable {
4237
/// refresh is not necessary.
4338
func filesDependenciesUpdated(_ changedFiles: Set<DocumentURI>) async
4439

45-
/// Notify the delegate that the file handling capability of this build system
46-
/// for some file has changed. The delegate should discard any cached file
47-
/// handling capability.
48-
func fileHandlingCapabilityChanged() async
40+
/// Notify the delegate that some information about the given build targets has changed and that it should recompute
41+
/// any information based on top of it.
42+
func buildTargetsChanged(_ changes: [BuildTargetEvent]?) async
43+
44+
/// Log the given message to the client's index log.
45+
func logMessageToIndexLog(taskID: IndexTaskID, message: String)
4946
}

Sources/BuildSystemIntegration/BuildSystemManager.swift

Lines changed: 18 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
8181

8282
/// The fallback build system. If present, used when the `buildSystem` is not
8383
/// set or cannot provide settings.
84-
let fallbackBuildSystem: FallbackBuildSystem?
84+
let fallbackBuildSystem: FallbackBuildSystem
8585

8686
/// Provider of file to main file mappings.
8787
var mainFilesProvider: MainFilesProvider?
@@ -101,11 +101,7 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
101101
/// The root of the project that this build system manages. For example, for SwiftPM packages, this is the folder
102102
/// containing Package.swift. For compilation databases it is the root folder based on which the compilation database
103103
/// was found.
104-
package var projectRoot: AbsolutePath? {
105-
get async {
106-
return await buildSystem?.underlyingBuildSystem.projectRoot
107-
}
108-
}
104+
package let projectRoot: AbsolutePath?
109105

110106
package var supportsPreparation: Bool {
111107
get async {
@@ -122,6 +118,7 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
122118
) async {
123119
self.fallbackBuildSystem = FallbackBuildSystem(options: options.fallbackBuildSystemOrDefault)
124120
self.toolchainRegistry = toolchainRegistry
121+
self.projectRoot = buildSystemKind?.projectRoot
125122
self.buildSystem = await BuiltInBuildSystemAdapter(
126123
buildSystemKind: buildSystemKind,
127124
toolchainRegistry: toolchainRegistry,
@@ -149,6 +146,8 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
149146
switch notification {
150147
case let notification as DidChangeBuildTargetNotification:
151148
await self.didChangeBuildTarget(notification: notification)
149+
case let notification as BuildServerProtocol.LogMessageNotification:
150+
await self.logMessage(notification: notification)
152151
default:
153152
logger.error("Ignoring unknown notification \(type(of: notification).method)")
154153
}
@@ -315,7 +314,7 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
315314
logger.error("Getting build settings failed: \(error.forLogging)")
316315
}
317316

318-
guard var settings = await fallbackBuildSystem?.buildSettings(for: document, language: language) else {
317+
guard var settings = await fallbackBuildSystem.buildSettings(for: document, language: language) else {
319318
return nil
320319
}
321320
if buildSystem == nil {
@@ -372,40 +371,17 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
372371
targets: [BuildTargetIdentifier],
373372
logMessageToIndexLog: @escaping @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void
374373
) async throws {
375-
try await buildSystem?.underlyingBuildSystem.prepare(targets: targets, logMessageToIndexLog: logMessageToIndexLog)
374+
let _: VoidResponse? = try await buildSystem?.send(PrepareTargetsRequest(targets: targets))
376375
}
377376

378377
package func registerForChangeNotifications(for uri: DocumentURI, language: Language) async {
379378
logger.debug("registerForChangeNotifications(\(uri.forLogging))")
380379
let mainFile = await mainFile(for: uri, language: language)
381380
self.watchedFiles[uri] = (mainFile, language)
382-
383-
// Register for change notifications of the main file in the underlying build
384-
// system. That way, iff the main file changes, we will also notify the
385-
// delegate about build setting changes of all header files that are based
386-
// on that main file.
387-
await buildSystem?.underlyingBuildSystem.registerForChangeNotifications(for: mainFile)
388381
}
389382

390383
package func unregisterForChangeNotifications(for uri: DocumentURI) async {
391-
guard let mainFile = self.watchedFiles[uri]?.mainFile else {
392-
logger.fault("Unbalanced calls for registerForChangeNotifications and unregisterForChangeNotifications")
393-
return
394-
}
395384
self.watchedFiles[uri] = nil
396-
397-
if watchedFilesReferencing(mainFiles: [mainFile]).isEmpty {
398-
// Nobody is interested in this main file anymore.
399-
// We are no longer interested in change notifications for it.
400-
await self.buildSystem?.underlyingBuildSystem.unregisterForChangeNotifications(for: mainFile)
401-
}
402-
}
403-
404-
package func fileHandlingCapability(for uri: DocumentURI) async -> FileHandlingCapability {
405-
return max(
406-
await buildSystem?.underlyingBuildSystem.fileHandlingCapability(for: uri) ?? .unhandled,
407-
fallbackBuildSystem != nil ? .fallback : .unhandled
408-
)
409385
}
410386

411387
package func sourceFiles() async -> [SourceFileInfo] {
@@ -451,12 +427,6 @@ extension BuildSystemManager: BuildSystemDelegate {
451427
}
452428
}
453429

454-
package func fileHandlingCapabilityChanged() async {
455-
if let delegate = self.delegate {
456-
await delegate.fileHandlingCapabilityChanged()
457-
}
458-
}
459-
460430
private func didChangeBuildTarget(notification: DidChangeBuildTargetNotification) async {
461431
// Every `DidChangeBuildTargetNotification` notification needs to invalidate the cache since the changed target
462432
// might gained a source file.
@@ -476,10 +446,21 @@ extension BuildSystemManager: BuildSystemDelegate {
476446
return updatedTargets.contains(cacheKey.target)
477447
}
478448

449+
await delegate?.buildTargetsChanged(notification.changes)
479450
// FIXME: (BSP Migration) Communicate that the build target has changed to the `BuildSystemManagerDelegate` and make
480451
// it responsible for figuring out which files are affected.
481452
await delegate?.fileBuildSettingsChanged(Set(watchedFiles.keys))
482453
}
454+
455+
private func logMessage(notification: BuildServerProtocol.LogMessageNotification) async {
456+
// FIXME: (BSP Integration) Remove the restriction that task IDs need to have a raw value that can be parsed by
457+
// `IndexTaskID.init`.
458+
guard let task = notification.task, let taskID = IndexTaskID(rawValue: task.id) else {
459+
logger.error("Ignoring log message notification with unknown task \(notification.task?.id ?? "<nil>")")
460+
return
461+
}
462+
delegate?.logMessageToIndexLog(taskID: taskID, message: notification.message)
463+
}
483464
}
484465

485466
extension BuildSystemManager {

0 commit comments

Comments
 (0)