diff --git a/Sources/SourceKitLSPAPI/BuildDescription.swift b/Sources/SourceKitLSPAPI/BuildDescription.swift index 3dc4ae5378e..568eb442631 100644 --- a/Sources/SourceKitLSPAPI/BuildDescription.swift +++ b/Sources/SourceKitLSPAPI/BuildDescription.swift @@ -27,6 +27,11 @@ public enum BuildDestination { case target } +public enum BuildTargetCompiler { + case swift + case clang +} + public protocol BuildTarget { /// Source files in the target var sources: [URL] { get } @@ -46,6 +51,9 @@ public protocol BuildTarget { /// The name of the target. It should be possible to build a target by passing this name to `swift build --target` var name: String { get } + /// The compiler that is responsible for building this target. + var compiler: BuildTargetCompiler { get } + var destination: BuildDestination { get } /// Whether the target is part of the root package that the user opened or if it's part of a package dependency. @@ -53,6 +61,8 @@ public protocol BuildTarget { var isTestTarget: Bool { get } + var outputPaths: [URL] { get throws } + func compileArguments(for fileURL: URL) throws -> [String] } @@ -100,10 +110,19 @@ private struct WrappedClangTargetBuildDescription: BuildTarget { return description.clangTarget.name } + var compiler: BuildTargetCompiler { .clang } + + public var destination: BuildDestination { return description.destination == .host ? .host : .target } + var outputPaths: [URL] { + get throws { + return try description.compilePaths().map(\.object.asURL) + } + } + public func compileArguments(for fileURL: URL) throws -> [String] { let filePath = try resolveSymlinks(try Basics.AbsolutePath(validating: fileURL.path)) let commandLine = try description.emitCommandLine(for: filePath) @@ -127,6 +146,8 @@ private struct WrappedSwiftTargetBuildDescription: BuildTarget { return description.target.name } + var compiler: BuildTargetCompiler { .swift } + public var destination: BuildDestination { return description.destination == .host ? .host : .target } @@ -155,6 +176,15 @@ private struct WrappedSwiftTargetBuildDescription: BuildTarget { return others.map(\.asURL) } + var outputPaths: [URL] { + get throws { + struct NotSupportedError: Error, CustomStringConvertible { + var description: String { "Getting output paths for a Swift target is not supported" } + } + throw NotSupportedError() + } + } + func compileArguments(for fileURL: URL) throws -> [String] { // Note: we ignore the `fileURL` here as the expectation is that we get a command line for the entire target // in case of Swift. diff --git a/Sources/SourceKitLSPAPI/PluginTargetBuildDescription.swift b/Sources/SourceKitLSPAPI/PluginTargetBuildDescription.swift index 30b1c5be2e5..2f80f0839c4 100644 --- a/Sources/SourceKitLSPAPI/PluginTargetBuildDescription.swift +++ b/Sources/SourceKitLSPAPI/PluginTargetBuildDescription.swift @@ -54,11 +54,22 @@ struct PluginTargetBuildDescription: BuildTarget { return target.name } + var compiler: BuildTargetCompiler { .swift } + var destination: BuildDestination { // Plugins are always built for the host. .host } + var outputPaths: [URL] { + get throws { + struct NotSupportedError: Error, CustomStringConvertible { + var description: String { "Getting output paths for a plugin target is not supported" } + } + throw NotSupportedError() + } + } + func compileArguments(for fileURL: URL) throws -> [String] { // FIXME: This is very odd and we should clean this up by merging `ManifestLoader` and `DefaultPluginScriptRunner` again. var args = ManifestLoader.interpreterFlags(for: self.toolsVersion, toolchain: toolchain) diff --git a/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift b/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift index b2a965b35d0..d54a7b16bad 100644 --- a/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift +++ b/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift @@ -319,6 +319,51 @@ final class SourceKitLSPAPITests: XCTestCase { ) } } + + func testClangOutputPaths() async throws { + let fs = InMemoryFileSystem(emptyFiles: + "/Pkg/Sources/lib/include/lib.h", + "/Pkg/Sources/lib/lib.cpp", + ) + + let observability = ObservabilitySystem.makeForTesting() + let graph = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "Pkg", + path: "/Pkg", + toolsVersion: .v5_10, + targets: [ + TargetDescription( + name: "lib", + dependencies: [] + ) + ]), + ], + observabilityScope: observability.topScope + ) + XCTAssertNoDiagnostics(observability.diagnostics) + let plan = try await BuildPlan( + destinationBuildParameters: mockBuildParameters( + destination: .target, + shouldLinkStaticSwiftStdlib: true + ), + toolsBuildParameters: mockBuildParameters( + destination: .host, + shouldLinkStaticSwiftStdlib: true + ), + graph: graph, + fileSystem: fs, + observabilityScope: observability.topScope + ) + let description = BuildDescription(buildPlan: plan) + + let target = try XCTUnwrap(description.getBuildTarget(for: XCTUnwrap(graph.module(for: "lib")), destination: .target)) + XCTAssertEqual(target.compiler, .clang) + XCTAssertEqual(try target.outputPaths.count, 1) + XCTAssertEqual(try target.outputPaths.last?.lastPathComponent, "lib.cpp.o") + } } extension SourceKitLSPAPI.BuildDescription {