Skip to content

Commit ced1502

Browse files
committed
Make Workspace the delegate of a BuildSystemManager
`Workspace` is responsible for creating the `BuildSystemManager` and responds to most of the delegate calls. It should thus also be the delegate of `BuildSystemManager`.
1 parent 9473aeb commit ced1502

File tree

6 files changed

+89
-82
lines changed

6 files changed

+89
-82
lines changed

Sources/BuildSystemIntegration/BuildSystemManager.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
8787
var mainFilesProvider: MainFilesProvider?
8888

8989
/// Build system delegate that will receive notifications about setting changes, etc.
90-
var delegate: BuildSystemManagerDelegate?
90+
private weak var delegate: BuildSystemManagerDelegate?
9191

9292
/// The list of toolchains that are available.
9393
///
@@ -157,6 +157,11 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate {
157157

158158
package func filesDidChange(_ events: [FileEvent]) async {
159159
await self.buildSystem?.send(BuildServerProtocol.DidChangeWatchedFilesNotification(changes: events))
160+
if let mainFilesProvider {
161+
var mainFiles = await Set(events.asyncFlatMap { await mainFilesProvider.mainFilesContainingFile($0.uri) })
162+
mainFiles.subtract(events.map(\.uri))
163+
await self.filesDependenciesUpdated(mainFiles)
164+
}
160165
}
161166

162167
/// Implementation of `MessageHandler`, handling notifications from the build system.

Sources/SourceKitLSP/SourceKitLSPServer.swift

Lines changed: 10 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -842,67 +842,6 @@ extension SourceKitLSPServer: MessageHandler {
842842
}
843843
}
844844

845-
// MARK: - Build System Delegate
846-
847-
extension SourceKitLSPServer: BuildSystemManagerDelegate {
848-
private func affectedOpenDocumentsForChangeSet(
849-
_ changes: Set<DocumentURI>,
850-
_ documentManager: DocumentManager
851-
) -> Set<DocumentURI> {
852-
// An empty change set is treated as if all open files have been modified.
853-
guard !changes.isEmpty else {
854-
return documentManager.openDocuments
855-
}
856-
return documentManager.openDocuments.intersection(changes)
857-
}
858-
859-
/// Handle a build settings change notification from the `BuildSystem`.
860-
/// This has two primary cases:
861-
/// - Initial settings reported for a given file, now we can fully open it
862-
/// - Changed settings for an already open file
863-
package func fileBuildSettingsChanged(_ changedFiles: Set<DocumentURI>) async {
864-
for uri in changedFiles {
865-
guard self.documentManager.openDocuments.contains(uri) else {
866-
continue
867-
}
868-
869-
guard let service = await self.workspaceForDocument(uri: uri)?.documentService(for: uri) else {
870-
continue
871-
}
872-
873-
await service.documentUpdatedBuildSettings(uri)
874-
}
875-
}
876-
877-
/// Handle a dependencies updated notification from the `BuildSystem`.
878-
/// We inform the respective language services as long as the given file is open
879-
/// (not queued for opening).
880-
package func filesDependenciesUpdated(_ changedFiles: Set<DocumentURI>) async {
881-
// Split the changedFiles into the workspaces they belong to.
882-
// Then invoke affectedOpenDocumentsForChangeSet for each workspace with its affected files.
883-
let changedFilesAndWorkspace = await changedFiles.asyncMap {
884-
return (uri: $0, workspace: await self.workspaceForDocument(uri: $0))
885-
}
886-
for workspace in self.workspaces {
887-
let changedFilesForWorkspace = Set(changedFilesAndWorkspace.filter({ $0.workspace === workspace }).map(\.uri))
888-
if changedFilesForWorkspace.isEmpty {
889-
continue
890-
}
891-
for uri in self.affectedOpenDocumentsForChangeSet(changedFilesForWorkspace, self.documentManager) {
892-
logger.log("Dependencies updated for opened file \(uri.forLogging)")
893-
if let service = workspace.documentService(for: uri) {
894-
await service.documentDependenciesUpdated(uri)
895-
}
896-
}
897-
}
898-
}
899-
900-
package func fileHandlingCapabilityChanged() {
901-
logger.log("Updating URI to workspace because file handling capability of a workspace changed")
902-
self.scheduleUpdateOfUriToWorkspace()
903-
}
904-
}
905-
906845
extension SourceKitLSPServer {
907846
nonisolated func logMessageToIndexLog(taskID: IndexTaskID, message: String) {
908847
var message: Substring = message[...]
@@ -980,6 +919,10 @@ extension SourceKitLSPServer {
980919
},
981920
reloadPackageStatusCallback: { [weak self] status in
982921
await self?.reloadPackageStatusCallback(status)
922+
},
923+
fileHandlingCapabilityChanged: { [weak self] in
924+
logger.log("Updating URI to workspace because file handling capability of a workspace changed")
925+
await self?.scheduleUpdateOfUriToWorkspace()
983926
}
984927
)
985928
if options.backgroundIndexingOrDefault, workspace.semanticIndexManager == nil,
@@ -1111,6 +1054,10 @@ extension SourceKitLSPServer {
11111054
},
11121055
reloadPackageStatusCallback: { [weak self] status in
11131056
await self?.reloadPackageStatusCallback(status)
1057+
},
1058+
fileHandlingCapabilityChanged: { [weak self] in
1059+
logger.log("Updating URI to workspace because file handling capability of a workspace changed")
1060+
await self?.scheduleUpdateOfUriToWorkspace()
11141061
}
11151062
)
11161063

