diff --git a/Package.swift b/Package.swift index f5c9932d0..b93f1079f 100644 --- a/Package.swift +++ b/Package.swift @@ -168,6 +168,18 @@ let package = Package( ] ), + // MARK: SemanticIndex + + .target( + name: "SemanticIndex", + dependencies: [ + "LSPLogging", + "SKCore", + .product(name: "IndexStoreDB", package: "indexstore-db"), + ], + exclude: ["CMakeLists.txt"] + ), + // MARK: SKCore // Data structures and algorithms useful across the project, but not necessarily // suitable for use in other packages. @@ -300,9 +312,11 @@ let package = Package( name: "SourceKitLSP", dependencies: [ "BuildServerProtocol", + "CAtomics", "LanguageServerProtocol", "LanguageServerProtocolJSONRPC", "LSPLogging", + "SemanticIndex", "SKCore", "SKSupport", "SKSwiftPMWorkspace", diff --git a/Sources/CAtomics/include/CAtomics.h b/Sources/CAtomics/include/CAtomics.h index effbe62be..a5d273647 100644 --- a/Sources/CAtomics/include/CAtomics.h +++ b/Sources/CAtomics/include/CAtomics.h @@ -63,4 +63,32 @@ static inline void atomic_uint8_set(AtomicUInt8 *atomic, uint8_t newValue) { atomic->value = newValue; } +// MARK: AtomicInt + +typedef struct { + _Atomic(int) value; +} AtomicUInt32; + +__attribute__((swift_name("AtomicUInt32.init(initialValue:)"))) +static inline AtomicUInt32 atomic_int_create(uint8_t initialValue) { + AtomicUInt32 atomic; + atomic.value = initialValue; + return atomic; +} + +__attribute__((swift_name("getter:AtomicUInt32.value(self:)"))) +static inline uint32_t atomic_int_get(AtomicUInt32 *atomic) { + return atomic->value; +} + +__attribute__((swift_name("setter:AtomicUInt32.value(self:_:)"))) +static inline void atomic_uint32_set(AtomicUInt32 *atomic, uint32_t newValue) { + atomic->value = newValue; +} + +__attribute__((swift_name("AtomicUInt32.fetchAndIncrement(self:)"))) +static inline uint32_t atomic_uint32_fetch_and_increment(AtomicUInt32 *atomic) { + return atomic->value++; +} + #endif // SOURCEKITLSP_CATOMICS_H diff --git a/Sources/CMakeLists.txt b/Sources/CMakeLists.txt index 368f340eb..4ed6c0350 100644 --- a/Sources/CMakeLists.txt +++ b/Sources/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(Diagnose) add_subdirectory(LanguageServerProtocol) add_subdirectory(LanguageServerProtocolJSONRPC) add_subdirectory(LSPLogging) +add_subdirectory(SemanticIndex) add_subdirectory(SKCore) add_subdirectory(SKSupport) add_subdirectory(SKSwiftPMWorkspace) diff --git a/Sources/LSPLogging/Logging.swift b/Sources/LSPLogging/Logging.swift index a67132cca..079924750 100644 --- a/Sources/LSPLogging/Logging.swift +++ b/Sources/LSPLogging/Logging.swift @@ -21,9 +21,6 @@ import Foundation -/// The subsystem that should be used for any logging by default. -public let subsystem = "org.swift.sourcekit-lsp" - #if canImport(os) && !SOURCEKITLSP_FORCE_NON_DARWIN_LOGGER import os // os_log @@ -44,5 +41,5 @@ public typealias Signposter = NonDarwinSignposter /// The logger that is used to log any messages. public var logger: Logger { - Logger(subsystem: subsystem, category: LoggingScope.scope) + Logger(subsystem: LoggingScope.subsystem, category: LoggingScope.scope) } diff --git a/Sources/LSPLogging/LoggingScope.swift b/Sources/LSPLogging/LoggingScope.swift index 644db803e..5ef40a303 100644 --- a/Sources/LSPLogging/LoggingScope.swift +++ b/Sources/LSPLogging/LoggingScope.swift @@ -13,15 +13,50 @@ import Foundation public final class LoggingScope { + /// The name of the current logging subsystem or `nil` if no logging scope is set. + @TaskLocal fileprivate static var _subsystem: String? + /// The name of the current logging scope or `nil` if no logging scope is set. @TaskLocal fileprivate static var _scope: String? + /// The name of the current logging subsystem. + public static var subsystem: String { + return _subsystem ?? "org.swift.sourcekit-lsp" + } + /// The name of the current logging scope. public static var scope: String { return _scope ?? "default" } } +/// Logs all messages created from the operation to the given subsystem. +/// +/// This overrides the current logging subsystem. +/// +/// - Note: Since this stores the logging subsystem in a task-local value, it only works when run inside a task. +/// Outside a task, this is a no-op. +public func withLoggingSubsystemAndScope( + subsystem: String, + scope: String?, + _ operation: () throws -> Result +) rethrows -> Result { + return try LoggingScope.$_subsystem.withValue(subsystem) { + return try LoggingScope.$_scope.withValue(scope, operation: operation) + } +} + +/// Same as `withLoggingSubsystemAndScope` but allows the operation to be `async`. +public func withLoggingSubsystemAndScope( + subsystem: String, + scope: String?, + _ operation: () async throws -> Result +) async rethrows -> Result { + return try await LoggingScope.$_subsystem.withValue(subsystem) { + return try await LoggingScope.$_scope.withValue(scope, operation: operation) + } +} + /// Create a new logging scope, which will be used as the category in any log messages created from the operation. /// /// This overrides the current logging scope. diff --git a/Sources/SKCore/BuildServerBuildSystem.swift b/Sources/SKCore/BuildServerBuildSystem.swift index c75a7cea0..f45dd93fa 100644 --- a/Sources/SKCore/BuildServerBuildSystem.swift +++ b/Sources/SKCore/BuildServerBuildSystem.swift @@ -263,10 +263,14 @@ extension BuildServerBuildSystem: BuildSystem { /// /// Returns `nil` if no build settings have been received from the build /// server yet or if no build settings are available for this file. - public func buildSettings(for document: DocumentURI, language: Language) async throws -> FileBuildSettings? { + public func buildSettings(for document: DocumentURI, language: Language) async -> FileBuildSettings? { return buildSettings[document] } + public func defaultLanguage(for document: DocumentURI) async -> Language? { + return nil + } + public func registerForChangeNotifications(for uri: DocumentURI) { let request = RegisterForChanges(uri: uri, action: .register) _ = self.buildServer?.send(request) { result in @@ -317,14 +321,14 @@ extension BuildServerBuildSystem: BuildSystem { return .unhandled } - public func testFiles() async -> [DocumentURI] { - // BuildServerBuildSystem does not support syntactic test discovery + public func sourceFiles() async -> [SourceFileInfo] { + // BuildServerBuildSystem does not support syntactic test discovery or background indexing. // (https://github.com/apple/sourcekit-lsp/issues/1173). return [] } - public func addTestFilesDidChangeCallback(_ callback: @escaping () async -> Void) { - // BuildServerBuildSystem does not support syntactic test discovery + public func addSourceFilesDidChangeCallback(_ callback: @escaping () async -> Void) { + // BuildServerBuildSystem does not support syntactic test discovery or background indexing. // (https://github.com/apple/sourcekit-lsp/issues/1173). } } diff --git a/Sources/SKCore/BuildSystem.swift b/Sources/SKCore/BuildSystem.swift index e9756023d..ee48db020 100644 --- a/Sources/SKCore/BuildSystem.swift +++ b/Sources/SKCore/BuildSystem.swift @@ -27,6 +27,22 @@ public enum FileHandlingCapability: Comparable, Sendable { case handled } +public struct SourceFileInfo: Sendable { + /// The URI of the source file. + public let uri: DocumentURI + + /// 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. + public let mayContainTests: Bool + + public init(uri: DocumentURI, mayContainTests: Bool) { + self.uri = uri + self.mayContainTests = mayContainTests + } +} + /// Provider of FileBuildSettings and other build-related information. /// /// The primary role of the build system is to answer queries for @@ -71,6 +87,13 @@ public protocol BuildSystem: AnyObject, Sendable { /// file or if it hasn't computed build settings for the file yet. func buildSettings(for document: DocumentURI, language: Language) async throws -> FileBuildSettings? + /// If the build system has knowledge about the language that this document should be compiled in, return it. + /// + /// This is used to determine the language in which a source file should be background indexed. + /// + /// If `nil` is returned, the language based on the file's extension. + func defaultLanguage(for document: DocumentURI) async -> Language? + /// Register the given file for build-system level change notifications, such /// as command line flag changes, dependency changes, etc. /// @@ -88,18 +111,13 @@ public protocol BuildSystem: AnyObject, Sendable { func fileHandlingCapability(for uri: DocumentURI) async -> FileHandlingCapability - /// Returns the list of files that might contain test cases. - /// - /// The returned file list is an over-approximation. It might contain tests from non-test targets or files that don't - /// actually contain any tests. Keeping this list as minimal as possible helps reduce the amount of work that the - /// syntactic test indexer needs to perform. - func testFiles() async -> [DocumentURI] + /// Returns the list of source files in the project. + func sourceFiles() async -> [SourceFileInfo] - /// Adds a callback that should be called when the value returned by `testFiles()` changes. + /// Adds a callback that should be called when the value returned by `sourceFiles()` changes. /// - /// The callback might also be called without an actual change to `testFiles`. - func addTestFilesDidChangeCallback(_ callback: @Sendable @escaping () async -> Void) async + /// The callback might also be called without an actual change to `sourceFiles`. + func addSourceFilesDidChangeCallback(_ callback: @Sendable @escaping () async -> Void) async } -public let buildTargetsNotSupported = - ResponseError.methodNotFound(BuildTargets.method) +public let buildTargetsNotSupported = ResponseError.methodNotFound(BuildTargets.method) diff --git a/Sources/SKCore/BuildSystemManager.swift b/Sources/SKCore/BuildSystemManager.swift index a6a44a067..ea726f942 100644 --- a/Sources/SKCore/BuildSystemManager.swift +++ b/Sources/SKCore/BuildSystemManager.swift @@ -50,6 +50,11 @@ public actor BuildSystemManager { /// Build system delegate that will receive notifications about setting changes, etc. var delegate: BuildSystemDelegate? + /// The list of toolchains that are available. + /// + /// Used to determine which toolchain to use for a given document. + private let toolchainRegistry: ToolchainRegistry + /// 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. @@ -65,6 +70,7 @@ public actor BuildSystemManager { buildSystem: BuildSystem?, fallbackBuildSystem: FallbackBuildSystem?, mainFilesProvider: MainFilesProvider?, + toolchainRegistry: ToolchainRegistry, fallbackSettingsTimeout: DispatchTimeInterval = .seconds(3) ) async { let buildSystemHasDelegate = await buildSystem?.delegate != nil @@ -72,6 +78,7 @@ public actor BuildSystemManager { self.buildSystem = buildSystem self.fallbackBuildSystem = fallbackBuildSystem self.mainFilesProvider = mainFilesProvider + self.toolchainRegistry = toolchainRegistry self.fallbackSettingsTimeout = fallbackSettingsTimeout await self.buildSystem?.setDelegate(self) } @@ -87,11 +94,34 @@ extension BuildSystemManager { self.delegate = delegate } + /// Returns the toolchain that should be used to process the given document. + public func toolchain(for uri: DocumentURI, _ language: Language) async -> Toolchain? { + // To support multiple toolchains within a single workspace, we need to ask the build system which toolchain to use + // for this document. + return await toolchainRegistry.defaultToolchain(for: language) + } + /// - Note: Needed so we can set the delegate from a different isolation context. public func setMainFilesProvider(_ mainFilesProvider: MainFilesProvider?) { self.mainFilesProvider = mainFilesProvider } + /// Returns the language that a document should be interpreted in for background tasks where the editor doesn't + /// specify the document's language. + public func defaultLanguage(for document: DocumentURI) async -> Language? { + if let defaultLanguage = await buildSystem?.defaultLanguage(for: document) { + return defaultLanguage + } + switch document.fileURL?.pathExtension { + case "c": return .c + case "cpp", "cc", "cxx": return .cpp + case "m": return .objective_c + case "mm", "h": return .objective_cpp + case "swift": return .swift + default: return nil + } + } + private func buildSettings( for document: DocumentURI, language: Language @@ -177,8 +207,17 @@ extension BuildSystemManager { ) } + public func sourceFiles() async -> [SourceFileInfo] { + return await buildSystem?.sourceFiles() ?? [] + } + public func testFiles() async -> [DocumentURI] { - return await buildSystem?.testFiles() ?? [] + return await sourceFiles().compactMap { (info: SourceFileInfo) -> DocumentURI? in + guard info.mayContainTests else { + return nil + } + return info.uri + } } } diff --git a/Sources/SKCore/CompilationDatabaseBuildSystem.swift b/Sources/SKCore/CompilationDatabaseBuildSystem.swift index 929110f21..031b1d10c 100644 --- a/Sources/SKCore/CompilationDatabaseBuildSystem.swift +++ b/Sources/SKCore/CompilationDatabaseBuildSystem.swift @@ -114,6 +114,10 @@ extension CompilationDatabaseBuildSystem: BuildSystem { ) } + public func defaultLanguage(for document: DocumentURI) async -> Language? { + return nil + } + public func registerForChangeNotifications(for uri: DocumentURI) async { self.watchedFiles.insert(uri) } @@ -192,11 +196,16 @@ extension CompilationDatabaseBuildSystem: BuildSystem { } } - public func testFiles() async -> [DocumentURI] { - return compdb?.allCommands.map { DocumentURI($0.url) } ?? [] + public func sourceFiles() async -> [SourceFileInfo] { + guard let compdb else { + return [] + } + return compdb.allCommands.map { + SourceFileInfo(uri: DocumentURI($0.url), mayContainTests: true) + } } - public func addTestFilesDidChangeCallback(_ callback: @escaping () async -> Void) async { + public func addSourceFilesDidChangeCallback(_ callback: @escaping () async -> Void) async { testFilesDidChangeCallbacks.append(callback) } } diff --git a/Sources/SKCore/TaskScheduler.swift b/Sources/SKCore/TaskScheduler.swift index fac5b3365..deb3a3399 100644 --- a/Sources/SKCore/TaskScheduler.swift +++ b/Sources/SKCore/TaskScheduler.swift @@ -20,6 +20,8 @@ public enum TaskDependencyAction { case cancelAndRescheduleDependency(TaskDescription) } +private let taskSchedulerSubsystem = "org.swift.sourcekit-lsp.task-scheduler" + public protocol TaskDescriptionProtocol: Identifiable, Sendable, CustomLogStringConvertible { /// Execute the task. /// @@ -232,9 +234,11 @@ fileprivate actor QueuedTask { /// a new task that depends on it. Otherwise a no-op. nonisolated func elevatePriority(to targetPriority: TaskPriority) { if priority < targetPriority { - logger.debug( - "Elevating priority of \(self.description.forLogging) from \(self.priority.rawValue) to \(targetPriority.rawValue)" - ) + withLoggingSubsystemAndScope(subsystem: taskSchedulerSubsystem, scope: nil) { + logger.debug( + "Elevating priority of \(self.description.forLogging) from \(self.priority.rawValue) to \(targetPriority.rawValue)" + ) + } Task(priority: targetPriority) { await self.resultTask.value } @@ -306,7 +310,9 @@ public actor TaskScheduler { priority: TaskPriority? = nil, _ taskDescription: TaskDescription ) async -> Task { - logger.debug("Scheduling \(taskDescription.forLogging)") + withLoggingSubsystemAndScope(subsystem: taskSchedulerSubsystem, scope: nil) { + logger.debug("Scheduling \(taskDescription.forLogging)") + } let queuedTask = await QueuedTask(priority: priority, description: taskDescription) pendingTasks.append(queuedTask) Task.detached(priority: priority ?? Task.currentPriority) { @@ -361,13 +367,17 @@ public actor TaskScheduler { case .cancelAndRescheduleDependency(let taskDescription): guard let dependency = self.currentlyExecutingTasks.first(where: { $0.description.id == taskDescription.id }) else { - logger.fault( - "Cannot find task to wait for \(taskDescription.forLogging) in list of currently executing tasks" - ) + withLoggingSubsystemAndScope(subsystem: taskSchedulerSubsystem, scope: nil) { + logger.fault( + "Cannot find task to wait for \(taskDescription.forLogging) in list of currently executing tasks" + ) + } return nil } if !taskDescription.isIdempotent { - logger.fault("Cannot reschedule task '\(taskDescription.forLogging)' since it is not idempotent") + withLoggingSubsystemAndScope(subsystem: taskSchedulerSubsystem, scope: nil) { + logger.fault("Cannot reschedule task '\(taskDescription.forLogging)' since it is not idempotent") + } return dependency } if dependency.priority > task.priority { @@ -378,9 +388,11 @@ public actor TaskScheduler { case .waitAndElevatePriorityOfDependency(let taskDescription): guard let dependency = self.currentlyExecutingTasks.first(where: { $0.description.id == taskDescription.id }) else { - logger.fault( - "Cannot find task to wait for '\(taskDescription.forLogging)' in list of currently executing tasks" - ) + withLoggingSubsystemAndScope(subsystem: taskSchedulerSubsystem, scope: nil) { + logger.fault( + "Cannot find task to wait for '\(taskDescription.forLogging)' in list of currently executing tasks" + ) + } return nil } return dependency @@ -398,9 +410,11 @@ public actor TaskScheduler { switch taskDependency { case .cancelAndRescheduleDependency(let taskDescription): guard let task = self.currentlyExecutingTasks.first(where: { $0.description.id == taskDescription.id }) else { - logger.fault( - "Cannot find task to reschedule \(taskDescription.forLogging) in list of currently executing tasks" - ) + withLoggingSubsystemAndScope(subsystem: taskSchedulerSubsystem, scope: nil) { + logger.fault( + "Cannot find task to reschedule \(taskDescription.forLogging) in list of currently executing tasks" + ) + } return nil } return task @@ -411,7 +425,9 @@ public actor TaskScheduler { if !rescheduleTasks.isEmpty { Task.detached(priority: task.priority) { for task in rescheduleTasks { - logger.debug("Suspending \(task.description.forLogging)") + withLoggingSubsystemAndScope(subsystem: taskSchedulerSubsystem, scope: nil) { + logger.debug("Suspending \(task.description.forLogging)") + } await task.cancelToBeRescheduled() } } @@ -422,19 +438,25 @@ public actor TaskScheduler { return } - logger.debug("Executing \(task.description.forLogging) with priority \(task.priority.rawValue)") + withLoggingSubsystemAndScope(subsystem: taskSchedulerSubsystem, scope: nil) { + logger.debug("Executing \(task.description.forLogging) with priority \(task.priority.rawValue)") + } currentlyExecutingTasks.append(task) pendingTasks.removeAll(where: { $0 === task }) Task.detached(priority: task.priority) { - logger.debug( - "Execution of \(task.description.forLogging) started with priority \(Task.currentPriority.rawValue)" - ) + withLoggingSubsystemAndScope(subsystem: taskSchedulerSubsystem, scope: nil) { + logger.debug( + "Execution of \(task.description.forLogging) started with priority \(Task.currentPriority.rawValue)" + ) + } // Await the task's return in a task so that this poker can continue checking if there are more execution // slots that can be filled with queued tasks. let finishStatus = await task.execute() - logger.debug( - "Execution of \(task.description.forLogging) finished with priority \(Task.currentPriority.rawValue)" - ) + withLoggingSubsystemAndScope(subsystem: taskSchedulerSubsystem, scope: nil) { + logger.debug( + "Execution of \(task.description.forLogging) finished with priority \(Task.currentPriority.rawValue)" + ) + } await self.finalizeTaskExecution(task: task, finishStatus: finishStatus) } } diff --git a/Sources/SKCore/ToolchainRegistry.swift b/Sources/SKCore/ToolchainRegistry.swift index 86edbe7df..ee0010015 100644 --- a/Sources/SKCore/ToolchainRegistry.swift +++ b/Sources/SKCore/ToolchainRegistry.swift @@ -12,6 +12,7 @@ import Dispatch import Foundation +import LanguageServerProtocol import SKSupport import struct TSCBasic.AbsolutePath @@ -244,6 +245,33 @@ public final actor ToolchainRegistry { public var darwinToolchainIdentifier: String { return darwinToolchainOverride ?? ToolchainRegistry.darwinDefaultToolchainIdentifier } + + /// The toolchain to use for a document in the given language if the build system doesn't override it. + func defaultToolchain(for language: Language) -> Toolchain? { + let supportsLang = { (toolchain: Toolchain) -> Bool in + // FIXME: the fact that we're looking at clangd/sourcekitd instead of the compiler indicates this method needs a parameter stating what kind of tool we're looking for. + switch language { + case .swift: + return toolchain.sourcekitd != nil + case .c, .cpp, .objective_c, .objective_cpp: + return toolchain.clangd != nil + default: + return false + } + } + + if let toolchain = self.default, supportsLang(toolchain) { + return toolchain + } + + for toolchain in toolchains { + if supportsLang(toolchain) { + return toolchain + } + } + + return nil + } } /// Inspecting internal state for testing purposes. diff --git a/Sources/SKSupport/AsyncQueue.swift b/Sources/SKSupport/AsyncQueue.swift index 4829983a7..31d58687d 100644 --- a/Sources/SKSupport/AsyncQueue.swift +++ b/Sources/SKSupport/AsyncQueue.swift @@ -144,7 +144,7 @@ public final class AsyncQueue: Sendable { let dependencies: [PendingTask] = tasks.filter { $0.metadata.isDependency(of: metadata) } // Schedule the task. - let task = Task { [pendingTasks] in + let task = Task(priority: priority) { [pendingTasks] in // IMPORTANT: The only throwing call in here must be the call to // operation. Otherwise the assumption that the task will never throw // if `operation` does not throw, which we are making in `async` does diff --git a/Sources/SKSupport/CMakeLists.txt b/Sources/SKSupport/CMakeLists.txt index efc4e010f..5453bb59d 100644 --- a/Sources/SKSupport/CMakeLists.txt +++ b/Sources/SKSupport/CMakeLists.txt @@ -5,11 +5,13 @@ add_library(SKSupport STATIC BuildConfiguration.swift ByteString.swift Collection+Only.swift + Collection+PartitionIntoBatches.swift Connection+Send.swift dlopen.swift DocumentURI+CustomLogStringConvertible.swift FileSystem.swift LineTable.swift + Process+WaitUntilExitWithCancellation.swift Random.swift Result.swift ThreadSafeBox.swift diff --git a/Sources/SKSupport/Collection+PartitionIntoBatches.swift b/Sources/SKSupport/Collection+PartitionIntoBatches.swift new file mode 100644 index 000000000..ed1effdc9 --- /dev/null +++ b/Sources/SKSupport/Collection+PartitionIntoBatches.swift @@ -0,0 +1,35 @@ +//===----------------------------------------------------------------------===// +// +// 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 extension Collection { + /// Partition the elements of the collection into `numberOfBatches` roughly equally sized batches. + /// + /// Elements are assigned to the batches round-robin. This ensures that elements that are close to each other in the + /// original collection end up in different batches. This is important because eg. test files will live close to each + /// other in the file system and test scanning wants to scan them in different batches so we don't end up with one + /// batch only containing source files and the other only containing test files. + func partition(intoNumberOfBatches numberOfBatches: Int) -> [[Element]] { + var batches: [[Element]] = Array( + repeating: { + var batch: [Element] = [] + batch.reserveCapacity(self.count / numberOfBatches) + return batch + }(), + count: numberOfBatches + ) + + for (index, element) in self.enumerated() { + batches[index % numberOfBatches].append(element) + } + return batches.filter { !$0.isEmpty } + } +} diff --git a/Sources/SKSupport/Process+WaitUntilExitWithCancellation.swift b/Sources/SKSupport/Process+WaitUntilExitWithCancellation.swift new file mode 100644 index 000000000..aa6f1c13f --- /dev/null +++ b/Sources/SKSupport/Process+WaitUntilExitWithCancellation.swift @@ -0,0 +1,27 @@ +//===----------------------------------------------------------------------===// +// +// 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 Foundation + +import class TSCBasic.Process +import struct TSCBasic.ProcessResult + +public extension Process { + /// Wait for the process to exit. If the task gets cancelled, during this time, send a `SIGINT` to the process. + func waitUntilExitSendingSigIntOnTaskCancellation() async throws -> ProcessResult { + return try await withTaskCancellationHandler { + try await waitUntilExit() + } onCancel: { + signal(SIGINT) + } + } +} diff --git a/Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift b/Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift index 4bb6ee64a..881ebc0f0 100644 --- a/Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift +++ b/Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift @@ -341,6 +341,11 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem { return nil } + public func defaultLanguage(for document: DocumentURI) async -> Language? { + // TODO (indexing): Query The SwiftPM build system for the document's language + return nil + } + public func registerForChangeNotifications(for uri: DocumentURI) async { self.watchedFiles.insert(uri) } @@ -437,17 +442,22 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem { } } - public func testFiles() -> [DocumentURI] { - return fileToTarget.compactMap { (path, target) -> DocumentURI? in + public func sourceFiles() -> [SourceFileInfo] { + return fileToTarget.compactMap { (path, target) -> SourceFileInfo? in guard target.isPartOfRootPackage else { // Don't consider files from package dependencies as possible test files. return nil } - return DocumentURI(path.asURL) + // We should only set mayContainTests to `true` for files from test targets + // (https://github.com/apple/sourcekit-lsp/issues/1174). + return SourceFileInfo( + uri: DocumentURI(path.asURL), + mayContainTests: true + ) } } - public func addTestFilesDidChangeCallback(_ callback: @Sendable @escaping () async -> Void) async { + public func addSourceFilesDidChangeCallback(_ callback: @Sendable @escaping () async -> Void) async { testFilesDidChangeCallbacks.append(callback) } } diff --git a/Sources/SKTestSupport/MultiFileTestProject.swift b/Sources/SKTestSupport/MultiFileTestProject.swift index cd9b8f000..68baba274 100644 --- a/Sources/SKTestSupport/MultiFileTestProject.swift +++ b/Sources/SKTestSupport/MultiFileTestProject.swift @@ -13,6 +13,7 @@ import Foundation import LanguageServerProtocol import SKCore +import SourceKitLSP /// The location of a test file within test workspace. public struct RelativeFileLocation: Hashable, ExpressibleByStringLiteral { @@ -79,6 +80,7 @@ public class MultiFileTestProject { public init( files: [RelativeFileLocation: String], workspaces: (URL) async throws -> [WorkspaceFolder] = { [WorkspaceFolder(uri: DocumentURI($0))] }, + serverOptions: SourceKitLSPServer.Options = .testDefault, usePullDiagnostics: Bool = true, testName: String = #function ) async throws { @@ -109,6 +111,7 @@ public class MultiFileTestProject { self.fileData = fileData self.testClient = try await TestSourceKitLSPClient( + serverOptions: serverOptions, usePullDiagnostics: usePullDiagnostics, workspaceFolders: workspaces(scratchDirectory), cleanUp: { [scratchDirectory] in diff --git a/Sources/SKTestSupport/SwiftPMTestProject.swift b/Sources/SKTestSupport/SwiftPMTestProject.swift index fa0a9dea7..aa2737cea 100644 --- a/Sources/SKTestSupport/SwiftPMTestProject.swift +++ b/Sources/SKTestSupport/SwiftPMTestProject.swift @@ -13,6 +13,7 @@ import Foundation import LanguageServerProtocol @_spi(Testing) import SKCore +import SourceKitLSP import TSCBasic public class SwiftPMTestProject: MultiFileTestProject { @@ -41,6 +42,7 @@ public class SwiftPMTestProject: MultiFileTestProject { workspaces: (URL) async throws -> [WorkspaceFolder] = { [WorkspaceFolder(uri: DocumentURI($0))] }, build: Bool = false, allowBuildFailure: Bool = false, + serverOptions: SourceKitLSPServer.Options = .testDefault, usePullDiagnostics: Bool = true, testName: String = #function ) async throws { @@ -63,6 +65,7 @@ public class SwiftPMTestProject: MultiFileTestProject { try await super.init( files: filesByPath, workspaces: workspaces, + serverOptions: serverOptions, usePullDiagnostics: usePullDiagnostics, testName: testName ) diff --git a/Sources/SemanticIndex/CMakeLists.txt b/Sources/SemanticIndex/CMakeLists.txt new file mode 100644 index 000000000..cc49bb903 --- /dev/null +++ b/Sources/SemanticIndex/CMakeLists.txt @@ -0,0 +1,11 @@ + +add_library(SemanticIndex STATIC + CheckedIndex.swift +) +set_target_properties(SemanticIndex PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) +target_link_libraries(SemanticIndex PRIVATE + LSPLogging + SKCore + IndexStoreDB + $<$>:Foundation>) diff --git a/Sources/SourceKitLSP/CheckedIndex.swift b/Sources/SemanticIndex/CheckedIndex.swift similarity index 95% rename from Sources/SourceKitLSP/CheckedIndex.swift rename to Sources/SemanticIndex/CheckedIndex.swift index 814ed8cc4..9df02bea5 100644 --- a/Sources/SourceKitLSP/CheckedIndex.swift +++ b/Sources/SemanticIndex/CheckedIndex.swift @@ -15,6 +15,15 @@ import IndexStoreDB import LSPLogging import LanguageServerProtocol +/// Essentially a `DocumentManager` from the `SourceKitLSP` module. +/// +/// Protocol is needed because the `SemanticIndex` module is lower-level than the `SourceKitLSP` module. +public protocol InMemoryDocumentManager { + /// Returns true if the file at the given URL has a different content in the document manager than on-disk. This is + /// the case if the user made edits to the file but didn't save them yet. + func fileHasInMemoryModifications(_ url: URL) -> Bool +} + public enum IndexCheckLevel { /// Consider the index out-of-date only if the source file has been deleted on disk. /// @@ -31,14 +40,14 @@ public enum IndexCheckLevel { /// Consider the index out-of-date if the source file has been deleted or modified on disk or if there are /// in-memory modifications in the given `DocumentManager`. - case inMemoryModifiedFiles(DocumentManager) + case inMemoryModifiedFiles(InMemoryDocumentManager) } /// A wrapper around `IndexStoreDB` that checks if returned symbol occurrences are up-to-date with regard to a /// `IndexCheckLevel`. /// /// - SeeAlso: Comment on `IndexOutOfDateChecker` -public class CheckedIndex { +public final class CheckedIndex: Sendable { private var checker: IndexOutOfDateChecker private let index: IndexStoreDB @@ -126,6 +135,11 @@ public class CheckedIndex { public func fileHasInMemoryModifications(_ url: URL) -> Bool { return checker.fileHasInMemoryModifications(url) } + + /// Wait for IndexStoreDB to be updated based on new unit files written to disk. + public func pollForUnitChangesAndWait() { + self.index.pollForUnitChangesAndWait() + } } /// A wrapper around `IndexStoreDB` that allows the retrieval of a `CheckedIndex` with a specified check level or the @@ -257,7 +271,7 @@ private struct IndexOutOfDateChecker { /// `documentManager` must always be the same between calls to `hasFileInMemoryModifications` since it is not part of /// the cache key. This is fine because we always assume the `documentManager` to come from the associated value of /// `CheckLevel.imMemoryModifiedFiles`, which is constant. - private mutating func fileHasInMemoryModifications(_ url: URL, documentManager: DocumentManager) -> Bool { + private mutating func fileHasInMemoryModifications(_ url: URL, documentManager: InMemoryDocumentManager) -> Bool { if let cached = fileHasInMemoryModificationsCache[url] { return cached } @@ -312,19 +326,3 @@ private struct IndexOutOfDateChecker { return fileExists } } - -extension DocumentManager { - /// Returns true if the file at the given URL has a different content in the document manager than on-disk. This is - /// the case if the user made edits to the file but didn't save them yet. - func fileHasInMemoryModifications(_ url: URL) -> Bool { - guard let document = try? latestSnapshot(DocumentURI(url)) else { - return false - } - - guard let onDiskFileContents = try? String(contentsOf: url, encoding: .utf8) else { - // If we can't read the file on disk, it can't match any on-disk state, so it's in-memory state - return true - } - return onDiskFileContents != document.lineTable.content - } -} diff --git a/Sources/SourceKitLSP/CMakeLists.txt b/Sources/SourceKitLSP/CMakeLists.txt index 12558497a..613b0b328 100644 --- a/Sources/SourceKitLSP/CMakeLists.txt +++ b/Sources/SourceKitLSP/CMakeLists.txt @@ -1,7 +1,6 @@ add_library(SourceKitLSP STATIC CapabilityRegistry.swift - CheckedIndex.swift DocumentManager.swift DocumentSnapshot+FromFileContents.swift IndexStoreDB+MainFilesProvider.swift @@ -62,6 +61,7 @@ target_link_libraries(SourceKitLSP PUBLIC LanguageServerProtocol LanguageServerProtocolJSONRPC LSPLogging + SemanticIndex SKCore SKSupport SKSwiftPMWorkspace diff --git a/Sources/SourceKitLSP/Clang/ClangLanguageService.swift b/Sources/SourceKitLSP/Clang/ClangLanguageService.swift index 9dfa85e44..7eaea0b34 100644 --- a/Sources/SourceKitLSP/Clang/ClangLanguageService.swift +++ b/Sources/SourceKitLSP/Clang/ClangLanguageService.swift @@ -45,7 +45,7 @@ fileprivate class ClangdStderrLogForwarder { // than 1000 bytes long but if we merge multiple lines into one message, we might easily exceed this limit. // b) It might be confusing why sometimes a single log message contains one line while sometimes it contains // multiple. - let logger = Logger(subsystem: subsystem, category: "clangd-stderr") + let logger = Logger(subsystem: LoggingScope.subsystem, category: "clangd-stderr") logger.info("\(String(data: self.buffer[...newlineIndex], encoding: .utf8) ?? "")") buffer = buffer[buffer.index(after: newlineIndex)...] } diff --git a/Sources/SourceKitLSP/DocumentManager.swift b/Sources/SourceKitLSP/DocumentManager.swift index 2cf11ef78..2798dd72e 100644 --- a/Sources/SourceKitLSP/DocumentManager.swift +++ b/Sources/SourceKitLSP/DocumentManager.swift @@ -14,6 +14,7 @@ import Dispatch import LSPLogging import LanguageServerProtocol import SKSupport +import SemanticIndex import SwiftSyntax /// An immutable snapshot of a document at a given time. @@ -83,7 +84,7 @@ public final class Document { } } -public final class DocumentManager { +public final class DocumentManager: InMemoryDocumentManager { public enum Error: Swift.Error { case alreadyOpen(DocumentURI) @@ -187,6 +188,18 @@ public final class DocumentManager { return document.latestSnapshot } } + + public func fileHasInMemoryModifications(_ url: URL) -> Bool { + guard let document = try? latestSnapshot(DocumentURI(url)) else { + return false + } + + guard let onDiskFileContents = try? String(contentsOf: url, encoding: .utf8) else { + // If we can't read the file on disk, it can't match any on-disk state, so it's in-memory state + return true + } + return onDiskFileContents != document.lineTable.content + } } extension DocumentManager { diff --git a/Sources/SourceKitLSP/IndexStoreDB+MainFilesProvider.swift b/Sources/SourceKitLSP/IndexStoreDB+MainFilesProvider.swift index 167ea2793..1d7c0f11b 100644 --- a/Sources/SourceKitLSP/IndexStoreDB+MainFilesProvider.swift +++ b/Sources/SourceKitLSP/IndexStoreDB+MainFilesProvider.swift @@ -11,10 +11,10 @@ //===----------------------------------------------------------------------===// import Foundation -import IndexStoreDB import LSPLogging import LanguageServerProtocol import SKCore +import SemanticIndex extension UncheckedIndex { public func mainFilesContainingFile(_ uri: DocumentURI) -> Set { diff --git a/Sources/SourceKitLSP/Rename.swift b/Sources/SourceKitLSP/Rename.swift index bf671f209..81c00fed9 100644 --- a/Sources/SourceKitLSP/Rename.swift +++ b/Sources/SourceKitLSP/Rename.swift @@ -14,6 +14,7 @@ import IndexStoreDB import LSPLogging import LanguageServerProtocol import SKSupport +import SemanticIndex import SourceKitD import SwiftSyntax diff --git a/Sources/SourceKitLSP/SourceKitLSPServer.swift b/Sources/SourceKitLSP/SourceKitLSPServer.swift index 868057344..fce17f037 100644 --- a/Sources/SourceKitLSP/SourceKitLSPServer.swift +++ b/Sources/SourceKitLSP/SourceKitLSPServer.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import BuildServerProtocol +import CAtomics import Dispatch import Foundation import IndexStoreDB @@ -20,6 +21,7 @@ import PackageLoading import SKCore import SKSupport import SKSwiftPMWorkspace +import SemanticIndex import SourceKitD import struct PackageModel.BuildFlags @@ -653,32 +655,6 @@ public actor SourceKitLSPServer { return try await client.send(request) } - func toolchain(for uri: DocumentURI, _ language: Language) async -> Toolchain? { - let supportsLang = { (toolchain: Toolchain) -> Bool in - // FIXME: the fact that we're looking at clangd/sourcekitd instead of the compiler indicates this method needs a parameter stating what kind of tool we're looking for. - switch language { - case .swift: - return toolchain.sourcekitd != nil - case .c, .cpp, .objective_c, .objective_cpp: - return toolchain.clangd != nil - default: - return false - } - } - - if let toolchain = await toolchainRegistry.default, supportsLang(toolchain) { - return toolchain - } - - for toolchain in await toolchainRegistry.toolchains { - if supportsLang(toolchain) { - return toolchain - } - } - - return nil - } - /// After the language service has crashed, send `DidOpenTextDocumentNotification`s to a newly instantiated language service for previously open documents. func reopenDocuments(for languageService: LanguageService) async { for documentUri in self.documentManager.openDocuments { @@ -815,7 +791,7 @@ public actor SourceKitLSPServer { return service } - guard let toolchain = await toolchain(for: uri, language), + guard let toolchain = await workspace.buildSystemManager.toolchain(for: uri, language), let service = await languageService(for: toolchain, language, in: workspace) else { return nil @@ -837,19 +813,7 @@ public actor SourceKitLSPServer { // MARK: - MessageHandler -private let notificationIDForLoggingLock = NSLock() -private var notificationIDForLogging: Int = 0 - -/// On every call, returns a new unique number that can be used to identify a notification. -/// -/// This is needed so we can consistently refer to a notification using the `category` of the logger. -/// Requests don't need this since they already have a unique ID in the LSP protocol. -private func getNextNotificationIDForLogging() -> Int { - return notificationIDForLoggingLock.withLock { - notificationIDForLogging += 1 - return notificationIDForLogging - } -} +private var notificationIDForLogging = AtomicUInt32(initialValue: 1) extension SourceKitLSPServer: MessageHandler { public nonisolated func handle(_ params: some NotificationType) { @@ -860,9 +824,10 @@ extension SourceKitLSPServer: MessageHandler { self.cancelRequest(params) } - let notificationID = getNextNotificationIDForLogging() + let notificationID = notificationIDForLogging.fetchAndIncrement() - let signposter = Logger(subsystem: subsystem, category: "notification-\(notificationID)").makeSignposter() + let signposter = Logger(subsystem: LoggingScope.subsystem, category: "notification-\(notificationID)") + .makeSignposter() let signpostID = signposter.makeSignpostID() let state = signposter.beginInterval("Notification", id: signpostID, "\(type(of: params))") messageHandlingQueue.async(metadata: TaskMetadata(params)) { @@ -911,7 +876,7 @@ extension SourceKitLSPServer: MessageHandler { id: RequestID, reply: @escaping (LSPResult) -> Void ) { - let signposter = Logger(subsystem: subsystem, category: "request-\(id)").makeSignposter() + let signposter = Logger(subsystem: LoggingScope.subsystem, category: "request-\(id)").makeSignposter() let signpostID = signposter.makeSignpostID() let state = signposter.beginInterval("Request", id: signpostID, "\(R.self)") @@ -1177,13 +1142,14 @@ extension SourceKitLSPServer { logger.log("Cannot open workspace before server is initialized") return nil } - let workspaceBuildSetup = self.buildSetup(for: workspaceFolder) + var options = self.options + options.buildSetup = self.options.buildSetup.merging(buildSetup(for: workspaceFolder)) return try? await Workspace( documentManager: self.documentManager, rootUri: workspaceFolder.uri, capabilityRegistry: capabilityRegistry, toolchainRegistry: self.toolchainRegistry, - buildSetup: self.options.buildSetup.merging(workspaceBuildSetup), + options: options, compilationDatabaseSearchPaths: self.options.compilationDatabaseSearchPaths, indexOptions: self.options.indexOptions, reloadPackageStatusCallback: { [weak self] status in @@ -1251,7 +1217,7 @@ extension SourceKitLSPServer { rootUri: req.rootURI, capabilityRegistry: self.capabilityRegistry!, toolchainRegistry: self.toolchainRegistry, - buildSetup: self.options.buildSetup, + options: self.options, underlyingBuildSystem: nil, index: nil, indexDelegate: nil diff --git a/Sources/SourceKitLSP/Swift/DocumentFormatting.swift b/Sources/SourceKitLSP/Swift/DocumentFormatting.swift index ed4d59da5..ed58a21a5 100644 --- a/Sources/SourceKitLSP/Swift/DocumentFormatting.swift +++ b/Sources/SourceKitLSP/Swift/DocumentFormatting.swift @@ -154,7 +154,7 @@ extension SwiftLanguageService { writeStream.send(snapshot.text) try writeStream.close() - let result = try await process.waitUntilExit() + let result = try await process.waitUntilExitSendingSigIntOnTaskCancellation() guard result.exitStatus == .terminated(code: 0) else { let swiftFormatErrorMessage: String switch result.stderrOutput { diff --git a/Sources/SourceKitLSP/Swift/SyntacticTestIndex.swift b/Sources/SourceKitLSP/Swift/SyntacticTestIndex.swift index c91e046b5..ec5c33068 100644 --- a/Sources/SourceKitLSP/Swift/SyntacticTestIndex.swift +++ b/Sources/SourceKitLSP/Swift/SyntacticTestIndex.swift @@ -213,27 +213,3 @@ actor SyntacticTestIndex { return await readTask.value } } - -fileprivate extension Collection { - /// Partition the elements of the collection into `numberOfBatches` roughly equally sized batches. - /// - /// Elements are assigned to the batches round-robin. This ensures that elements that are close to each other in the - /// original collection end up in different batches. This is important because eg. test files will live close to each - /// other in the file system and test scanning wants to scan them in different batches so we don't end up with one - /// batch only containing source files and the other only containing test files. - func partition(intoNumberOfBatches numberOfBatches: Int) -> [[Element]] { - var batches: [[Element]] = Array( - repeating: { - var batch: [Element] = [] - batch.reserveCapacity(self.count / numberOfBatches) - return batch - }(), - count: numberOfBatches - ) - - for (index, element) in self.enumerated() { - batches[index % numberOfBatches].append(element) - } - return batches.filter { !$0.isEmpty } - } -} diff --git a/Sources/SourceKitLSP/Workspace.swift b/Sources/SourceKitLSP/Workspace.swift index 455da594c..cb8d8ef52 100644 --- a/Sources/SourceKitLSP/Workspace.swift +++ b/Sources/SourceKitLSP/Workspace.swift @@ -16,6 +16,7 @@ import LanguageServerProtocol import SKCore import SKSupport import SKSwiftPMWorkspace +import SemanticIndex import struct TSCBasic.AbsolutePath import struct TSCBasic.RelativePath @@ -78,25 +79,26 @@ public final class Workspace { rootUri: DocumentURI?, capabilityRegistry: CapabilityRegistry, toolchainRegistry: ToolchainRegistry, - buildSetup: BuildSetup, + options: SourceKitLSPServer.Options, underlyingBuildSystem: BuildSystem?, index uncheckedIndex: UncheckedIndex?, indexDelegate: SourceKitIndexDelegate? ) async { self.documentManager = documentManager - self.buildSetup = buildSetup + self.buildSetup = options.buildSetup self.rootUri = rootUri self.capabilityRegistry = capabilityRegistry self.uncheckedIndex = uncheckedIndex self.buildSystemManager = await BuildSystemManager( buildSystem: underlyingBuildSystem, fallbackBuildSystem: FallbackBuildSystem(buildSetup: buildSetup), - mainFilesProvider: uncheckedIndex + mainFilesProvider: uncheckedIndex, + toolchainRegistry: toolchainRegistry ) await indexDelegate?.addMainFileChangedCallback { [weak self] in await self?.buildSystemManager.mainFilesChanged() } - await underlyingBuildSystem?.addTestFilesDidChangeCallback { [weak self] in + await underlyingBuildSystem?.addSourceFilesDidChangeCallback { [weak self] in guard let self else { return } @@ -117,36 +119,36 @@ public final class Workspace { rootUri: DocumentURI, capabilityRegistry: CapabilityRegistry, toolchainRegistry: ToolchainRegistry, - buildSetup: BuildSetup, + options: SourceKitLSPServer.Options, compilationDatabaseSearchPaths: [RelativePath], indexOptions: IndexOptions = IndexOptions(), reloadPackageStatusCallback: @escaping (ReloadPackageStatus) async -> Void ) async throws { var buildSystem: BuildSystem? = nil - func createSwiftPMBuildSystem(rootUrl: URL) async -> SwiftPMBuildSystem? { - return await SwiftPMBuildSystem( - url: rootUrl, - toolchainRegistry: toolchainRegistry, - buildSetup: buildSetup, - reloadPackageStatusCallback: reloadPackageStatusCallback - ) - } + if let rootUrl = rootUri.fileURL, let rootPath = try? AbsolutePath(validating: rootUrl.path) { + func createSwiftPMBuildSystem(rootUrl: URL) async -> SwiftPMBuildSystem? { + return await SwiftPMBuildSystem( + url: rootUrl, + toolchainRegistry: toolchainRegistry, + buildSetup: options.buildSetup, + reloadPackageStatusCallback: reloadPackageStatusCallback + ) + } - func createCompilationDatabaseBuildSystem(rootPath: AbsolutePath) -> CompilationDatabaseBuildSystem? { - return CompilationDatabaseBuildSystem( - projectRoot: rootPath, - searchPaths: compilationDatabaseSearchPaths - ) - } + func createCompilationDatabaseBuildSystem(rootPath: AbsolutePath) -> CompilationDatabaseBuildSystem? { + return CompilationDatabaseBuildSystem( + projectRoot: rootPath, + searchPaths: compilationDatabaseSearchPaths + ) + } - func createBuildServerBuildSystem(rootPath: AbsolutePath) async -> BuildServerBuildSystem? { - return await BuildServerBuildSystem(projectRoot: rootPath, buildSetup: buildSetup) - } + func createBuildServerBuildSystem(rootPath: AbsolutePath) async -> BuildServerBuildSystem? { + return await BuildServerBuildSystem(projectRoot: rootPath, buildSetup: options.buildSetup) + } - if let rootUrl = rootUri.fileURL, let rootPath = try? AbsolutePath(validating: rootUrl.path) { let defaultBuildSystem: BuildSystem? = - switch buildSetup.defaultWorkspaceType { + switch options.buildSetup.defaultWorkspaceType { case .buildServer: await createBuildServerBuildSystem(rootPath: rootPath) case .compilationDatabase: createCompilationDatabaseBuildSystem(rootPath: rootPath) case .swiftPM: await createSwiftPMBuildSystem(rootUrl: rootUrl) @@ -184,6 +186,7 @@ public final class Workspace { var index: IndexStoreDB? = nil var indexDelegate: SourceKitIndexDelegate? = nil + let indexOptions = options.indexOptions if let storePath = await firstNonNil(indexOptions.indexStorePath, await buildSystem?.indexStorePath), let dbPath = await firstNonNil(indexOptions.indexDatabasePath, await buildSystem?.indexDatabasePath), let libPath = await toolchainRegistry.default?.libIndexStore @@ -212,7 +215,7 @@ public final class Workspace { rootUri: rootUri, capabilityRegistry: capabilityRegistry, toolchainRegistry: toolchainRegistry, - buildSetup: buildSetup, + options: options, underlyingBuildSystem: buildSystem, index: UncheckedIndex(index), indexDelegate: indexDelegate diff --git a/Tests/LSPLoggingTests/LoggingTests.swift b/Tests/LSPLoggingTests/LoggingTests.swift index 710726747..8e596704c 100644 --- a/Tests/LSPLoggingTests/LoggingTests.swift +++ b/Tests/LSPLoggingTests/LoggingTests.swift @@ -25,7 +25,7 @@ fileprivate func assertLogging( // nonisolated(unsafe) because calls of `assertLogging` do not log to `logHandler` concurrently. nonisolated(unsafe) var messages: [String] = [] let logger = NonDarwinLogger( - subsystem: subsystem, + subsystem: LoggingScope.subsystem, category: "test", logLevel: logLevel, privacyLevel: privacyLevel, @@ -75,7 +75,7 @@ final class LoggingTests: XCTestCase { // nonisolated(unsafe) because we only have a single call to `logger.log` and that cannot race. nonisolated(unsafe) var message: String = "" let logger = NonDarwinLogger( - subsystem: subsystem, + subsystem: LoggingScope.subsystem, category: "test", logHandler: { message = $0 diff --git a/Tests/SKCoreTests/BuildSystemManagerTests.swift b/Tests/SKCoreTests/BuildSystemManagerTests.swift index 54a20dddb..8dd89ccf7 100644 --- a/Tests/SKCoreTests/BuildSystemManagerTests.swift +++ b/Tests/SKCoreTests/BuildSystemManagerTests.swift @@ -13,7 +13,7 @@ import BuildServerProtocol import LSPTestSupport import LanguageServerProtocol -import SKCore +@_spi(Testing) import SKCore import TSCBasic import XCTest @@ -37,7 +37,8 @@ final class BuildSystemManagerTests: XCTestCase { let bsm = await BuildSystemManager( buildSystem: nil, fallbackBuildSystem: FallbackBuildSystem(buildSetup: .default), - mainFilesProvider: mainFiles + mainFilesProvider: mainFiles, + toolchainRegistry: ToolchainRegistry.forTesting ) defer { withExtendedLifetime(bsm) {} } // Keep BSM alive for callbacks. @@ -88,13 +89,14 @@ final class BuildSystemManagerTests: XCTestCase { } func testSettingsMainFile() async throws { - let a = try try DocumentURI(string: "bsm:a.swift") + let a = try DocumentURI(string: "bsm:a.swift") let mainFiles = ManualMainFilesProvider([a: [a]]) let bs = ManualBuildSystem() let bsm = await BuildSystemManager( buildSystem: bs, fallbackBuildSystem: nil, - mainFilesProvider: mainFiles + mainFilesProvider: mainFiles, + toolchainRegistry: ToolchainRegistry.forTesting ) defer { withExtendedLifetime(bsm) {} } // Keep BSM alive for callbacks. let del = await BSMDelegate(bsm) @@ -117,7 +119,8 @@ final class BuildSystemManagerTests: XCTestCase { let bsm = await BuildSystemManager( buildSystem: bs, fallbackBuildSystem: nil, - mainFilesProvider: mainFiles + mainFilesProvider: mainFiles, + toolchainRegistry: ToolchainRegistry.forTesting ) defer { withExtendedLifetime(bsm) {} } // Keep BSM alive for callbacks. let del = await BSMDelegate(bsm) @@ -139,7 +142,8 @@ final class BuildSystemManagerTests: XCTestCase { let bsm = await BuildSystemManager( buildSystem: bs, fallbackBuildSystem: fallback, - mainFilesProvider: mainFiles + mainFilesProvider: mainFiles, + toolchainRegistry: ToolchainRegistry.forTesting ) defer { withExtendedLifetime(bsm) {} } // Keep BSM alive for callbacks. let del = await BSMDelegate(bsm) @@ -168,7 +172,8 @@ final class BuildSystemManagerTests: XCTestCase { let bsm = await BuildSystemManager( buildSystem: bs, fallbackBuildSystem: nil, - mainFilesProvider: mainFiles + mainFilesProvider: mainFiles, + toolchainRegistry: ToolchainRegistry.forTesting ) defer { withExtendedLifetime(bsm) {} } // Keep BSM alive for callbacks. let del = await BSMDelegate(bsm) @@ -208,7 +213,8 @@ final class BuildSystemManagerTests: XCTestCase { let bsm = await BuildSystemManager( buildSystem: bs, fallbackBuildSystem: nil, - mainFilesProvider: mainFiles + mainFilesProvider: mainFiles, + toolchainRegistry: ToolchainRegistry.forTesting ) defer { withExtendedLifetime(bsm) {} } // Keep BSM alive for callbacks. let del = await BSMDelegate(bsm) @@ -246,7 +252,8 @@ final class BuildSystemManagerTests: XCTestCase { let bsm = await BuildSystemManager( buildSystem: bs, fallbackBuildSystem: nil, - mainFilesProvider: mainFiles + mainFilesProvider: mainFiles, + toolchainRegistry: ToolchainRegistry.forTesting ) defer { withExtendedLifetime(bsm) {} } // Keep BSM alive for callbacks. let del = await BSMDelegate(bsm) @@ -300,7 +307,8 @@ final class BuildSystemManagerTests: XCTestCase { let bsm = await BuildSystemManager( buildSystem: bs, fallbackBuildSystem: nil, - mainFilesProvider: mainFiles + mainFilesProvider: mainFiles, + toolchainRegistry: ToolchainRegistry.forTesting ) defer { withExtendedLifetime(bsm) {} } // Keep BSM alive for callbacks. let del = await BSMDelegate(bsm) @@ -341,7 +349,8 @@ final class BuildSystemManagerTests: XCTestCase { let bsm = await BuildSystemManager( buildSystem: bs, fallbackBuildSystem: nil, - mainFilesProvider: mainFiles + mainFilesProvider: mainFiles, + toolchainRegistry: ToolchainRegistry.forTesting ) defer { withExtendedLifetime(bsm) {} } // Keep BSM alive for callbacks. let del = await BSMDelegate(bsm) @@ -383,7 +392,8 @@ final class BuildSystemManagerTests: XCTestCase { let bsm = await BuildSystemManager( buildSystem: bs, fallbackBuildSystem: nil, - mainFilesProvider: mainFiles + mainFilesProvider: mainFiles, + toolchainRegistry: ToolchainRegistry.forTesting ) defer { withExtendedLifetime(bsm) {} } // Keep BSM alive for callbacks. let del = await BSMDelegate(bsm) @@ -439,6 +449,10 @@ class ManualBuildSystem: BuildSystem { return map[uri] } + public func defaultLanguage(for document: DocumentURI) async -> Language? { + return nil + } + func registerForChangeNotifications(for uri: DocumentURI) async { } @@ -459,11 +473,11 @@ class ManualBuildSystem: BuildSystem { } } - func testFiles() async -> [DocumentURI] { + func sourceFiles() async -> [SourceFileInfo] { return [] } - func addTestFilesDidChangeCallback(_ callback: @escaping () async -> Void) {} + func addSourceFilesDidChangeCallback(_ callback: @escaping () async -> Void) {} } /// A `BuildSystemDelegate` setup for testing. diff --git a/Tests/SourceKitLSPTests/BuildSystemTests.swift b/Tests/SourceKitLSPTests/BuildSystemTests.swift index 4e5cd46f0..f31103cea 100644 --- a/Tests/SourceKitLSPTests/BuildSystemTests.swift +++ b/Tests/SourceKitLSPTests/BuildSystemTests.swift @@ -43,6 +43,10 @@ final class TestBuildSystem: BuildSystem { return buildSettingsByFile[document] } + public func defaultLanguage(for document: DocumentURI) async -> Language? { + return nil + } + func registerForChangeNotifications(for uri: DocumentURI) async { watchedFiles.insert(uri) } @@ -61,11 +65,11 @@ final class TestBuildSystem: BuildSystem { } } - func testFiles() async -> [DocumentURI] { + func sourceFiles() async -> [SourceFileInfo] { return [] } - func addTestFilesDidChangeCallback(_ callback: @escaping () async -> Void) async {} + func addSourceFilesDidChangeCallback(_ callback: @escaping () async -> Void) async {} } final class BuildSystemTests: XCTestCase { @@ -101,7 +105,7 @@ final class BuildSystemTests: XCTestCase { rootUri: nil, capabilityRegistry: CapabilityRegistry(clientCapabilities: ClientCapabilities()), toolchainRegistry: ToolchainRegistry.forTesting, - buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, + options: SourceKitLSPServer.Options.testDefault, underlyingBuildSystem: buildSystem, index: nil, indexDelegate: nil diff --git a/Tests/SourceKitLSPTests/MainFilesProviderTests.swift b/Tests/SourceKitLSPTests/MainFilesProviderTests.swift index 9a3ed121f..492fb9e4d 100644 --- a/Tests/SourceKitLSPTests/MainFilesProviderTests.swift +++ b/Tests/SourceKitLSPTests/MainFilesProviderTests.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import IndexStoreDB import LSPTestSupport import LanguageServerProtocol import SKCore @@ -40,7 +39,7 @@ final class MainFilesProviderTests: XCTestCase { name: "MyLibrary", targets: [ .target( - name: "MyLibrary", + name: "MyLibrary", cSettings: [.define("VARIABLE_NAME", to: "fromMyLibrary"), .unsafeFlags(["-Wunused-variable"])] ) ]