diff --git a/Sources/BuildServerProtocol/BuildTargetSourcesRequest.swift b/Sources/BuildServerProtocol/BuildTargetSourcesRequest.swift index e02b94f0..fd3fa3ca 100644 --- a/Sources/BuildServerProtocol/BuildTargetSourcesRequest.swift +++ b/Sources/BuildServerProtocol/BuildTargetSourcesRequest.swift @@ -106,14 +106,29 @@ public struct SourceItemDataKind: RawRepresentable, Codable, Hashable, Sendable public struct SourceKitSourceItemData: LSPAnyCodable, Codable { public var language: Language? - public init(language: Language? = nil) { + /// Whether the file is a header file that is clearly associated with one target. + /// + /// For example header files in SwiftPM projects are always associated to one target and SwiftPM can provide build + /// settings for that header file. + /// + /// In general, build systems don't need to list all header files in the `buildTarget/sources` request: Semantic + /// functionality for header files is usually provided by finding a main file that includes the header file and + /// inferring build settings from it. Listing header files in `buildTarget/sources` allows SourceKit-LSP to provide + /// semantic functionality for header files if they haven't been included by any main file. + public var isHeader: Bool? + + public init(language: Language? = nil, isHeader: Bool? = nil) { self.language = language + self.isHeader = isHeader } public init?(fromLSPDictionary dictionary: [String: LanguageServerProtocol.LSPAny]) { if case .string(let language) = dictionary[CodingKeys.language.stringValue] { self.language = Language(rawValue: language) } + if case .bool(let isHeader) = dictionary[CodingKeys.isHeader.stringValue] { + self.isHeader = isHeader + } } public func encodeToLSPAny() -> LanguageServerProtocol.LSPAny { @@ -121,6 +136,9 @@ public struct SourceKitSourceItemData: LSPAnyCodable, Codable { if let language { result[CodingKeys.language.stringValue] = .string(language.rawValue) } + if let isHeader { + result[CodingKeys.isHeader.stringValue] = .bool(isHeader) + } return .dictionary(result) } } diff --git a/Sources/BuildServerProtocol/BuildTargetsRequest.swift b/Sources/BuildServerProtocol/BuildTargetsRequest.swift index d6a033e3..ac5ca66a 100644 --- a/Sources/BuildServerProtocol/BuildTargetsRequest.swift +++ b/Sources/BuildServerProtocol/BuildTargetsRequest.swift @@ -150,6 +150,11 @@ public struct BuildTargetTag: Codable, Hashable, RawRepresentable, Sendable { /// /// **(BSP Extension)** public static let dependency: Self = Self(rawValue: "dependency") + + /// This target only exists to provide compiler arguments for SourceKit-LSP can't be built standalone. + /// + /// For example, a SwiftPM package manifest is in a non-buildable target. + public static let notBuildable: Self = Self(rawValue: "not-buildable") } public struct BuildTargetCapabilities: Codable, Hashable, Sendable { diff --git a/Sources/BuildServerProtocol/CMakeLists.txt b/Sources/BuildServerProtocol/CMakeLists.txt index c47337ea..512ff554 100644 --- a/Sources/BuildServerProtocol/CMakeLists.txt +++ b/Sources/BuildServerProtocol/CMakeLists.txt @@ -4,13 +4,13 @@ add_library(BuildServerProtocol STATIC DidChangeBuildTargetNotification.swift DidChangeWatchedFilesNotification.swift InitializeBuild.swift - InverseSourcesRequest.swift LogMessageNotification.swift Messages.swift PrepareTargetsRequest.swift RegisterForChangeNotifications.swift ShutdownBuild.swift SourceKitOptionsRequest.swift + TextDocumentIdentifier.swift WaitForBuildSystemUpdates.swift WorkDoneProgress.swift) set_target_properties(BuildServerProtocol PROPERTIES diff --git a/Sources/BuildServerProtocol/InverseSourcesRequest.swift b/Sources/BuildServerProtocol/InverseSourcesRequest.swift deleted file mode 100644 index 4973ca8b..00000000 --- a/Sources/BuildServerProtocol/InverseSourcesRequest.swift +++ /dev/null @@ -1,45 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -import LanguageServerProtocol - -public struct TextDocumentIdentifier: Codable, Sendable, Hashable { - /// The text document's URI. - public var uri: URI - - public init(uri: URI) { - self.uri = uri - } -} - -/// The inverse sources request is sent from the client to the server to query for the list of build targets containing -/// a text document. The server communicates during the initialize handshake whether this method is supported or not. -/// This request can be viewed as the inverse of buildTarget/sources, except it only works for text documents and not -/// directories. -public struct InverseSourcesRequest: RequestType, Hashable { - public static let method: String = "buildTarget/inverseSources" - public typealias Response = InverseSourcesResponse - - public var textDocument: TextDocumentIdentifier - - public init(textDocument: TextDocumentIdentifier) { - self.textDocument = textDocument - } -} - -public struct InverseSourcesResponse: ResponseType, Hashable { - public var targets: [BuildTargetIdentifier] - - public init(targets: [BuildTargetIdentifier]) { - self.targets = targets - } -} diff --git a/Sources/BuildServerProtocol/Messages.swift b/Sources/BuildServerProtocol/Messages.swift index 839cc931..992c662f 100644 --- a/Sources/BuildServerProtocol/Messages.swift +++ b/Sources/BuildServerProtocol/Messages.swift @@ -18,7 +18,6 @@ fileprivate let requestTypes: [_RequestType.Type] = [ BuildTargetSourcesRequest.self, BuildServerProtocol.CreateWorkDoneProgressRequest.self, InitializeBuildRequest.self, - InverseSourcesRequest.self, PrepareTargetsRequest.self, RegisterForChanges.self, ShutdownBuild.self, diff --git a/Sources/BuildServerProtocol/TextDocumentIdentifier.swift b/Sources/BuildServerProtocol/TextDocumentIdentifier.swift new file mode 100644 index 00000000..14d01020 --- /dev/null +++ b/Sources/BuildServerProtocol/TextDocumentIdentifier.swift @@ -0,0 +1,20 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +public struct TextDocumentIdentifier: Codable, Sendable, Hashable { + /// The text document's URI. + public var uri: URI + + public init(_ uri: URI) { + self.uri = uri + } +} diff --git a/Sources/BuildSystemIntegration/BuildServerBuildSystem.swift b/Sources/BuildSystemIntegration/BuildServerBuildSystem.swift index 4f89ba87..dbcc1d52 100644 --- a/Sources/BuildSystemIntegration/BuildServerBuildSystem.swift +++ b/Sources/BuildSystemIntegration/BuildServerBuildSystem.swift @@ -285,10 +285,6 @@ extension BuildServerBuildSystem: BuiltInBuildSystem { return BuildTargetSourcesResponse(items: []) } - package func inverseSources(request: InverseSourcesRequest) -> InverseSourcesResponse { - return InverseSourcesResponse(targets: [BuildTargetIdentifier.dummy]) - } - package func didChangeWatchedFiles(notification: BuildServerProtocol.DidChangeWatchedFilesNotification) {} package func prepare(request: PrepareTargetsRequest) async throws -> VoidResponse { diff --git a/Sources/BuildSystemIntegration/BuildSystemManager.swift b/Sources/BuildSystemIntegration/BuildSystemManager.swift index 6a5855e2..01551891 100644 --- a/Sources/BuildSystemIntegration/BuildSystemManager.swift +++ b/Sources/BuildSystemIntegration/BuildSystemManager.swift @@ -26,13 +26,13 @@ import struct TSCBasic.AbsolutePath import os #endif -fileprivate class RequestCache { - private var storage: [Request: Task] = [:] +fileprivate class Cache { + private var storage: [Key: Task] = [:] func get( - _ key: Request, + _ key: Key, isolation: isolated any Actor = #isolation, - compute: @Sendable @escaping (Request) async throws(Error) -> Result + compute: @Sendable @escaping (Key) async throws(Error) -> Result ) async throws(Error) -> Result { let task: Task if let cached = storage[key] { @@ -46,7 +46,7 @@ fileprivate class RequestCache Bool, isolation: isolated any Actor = #isolation) { + func clear(where condition: (Key) -> Bool, isolation: isolated any Actor = #isolation) { for key in storage.keys { if condition(key) { storage[key] = nil @@ -60,15 +60,38 @@ fileprivate class RequestCache + /// `true` if this file belongs to the root project that the user is working on. It is false, if the file belongs /// to a dependency of the project. - package let isPartOfRootProject: Bool + package var isPartOfRootProject: Bool /// Whether the file might contain test cases. This property is an over-approximation. It might be true for files /// from non-test targets or files that don't actually contain any tests. Keeping this list of files with /// `mayContainTets` minimal as possible helps reduce the amount of work that the syntactic test indexer needs to /// perform. - package let mayContainTests: Bool + package var mayContainTests: Bool + + func merging(_ other: SourceFileInfo?) -> SourceFileInfo { + guard let other else { + return self + } + return SourceFileInfo( + targets: targets.union(other.targets), + isPartOfRootProject: other.isPartOfRootProject || isPartOfRootProject, + mayContainTests: other.mayContainTests || mayContainTests + ) + } +} + +fileprivate extension SourceItem { + var sourceKitData: SourceKitSourceItemData? { + guard dataKind == .sourceKit, case .dictionary(let data) = data else { + return nil + } + return SourceKitSourceItemData(fromLSPDictionary: data) + } } /// `BuildSystem` that integrates client-side information such as main-file lookup as well as providing @@ -134,15 +157,25 @@ package actor BuildSystemManager: QueueBasedMessageHandler { /// Force-unwrapped optional because initializing it requires access to `self`. private var filesDependenciesUpdatedDebouncer: Debouncer>! = nil - private var cachedTargetsForDocument = RequestCache() + private var cachedSourceKitOptions = Cache() - private var cachedSourceKitOptions = RequestCache() - - private var cachedBuildTargets = RequestCache< + private var cachedBuildTargets = Cache< BuildTargetsRequest, [BuildTargetIdentifier: (target: BuildTarget, depth: Int)] >() - private var cachedTargetSources = RequestCache() + private var cachedTargetSources = Cache() + + private struct SourceFilesAndDirectoriesKey: Hashable { + let includeNonBuildableFiles: Bool + let sourcesItems: [SourcesItem] + } + + private struct SourceFilesAndDirectories { + let files: [DocumentURI: SourceFileInfo] + let directories: [DocumentURI: SourceFileInfo] + } + + private let cachedSourceFilesAndDirectories = Cache() /// 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 @@ -428,22 +461,27 @@ package actor BuildSystemManager: QueueBasedMessageHandler { } /// Returns all the `ConfiguredTarget`s that the document is part of. - package func targets(for document: DocumentURI) async -> [BuildTargetIdentifier] { - guard let connectionToBuildSystem else { - return [] - } - - // FIXME: (BSP migration) Only use `InverseSourcesRequest` if the BSP server declared it can handle it in the - // capabilities - let request = InverseSourcesRequest(textDocument: TextDocumentIdentifier(uri: document)) - do { - let response = try await cachedTargetsForDocument.get(request) { document in - return try await connectionToBuildSystem.send(request) + package func targets(for document: DocumentURI) async -> Set { + return await orLog("Getting targets for source file") { + var result: Set = [] + let filesAndDirectories = try await sourceFilesAndDirectories(includeNonBuildableFiles: true) + if let targets = filesAndDirectories.files[document]?.targets { + result.formUnion(targets) } - return response.targets - } catch { - return [] - } + if !filesAndDirectories.directories.isEmpty, + let documentPath = AbsolutePath(validatingOrNil: document.fileURL?.path) + { + for (directory, info) in filesAndDirectories.directories { + guard let directoryPath = AbsolutePath(validatingOrNil: directory.fileURL?.path) else { + continue + } + if documentPath.isDescendantOfOrEqual(to: directoryPath) { + result.formUnion(info.targets) + } + } + } + return result + } ?? [] } /// Returns the `BuildTargetIdentifier` that should be used for semantic functionality of the given document. @@ -497,7 +535,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler { guard let connectionToBuildSystem, let target else { return nil } - let request = SourceKitOptionsRequest(textDocument: TextDocumentIdentifier(uri: document), target: target) + let request = SourceKitOptionsRequest(textDocument: TextDocumentIdentifier(document), target: target) // TODO: We should only wait `fallbackSettingsTimeout` for build settings // and return fallback afterwards. @@ -700,33 +738,65 @@ package actor BuildSystemManager: QueueBasedMessageHandler { return response.items } - package func sourceFiles() async throws -> [DocumentURI: SourceFileInfo] { - let targets = try await self.buildTargets() - var sourceFiles: [DocumentURI: SourceFileInfo] = [:] - for sourcesItem in try await self.sourceFiles(in: targets.keys) { - let target = targets[sourcesItem.target]?.target - let isPartOfRootProject = !(target?.tags.contains(.dependency) ?? false) - let mayContainTests = target?.tags.contains(.test) ?? true + /// Returns all source files in the project that can be built. + /// + /// - SeeAlso: Comment in `sourceFilesAndDirectories` for a definition of what `buildable` means. + package func buildableSourceFiles() async throws -> [DocumentURI: SourceFileInfo] { + return try await sourceFilesAndDirectories(includeNonBuildableFiles: false).files + } - for sourceItem in sourcesItem.sources { - if let existingEntry = sourceFiles[sourceItem.uri] { - sourceFiles[sourceItem.uri] = SourceFileInfo( - isPartOfRootProject: existingEntry.isPartOfRootProject || isPartOfRootProject, - mayContainTests: existingEntry.mayContainTests || mayContainTests - ) - } else { - sourceFiles[sourceItem.uri] = SourceFileInfo( + /// Get all files and directories that are known to the build system, ie. that are returned by a `buildTarget/sources` + /// request for any target in the project. + /// + /// Source files returned here fall into two categories: + /// - Buildable source files are files that can be built by the build system and that make sense to background index + /// - Non-buildable source files include eg. the SwiftPM package manifest or header files. We have sufficient + /// compiler arguments for these files to provide semantic editor functionality but we can't build them. + /// + /// `includeNonBuildableFiles` determines whether non-buildable files should be included. + private func sourceFilesAndDirectories(includeNonBuildableFiles: Bool) async throws -> SourceFilesAndDirectories { + let targets = try await self.buildTargets() + let sourcesItems = try await self.sourceFiles(in: targets.keys) + + let key = SourceFilesAndDirectoriesKey( + includeNonBuildableFiles: includeNonBuildableFiles, + sourcesItems: sourcesItems + ) + + return try await cachedSourceFilesAndDirectories.get(key) { key in + var files: [DocumentURI: SourceFileInfo] = [:] + var directories: [DocumentURI: SourceFileInfo] = [:] + for sourcesItem in key.sourcesItems { + let target = targets[sourcesItem.target]?.target + let isPartOfRootProject = !(target?.tags.contains(.dependency) ?? false) + let mayContainTests = target?.tags.contains(.test) ?? true + if !key.includeNonBuildableFiles && (target?.tags.contains(.notBuildable) ?? false) { + continue + } + + for sourceItem in sourcesItem.sources { + if !key.includeNonBuildableFiles && sourceItem.sourceKitData?.isHeader ?? false { + continue + } + let info = SourceFileInfo( + targets: [sourcesItem.target], isPartOfRootProject: isPartOfRootProject, mayContainTests: mayContainTests ) + switch sourceItem.kind { + case .file: + files[sourceItem.uri] = info.merging(files[sourceItem.uri]) + case .directory: + directories[sourceItem.uri] = info.merging(directories[sourceItem.uri]) + } } } + return SourceFilesAndDirectories(files: files, directories: directories) } - return sourceFiles } package func testFiles() async throws -> [DocumentURI] { - return try await sourceFiles().compactMap { (uri, info) -> DocumentURI? in + return try await buildableSourceFiles().compactMap { (uri, info) -> DocumentURI? in guard info.isPartOfRootProject, info.mayContainTests else { return nil } @@ -749,10 +819,6 @@ extension BuildSystemManager { } private func didChangeBuildTarget(notification: DidChangeBuildTargetNotification) async { - // Every `DidChangeBuildTargetNotification` notification needs to invalidate the cache since the changed target - // might gained a source file. - self.cachedTargetsForDocument.clearAll() - let updatedTargets: Set? = if let changes = notification.changes { Set(changes.map(\.target)) @@ -774,6 +840,7 @@ extension BuildSystemManager { } return !updatedTargets.intersection(cacheKey.targets).isEmpty } + self.cachedSourceFilesAndDirectories.clearAll() await delegate?.buildTargetsChanged(notification.changes) // FIXME: (BSP Migration) Communicate that the build target has changed to the `BuildSystemManagerDelegate` and make diff --git a/Sources/BuildSystemIntegration/BuildSystemMessageDependencyTracker.swift b/Sources/BuildSystemIntegration/BuildSystemMessageDependencyTracker.swift index bb8a4571..4f7b0736 100644 --- a/Sources/BuildSystemIntegration/BuildSystemMessageDependencyTracker.swift +++ b/Sources/BuildSystemIntegration/BuildSystemMessageDependencyTracker.swift @@ -78,8 +78,6 @@ package enum BuildSystemMessageDependencyTracker: DependencyTracker { self = .logging case is InitializeBuildRequest: self = .stateChange - case is InverseSourcesRequest: - self = .stateRead case is PrepareTargetsRequest: self = .stateRead case is RegisterForChanges: diff --git a/Sources/BuildSystemIntegration/BuiltInBuildSystem.swift b/Sources/BuildSystemIntegration/BuiltInBuildSystem.swift index 317ecf84..9921153e 100644 --- a/Sources/BuildSystemIntegration/BuiltInBuildSystem.swift +++ b/Sources/BuildSystemIntegration/BuiltInBuildSystem.swift @@ -67,9 +67,6 @@ package protocol BuiltInBuildSystem: AnyObject, Sendable { /// Called when files in the project change. func didChangeWatchedFiles(notification: BuildServerProtocol.DidChangeWatchedFilesNotification) async - /// Return the list of targets that the given document can be built for. - func inverseSources(request: InverseSourcesRequest) async throws -> InverseSourcesResponse - /// Prepare the given targets for indexing and semantic functionality. This should build all swift modules of target /// dependencies. func prepare(request: PrepareTargetsRequest) async throws -> VoidResponse diff --git a/Sources/BuildSystemIntegration/BuiltInBuildSystemAdapter.swift b/Sources/BuildSystemIntegration/BuiltInBuildSystemAdapter.swift index f2a16ba6..d3aad88f 100644 --- a/Sources/BuildSystemIntegration/BuiltInBuildSystemAdapter.swift +++ b/Sources/BuildSystemIntegration/BuiltInBuildSystemAdapter.swift @@ -147,8 +147,6 @@ package actor BuiltInBuildSystemAdapter: QueueBasedMessageHandler { await request.reply { try await underlyingBuildSystem.buildTargetSources(request: request.params) } case let request as RequestAndReply: await request.reply { await self.initialize(request: request.params) } - case let request as RequestAndReply: - await request.reply { try await underlyingBuildSystem.inverseSources(request: request.params) } case let request as RequestAndReply: await request.reply { try await underlyingBuildSystem.prepare(request: request.params) } case let request as RequestAndReply: diff --git a/Sources/BuildSystemIntegration/CompilationDatabase.swift b/Sources/BuildSystemIntegration/CompilationDatabase.swift index 6331245c..3ab14412 100644 --- a/Sources/BuildSystemIntegration/CompilationDatabase.swift +++ b/Sources/BuildSystemIntegration/CompilationDatabase.swift @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +import BuildServerProtocol import Foundation import LanguageServerProtocol import SKLogging @@ -67,7 +68,7 @@ extension CompilationDatabase.Command { package protocol CompilationDatabase { typealias Command = CompilationDatabaseCompileCommand subscript(_ uri: DocumentURI) -> [Command] { get } - var allCommands: AnySequence { get } + var sourceItems: [SourceItem] { get } } /// Loads the compilation database located in `directory`, if one can be found in `additionalSearchPaths` or in the default search paths of "." and "build". @@ -111,14 +112,18 @@ package func tryLoadCompilationDatabase( /// /// See https://clang.llvm.org/docs/JSONCompilationDatabase.html under Alternatives package struct FixedCompilationDatabase: CompilationDatabase, Equatable { - package var allCommands: AnySequence { AnySequence([]) } - private let fixedArgs: [String] private let directory: String package subscript(path: DocumentURI) -> [CompilationDatabaseCompileCommand] { [Command(directory: directory, filename: path.pseudoPath, commandLine: fixedArgs + [path.pseudoPath])] } + + package var sourceItems: [SourceItem] { + return [ + SourceItem(uri: URI(filePath: directory, isDirectory: true), kind: .directory, generated: false) + ] + } } extension FixedCompilationDatabase { @@ -188,7 +193,11 @@ package struct JSONCompilationDatabase: CompilationDatabase, Equatable { return [] } - package var allCommands: AnySequence { AnySequence(commands) } + package var sourceItems: [SourceItem] { + return commands.map { + SourceItem(uri: $0.uri, kind: .file, generated: false) + } + } package mutating func add(_ command: CompilationDatabaseCompileCommand) { let uri = command.uri diff --git a/Sources/BuildSystemIntegration/CompilationDatabaseBuildSystem.swift b/Sources/BuildSystemIntegration/CompilationDatabaseBuildSystem.swift index 73e7081a..6346a8d1 100644 --- a/Sources/BuildSystemIntegration/CompilationDatabaseBuildSystem.swift +++ b/Sources/BuildSystemIntegration/CompilationDatabaseBuildSystem.swift @@ -24,6 +24,26 @@ import protocol TSCBasic.FileSystem import struct TSCBasic.RelativePath import var TSCBasic.localFileSystem +fileprivate enum Cachable { + case noValue + case value(Value) + + mutating func get(_ compute: () -> Value) -> Value { + switch self { + case .noValue: + let value = compute() + self = .value(value) + return value + case .value(let value): + return value + } + } + + mutating func reset() { + self = .noValue + } +} + /// A `BuildSystem` based on loading clang-compatible compilation database(s). /// /// Provides build settings from a `CompilationDatabase` found by searching a project. For now, only @@ -34,7 +54,7 @@ package actor CompilationDatabaseBuildSystem { didSet { // Build settings have changed and thus the index store path might have changed. // Recompute it on demand. - _indexStorePath = nil + _indexStorePath.reset() } } @@ -46,24 +66,25 @@ package actor CompilationDatabaseBuildSystem { let fileSystem: FileSystem - private var _indexStorePath: AbsolutePath? + private var _indexStorePath: Cachable = .noValue package var indexStorePath: AbsolutePath? { - if let indexStorePath = _indexStorePath { - return indexStorePath - } + _indexStorePath.get { + guard let compdb else { + return nil + } - if let allCommands = self.compdb?.allCommands { - for command in allCommands { - let args = command.commandLine - for i in args.indices.reversed() { - if args[i] == "-index-store-path" && i != args.endIndex - 1 { - _indexStorePath = try? AbsolutePath(validating: args[i + 1]) - return _indexStorePath + for sourceItem in compdb.sourceItems { + for command in compdb[sourceItem.uri] { + let args = command.commandLine + for i in args.indices.reversed() { + if args[i] == "-index-store-path" && i + 1 < args.count { + return AbsolutePath(validatingOrNil: args[i + 1]) + } } } } + return nil } - return nil } package init?( @@ -120,10 +141,7 @@ extension CompilationDatabaseBuildSystem: BuiltInBuildSystem { guard let compdb else { return BuildTargetSourcesResponse(items: []) } - let sources = compdb.allCommands.map { - SourceItem(uri: $0.uri, kind: .file, generated: false) - } - return BuildTargetSourcesResponse(items: [SourcesItem(target: .dummy, sources: sources)]) + return BuildTargetSourcesResponse(items: [SourcesItem(target: .dummy, sources: compdb.sourceItems)]) } package func didChangeWatchedFiles(notification: BuildServerProtocol.DidChangeWatchedFilesNotification) { @@ -132,10 +150,6 @@ extension CompilationDatabaseBuildSystem: BuiltInBuildSystem { } } - package func inverseSources(request: InverseSourcesRequest) -> InverseSourcesResponse { - return InverseSourcesResponse(targets: [BuildTargetIdentifier.dummy]) - } - package func prepare(request: PrepareTargetsRequest) async throws -> VoidResponse { throw PrepareNotSupportedError() } diff --git a/Sources/BuildSystemIntegration/SwiftPMBuildSystem.swift b/Sources/BuildSystemIntegration/SwiftPMBuildSystem.swift index 6871e604..fe410f63 100644 --- a/Sources/BuildSystemIntegration/SwiftPMBuildSystem.swift +++ b/Sources/BuildSystemIntegration/SwiftPMBuildSystem.swift @@ -125,7 +125,7 @@ extension BuildTargetIdentifier { } fileprivate extension TSCBasic.AbsolutePath { - var asURI: DocumentURI? { + var asURI: DocumentURI { DocumentURI(self.asURL) } } @@ -512,7 +512,7 @@ extension SwiftPMBuildSystem: BuildSystemIntegration.BuiltInBuildSystem { } package func buildTargets(request: BuildTargetsRequest) async throws -> BuildTargetsResponse { - let targets = self.swiftPMTargets.map { (targetId, target) in + var targets = self.swiftPMTargets.map { (targetId, target) in var tags: [BuildTargetTag] = [.test] if !target.isPartOfRootPackage { tags.append(.dependency) @@ -530,6 +530,17 @@ extension SwiftPMBuildSystem: BuildSystemIntegration.BuiltInBuildSystem { data: SourceKitBuildTarget(toolchain: toolchain.path?.asURI).encodeToLSPAny() ) } + targets.append( + BuildTarget( + id: .forPackageManifest, + displayName: "Package.swift", + baseDirectory: nil, + tags: [.notBuildable], + capabilities: BuildTargetCapabilities(), + languageIds: [.swift], + dependencies: [] + ) + ) return BuildTargetsResponse(targets: targets) } @@ -538,12 +549,35 @@ extension SwiftPMBuildSystem: BuildSystemIntegration.BuiltInBuildSystem { // TODO: Query The SwiftPM build system for the document's language and add it to SourceItem.data // (https://github.com/swiftlang/sourcekit-lsp/issues/1267) for target in request.targets { + if target == .forPackageManifest { + result.append( + SourcesItem( + target: target, + sources: [ + SourceItem( + uri: projectRoot.appending(component: "Package.swift").asURI, + kind: .file, + generated: false + ) + ] + ) + ) + } guard let swiftPMTarget = self.swiftPMTargets[target] else { continue } - let sources = swiftPMTarget.sources.map { + var sources = swiftPMTarget.sources.map { SourceItem(uri: DocumentURI($0), kind: .file, generated: false) } + sources += swiftPMTarget.headers.map { + SourceItem( + uri: DocumentURI($0), + kind: .file, + generated: false, + dataKind: .sourceKit, + data: SourceKitSourceItemData(isHeader: true).encodeToLSPAny() + ) + } result.append(SourcesItem(target: target, sources: sources)) } return BuildTargetSourcesResponse(items: result) @@ -614,10 +648,6 @@ extension SwiftPMBuildSystem: BuildSystemIntegration.BuiltInBuildSystem { return [] } - package func inverseSources(request: InverseSourcesRequest) -> InverseSourcesResponse { - return InverseSourcesResponse(targets: targets(for: request.textDocument.uri)) - } - package func waitForUpBuildSystemUpdates(request: WaitForBuildSystemUpdatesRequest) async -> VoidResponse { await self.packageLoadingQueue.async {}.valuePropagatingCancellation return VoidResponse() diff --git a/Sources/BuildSystemIntegration/TestBuildSystem.swift b/Sources/BuildSystemIntegration/TestBuildSystem.swift index c0a3bd7f..380cc29a 100644 --- a/Sources/BuildSystemIntegration/TestBuildSystem.swift +++ b/Sources/BuildSystemIntegration/TestBuildSystem.swift @@ -49,19 +49,30 @@ package actor TestBuildSystem: BuiltInBuildSystem { } package func buildTargets(request: BuildTargetsRequest) async throws -> BuildTargetsResponse { - return BuildTargetsResponse(targets: []) + return BuildTargetsResponse(targets: [ + BuildTarget( + id: .dummy, + displayName: nil, + baseDirectory: nil, + tags: [], + capabilities: BuildTargetCapabilities(), + languageIds: [], + dependencies: [] + ) + ]) } package func buildTargetSources(request: BuildTargetSourcesRequest) async throws -> BuildTargetSourcesResponse { - return BuildTargetSourcesResponse(items: []) + return BuildTargetSourcesResponse(items: [ + SourcesItem( + target: .dummy, + sources: buildSettingsByFile.keys.map { SourceItem(uri: $0, kind: .file, generated: false) } + ) + ]) } package func didChangeWatchedFiles(notification: BuildServerProtocol.DidChangeWatchedFilesNotification) async {} - package func inverseSources(request: InverseSourcesRequest) -> InverseSourcesResponse { - return InverseSourcesResponse(targets: [BuildTargetIdentifier.dummy]) - } - package func prepare(request: PrepareTargetsRequest) async throws -> VoidResponse { throw PrepareNotSupportedError() } diff --git a/Sources/SemanticIndex/SemanticIndexManager.swift b/Sources/SemanticIndex/SemanticIndexManager.swift index 136f582b..7850310e 100644 --- a/Sources/SemanticIndex/SemanticIndexManager.swift +++ b/Sources/SemanticIndex/SemanticIndexManager.swift @@ -270,7 +270,7 @@ package final actor SemanticIndexManager { filesToIndex } else { await orLog("Getting files to index") { - try await self.buildSystemManager.sourceFiles().keys.sorted { $0.stringValue < $1.stringValue } + try await self.buildSystemManager.buildableSourceFiles().keys.sorted { $0.stringValue < $1.stringValue } } ?? [] } if !indexFilesWithUpToDateUnit { @@ -366,7 +366,7 @@ package final actor SemanticIndexManager { toCover files: some Collection & Sendable ) async -> [FileToIndex] { let sourceFiles = await orLog("Getting source files in project") { - Set(try await buildSystemManager.sourceFiles().keys) + Set(try await buildSystemManager.buildableSourceFiles().keys) } guard let sourceFiles else { return [] diff --git a/Tests/BuildSystemIntegrationTests/BuildServerBuildSystemTests.swift b/Tests/BuildSystemIntegrationTests/BuildServerBuildSystemTests.swift index ef01681c..045c054f 100644 --- a/Tests/BuildSystemIntegrationTests/BuildServerBuildSystemTests.swift +++ b/Tests/BuildSystemIntegrationTests/BuildServerBuildSystemTests.swift @@ -81,12 +81,8 @@ final class BuildServerBuildSystemTests: XCTestCase { ) _ = try await buildSystem.sourceKitOptions( request: SourceKitOptionsRequest( - textDocument: TextDocumentIdentifier(uri: uri), - target: try unwrap( - await buildSystem.inverseSources( - request: InverseSourcesRequest(textDocument: TextDocumentIdentifier(uri: uri)) - ).targets.only - ) + textDocument: TextDocumentIdentifier(uri), + target: .dummy ) ) @@ -118,12 +114,8 @@ final class BuildServerBuildSystemTests: XCTestCase { ) _ = try await buildSystem.sourceKitOptions( request: SourceKitOptionsRequest( - textDocument: TextDocumentIdentifier(uri: uri), - target: try unwrap( - await buildSystem.inverseSources( - request: InverseSourcesRequest(textDocument: TextDocumentIdentifier(uri: uri)) - ).targets.only - ) + textDocument: TextDocumentIdentifier(uri), + target: .dummy ) ) diff --git a/Tests/BuildSystemIntegrationTests/CompilationDatabaseTests.swift b/Tests/BuildSystemIntegrationTests/CompilationDatabaseTests.swift index ab324ae1..fdeba05c 100644 --- a/Tests/BuildSystemIntegrationTests/CompilationDatabaseTests.swift +++ b/Tests/BuildSystemIntegrationTests/CompilationDatabaseTests.swift @@ -292,7 +292,7 @@ final class CompilationDatabaseTests: XCTestCase { ) { buildSystem in let settings = try await buildSystem.sourceKitOptions( request: SourceKitOptionsRequest( - textDocument: TextDocumentIdentifier(uri: DocumentURI(URL(fileURLWithPath: "/a/a.swift"))), + textDocument: TextDocumentIdentifier(DocumentURI(URL(fileURLWithPath: "/a/a.swift"))), target: BuildTargetIdentifier.dummy ) ) diff --git a/Tests/BuildSystemIntegrationTests/SwiftPMBuildSystemTests.swift b/Tests/BuildSystemIntegrationTests/SwiftPMBuildSystemTests.swift index 9585bac5..87293082 100644 --- a/Tests/BuildSystemIntegrationTests/SwiftPMBuildSystemTests.swift +++ b/Tests/BuildSystemIntegrationTests/SwiftPMBuildSystemTests.swift @@ -35,7 +35,7 @@ fileprivate extension SwiftPMBuildSystem { return nil } return try await sourceKitOptions( - request: SourceKitOptionsRequest(textDocument: TextDocumentIdentifier(uri: uri), target: target) + request: SourceKitOptionsRequest(textDocument: TextDocumentIdentifier(uri), target: target) ) }