@@ -1119,9 +1066,6 @@ extension SourceKitLSPServer {
11191066
}.value
11201067

11211068
assert(!self.workspaces.isEmpty)
1122-
for workspace in self.workspaces {
1123-
await workspace.buildSystemManager.setDelegate(self)
1124-
}
11251069

11261070
return InitializeResult(
11271071
capabilities: await self.serverCapabilities(
@@ -1340,9 +1284,6 @@ extension SourceKitLSPServer {
13401284
for workspace in self.workspaces {
13411285
await workspace.buildSystemManager.setMainFilesProvider(nil)
13421286
workspace.closeIndex()
1343-
1344-
// Break retain cycle with the BSM.
1345-
await workspace.buildSystemManager.setDelegate(nil)
13461287
}
13471288
}
13481289

@@ -1541,9 +1482,6 @@ extension SourceKitLSPServer {
15411482
)
15421483
}
15431484
}
1544-
for workspace in newWorkspaces {
1545-
await workspace.buildSystemManager.setDelegate(self)
1546-
}
15471485
self.workspacesAndIsImplicit += newWorkspaces.map { (workspace: $0, isImplicit: false) }
15481486
}
15491487
}.value
@@ -1940,7 +1878,7 @@ extension SourceKitLSPServer {
19401878
== canonicalOriginatorLocation
19411879
}
19421880

1943-
var locations = try await symbols.asyncMap { (symbol) -> [Location] in
1881+
var locations = try await symbols.asyncFlatMap { (symbol) -> [Location] in
19441882
var locations: [Location]
19451883
if let bestLocalDeclaration = symbol.bestLocalDeclaration,
19461884
!(symbol.isDynamic ?? true),
@@ -1978,7 +1916,7 @@ extension SourceKitLSPServer {
19781916
}
19791917

19801918
return locations
1981-
}.flatMap { $0 }
1919+
}
19821920

19831921
// Remove any duplicate locations. We might end up with duplicate locations when performing a definition request
19841922
// on eg. `MyStruct()` when no explicit initializer is declared. In this case we get two symbol infos, one for the

