Skip to content

Commit e5caf44

Browse files
committed
Add a BuildSystemDelegate which supports notifications for build settings changes
Introduce a `BuildSystemDelegate` to handle notifications from the build system * `SourceKitServer` is the main delegate to process these notifications * Currently limited to changes in `FileBuildSettings` * Delegate informs the `BuildSystem` of files to watch via `registerChangeWatching(for: URL)` and `unregisterChangeWatching(for: URL)` * In the future we could have more integration for handling changes in dependencies Handling changes in `FileBuildSettings` * `SourceKitServer` sends notifications to the internal LSPs informing them of any opened documents that have changes in their compiler flags * For clangd, we send a notification to update the compilation database * For SourceKit/sourcekitd we must close and reopen the file to force a new AST with the new compiler flags
1 parent c6fedf7 commit e5caf44

15 files changed

+510
-21
lines changed

Sources/LanguageServerProtocol/WorkspaceSettings.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
public enum WorkspaceSettingsChange: Codable, Hashable {
1717

1818
case clangd(ClangWorkspaceSettings)
19+
case documentUpdated(DocumentUpdatedBuildSettings)
1920
case unknown
2021

2122
public init(from decoder: Decoder) throws {
@@ -24,6 +25,8 @@ public enum WorkspaceSettingsChange: Codable, Hashable {
2425
// it will rectify this issue.
2526
if let settings = try? ClangWorkspaceSettings(from: decoder) {
2627
self = .clangd(settings)
28+
} else if let settings = try? DocumentUpdatedBuildSettings(from: decoder) {
29+
self = .documentUpdated(settings)
2730
} else {
2831
self = .unknown
2932
}
@@ -33,6 +36,8 @@ public enum WorkspaceSettingsChange: Codable, Hashable {
3336
switch self {
3437
case .clangd(let settings):
3538
try settings.encode(to: encoder)
39+
case .documentUpdated(let settings):
40+
try settings.encode(to: encoder)
3641
case .unknown:
3742
break // Nothing to do.
3843
}
@@ -74,3 +79,17 @@ public struct ClangCompileCommand: Codable, Hashable {
7479
self.workingDirectory = workingDirectory
7580
}
7681
}
82+
83+
/// Workspace settings for a document that has updated build settings.
84+
public struct DocumentUpdatedBuildSettings: Codable, Hashable {
85+
/// The url of the document that has updated.
86+
public var url: URL
87+
88+
/// The language of the document that has updated.
89+
public var language: Language
90+
91+
public init(url: URL, language: Language) {
92+
self.url = url
93+
self.language = language
94+
}
95+
}

Sources/SKCore/BuildServerBuildSystem.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ public final class BuildServerBuildSystem {
3030
var buildServer: Connection?
3131
public private(set) var indexStorePath: AbsolutePath?
3232

33+
/// Delegate to handle any build system events.
34+
public weak var delegate: BuildSystemDelegate? = nil
35+
3336
public init(projectRoot: AbsolutePath, buildFolder: AbsolutePath?, fileSystem: FileSystem = localFileSystem) throws {
3437
let configPath = projectRoot.appending(component: "buildServer.json")
3538
let config = try loadBuildServerConfig(path: configPath, fileSystem: fileSystem)
@@ -116,6 +119,19 @@ final class BuildServerHandler: LanguageServerEndpoint {
116119

117120
extension BuildServerBuildSystem: BuildSystem {
118121

122+
/// Register the given file for build-system level change notifications, such as command
123+
/// line flag changes, dependency changes, etc.
124+
public func registerForChangeNotifications(for url: LanguageServerProtocol.URL) {
125+
// TODO: Implement via BSP extensions.
126+
}
127+
128+
/// Unregister the given file for build-system level change notifications, such as command
129+
/// line flag changes, dependency changes, etc.
130+
public func unregisterForChangeNotifications(for url: LanguageServerProtocol.URL) {
131+
// TODO: Implement via BSP extensions.
132+
}
133+
134+
119135
public var indexDatabasePath: AbsolutePath? {
120136
return buildFolder?.appending(components: "index", "db")
121137
}

Sources/SKCore/BuildSystem.swift

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ import TSCBasic
1515

1616
/// Provider of FileBuildSettings and other build-related information.
1717
///
18-
/// The primary role of the build system is to answer queries for FileBuildSettings and (TODO) to
19-
/// notify clients when they change. The BuildSystem is also the source of related informatino,
20-
/// such as where the index datastore is located.
18+
/// The primary role of the build system is to answer queries for FileBuildSettings and to notify
19+
/// its delegate when they change. The BuildSystem is also the source of related information, such
20+
/// as where the index datastore is located.
2121
///
2222
/// For example, a SwiftPMWorkspace provides compiler arguments for the files contained in a
2323
/// SwiftPM package root directory.
24-
public protocol BuildSystem {
24+
public protocol BuildSystem: AnyObject {
2525

2626
/// The path to the raw index store data, if any.
2727
var indexStorePath: AbsolutePath? { get }
@@ -35,5 +35,14 @@ public protocol BuildSystem {
3535
/// Returns the toolchain to use to compile this file
3636
func toolchain(for: URL, _ language: Language) -> Toolchain?
3737

38-
// TODO: notifications when settings change.
38+
/// Delegate to handle any build system events such as file build settings changing.
39+
var delegate: BuildSystemDelegate? { get set }
40+
41+
/// Register the given file for build-system level change notifications, such as command
42+
/// line flag changes, dependency changes, etc.
43+
func registerForChangeNotifications(for: URL)
44+
45+
/// Unregister the given file for build-system level change notifications, such as command
46+
/// line flag changes, dependency changes, etc.
47+
func unregisterForChangeNotifications(for: URL)
3948
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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+
import TSCBasic
15+
16+
/// Handles build system events, such as file build settings changes.
17+
public protocol BuildSystemDelegate: AnyObject {
18+
19+
/// Notify the delegate that the given files' build settings have changed.
20+
///
21+
/// The callee should request new build settings for any of the given files that they are interested in.
22+
func fileBuildSettingsChanged(_ changedFiles: Set<URL>)
23+
}

Sources/SKCore/BuildSystemList.swift

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ import LanguageServerProtocol
1616
/// Provides build settings from a list of build systems in priority order.
1717
public final class BuildSystemList {
1818

19+
/// Delegate to handle any build system events.
20+
public var delegate: BuildSystemDelegate? {
21+
get { return providers.first?.delegate }
22+
set { providers.first?.delegate = newValue }
23+
}
24+
1925
/// The build systems to try (in order).
2026
public var providers: [BuildSystem] = [
2127
FallbackBuildSystem()
@@ -25,7 +31,6 @@ public final class BuildSystemList {
2531
}
2632

2733
extension BuildSystemList: BuildSystem {
28-
2934
public var indexStorePath: AbsolutePath? { return providers.first?.indexStorePath }
3035

3136
public var indexDatabasePath: AbsolutePath? { return providers.first?.indexDatabasePath }
@@ -39,6 +44,20 @@ extension BuildSystemList: BuildSystem {
3944
return nil
4045
}
4146

47+
/// Register the given file for build-system level change notifications, such as command
48+
/// line flag changes, dependency changes, etc.
49+
public func registerForChangeNotifications(for url: URL) {
50+
// Only register with the primary build system, since we only use its delegate.
51+
providers.first?.registerForChangeNotifications(for: url)
52+
}
53+
54+
/// Unregister the given file for build-system level change notifications, such as command
55+
/// line flag changes, dependency changes, etc.
56+
public func unregisterForChangeNotifications(for url: URL) {
57+
// Only unregister with the primary build system, since we only use its delegate.
58+
providers.first?.unregisterForChangeNotifications(for: url)
59+
}
60+
4261
public func toolchain(for url: URL, _ language: Language) -> Toolchain? {
4362
return providers.first?.toolchain(for: url, language)
4463
}

Sources/SKCore/CompilationDatabaseBuildSystem.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ public final class CompilationDatabaseBuildSystem {
2323
/// The compilation database.
2424
var compdb: CompilationDatabase? = nil
2525

26+
/// Delegate to handle any build system events.
27+
public weak var delegate: BuildSystemDelegate? = nil
28+
2629
let fileSystem: FileSystem
2730

2831
public init(projectRoot: AbsolutePath? = nil, fileSystem: FileSystem = localFileSystem) {
@@ -50,6 +53,12 @@ extension CompilationDatabaseBuildSystem: BuildSystem {
5053

5154
public func toolchain(for: URL, _ language: Language) -> Toolchain? { return nil }
5255

56+
/// We don't support change watching.
57+
public func registerForChangeNotifications(for: URL) {}
58+
59+
/// We don't support change watching.
60+
public func unregisterForChangeNotifications(for: URL) {}
61+
5362
func database(for url: URL) -> CompilationDatabase? {
5463
if let path = try? AbsolutePath(validating: url.path) {
5564
return database(for: path)

Sources/SKCore/FallbackBuildSystem.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ public final class FallbackBuildSystem: BuildSystem {
3131
return nil
3232
}()
3333

34+
/// Delegate to handle any build system events.
35+
public weak var delegate: BuildSystemDelegate? = nil
36+
3437
public var indexStorePath: AbsolutePath? { return nil }
3538

3639
public var indexDatabasePath: AbsolutePath? { return nil }
@@ -50,6 +53,12 @@ public final class FallbackBuildSystem: BuildSystem {
5053
}
5154
}
5255

56+
/// We don't support change watching.
57+
public func registerForChangeNotifications(for: URL) {}
58+
59+
/// We don't support change watching.
60+
public func unregisterForChangeNotifications(for: URL) {}
61+
5362
public func toolchain(for: URL, _ language: Language) -> Toolchain? { return nil }
5463

5564
func settingsSwift(_ path: AbsolutePath) -> FileBuildSettings {

Sources/SKCore/FileBuildSettings.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
///
1515
/// Encapsulates all the settings needed to compile a single file, including the compiler arguments
1616
/// and working directory. FileBuildSettings are typically the result of a BuildSystem query.
17-
public struct FileBuildSettings {
17+
public struct FileBuildSettings: Equatable {
1818

1919
/// The compiler arguments to use for this file.
2020
public var compilerArguments: [String]

Sources/SKSwiftPMWorkspace/SwiftPMWorkspace.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ public final class SwiftPMWorkspace {
3737
case cannotDetermineHostToolchain
3838
}
3939

40+
/// Delegate to handle any build system events.
41+
public weak var delegate: BuildSystemDelegate? = nil
42+
4043
let workspacePath: AbsolutePath
4144
let packageRoot: AbsolutePath
4245
var packageGraph: PackageGraph
@@ -218,6 +221,18 @@ extension SwiftPMWorkspace: BuildSystem {
218221
return nil
219222
}
220223

224+
/// Register the given file for build-system level change notifications, such as command
225+
/// line flag changes, dependency changes, etc.
226+
public func registerForChangeNotifications(for url: LanguageServerProtocol.URL) {
227+
// TODO: Support for change detection (via file watching)
228+
}
229+
230+
/// Unregister the given file for build-system level change notifications, such as command
231+
/// line flag changes, dependency changes, etc.
232+
public func unregisterForChangeNotifications(for url: LanguageServerProtocol.URL) {
233+
// TODO: Support for change detection (via file watching)
234+
}
235+
221236
/// Returns the resolved target description for the given file, if one is known.
222237
func targetDescription(for file: AbsolutePath) -> TargetBuildDescription? {
223238
if let td = fileToTarget[file] {

Sources/SourceKit/DocumentManager.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ public final class DocumentManager {
6161

6262
var documents: [URL: Document] = [:]
6363

64+
/// All currently opened documents.
65+
public var openDocuments: Set<URL> {
66+
return queue.sync {
67+
return Set(documents.keys)
68+
}
69+
}
70+
6471
/// Opens a new document with the given content and metadata.
6572
///
6673
/// - returns: The initial contents of the file.

Sources/SourceKit/SourceKitServer.swift

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,32 @@ public final class SourceKitServer: LanguageServer {
216216
}
217217
}
218218

219+
// MARK: - Build System Delegate
220+
221+
extension SourceKitServer: BuildSystemDelegate {
222+
public func fileBuildSettingsChanged(_ changedFiles: Set<URL>) {
223+
guard let workspace = self.workspace else {
224+
return
225+
}
226+
let documentManager = workspace.documentManager
227+
let openDocuments = documentManager.openDocuments
228+
for url in changedFiles {
229+
guard openDocuments.contains(url) else {
230+
continue
231+
}
232+
233+
log("Build settings changed for opened file \(url)")
234+
if let snapshot = documentManager.latestSnapshot(url),
235+
let service = languageService(for: url, snapshot.document.language, in: workspace) {
236+
service.send(
237+
DidChangeConfiguration(settings:
238+
.documentUpdated(
239+
DocumentUpdatedBuildSettings(url: url, language: snapshot.document.language))))
240+
}
241+
}
242+
}
243+
}
244+
219245
// MARK: - Request and notification handling
220246

221247
extension SourceKitServer {
@@ -259,6 +285,9 @@ extension SourceKitServer {
259285
)
260286
}
261287

288+
assert(self.workspace != nil)
289+
self.workspace?.buildSettings.delegate = self
290+
262291
req.reply(InitializeResult(capabilities: ServerCapabilities(
263292
textDocumentSync: TextDocumentSyncOptions(
264293
openClose: true,
@@ -311,15 +340,21 @@ extension SourceKitServer {
311340
func openDocument(_ note: Notification<DidOpenTextDocument>, workspace: Workspace) {
312341
workspace.documentManager.open(note)
313342

314-
if let service = languageService(for: note.params.textDocument.url, note.params.textDocument.language, in: workspace) {
343+
let textDocument = note.params.textDocument
344+
workspace.buildSettings.registerForChangeNotifications(for: textDocument.url)
345+
346+
if let service = languageService(for: textDocument.url, textDocument.language, in: workspace) {
315347
service.send(note.params)
316348
}
317349
}
318350

319351
func closeDocument(_ note: Notification<DidCloseTextDocument>, workspace: Workspace) {
320352
workspace.documentManager.close(note)
321353

322-
if let service = workspace.documentService[note.params.textDocument.url] {
354+
let url = note.params.textDocument.url
355+
workspace.buildSettings.unregisterForChangeNotifications(for: url)
356+
357+
if let service = workspace.documentService[url] {
323358
service.send(note.params)
324359
}
325360
}

Sources/SourceKit/clangd/ClangLanguageServer.swift

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ final class ClangLanguageServerShim: LanguageServer {
2929
let clang: AbsolutePath?
3030

3131
/// Creates a language server for the given client using the sourcekitd dylib at the specified path.
32-
public init(client: Connection, clangd: Connection, buildSystem: BuildSystem, clang: AbsolutePath?) throws {
33-
32+
public init(client: Connection, clangd: Connection, buildSystem: BuildSystem,
33+
clang: AbsolutePath?) throws {
3434
self.clangd = clangd
3535
self.buildSystem = buildSystem
3636
self.clang = clang
@@ -39,6 +39,7 @@ final class ClangLanguageServerShim: LanguageServer {
3939

4040
public override func _registerBuiltinHandlers() {
4141
_register(ClangLanguageServerShim.initialize)
42+
_register(ClangLanguageServerShim.didChangeConfiguration)
4243
_register(ClangLanguageServerShim.openDocument)
4344
_register(ClangLanguageServerShim.foldingRange)
4445
}
@@ -97,9 +98,29 @@ extension ClangLanguageServerShim {
9798
}
9899
}
99100

101+
// MARK: - Workspace
102+
103+
func didChangeConfiguration(_ note: Notification<DidChangeConfiguration>) {
104+
switch note.params.settings {
105+
case .clangd:
106+
break
107+
case .documentUpdated(let settings):
108+
updateDocumentSettings(url: settings.url, language: settings.language)
109+
case .unknown:
110+
break
111+
}
112+
}
113+
114+
// MARK: - Text synchronization
115+
100116
func openDocument(_ note: Notification<DidOpenTextDocument>) {
101-
let url = note.params.textDocument.url
102-
let settings = buildSystem.settings(for: url, note.params.textDocument.language)
117+
let textDocument = note.params.textDocument
118+
updateDocumentSettings(url: textDocument.url, language: textDocument.language)
119+
clangd.send(note.params)
120+
}
121+
122+
private func updateDocumentSettings(url: URL, language: Language) {
123+
let settings = buildSystem.settings(for: url, language)
103124

104125
logAsync(level: settings == nil ? .warning : .debug) { _ in
105126
let settingsStr = settings == nil ? "nil" : settings!.compilerArguments.description
@@ -111,8 +132,6 @@ extension ClangLanguageServerShim {
111132
ClangWorkspaceSettings(
112133
compilationDatabaseChanges: [url.path: ClangCompileCommand(settings, clang: clang)]))))
113134
}
114-
115-
clangd.send(note.params)
116135
}
117136

118137
func foldingRange(_ req: Request<FoldingRangeRequest>) {

0 commit comments

Comments
 (0)