diff --git a/Sources/BuildSystemIntegration/BuildServerBuildSystem.swift b/Sources/BuildSystemIntegration/BuildServerBuildSystem.swift index 67b1f9cd..5fa41cdb 100644 --- a/Sources/BuildSystemIntegration/BuildServerBuildSystem.swift +++ b/Sources/BuildSystemIntegration/BuildServerBuildSystem.swift @@ -331,29 +331,6 @@ extension BuildServerBuildSystem: BuiltInBuildSystem { package func didChangeWatchedFiles(notification: BuildServerProtocol.DidChangeWatchedFilesNotification) {} - package func fileHandlingCapability(for uri: DocumentURI) -> FileHandlingCapability { - guard - let fileUrl = uri.fileURL, - let path = try? AbsolutePath(validating: fileUrl.path) - else { - return .unhandled - } - - // TODO: We should not make any assumptions about which files the build server can handle. - // Instead we should query the build server which files it can handle - // (https://github.com/swiftlang/sourcekit-lsp/issues/492). - - if projectRoot.isAncestorOfOrEqual(to: path) { - return .handled - } - - if let realpath = try? resolveSymlinks(path), realpath != path, projectRoot.isAncestorOfOrEqual(to: realpath) { - return .handled - } - - return .unhandled - } - package func sourceFiles() async -> [SourceFileInfo] { // BuildServerBuildSystem does not support syntactic test discovery or background indexing. // (https://github.com/swiftlang/sourcekit-lsp/issues/1173). diff --git a/Sources/BuildSystemIntegration/BuildSystemDelegate.swift b/Sources/BuildSystemIntegration/BuildSystemDelegate.swift index 87f3d726..eb477ff2 100644 --- a/Sources/BuildSystemIntegration/BuildSystemDelegate.swift +++ b/Sources/BuildSystemIntegration/BuildSystemDelegate.swift @@ -22,11 +22,6 @@ package protocol BuildSystemDelegate: AnyObject, Sendable { /// The callee should refresh ASTs unless it is able to determine that a /// refresh is not necessary. func filesDependenciesUpdated(_ changedFiles: Set) async - - /// Notify the delegate that the file handling capability of this build system - /// for some file has changed. The delegate should discard any cached file - /// handling capability. - func fileHandlingCapabilityChanged() async } /// Handles build system events, such as file build settings changes. @@ -42,8 +37,7 @@ package protocol BuildSystemManagerDelegate: AnyObject, Sendable { /// refresh is not necessary. func filesDependenciesUpdated(_ changedFiles: Set) async - /// Notify the delegate that the file handling capability of this build system - /// for some file has changed. The delegate should discard any cached file - /// handling capability. - func fileHandlingCapabilityChanged() async + /// Notify the delegate that some information about the given build targets has changed and that it should recompute + /// any information based on top of it. + func buildTargetsChanged(_ changes: [BuildTargetEvent]?) async } diff --git a/Sources/BuildSystemIntegration/BuildSystemManager.swift b/Sources/BuildSystemIntegration/BuildSystemManager.swift index fa996d7c..1b759038 100644 --- a/Sources/BuildSystemIntegration/BuildSystemManager.swift +++ b/Sources/BuildSystemIntegration/BuildSystemManager.swift @@ -81,7 +81,7 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate { /// The fallback build system. If present, used when the `buildSystem` is not /// set or cannot provide settings. - let fallbackBuildSystem: FallbackBuildSystem? + let fallbackBuildSystem: FallbackBuildSystem /// Provider of file to main file mappings. var mainFilesProvider: MainFilesProvider? @@ -101,11 +101,7 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate { /// The root of the project that this build system manages. For example, for SwiftPM packages, this is the folder /// containing Package.swift. For compilation databases it is the root folder based on which the compilation database /// was found. - package var projectRoot: AbsolutePath? { - get async { - return await buildSystem?.underlyingBuildSystem.projectRoot - } - } + package let projectRoot: AbsolutePath? package var supportsPreparation: Bool { get async { @@ -122,6 +118,7 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate { ) async { self.fallbackBuildSystem = FallbackBuildSystem(options: options.fallbackBuildSystemOrDefault) self.toolchainRegistry = toolchainRegistry + self.projectRoot = buildSystemKind?.projectRoot self.buildSystem = await BuiltInBuildSystemAdapter( buildSystemKind: buildSystemKind, toolchainRegistry: toolchainRegistry, @@ -315,7 +312,7 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate { logger.error("Getting build settings failed: \(error.forLogging)") } - guard var settings = await fallbackBuildSystem?.buildSettings(for: document, language: language) else { + guard var settings = await fallbackBuildSystem.buildSettings(for: document, language: language) else { return nil } if buildSystem == nil { @@ -401,13 +398,6 @@ package actor BuildSystemManager: BuiltInBuildSystemAdapterDelegate { } } - package func fileHandlingCapability(for uri: DocumentURI) async -> FileHandlingCapability { - return max( - await buildSystem?.underlyingBuildSystem.fileHandlingCapability(for: uri) ?? .unhandled, - fallbackBuildSystem != nil ? .fallback : .unhandled - ) - } - package func sourceFiles() async -> [SourceFileInfo] { return await buildSystem?.underlyingBuildSystem.sourceFiles() ?? [] } @@ -451,12 +441,6 @@ extension BuildSystemManager: BuildSystemDelegate { } } - package func fileHandlingCapabilityChanged() async { - if let delegate = self.delegate { - await delegate.fileHandlingCapabilityChanged() - } - } - private func didChangeBuildTarget(notification: DidChangeBuildTargetNotification) async { // Every `DidChangeBuildTargetNotification` notification needs to invalidate the cache since the changed target // might gained a source file. @@ -476,6 +460,7 @@ extension BuildSystemManager: BuildSystemDelegate { return updatedTargets.contains(cacheKey.target) } + await delegate?.buildTargetsChanged(notification.changes) // FIXME: (BSP Migration) Communicate that the build target has changed to the `BuildSystemManagerDelegate` and make // it responsible for figuring out which files are affected. await delegate?.fileBuildSettingsChanged(Set(watchedFiles.keys)) diff --git a/Sources/BuildSystemIntegration/BuiltInBuildSystem.swift b/Sources/BuildSystemIntegration/BuiltInBuildSystem.swift index c37d99c7..14cf0439 100644 --- a/Sources/BuildSystemIntegration/BuiltInBuildSystem.swift +++ b/Sources/BuildSystemIntegration/BuiltInBuildSystem.swift @@ -19,18 +19,6 @@ import ToolchainRegistry import struct TSCBasic.AbsolutePath import struct TSCBasic.RelativePath -/// Defines how well a `BuildSystem` can handle a file with a given URI. -package enum FileHandlingCapability: Comparable, Sendable { - /// The build system can't handle the file at all - case unhandled - - /// The build system has fallback build settings for the file - case fallback - - /// The build system knows how to handle the file - case handled -} - package struct SourceFileInfo: Sendable { /// The URI of the source file. package let uri: DocumentURI @@ -172,8 +160,6 @@ package protocol BuiltInBuildSystem: AnyObject, Sendable { /// Called when files in the project change. func didChangeWatchedFiles(notification: BuildServerProtocol.DidChangeWatchedFilesNotification) async - func fileHandlingCapability(for uri: DocumentURI) async -> FileHandlingCapability - /// Returns the list of source files in the project. /// /// Header files should not be considered as source files because they cannot be compiled. diff --git a/Sources/BuildSystemIntegration/CompilationDatabaseBuildSystem.swift b/Sources/BuildSystemIntegration/CompilationDatabaseBuildSystem.swift index 134710df..ef2136ee 100644 --- a/Sources/BuildSystemIntegration/CompilationDatabaseBuildSystem.swift +++ b/Sources/BuildSystemIntegration/CompilationDatabaseBuildSystem.swift @@ -210,14 +210,6 @@ extension CompilationDatabaseBuildSystem: BuiltInBuildSystem { } } - package func fileHandlingCapability(for uri: DocumentURI) -> FileHandlingCapability { - if database(for: uri) != nil { - return .handled - } else { - return .unhandled - } - } - package func sourceFiles() async -> [SourceFileInfo] { guard let compdb else { return [] diff --git a/Sources/BuildSystemIntegration/SwiftPMBuildSystem.swift b/Sources/BuildSystemIntegration/SwiftPMBuildSystem.swift index cd15d522..650447e0 100644 --- a/Sources/BuildSystemIntegration/SwiftPMBuildSystem.swift +++ b/Sources/BuildSystemIntegration/SwiftPMBuildSystem.swift @@ -489,10 +489,7 @@ extension SwiftPMBuildSystem { targets[targetIdentifier] = (buildTarget, depth) } - if let delegate = self.delegate { - await delegate.fileHandlingCapabilityChanged() - await messageHandler?.sendNotificationToSourceKitLSP(DidChangeBuildTargetNotification(changes: nil)) - } + await messageHandler?.sendNotificationToSourceKitLSP(DidChangeBuildTargetNotification(changes: nil)) for testFilesDidChangeCallback in testFilesDidChangeCallbacks { await testFilesDidChangeCallback() } @@ -851,13 +848,6 @@ extension SwiftPMBuildSystem: BuildSystemIntegration.BuiltInBuildSystem { await self.fileDependenciesUpdatedDebouncer.scheduleCall(filesWithUpdatedDependencies) } - package func fileHandlingCapability(for uri: DocumentURI) -> FileHandlingCapability { - if targets(for: uri).isEmpty { - return .unhandled - } - return .handled - } - package func sourceFiles() -> [SourceFileInfo] { var sourceFiles: [DocumentURI: SourceFileInfo] = [:] for (buildTarget, depth) in self.targets.values { diff --git a/Sources/BuildSystemIntegration/TestBuildSystem.swift b/Sources/BuildSystemIntegration/TestBuildSystem.swift index eef17266..2d8b95a4 100644 --- a/Sources/BuildSystemIntegration/TestBuildSystem.swift +++ b/Sources/BuildSystemIntegration/TestBuildSystem.swift @@ -102,14 +102,6 @@ package actor TestBuildSystem: BuiltInBuildSystem { package func didChangeWatchedFiles(notification: BuildServerProtocol.DidChangeWatchedFilesNotification) async {} - package func fileHandlingCapability(for uri: DocumentURI) -> FileHandlingCapability { - if buildSettingsByFile[uri] != nil { - return .handled - } else { - return .unhandled - } - } - package func sourceFiles() async -> [SourceFileInfo] { return [] } diff --git a/Sources/SourceKitLSP/SourceKitLSPServer.swift b/Sources/SourceKitLSP/SourceKitLSPServer.swift index 0d4945de..078f74d8 100644 --- a/Sources/SourceKitLSP/SourceKitLSPServer.swift +++ b/Sources/SourceKitLSP/SourceKitLSPServer.swift @@ -327,32 +327,21 @@ package actor SourceKitLSPServer { private func computeWorkspaceForDocument(uri: DocumentURI) async -> Workspace? { // Pick the workspace with the best FileHandlingCapability for this file. // If there is a tie, use the workspace that occurred first in the list. - var bestWorkspace: (workspace: Workspace?, fileHandlingCapability: FileHandlingCapability) = (nil, .unhandled) - for workspace in self.workspaces { - let fileHandlingCapability = await workspace.buildSystemManager.fileHandlingCapability(for: uri) - if fileHandlingCapability > bestWorkspace.fileHandlingCapability { - bestWorkspace = (workspace, fileHandlingCapability) - } + var bestWorkspace = await self.workspaces.asyncFirst { + await !$0.buildSystemManager.targets(for: uri).isEmpty } - if bestWorkspace.fileHandlingCapability < .handled { + if bestWorkspace == nil { // We weren't able to handle the document with any of the known workspaces. See if any of the document's parent // directories contain a workspace that might be able to handle the document if let workspace = await self.findImplicitWorkspace(for: uri) { logger.log("Opening implicit workspace at \(workspace.rootUri.forLogging) to handle \(uri.forLogging)") self.workspacesAndIsImplicit.append((workspace: workspace, isImplicit: true)) - bestWorkspace = (workspace, .handled) + bestWorkspace = workspace } } - self.workspaceForUri[uri] = WeakWorkspace(bestWorkspace.workspace) - if let workspace = bestWorkspace.workspace { - return workspace - } - if let workspace = self.workspaces.only { - // Special handling: If there is only one workspace, open all files in it, even it it cannot handle the document. - // This retains the behavior of SourceKit-LSP before it supported multiple workspaces. - return workspace - } - return nil + let workspace = bestWorkspace ?? self.workspaces.first + self.workspaceForUri[uri] = WeakWorkspace(workspace) + return workspace } /// Check that the entries in `workspaceForUri` are still up-to-date after workspaces might have changed. diff --git a/Sources/SourceKitLSP/Workspace.swift b/Sources/SourceKitLSP/Workspace.swift index f52ea5bb..2202ae19 100644 --- a/Sources/SourceKitLSP/Workspace.swift +++ b/Sources/SourceKitLSP/Workspace.swift @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +import BuildServerProtocol import BuildSystemIntegration import IndexStoreDB import LanguageServerProtocol @@ -82,7 +83,8 @@ package final class Workspace: Sendable, BuildSystemManagerDelegate { /// `nil` if background indexing is not enabled. let semanticIndexManager: SemanticIndexManager? - /// A callback that should be called when the file handling capability of this workspace changes. + /// A callback that should be called when the file handling capability (ie. the presence of a target for a source + /// files) of this workspace changes. private let fileHandlingCapabilityChangedCallback: @Sendable () async -> Void private init( @@ -316,7 +318,7 @@ package final class Workspace: Sendable, BuildSystemManagerDelegate { } } - package func fileHandlingCapabilityChanged() async { + package func buildTargetsChanged(_ changes: [BuildTargetEvent]?) async { await self.fileHandlingCapabilityChangedCallback() } } diff --git a/Sources/SwiftExtensions/Sequence+AsyncMap.swift b/Sources/SwiftExtensions/Sequence+AsyncMap.swift index 3e7e1dee..a2d27654 100644 --- a/Sources/SwiftExtensions/Sequence+AsyncMap.swift +++ b/Sources/SwiftExtensions/Sequence+AsyncMap.swift @@ -68,4 +68,17 @@ extension Sequence { return result } + + /// Just like `Sequence.first` but allows an `async` predicate function. + package func asyncFirst( + @_inheritActorContext _ predicate: @Sendable (Element) async throws -> Bool + ) async rethrows -> Element? { + for element in self { + if try await predicate(element) { + return element + } + } + + return nil + } } diff --git a/Tests/BuildSystemIntegrationTests/BuildServerBuildSystemTests.swift b/Tests/BuildSystemIntegrationTests/BuildServerBuildSystemTests.swift index aeb860d7..22edcb99 100644 --- a/Tests/BuildSystemIntegrationTests/BuildServerBuildSystemTests.swift +++ b/Tests/BuildSystemIntegrationTests/BuildServerBuildSystemTests.swift @@ -134,8 +134,6 @@ final class TestDelegate: BuildSystemDelegate, BuiltInBuildSystemMessageHandler } } - func fileHandlingCapabilityChanged() {} - func sendRequestToSourceKitLSP(_ request: R) async throws -> R.Response { throw ResponseError.methodNotFound(R.method) } diff --git a/Tests/BuildSystemIntegrationTests/BuildSystemManagerTests.swift b/Tests/BuildSystemIntegrationTests/BuildSystemManagerTests.swift index f9372412..1da74e4d 100644 --- a/Tests/BuildSystemIntegrationTests/BuildSystemManagerTests.swift +++ b/Tests/BuildSystemIntegrationTests/BuildSystemManagerTests.swift @@ -449,5 +449,5 @@ private actor BSMDelegate: BuildSystemManagerDelegate { } } - func fileHandlingCapabilityChanged() {} + func buildTargetsChanged(_ changes: [BuildTargetEvent]?) async {} }