From 42aaad6d4b9cd5e18112412d346c054f1a8f46a7 Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Thu, 7 Nov 2024 19:59:16 -0500 Subject: [PATCH] Expose all target files to SourceKit-LSP (#8107) Add `resources`, `ignored`, and `other` files to `BuildTarget` for use by SourceKit-LSP. This is tracked by rdar://139431738 as part of swiftlang/vscode-swift#562 ### Motivation: SourceKit-LSP needs to be able to determine which target contains a given DocC catalog in order to fulfill documentation related requests. However, the catalog is not listed as one of the `sources` or `headers`. Rather, it is part of `other` files in most cases. Expose all file types to SourceKit-LSP so that it can properly determine which target a given file belongs to. ### Modifications: Added three new properties to `BuildTarget`: `resources`, `ignored`, and `other` which are arrays of `URL` pointing to the given file types in the target. ### Result: SourceKit-LSP will be able to see all files contained within a given target. --- .../ClangModuleBuildDescription.swift | 10 +++++ .../SwiftModuleBuildDescription.swift | 10 +++++ .../SourceKitLSPAPI/BuildDescription.swift | 33 ++++++++++++++ .../PluginTargetBuildDescription.swift | 12 ++++++ .../SourceKitLSPAPITests.swift | 43 ++++++++++++++++--- 5 files changed, 102 insertions(+), 6 deletions(-) diff --git a/Sources/Build/BuildDescription/ClangModuleBuildDescription.swift b/Sources/Build/BuildDescription/ClangModuleBuildDescription.swift index b149675ebe2..139bf38aa21 100644 --- a/Sources/Build/BuildDescription/ClangModuleBuildDescription.swift +++ b/Sources/Build/BuildDescription/ClangModuleBuildDescription.swift @@ -57,6 +57,16 @@ public final class ClangModuleBuildDescription { self.target.underlying.resources + self.pluginDerivedResources } + /// The list of files in the target that were marked as ignored. + public var ignored: [AbsolutePath] { + self.target.underlying.ignored + } + + /// The list of other kinds of files in the target. + public var others: [AbsolutePath] { + self.target.underlying.others + } + /// Path to the bundle generated for this module (if any). var bundlePath: AbsolutePath? { guard !self.resources.isEmpty else { diff --git a/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift b/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift index 3e6dcd88ca4..3d7c6f831dc 100644 --- a/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift +++ b/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift @@ -108,6 +108,16 @@ public final class SwiftModuleBuildDescription { self.target.underlying.resources + self.pluginDerivedResources } + /// The list of files in the target that were marked as ignored. + public var ignored: [AbsolutePath] { + self.target.underlying.ignored + } + + /// The list of other kinds of files in the target. + public var others: [AbsolutePath] { + self.target.underlying.others + } + /// The objects in this target, containing either machine code or bitcode /// depending on the build parameters used. public var objects: [AbsolutePath] { diff --git a/Sources/SourceKitLSPAPI/BuildDescription.swift b/Sources/SourceKitLSPAPI/BuildDescription.swift index 0c72f4eb57c..5f516e31e9d 100644 --- a/Sources/SourceKitLSPAPI/BuildDescription.swift +++ b/Sources/SourceKitLSPAPI/BuildDescription.swift @@ -37,6 +37,15 @@ public protocol BuildTarget { /// Header files in the target var headers: [URL] { get } + /// The resource files in the target. + var resources: [URL] { get } + + /// Files in the target that were marked as ignored. + var ignored: [URL] { get } + + /// Other kinds of files in the target. + var others: [URL] { get } + /// 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 } @@ -72,6 +81,18 @@ private struct WrappedClangTargetBuildDescription: BuildTarget { return description.clangTarget.headers.map(\.asURL) } + var resources: [URL] { + return description.resources.map { URL(fileURLWithPath: $0.path.pathString) } + } + + var ignored: [URL] { + return description.ignored.map { URL(fileURLWithPath: $0.pathString) } + } + + var others: [URL] { + return description.others.map { URL(fileURLWithPath: $0.pathString) } + } + public var name: String { return description.clangTarget.name } @@ -113,6 +134,18 @@ private struct WrappedSwiftTargetBuildDescription: BuildTarget { var headers: [URL] { [] } + var resources: [URL] { + return description.resources.map { URL(fileURLWithPath: $0.path.pathString) } + } + + var ignored: [URL] { + return description.ignored.map { URL(fileURLWithPath: $0.pathString) } + } + + var others: [URL] { + return description.others.map { URL(fileURLWithPath: $0.pathString) } + } + 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 5e7809c366e..6a956ce005d 100644 --- a/Sources/SourceKitLSPAPI/PluginTargetBuildDescription.swift +++ b/Sources/SourceKitLSPAPI/PluginTargetBuildDescription.swift @@ -41,6 +41,18 @@ struct PluginTargetBuildDescription: BuildTarget { var headers: [URL] { [] } + var resources: [URL] { + return target.underlying.resources.map { URL(fileURLWithPath: $0.path.pathString) } + } + + var ignored: [URL] { + return target.underlying.ignored.map { URL(fileURLWithPath: $0.pathString) } + } + + var others: [URL] { + return target.underlying.others.map { URL(fileURLWithPath: $0.pathString) } + } + var name: String { return target.name } diff --git a/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift b/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift index 5f4f6ca5686..20e6e1766ac 100644 --- a/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift +++ b/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift @@ -26,7 +26,13 @@ final class SourceKitLSPAPITests: XCTestCase { func testBasicSwiftPackage() async throws { let fs = InMemoryFileSystem(emptyFiles: "/Pkg/Sources/exe/main.swift", + "/Pkg/Sources/exe/README.md", + "/Pkg/Sources/exe/exe.docc/GettingStarted.md", + "/Pkg/Sources/exe/Resources/some_file.txt", "/Pkg/Sources/lib/lib.swift", + "/Pkg/Sources/lib/README.md", + "/Pkg/Sources/lib/lib.docc/GettingStarted.md", + "/Pkg/Sources/lib/Resources/some_file.txt", "/Pkg/Plugins/plugin/plugin.swift" ) @@ -37,9 +43,19 @@ final class SourceKitLSPAPITests: XCTestCase { Manifest.createRootManifest( displayName: "Pkg", path: "/Pkg", + toolsVersion: .v5_10, targets: [ - TargetDescription(name: "exe", dependencies: ["lib"]), - TargetDescription(name: "lib", dependencies: []), + TargetDescription( + name: "exe", + dependencies: ["lib"], + resources: [.init(rule: .copy, path: "Resources/some_file.txt")], + type: .executable + ), + TargetDescription( + name: "lib", + dependencies: [], + resources: [.init(rule: .copy, path: "Resources/some_file.txt")] + ), TargetDescription(name: "plugin", type: .plugin, pluginCapability: .buildTool) ]), ], @@ -67,10 +83,14 @@ final class SourceKitLSPAPITests: XCTestCase { graph: graph, partialArguments: [ "-module-name", "exe", + "-package-name", "pkg", "-emit-dependencies", "-emit-module", - "-emit-module-path", "/path/to/build/\(plan.destinationBuildParameters.triple)/debug/exe.build/exe.swiftmodule" + "-emit-module-path", "/path/to/build/\(plan.destinationBuildParameters.triple)/debug/Modules/exe.swiftmodule" ], + resources: [.init(filePath: "/Pkg/Sources/exe/Resources/some_file.txt")], + ignoredFiles: [.init(filePath: "/Pkg/Sources/exe/exe.docc")], + otherFiles: [.init(filePath: "/Pkg/Sources/exe/README.md")], isPartOfRootPackage: true ) try description.checkArguments( @@ -78,10 +98,14 @@ final class SourceKitLSPAPITests: XCTestCase { graph: graph, partialArguments: [ "-module-name", "lib", + "-package-name", "pkg", "-emit-dependencies", "-emit-module", "-emit-module-path", "/path/to/build/\(plan.destinationBuildParameters.triple)/debug/Modules/lib.swiftmodule" ], + resources: [.init(filePath: "/Pkg/Sources/lib/Resources/some_file.txt")], + ignoredFiles: [.init(filePath: "/Pkg/Sources/lib/lib.docc")], + otherFiles: [.init(filePath: "/Pkg/Sources/lib/README.md")], isPartOfRootPackage: true ) try description.checkArguments( @@ -238,18 +262,25 @@ extension SourceKitLSPAPI.BuildDescription { for targetName: String, graph: ModulesGraph, partialArguments: [String], + resources: [URL] = [], + ignoredFiles: [URL] = [], + otherFiles: [URL] = [], isPartOfRootPackage: Bool, destination: BuildParameters.Destination = .target ) throws -> Bool { let target = try XCTUnwrap(graph.module(for: targetName)) let buildTarget = try XCTUnwrap(self.getBuildTarget(for: target, destination: destination)) - guard let file = buildTarget.sources.first else { - XCTFail("build target \(targetName) contains no files") + XCTAssertEqual(buildTarget.resources, resources, "build target \(targetName) contains unexpected resource files") + XCTAssertEqual(buildTarget.ignored, ignoredFiles, "build target \(targetName) contains unexpected ignored files") + XCTAssertEqual(buildTarget.others, otherFiles, "build target \(targetName) contains unexpected other files") + + guard let source = buildTarget.sources.first else { + XCTFail("build target \(targetName) contains no source files") return false } - let arguments = try buildTarget.compileArguments(for: file) + let arguments = try buildTarget.compileArguments(for: source) let result = arguments.contains(partialArguments) XCTAssertTrue(result, "could not match \(partialArguments) to actual arguments \(arguments)")