diff --git a/Sources/SourceKitLSPAPI/BuildDescription.swift b/Sources/SourceKitLSPAPI/BuildDescription.swift index 5cdec76454f..af012d12802 100644 --- a/Sources/SourceKitLSPAPI/BuildDescription.swift +++ b/Sources/SourceKitLSPAPI/BuildDescription.swift @@ -22,21 +22,33 @@ import class Build.BuildPlan import class Build.ClangTargetBuildDescription import class Build.SwiftTargetBuildDescription import struct PackageGraph.ResolvedModule +import struct PackageGraph.ModulesGraph public protocol BuildTarget { var sources: [URL] { get } + /// Whether the target is part of the root package that the user opened or if it's part of a package dependency. + var isPartOfRootPackage: Bool { get } + func compileArguments(for fileURL: URL) throws -> [String] - } +} + +private struct WrappedClangTargetBuildDescription: BuildTarget { + private let description: ClangTargetBuildDescription + let isPartOfRootPackage: Bool + + init(description: ClangTargetBuildDescription, isPartOfRootPackage: Bool) { + self.description = description + self.isPartOfRootPackage = isPartOfRootPackage + } -extension ClangTargetBuildDescription: BuildTarget { public var sources: [URL] { - return (try? compilePaths().map { URL(fileURLWithPath: $0.source.pathString) }) ?? [] + return (try? description.compilePaths().map { URL(fileURLWithPath: $0.source.pathString) }) ?? [] } public func compileArguments(for fileURL: URL) throws -> [String] { let filePath = try resolveSymlinks(try AbsolutePath(validating: fileURL.path)) - let commandLine = try self.emitCommandLine(for: filePath) + let commandLine = try description.emitCommandLine(for: filePath) // First element on the command line is the compiler itself, not an argument. return Array(commandLine.dropFirst()) } @@ -44,9 +56,11 @@ extension ClangTargetBuildDescription: BuildTarget { private struct WrappedSwiftTargetBuildDescription: BuildTarget { private let description: SwiftTargetBuildDescription + let isPartOfRootPackage: Bool - init(description: SwiftTargetBuildDescription) { + init(description: SwiftTargetBuildDescription, isPartOfRootPackage: Bool) { self.description = description + self.isPartOfRootPackage = isPartOfRootPackage } var sources: [URL] { @@ -71,17 +85,27 @@ public struct BuildDescription { } // FIXME: should not use `ResolvedTarget` in the public interface - public func getBuildTarget(for target: ResolvedModule) -> BuildTarget? { + public func getBuildTarget(for target: ResolvedModule, in modulesGraph: ModulesGraph) -> BuildTarget? { if let description = buildPlan.targetMap[target.id] { switch description { case .clang(let description): - return description + return WrappedClangTargetBuildDescription( + description: description, + isPartOfRootPackage: modulesGraph.rootPackages.map(\.id).contains(description.package.id) + ) case .swift(let description): - return WrappedSwiftTargetBuildDescription(description: description) + return WrappedSwiftTargetBuildDescription( + description: description, + isPartOfRootPackage: modulesGraph.rootPackages.map(\.id).contains(description.package.id) + ) } } else { if target.type == .plugin, let package = self.buildPlan.graph.package(for: target) { - return PluginTargetBuildDescription(target: target, toolsVersion: package.manifest.toolsVersion) + return PluginTargetBuildDescription( + target: target, + toolsVersion: package.manifest.toolsVersion, + isPartOfRootPackage: modulesGraph.rootPackages.map(\.id).contains(package.id) + ) } return nil } diff --git a/Sources/SourceKitLSPAPI/PluginTargetBuildDescription.swift b/Sources/SourceKitLSPAPI/PluginTargetBuildDescription.swift index 9fc7403baba..8e6a147cc3e 100644 --- a/Sources/SourceKitLSPAPI/PluginTargetBuildDescription.swift +++ b/Sources/SourceKitLSPAPI/PluginTargetBuildDescription.swift @@ -21,11 +21,13 @@ private import class PackageModel.UserToolchain struct PluginTargetBuildDescription: BuildTarget { private let target: ResolvedModule private let toolsVersion: ToolsVersion + let isPartOfRootPackage: Bool - init(target: ResolvedModule, toolsVersion: ToolsVersion) { + init(target: ResolvedModule, toolsVersion: ToolsVersion, isPartOfRootPackage: Bool) { assert(target.type == .plugin) self.target = target self.toolsVersion = toolsVersion + self.isPartOfRootPackage = isPartOfRootPackage } var sources: [URL] { diff --git a/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift b/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift index 0ef54fbc308..92b275bab08 100644 --- a/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift +++ b/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift @@ -63,7 +63,8 @@ class SourceKitLSPAPITests: XCTestCase { "-emit-dependencies", "-emit-module", "-emit-module-path", "/path/to/build/\(buildParameters.triple)/debug/exe.build/exe.swiftmodule" - ] + ], + isPartOfRootPackage: true ) try description.checkArguments( for: "lib", @@ -73,7 +74,8 @@ class SourceKitLSPAPITests: XCTestCase { "-emit-dependencies", "-emit-module", "-emit-module-path", "/path/to/build/\(buildParameters.triple)/debug/Modules/lib.swiftmodule" - ] + ], + isPartOfRootPackage: true ) } } @@ -82,10 +84,11 @@ extension SourceKitLSPAPI.BuildDescription { @discardableResult func checkArguments( for targetName: String, graph: ModulesGraph, - partialArguments: [String] + partialArguments: [String], + isPartOfRootPackage: Bool ) throws -> Bool { let target = try XCTUnwrap(graph.allTargets.first(where: { $0.name == targetName })) - let buildTarget = try XCTUnwrap(self.getBuildTarget(for: target)) + let buildTarget = try XCTUnwrap(self.getBuildTarget(for: target, in: graph)) guard let file = buildTarget.sources.first else { XCTFail("build target \(targetName) contains no files") @@ -96,6 +99,7 @@ extension SourceKitLSPAPI.BuildDescription { let result = arguments.contains(partialArguments) XCTAssertTrue(result, "could not match \(partialArguments) to actual arguments \(arguments)") + XCTAssertEqual(buildTarget.isPartOfRootPackage, isPartOfRootPackage) return result } }