diff --git a/Documentation/Background Indexing.md b/Documentation/Background Indexing.md index c5bc53ff5..209cd006d 100644 --- a/Documentation/Background Indexing.md +++ b/Documentation/Background Indexing.md @@ -22,9 +22,10 @@ Next, point your editor to use the just-built copy of SourceKit-LSP and enable b "swift.sourcekit-lsp.serverArguments": [ "--experimental-feature", "background-indexing" ], ``` +Background indexing requires a Swift 6 toolchain. You can download Swift 6 nightly toolchains from https://www.swift.org/download/#swift-60-development. + ## Known issues -- The only supported toolchain for background indexing are currently [Swift 6.0 nightly toolchain snapshots](https://www.swift.org/download/#swift-60-development). Older toolchains are not supported and the nightly toolchains from `main` are having issues because building a target non-deterministically builds for tools or the destination [#1288](https://github.com/apple/sourcekit-lsp/pull/1288#issuecomment-2111400459) [rdar://128100158](rdar://128100158) - Not really a background indexing related issue but Swift nightly toolchain snapshots are crashing on macOS 14.4 and 14.5 (swift#73327)[https://github.com/apple/swift/issues/73327] - Workaround: Run the toolchains on an older version of macOS, if possible - Background Indexing is only supported for SwiftPM projects [#1269](https://github.com/apple/sourcekit-lsp/issues/1269), [#1271](https://github.com/apple/sourcekit-lsp/issues/1271) diff --git a/Sources/SKCore/BuildServerBuildSystem.swift b/Sources/SKCore/BuildServerBuildSystem.swift index a52fd5301..6da6e1929 100644 --- a/Sources/SKCore/BuildServerBuildSystem.swift +++ b/Sources/SKCore/BuildServerBuildSystem.swift @@ -278,6 +278,10 @@ extension BuildServerBuildSystem: BuildSystem { return nil } + public func toolchain(for uri: DocumentURI, _ language: Language) async -> SKCore.Toolchain? { + return nil + } + public func configuredTargets(for document: DocumentURI) async -> [ConfiguredTarget] { return [ConfiguredTarget(targetID: "dummy", runDestinationID: "dummy")] } diff --git a/Sources/SKCore/BuildSystem.swift b/Sources/SKCore/BuildSystem.swift index f409c1ab5..7ea32e4c0 100644 --- a/Sources/SKCore/BuildSystem.swift +++ b/Sources/SKCore/BuildSystem.swift @@ -179,6 +179,11 @@ public protocol BuildSystem: AnyObject, Sendable { /// If `nil` is returned, the language based on the file's extension. func defaultLanguage(for document: DocumentURI) async -> Language? + /// The toolchain that should be used to open the given document. + /// + /// If `nil` is returned, then the default toolchain for the given language is used. + func toolchain(for uri: DocumentURI, _ language: Language) async -> Toolchain? + /// Register the given file for build-system level change notifications, such /// as command line flag changes, dependency changes, etc. /// diff --git a/Sources/SKCore/BuildSystemManager.swift b/Sources/SKCore/BuildSystemManager.swift index 415e98fc6..51a84faa8 100644 --- a/Sources/SKCore/BuildSystemManager.swift +++ b/Sources/SKCore/BuildSystemManager.swift @@ -104,9 +104,18 @@ extension BuildSystemManager { /// 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) + if let toolchain = await buildSystem?.toolchain(for: uri, language) { + return toolchain + } + + switch language { + case .swift: + return await toolchainRegistry.preferredToolchain(containing: [\.sourcekitd, \.swift, \.swiftc]) + case .c, .cpp, .objective_c, .objective_cpp: + return await toolchainRegistry.preferredToolchain(containing: [\.clang, \.clangd]) + default: + return nil + } } /// - Note: Needed so we can set the delegate from a different isolation context. diff --git a/Sources/SKCore/CompilationDatabaseBuildSystem.swift b/Sources/SKCore/CompilationDatabaseBuildSystem.swift index f8b541803..fe3ae73f8 100644 --- a/Sources/SKCore/CompilationDatabaseBuildSystem.swift +++ b/Sources/SKCore/CompilationDatabaseBuildSystem.swift @@ -119,6 +119,10 @@ extension CompilationDatabaseBuildSystem: BuildSystem { return nil } + public func toolchain(for uri: DocumentURI, _ language: Language) async -> SKCore.Toolchain? { + return nil + } + public func configuredTargets(for document: DocumentURI) async -> [ConfiguredTarget] { return [ConfiguredTarget(targetID: "dummy", runDestinationID: "dummy")] } diff --git a/Sources/SKCore/ToolchainRegistry.swift b/Sources/SKCore/ToolchainRegistry.swift index ee0010015..11ae357ab 100644 --- a/Sources/SKCore/ToolchainRegistry.swift +++ b/Sources/SKCore/ToolchainRegistry.swift @@ -246,26 +246,14 @@ public final actor ToolchainRegistry { 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) { + /// Returns the preferred toolchain that contains all the tools at the given key paths. + public func preferredToolchain(containing requiredTools: [KeyPath]) -> Toolchain? { + if let toolchain = self.default, requiredTools.allSatisfy({ toolchain[keyPath: $0] != nil }) { return toolchain } for toolchain in toolchains { - if supportsLang(toolchain) { + if requiredTools.allSatisfy({ toolchain[keyPath: $0] != nil }) { return toolchain } } diff --git a/Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift b/Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift index e6ec43218..ae9f76aa8 100644 --- a/Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift +++ b/Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift @@ -59,15 +59,29 @@ public typealias BuildServerTarget = BuildServerProtocol.BuildTarget /// Same as `toolchainRegistry.default`. /// -/// Needed to work around a compiler crash that prevents us from accessing `toolchainRegistry.default` in +/// Needed to work around a compiler crash that prevents us from accessing `toolchainRegistry.preferredToolchain` in /// `SwiftPMWorkspace.init`. -private func getDefaultToolchain(_ toolchainRegistry: ToolchainRegistry) async -> SKCore.Toolchain? { - return await toolchainRegistry.default +private func preferredToolchain(_ toolchainRegistry: ToolchainRegistry) async -> SKCore.Toolchain? { + return await toolchainRegistry.preferredToolchain(containing: [ + \.clang, \.clangd, \.sourcekitd, \.swift, \.swiftc, + ]) +} + +fileprivate extension BuildTriple { + /// A string that can be used to identify the build triple in `ConfiguredTarget.runDestinationID`. + var id: String { + switch self { + case .tools: + return "tools" + case .destination: + return "destination" + } + } } fileprivate extension ConfiguredTarget { init(_ buildTarget: any SwiftBuildTarget) { - self.init(targetID: buildTarget.name, runDestinationID: "dummy") + self.init(targetID: buildTarget.name, runDestinationID: buildTarget.buildTriple.id) } static let forPackageManifest = ConfiguredTarget(targetID: "", runDestinationID: "") @@ -111,14 +125,14 @@ public actor SwiftPMBuildSystem { @_spi(Testing) public let toolsBuildParameters: BuildParameters @_spi(Testing) public let destinationBuildParameters: BuildParameters private let fileSystem: FileSystem - private let toolchainRegistry: ToolchainRegistry + private let toolchain: SKCore.Toolchain private let swiftBuildSupportsPrepareForIndexingTask = SwiftExtensions.ThreadSafeBox?>( initialValue: nil ) - private var fileToTarget: [DocumentURI: SwiftBuildTarget] = [:] - private var sourceDirToTarget: [DocumentURI: SwiftBuildTarget] = [:] + private var fileToTargets: [DocumentURI: [SwiftBuildTarget]] = [:] + private var sourceDirToTargets: [DocumentURI: [SwiftBuildTarget]] = [:] /// Maps configured targets ids to their SwiftPM build target as well as an index in their topological sorting. /// @@ -172,7 +186,11 @@ public actor SwiftPMBuildSystem { ) async throws { self.workspacePath = workspacePath self.fileSystem = fileSystem - self.toolchainRegistry = toolchainRegistry + guard let toolchain = await preferredToolchain(toolchainRegistry) else { + throw Error.cannotDetermineHostToolchain + } + + self.toolchain = toolchain self.experimentalFeatures = experimentalFeatures guard let packageRoot = findPackageDirectory(containing: workspacePath, fileSystem) else { @@ -181,12 +199,12 @@ public actor SwiftPMBuildSystem { self.projectRoot = try resolveSymlinks(packageRoot) - guard let destinationToolchainBinDir = await getDefaultToolchain(toolchainRegistry)?.swiftc?.parentDirectory else { + guard let destinationToolchainBinDir = toolchain.swiftc?.parentDirectory else { throw Error.cannotDetermineHostToolchain } let swiftSDK = try SwiftSDK.hostSwiftSDK(AbsolutePath(destinationToolchainBinDir)) - let toolchain = try UserToolchain(swiftSDK: swiftSDK) + let swiftPMToolchain = try UserToolchain(swiftSDK: swiftSDK) var location = try Workspace.Location( forRootPackage: AbsolutePath(packageRoot), @@ -205,7 +223,7 @@ public actor SwiftPMBuildSystem { fileSystem: fileSystem, location: location, configuration: configuration, - customHostToolchain: toolchain + customHostToolchain: swiftPMToolchain ) let buildConfiguration: PackageModel.BuildConfiguration @@ -218,17 +236,21 @@ public actor SwiftPMBuildSystem { self.toolsBuildParameters = try BuildParameters( destination: .host, - dataPath: location.scratchDirectory.appending(component: toolchain.targetTriple.platformBuildPathComponent), + dataPath: location.scratchDirectory.appending( + component: swiftPMToolchain.targetTriple.platformBuildPathComponent + ), configuration: buildConfiguration, - toolchain: toolchain, + toolchain: swiftPMToolchain, flags: buildSetup.flags ) self.destinationBuildParameters = try BuildParameters( destination: .target, - dataPath: location.scratchDirectory.appending(component: toolchain.targetTriple.platformBuildPathComponent), + dataPath: location.scratchDirectory.appending( + component: swiftPMToolchain.targetTriple.platformBuildPathComponent + ), configuration: buildConfiguration, - toolchain: toolchain, + toolchain: swiftPMToolchain, flags: buildSetup.flags ) @@ -320,7 +342,7 @@ extension SwiftPMBuildSystem { self.targets = Dictionary( try buildDescription.allTargetsInTopologicalOrder(in: modulesGraph).enumerated().map { (index, target) in - return (key: ConfiguredTarget(target), (index, target)) + return (key: ConfiguredTarget(target), value: (index, target)) }, uniquingKeysWith: { first, second in logger.fault("Found two targets with the same name \(first.buildTarget.name)") @@ -328,32 +350,26 @@ extension SwiftPMBuildSystem { } ) - self.fileToTarget = [DocumentURI: SwiftBuildTarget]( + self.fileToTargets = [DocumentURI: [SwiftBuildTarget]]( modulesGraph.allTargets.flatMap { target in - return target.sources.paths.compactMap { + return target.sources.paths.compactMap { (filePath) -> (key: DocumentURI, value: [SwiftBuildTarget])? in guard let buildTarget = buildDescription.getBuildTarget(for: target, in: modulesGraph) else { return nil } - return (key: DocumentURI($0.asURL), value: buildTarget) + return (key: DocumentURI(filePath.asURL), value: [buildTarget]) } }, - uniquingKeysWith: { td, _ in - // FIXME: is there a preferred target? - return td - } + uniquingKeysWith: { $0 + $1 } ) - self.sourceDirToTarget = [DocumentURI: SwiftBuildTarget]( - modulesGraph.allTargets.compactMap { (target) -> (DocumentURI, SwiftBuildTarget)? in + self.sourceDirToTargets = [DocumentURI: [SwiftBuildTarget]]( + modulesGraph.allTargets.compactMap { (target) -> (DocumentURI, [SwiftBuildTarget])? in guard let buildTarget = buildDescription.getBuildTarget(for: target, in: modulesGraph) else { return nil } - return (key: DocumentURI(target.sources.root.asURL), value: buildTarget) + return (key: DocumentURI(target.sources.root.asURL), value: [buildTarget]) }, - uniquingKeysWith: { td, _ in - // FIXME: is there a preferred target? - return td - } + uniquingKeysWith: { $0 + $1 } ) guard let delegate = self.delegate else { @@ -405,7 +421,7 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem { #endif // Fix up compiler arguments that point to a `/Modules` subdirectory if the Swift version in the toolchain is less // than 6.0 because it places the modules one level higher up. - let toolchainVersion = await orLog("Getting Swift version") { try await toolchainRegistry.default?.swiftVersion } + let toolchainVersion = await orLog("Getting Swift version") { try await toolchain.swiftVersion } guard let toolchainVersion, toolchainVersion < SwiftVersion(6, 0) else { return compileArguments } @@ -465,14 +481,19 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem { return nil } + public func toolchain(for uri: DocumentURI, _ language: Language) async -> SKCore.Toolchain? { + return toolchain + } + public func configuredTargets(for uri: DocumentURI) -> [ConfiguredTarget] { guard let url = uri.fileURL, let path = try? AbsolutePath(validating: url.path) else { // We can't determine targets for non-file URIs. return [] } - if let target = buildTarget(for: uri) { - return [ConfiguredTarget(target)] + let targets = buildTargets(for: uri) + if !targets.isEmpty { + return targets.map(ConfiguredTarget.init) } if path.basename == "Package.swift" { @@ -481,8 +502,8 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem { return [ConfiguredTarget.forPackageManifest] } - if let target = try? inferredTarget(for: path) { - return [target] + if let targets = try? inferredTargets(for: path) { + return targets } return [] @@ -528,7 +549,7 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem { // TODO (indexing): Support preparation of multiple targets at once. // https://github.com/apple/sourcekit-lsp/issues/1262 for target in targets { - try await prepare(singleTarget: target, logMessageToIndexLog: logMessageToIndexLog) + await orLog("Preparing") { try await prepare(singleTarget: target, logMessageToIndexLog: logMessageToIndexLog) } } let filesInPreparedTargets = targets.flatMap { self.targets[$0]?.buildTarget.sources ?? [] } await fileDependenciesUpdatedDebouncer.scheduleCall(Set(filesInPreparedTargets.map(DocumentURI.init))) @@ -545,16 +566,13 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem { // TODO (indexing): Add a proper 'prepare' job in SwiftPM instead of building the target. // https://github.com/apple/sourcekit-lsp/issues/1254 - guard let toolchain = await toolchainRegistry.default else { - logger.error("Not preparing because not toolchain exists") - return - } guard let swift = toolchain.swift else { logger.error( - "Not preparing because toolchain at \(toolchain.identifier) does not contain a Swift compiler" + "Not preparing because toolchain at \(self.toolchain.identifier) does not contain a Swift compiler" ) return } + logger.debug("Preparing '\(target.targetID)' using \(self.toolchain.identifier)") var arguments = [ swift.pathString, "build", "--package-path", workspacePath.pathString, @@ -655,21 +673,21 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem { self.watchedFiles.remove(uri) } - /// Returns the resolved target description for the given file, if one is known. - private func buildTarget(for file: DocumentURI) -> SwiftBuildTarget? { - if let td = fileToTarget[file] { - return td + /// Returns the resolved target descriptions for the given file, if one is known. + private func buildTargets(for file: DocumentURI) -> [SwiftBuildTarget] { + if let targets = fileToTargets[file] { + return targets } if let fileURL = file.fileURL, let realpath = try? resolveSymlinks(AbsolutePath(validating: fileURL.path)), - let td = fileToTarget[DocumentURI(realpath.asURL)] + let targets = fileToTargets[DocumentURI(realpath.asURL)] { - fileToTarget[file] = td - return td + fileToTargets[file] = targets + return targets } - return nil + return [] } /// An event is relevant if it modifies a file that matches one of the file rules used by the SwiftPM workspace. @@ -707,10 +725,10 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem { // If a Swift file within a target is updated, reload all the other files within the target since they might be // referring to a function in the updated file. for event in events { - guard event.uri.fileURL?.pathExtension == "swift", let target = fileToTarget[event.uri] else { + guard event.uri.fileURL?.pathExtension == "swift", let targets = fileToTargets[event.uri] else { continue } - filesWithUpdatedDependencies.formUnion(target.sources.map { DocumentURI($0) }) + filesWithUpdatedDependencies.formUnion(targets.flatMap(\.sources).map(DocumentURI.init)) } // If a `.swiftmodule` file is updated, this means that we have performed a build / are @@ -723,7 +741,7 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem { // If we have background indexing enabled, this is not necessary because we call `fileDependenciesUpdated` when // preparation of a target finishes. if !isForIndexBuild, events.contains(where: { $0.uri.fileURL?.pathExtension == "swiftmodule" }) { - filesWithUpdatedDependencies.formUnion(self.fileToTarget.keys) + filesWithUpdatedDependencies.formUnion(self.fileToTargets.keys) } await self.fileDependenciesUpdatedDebouncer.scheduleCall(filesWithUpdatedDependencies) } @@ -736,12 +754,12 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem { } public func sourceFiles() -> [SourceFileInfo] { - return fileToTarget.compactMap { (uri, target) -> SourceFileInfo? in + return fileToTargets.compactMap { (uri, targets) -> SourceFileInfo? in // We should only set mayContainTests to `true` for files from test targets // (https://github.com/apple/sourcekit-lsp/issues/1174). return SourceFileInfo( uri: uri, - isPartOfRootProject: target.isPartOfRootPackage, + isPartOfRootProject: targets.contains(where: \.isPartOfRootPackage), mayContainTests: true ) } @@ -777,24 +795,25 @@ extension SwiftPMBuildSystem { /// This finds the target a file belongs to based on its location in the file system. /// /// This is primarily intended to find the target a header belongs to. - private func inferredTarget(for path: AbsolutePath) throws -> ConfiguredTarget? { - func impl(_ path: AbsolutePath) throws -> ConfiguredTarget? { + private func inferredTargets(for path: AbsolutePath) throws -> [ConfiguredTarget] { + func impl(_ path: AbsolutePath) throws -> [ConfiguredTarget] { var dir = path.parentDirectory while !dir.isRoot { - if let buildTarget = sourceDirToTarget[DocumentURI(dir.asURL)] { - return ConfiguredTarget(buildTarget) + if let buildTargets = sourceDirToTargets[DocumentURI(dir.asURL)] { + return buildTargets.map(ConfiguredTarget.init) } dir = dir.parentDirectory } - return nil + return [] } - if let result = try impl(path) { + let result = try impl(path) + if !result.isEmpty { return result } let canonicalPath = try resolveSymlinks(path) - return try canonicalPath == path ? nil : impl(canonicalPath) + return try canonicalPath == path ? [] : impl(canonicalPath) } } diff --git a/Sources/SKTestSupport/SwiftPMTestProject.swift b/Sources/SKTestSupport/SwiftPMTestProject.swift index 18de4f72f..898f195e6 100644 --- a/Sources/SKTestSupport/SwiftPMTestProject.swift +++ b/Sources/SKTestSupport/SwiftPMTestProject.swift @@ -163,7 +163,7 @@ public class SwiftPMTestProject: MultiFileTestProject { for (fileLocation, contents) in files { let directories = switch fileLocation.directories.first { - case "Sources", "Tests": + case "Sources", "Tests", "Plugins": fileLocation.directories case nil: ["Sources", "MyLibrary"] diff --git a/Sources/SourceKitLSP/SourceKitLSPServer.swift b/Sources/SourceKitLSP/SourceKitLSPServer.swift index 65016b27c..ae723fa83 100644 --- a/Sources/SourceKitLSP/SourceKitLSPServer.swift +++ b/Sources/SourceKitLSP/SourceKitLSPServer.swift @@ -521,7 +521,7 @@ public actor SourceKitLSPServer { return nil } - logger.log("Using toolchain \(toolchain.displayName) (\(toolchain.identifier)) for \(uri.forLogging)") + logger.log("Using toolchain \(toolchain.identifier) (\(toolchain.identifier)) for \(uri.forLogging)") return workspace.documentService.withLock { documentService in if let concurrentlySetService = documentService[uri] { diff --git a/Tests/SKCoreTests/BuildSystemManagerTests.swift b/Tests/SKCoreTests/BuildSystemManagerTests.swift index ab894d5ea..862df9735 100644 --- a/Tests/SKCoreTests/BuildSystemManagerTests.swift +++ b/Tests/SKCoreTests/BuildSystemManagerTests.swift @@ -465,6 +465,10 @@ class ManualBuildSystem: BuildSystem { return nil } + public func toolchain(for uri: DocumentURI, _ language: Language) async -> SKCore.Toolchain? { + return nil + } + public func configuredTargets(for document: DocumentURI) async -> [ConfiguredTarget] { return [ConfiguredTarget(targetID: "dummy", runDestinationID: "dummy")] } diff --git a/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift b/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift index 4cc2857bb..a4268dde8 100644 --- a/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift +++ b/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift @@ -276,7 +276,7 @@ final class BackgroundIndexingTests: XCTestCase { func testIndexCFile() async throws { let project = try await SwiftPMTestProject( files: [ - "MyLibrary/include/dummy.h": "", + "MyLibrary/include/destination.h": "", "MyFile.c": """ void 1️⃣someFunc() {} @@ -532,11 +532,11 @@ final class BackgroundIndexingTests: XCTestCase { var serverOptions = SourceKitLSPServer.Options.testDefault let expectedPreparationTracker = ExpectedIndexTaskTracker(expectedPreparations: [ [ - ExpectedPreparation(targetID: "LibA", runDestinationID: "dummy"), - ExpectedPreparation(targetID: "LibB", runDestinationID: "dummy"), + ExpectedPreparation(targetID: "LibA", runDestinationID: "destination"), + ExpectedPreparation(targetID: "LibB", runDestinationID: "destination"), ], [ - ExpectedPreparation(targetID: "LibB", runDestinationID: "dummy") + ExpectedPreparation(targetID: "LibB", runDestinationID: "destination") ], ]) serverOptions.indexTestHooks = expectedPreparationTracker.testHooks @@ -639,16 +639,16 @@ final class BackgroundIndexingTests: XCTestCase { let expectedPreparationTracker = ExpectedIndexTaskTracker(expectedPreparations: [ // Preparation of targets during the initial of the target [ - ExpectedPreparation(targetID: "LibA", runDestinationID: "dummy"), - ExpectedPreparation(targetID: "LibB", runDestinationID: "dummy"), - ExpectedPreparation(targetID: "LibC", runDestinationID: "dummy"), - ExpectedPreparation(targetID: "LibD", runDestinationID: "dummy"), + ExpectedPreparation(targetID: "LibA", runDestinationID: "destination"), + ExpectedPreparation(targetID: "LibB", runDestinationID: "destination"), + ExpectedPreparation(targetID: "LibC", runDestinationID: "destination"), + ExpectedPreparation(targetID: "LibD", runDestinationID: "destination"), ], // LibB's preparation has already started by the time we browse through the other files, so we finish its preparation [ ExpectedPreparation( targetID: "LibB", - runDestinationID: "dummy", + runDestinationID: "destination", didStart: { libBStartedPreparation.fulfill() }, didFinish: { self.wait(for: [allDocumentsOpened], timeout: defaultTimeout) } ) @@ -657,7 +657,7 @@ final class BackgroundIndexingTests: XCTestCase { [ ExpectedPreparation( targetID: "LibD", - runDestinationID: "dummy", + runDestinationID: "destination", didFinish: { libDPreparedForEditing.fulfill() } ) ], @@ -999,4 +999,54 @@ final class BackgroundIndexingTests: XCTestCase { "Did not get expected diagnostic: \(diagnostics)" ) } + + func testLibraryUsedByExecutableTargetAndPackagePlugin() async throws { + try await SkipUnless.swiftpmStoresModulesInSubdirectory() + let project = try await SwiftPMTestProject( + files: [ + "Lib/MyFile.swift": """ + public func 1️⃣foo() {} + """, + "MyExec/MyExec.swift": """ + import Lib + func bar() { + 2️⃣foo() + } + """, + "Plugins/MyPlugin/MyPlugin.swift": """ + import PackagePlugin + @main + struct MyPlugin: CommandPlugin { + func performCommand(context: PluginContext, arguments: [String]) async throws {} + } + """, + ], + manifest: """ + // swift-tools-version: 5.7 + import PackageDescription + let package = Package( + name: "MyLibrary", + targets: [ + .target(name: "Lib"), + .executableTarget(name: "MyExec", dependencies: ["Lib"]), + .plugin( + name: "MyPlugin", + capability: .command( + intent: .sourceCodeFormatting(), + permissions: [] + ), + dependencies: ["MyExec"] + ) + ] + ) + """, + enableBackgroundIndexing: true + ) + + let (uri, positions) = try project.openDocument("MyExec.swift") + let definition = try await project.testClient.send( + DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["2️⃣"]) + ) + XCTAssertEqual(definition, .locations([try project.location(from: "1️⃣", to: "1️⃣", in: "MyFile.swift")])) + } } diff --git a/Tests/SourceKitLSPTests/BuildSystemTests.swift b/Tests/SourceKitLSPTests/BuildSystemTests.swift index c5439ca67..ab365e6a1 100644 --- a/Tests/SourceKitLSPTests/BuildSystemTests.swift +++ b/Tests/SourceKitLSPTests/BuildSystemTests.swift @@ -57,6 +57,10 @@ actor TestBuildSystem: BuildSystem { return nil } + public func toolchain(for uri: DocumentURI, _ language: Language) async -> SKCore.Toolchain? { + return nil + } + public func configuredTargets(for document: DocumentURI) async -> [ConfiguredTarget] { return [ConfiguredTarget(targetID: "dummy", runDestinationID: "dummy")] }