Sources/SourceKitLSP/Workspace.swift

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ fileprivate func firstNonNil<T>(
4848
/// "initialize" request has been made.
4949
///
5050
/// Typically a workspace is contained in a root directory.
51-
package final class Workspace: Sendable {
51+
package final class Workspace: Sendable, BuildSystemManagerDelegate {
5252

5353
/// The root directory of the workspace.
5454
package let rootUri: DocumentURI?
@@ -85,6 +85,9 @@ package final class Workspace: Sendable {
8585
/// `nil` if background indexing is not enabled.
8686
let semanticIndexManager: SemanticIndexManager?
8787

88+
/// A callback that should be called when the file handling capability of this workspace changes.
89+
private let fileHandlingCapabilityChangedCallback: @Sendable () async -> Void
90+
8891
private init(
8992
documentManager: DocumentManager,
9093
rootUri: DocumentURI?,
@@ -97,14 +100,16 @@ package final class Workspace: Sendable {
97100
indexTaskScheduler: TaskScheduler<AnyIndexTaskDescription>,
98101
logMessageToIndexLog: @escaping @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void,
99102
indexTasksWereScheduled: @escaping @Sendable (Int) -> Void,
100-
indexProgressStatusDidChange: @escaping @Sendable () -> Void
103+
indexProgressStatusDidChange: @escaping @Sendable () -> Void,
104+
fileHandlingCapabilityChanged: @escaping @Sendable () async -> Void
101105
) async {
102106
self.documentManager = documentManager
103107
self.rootUri = rootUri
104108
self.capabilityRegistry = capabilityRegistry
105109
self.options = options
106110
self._uncheckedIndex = ThreadSafeBox(initialValue: uncheckedIndex)
107111
self.buildSystemManager = buildSystemManager
112+
self.fileHandlingCapabilityChangedCallback = fileHandlingCapabilityChanged
108113
if options.backgroundIndexingOrDefault, let uncheckedIndex, await buildSystemManager.supportsPreparation {
109114
self.semanticIndexManager = SemanticIndexManager(
110115
index: uncheckedIndex,
@@ -153,7 +158,8 @@ package final class Workspace: Sendable {
153158
logMessageToIndexLog: @escaping @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void,
154159
indexTasksWereScheduled: @Sendable @escaping (Int) -> Void,
155160
indexProgressStatusDidChange: @Sendable @escaping () -> Void,
156-
reloadPackageStatusCallback: @Sendable @escaping (ReloadPackageStatus) async -> Void
161+
reloadPackageStatusCallback: @Sendable @escaping (ReloadPackageStatus) async -> Void,
162+
fileHandlingCapabilityChanged: @Sendable @escaping () async -> Void
157163
) async {
158164
let buildSystemManager = await BuildSystemManager(
159165
buildSystemKind: buildSystemKind,
@@ -227,8 +233,10 @@ package final class Workspace: Sendable {
227233
indexTaskScheduler: indexTaskScheduler,
228234
logMessageToIndexLog: logMessageToIndexLog,
229235
indexTasksWereScheduled: indexTasksWereScheduled,
230-
indexProgressStatusDidChange: indexProgressStatusDidChange
236+
indexProgressStatusDidChange: indexProgressStatusDidChange,
237+
fileHandlingCapabilityChanged: fileHandlingCapabilityChanged
231238
)
239+
await buildSystemManager.setDelegate(self)
232240
}
233241

234242
package static func forTesting(
@@ -249,7 +257,8 @@ package final class Workspace: Sendable {
249257
indexTaskScheduler: indexTaskScheduler,
250258
logMessageToIndexLog: { _, _ in },
251259
indexTasksWereScheduled: { _ in },
252-
indexProgressStatusDidChange: {}
260+
indexProgressStatusDidChange: {},
261+
fileHandlingCapabilityChanged: {}
253262
)
254263
}
255264

@@ -291,6 +300,39 @@ package final class Workspace: Sendable {
291300
return newLanguageService
292301
}
293302
}
303+
304+
/// Handle a build settings change notification from the `BuildSystem`.
305+
/// This has two primary cases:
306+
/// - Initial settings reported for a given file, now we can fully open it
307+
/// - Changed settings for an already open file
308+
package func fileBuildSettingsChanged(_ changedFiles: Set<DocumentURI>) async {
309+
for uri in changedFiles {
310+
guard self.documentManager.openDocuments.contains(uri) else {
311+
continue
312+
}
313+
314+
await self.documentService(for: uri)?.documentUpdatedBuildSettings(uri)
315+
}
316+
}
317+
318+
/// Handle a dependencies updated notification from the `BuildSystem`.
319+
/// We inform the respective language services as long as the given file is open
320+
/// (not queued for opening).
321+
package func filesDependenciesUpdated(_ changedFiles: Set<DocumentURI>) async {
322+
for uri in changedFiles {
323+
guard self.documentManager.openDocuments.contains(uri) else {
324+
continue
325+
}
326+
logger.log("Dependencies updated for opened file \(uri.forLogging)")
327+
if let service = documentService(for: uri) {
328+
await service.documentDependenciesUpdated(uri)
329+
}
330+
}
331+
}
332+
333+
package func fileHandlingCapabilityChanged() async {
334+
await self.fileHandlingCapabilityChangedCallback()
335+
}
294336
}
295337

296338
/// Wrapper around a workspace that isn't being retained.

Sources/SwiftExtensions/Sequence+AsyncMap.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,20 @@ extension Sequence {
2525
return result
2626
}
2727

28+
/// Just like `Sequence.flatMap` but allows an `async` transform function.
29+
package func asyncFlatMap<SegmentOfResult: Sequence>(
30+
@_inheritActorContext _ transform: @Sendable (Element) async throws -> SegmentOfResult
31+
) async rethrows -> [SegmentOfResult.Element] {
32+
var result: [SegmentOfResult.Element] = []
33+
result.reserveCapacity(self.underestimatedCount)
34+
35+
for element in self {
36+
result += try await transform(element)
37+
}
38+
39+
return result
40+
}
41+
2842
/// Just like `Sequence.compactMap` but allows an `async` transform function.
2943
package func asyncCompactMap<T>(
3044
@_inheritActorContext _ transform: @Sendable (Element) async throws -> T?

Tests/SourceKitLSPTests/BuildSystemTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ final class BuildSystemTests: XCTestCase {
157157
)
158158

159159
await server.setWorkspaces([(workspace: workspace, isImplicit: false)])
160-
await workspace.buildSystemManager.setDelegate(server)
160+
await workspace.buildSystemManager.setDelegate(workspace)
161161
}
162162

163163
override func tearDown() {

Tests/SourceKitLSPTests/DependencyTrackingTests.swift

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ final class DependencyTrackingTests: XCTestCase {
4040
usePullDiagnostics: false
4141
)
4242

43-
let (libBUri, _) = try project.openDocument("LibB.swift")
43+
_ = try project.openDocument("LibB.swift")
4444

4545
let initialDiags = try await project.testClient.nextDiagnosticsNotification()
4646
// Semantic analysis: expect module import error.
@@ -58,7 +58,14 @@ final class DependencyTrackingTests: XCTestCase {
5858

5959
try await SwiftPMTestProject.build(at: project.scratchDirectory)
6060

61-
await project.testClient.server.filesDependenciesUpdated([libBUri])
61+
project.testClient.send(
62+
DidChangeWatchedFilesNotification(
63+
changes:
64+
FileManager.default.findFiles(withExtension: "swiftmodule", in: project.scratchDirectory).map {
65+
FileEvent(uri: DocumentURI($0), type: .created)
66+
}
67+
)
68+
)
6269

6370
let updatedDiags = try await project.testClient.nextDiagnosticsNotification()
6471
// Semantic analysis: no more errors expected, import should resolve since we built.
@@ -102,7 +109,8 @@ final class DependencyTrackingTests: XCTestCase {
102109
let contents = "int libX(int value);"
103110
try contents.write(to: generatedHeaderURL, atomically: true, encoding: .utf8)
104111

105-
await project.testClient.server.filesDependenciesUpdated([mainUri])
112+
let workspace = try await unwrap(project.testClient.server.workspaceForDocument(uri: mainUri))
113+
await workspace.filesDependenciesUpdated([mainUri])
106114

107115
let updatedDiags = try await project.testClient.nextDiagnosticsNotification()
108116
// No more errors expected, import should resolve since we the generated header file

0 commit comments

Comments
 (0)