Skip to content

Commit d457fa4

Browse files
authored
Public API for getting information about build targets (#6763)
Until now, SourceKit-LSP has been using a couple of internal data structures of the build plan, which were incidentally public, for this. This changes exposes a new public API for accessing information about build targets, including those for plugins. Note that this API is part of a new module which could be the start of a "proper" public API for SwiftPM. That said, right now it depends on several types of existing modules in its public interface and that isn't likely to be resolved as part of this particular change. rdar://112120976
1 parent 23042d6 commit d457fa4

File tree

9 files changed

+330
-67
lines changed

9 files changed

+330
-67
lines changed

Package.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ let swiftPMDataModelProduct = (
4242
"PackageMetadata",
4343
"PackageModel",
4444
"SourceControl",
45+
"SourceKitLSPAPI",
4546
"Workspace",
4647
]
4748
)
@@ -148,6 +149,14 @@ let package = Package(
148149
linkerSettings: packageLibraryLinkSettings
149150
),
150151

152+
.target(
153+
name: "SourceKitLSPAPI",
154+
dependencies: [
155+
"Build",
156+
"SPMBuildCore"
157+
]
158+
),
159+
151160
// MARK: SwiftPM specific support libraries
152161

153162
.systemLibrary(name: "SPMSQLite3", pkgConfig: systemSQLitePkgConfig),
@@ -518,6 +527,7 @@ let package = Package(
518527
name: "SPMTestSupport",
519528
dependencies: [
520529
"Basics",
530+
"Build",
521531
"PackageFingerprint",
522532
"PackageGraph",
523533
"PackageLoading",
@@ -537,6 +547,14 @@ let package = Package(
537547

538548
// MARK: SwiftPM tests
539549

550+
.testTarget(
551+
name: "SourceKitLSPAPITests",
552+
dependencies: [
553+
"SourceKitLSPAPI",
554+
"SPMTestSupport",
555+
]
556+
),
557+
540558
.testTarget(
541559
name: "BasicsTests",
542560
dependencies: ["Basics", "SPMTestSupport", "tsan_utils"],

Sources/Build/BuildDescription/ClangTargetBuildDescription.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,42 @@ public final class ClangTargetBuildDescription {
321321
return args
322322
}
323323

324+
public func emitCommandLine(for filePath: AbsolutePath) throws -> [String] {
325+
let standards = [
326+
(clangTarget.cxxLanguageStandard, SupportedLanguageExtension.cppExtensions),
327+
(clangTarget.cLanguageStandard, SupportedLanguageExtension.cExtensions),
328+
]
329+
330+
guard let path = try self.compilePaths().first(where: { $0.source == filePath }) else {
331+
throw BuildDescriptionError.requestedFileNotPartOfTarget(
332+
targetName: self.target.name,
333+
requestedFilePath: filePath
334+
)
335+
}
336+
337+
let isCXX = path.source.extension.map { SupportedLanguageExtension.cppExtensions.contains($0) } ?? false
338+
let isC = path.source.extension.map { $0 == SupportedLanguageExtension.c.rawValue } ?? false
339+
340+
var args = try basicArguments(isCXX: isCXX, isC: isC)
341+
342+
args += ["-MD", "-MT", "dependencies", "-MF", path.deps.pathString]
343+
344+
// Add language standard flag if needed.
345+
if let ext = path.source.extension {
346+
for (standard, validExtensions) in standards {
347+
if let standard, validExtensions.contains(ext) {
348+
args += ["-std=\(standard)"]
349+
}
350+
}
351+
}
352+
353+
args += ["-c", path.source.pathString, "-o", path.object.pathString]
354+
355+
let clangCompiler = try buildParameters.toolchain.getClangCompiler().pathString
356+
args.insert(clangCompiler, at: 0)
357+
return args
358+
}
359+
324360
/// Returns the build flags from the declared build settings.
325361
private func buildSettingsFlags() throws -> [String] {
326362
let scope = buildParameters.createScope(for: target)

Sources/Build/BuildDescription/TargetBuildDescription.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ import class PackageGraph.ResolvedTarget
1616
import struct PackageModel.Resource
1717
import struct SPMBuildCore.BuildToolPluginInvocationResult
1818

19+
public enum BuildDescriptionError: Swift.Error {
20+
case requestedFileNotPartOfTarget(targetName: String, requestedFilePath: AbsolutePath)
21+
}
22+
1923
/// A target description which can either be for a Swift or Clang target.
2024
public enum TargetBuildDescription {
2125
/// Swift target description.

Sources/Build/BuildManifest/LLBuildManifestBuilder+Clang.swift

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,6 @@ extension LLBuildManifestBuilder {
2121
func createClangCompileCommand(
2222
_ target: ClangTargetBuildDescription
2323
) throws {
24-
let standards = [
25-
(target.clangTarget.cxxLanguageStandard, SupportedLanguageExtension.cppExtensions),
26-
(target.clangTarget.cLanguageStandard, SupportedLanguageExtension.cExtensions),
27-
]
28-
2924
var inputs: [Node] = []
3025

3126
// Add resources node as the input to the target. This isn't great because we
@@ -79,26 +74,7 @@ extension LLBuildManifestBuilder {
7974
var objectFileNodes: [Node] = []
8075

8176
for path in try target.compilePaths() {
82-
let isCXX = path.source.extension.map { SupportedLanguageExtension.cppExtensions.contains($0) } ?? false
83-
let isC = path.source.extension.map { $0 == SupportedLanguageExtension.c.rawValue } ?? false
84-
85-
var args = try target.basicArguments(isCXX: isCXX, isC: isC)
86-
87-
args += ["-MD", "-MT", "dependencies", "-MF", path.deps.pathString]
88-
89-
// Add language standard flag if needed.
90-
if let ext = path.source.extension {
91-
for (standard, validExtensions) in standards {
92-
if let standard, validExtensions.contains(ext) {
93-
args += ["-std=\(standard)"]
94-
}
95-
}
96-
}
97-
98-
args += ["-c", path.source.pathString, "-o", path.object.pathString]
99-
100-
let clangCompiler = try target.buildParameters.toolchain.getClangCompiler().pathString
101-
args.insert(clangCompiler, at: 0)
77+
let args = try target.emitCommandLine(for: path.source)
10278

10379
let objectFileNode: Node = .file(path.object)
10480
objectFileNodes.append(objectFileNode)

Tests/BuildTests/MockBuildTestHelper.swift renamed to Sources/SPMTestSupport/MockBuildTestHelper.swift

Lines changed: 45 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -10,63 +10,66 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
@testable import PackageModel
14-
@testable import TSCUtility
15-
@testable import Build
1613
import Basics
14+
import Build
15+
import PackageModel
1716
import SPMBuildCore
17+
import TSCUtility
1818
import XCTest
1919

20-
struct MockToolchain: PackageModel.Toolchain {
20+
public struct MockToolchain: PackageModel.Toolchain {
2121
#if os(Windows)
22-
let librarianPath = AbsolutePath("/fake/path/to/link.exe")
22+
public let librarianPath = AbsolutePath("/fake/path/to/link.exe")
2323
#elseif os(iOS) || os(macOS) || os(tvOS) || os(watchOS)
24-
let librarianPath = AbsolutePath("/fake/path/to/libtool")
24+
public let librarianPath = AbsolutePath("/fake/path/to/libtool")
2525
#else
26-
let librarianPath = AbsolutePath("/fake/path/to/llvm-ar")
26+
public let librarianPath = AbsolutePath("/fake/path/to/llvm-ar")
2727
#endif
28-
let swiftCompilerPath = AbsolutePath("/fake/path/to/swiftc")
29-
let includeSearchPaths = [AbsolutePath]()
30-
let librarySearchPaths = [AbsolutePath]()
31-
let swiftResourcesPath: AbsolutePath? = nil
32-
let swiftStaticResourcesPath: AbsolutePath? = nil
33-
let isSwiftDevelopmentToolchain = false
34-
let sdkRootPath: AbsolutePath? = nil
35-
let swiftPluginServerPath: AbsolutePath? = nil
36-
let extraFlags = PackageModel.BuildFlags()
37-
let installedSwiftPMConfiguration = InstalledSwiftPMConfiguration.default
38-
39-
func getClangCompiler() throws -> AbsolutePath {
28+
public let swiftCompilerPath = AbsolutePath("/fake/path/to/swiftc")
29+
public let includeSearchPaths = [AbsolutePath]()
30+
public let librarySearchPaths = [AbsolutePath]()
31+
public let swiftResourcesPath: AbsolutePath? = nil
32+
public let swiftStaticResourcesPath: AbsolutePath? = nil
33+
public let isSwiftDevelopmentToolchain = false
34+
public let sdkRootPath: AbsolutePath? = nil
35+
public let swiftPluginServerPath: AbsolutePath? = nil
36+
public let extraFlags = PackageModel.BuildFlags()
37+
public let installedSwiftPMConfiguration = InstalledSwiftPMConfiguration.default
38+
39+
public func getClangCompiler() throws -> AbsolutePath {
4040
return "/fake/path/to/clang"
4141
}
4242

43-
func _isClangCompilerVendorApple() throws -> Bool? {
43+
public func _isClangCompilerVendorApple() throws -> Bool? {
4444
#if os(macOS)
4545
return true
4646
#else
4747
return false
4848
#endif
4949
}
50+
51+
public init() {
52+
}
5053
}
5154

5255

5356
extension Basics.Triple {
54-
static let x86_64MacOS = try! Self("x86_64-apple-macosx")
55-
static let x86_64Linux = try! Self("x86_64-unknown-linux-gnu")
56-
static let arm64Linux = try! Self("aarch64-unknown-linux-gnu")
57-
static let arm64Android = try! Self("aarch64-unknown-linux-android")
58-
static let windows = try! Self("x86_64-unknown-windows-msvc")
59-
static let wasi = try! Self("wasm32-unknown-wasi")
57+
public static let x86_64MacOS = try! Self("x86_64-apple-macosx")
58+
public static let x86_64Linux = try! Self("x86_64-unknown-linux-gnu")
59+
public static let arm64Linux = try! Self("aarch64-unknown-linux-gnu")
60+
public static let arm64Android = try! Self("aarch64-unknown-linux-android")
61+
public static let windows = try! Self("x86_64-unknown-windows-msvc")
62+
public static let wasi = try! Self("wasm32-unknown-wasi")
6063
}
6164

62-
let hostTriple = try! UserToolchain.default.targetTriple
65+
public let hostTriple = try! UserToolchain.default.targetTriple
6366
#if os(macOS)
64-
let defaultTargetTriple: String = hostTriple.tripleString(forPlatformVersion: "10.13")
67+
public let defaultTargetTriple: String = hostTriple.tripleString(forPlatformVersion: "10.13")
6568
#else
66-
let defaultTargetTriple: String = hostTriple.tripleString
69+
public let defaultTargetTriple: String = hostTriple.tripleString
6770
#endif
6871

69-
func mockBuildParameters(
72+
public func mockBuildParameters(
7073
buildPath: AbsolutePath = "/path/to/build",
7174
config: BuildConfiguration = .debug,
7275
toolchain: PackageModel.Toolchain = MockToolchain(),
@@ -108,7 +111,7 @@ func mockBuildParameters(
108111
)
109112
}
110113

111-
func mockBuildParameters(environment: BuildEnvironment) -> BuildParameters {
114+
public func mockBuildParameters(environment: BuildEnvironment) -> BuildParameters {
112115
let triple: Basics.Triple
113116
switch environment.platform {
114117
case .macOS:
@@ -130,34 +133,34 @@ enum BuildError: Swift.Error {
130133
case error(String)
131134
}
132135

133-
struct BuildPlanResult {
136+
public struct BuildPlanResult {
134137

135-
let plan: Build.BuildPlan
136-
let targetMap: [String: TargetBuildDescription]
137-
let productMap: [String: Build.ProductBuildDescription]
138+
public let plan: Build.BuildPlan
139+
public let targetMap: [String: TargetBuildDescription]
140+
public let productMap: [String: Build.ProductBuildDescription]
138141

139-
init(plan: Build.BuildPlan) throws {
142+
public init(plan: Build.BuildPlan) throws {
140143
self.plan = plan
141144
self.productMap = try Dictionary(throwingUniqueKeysWithValues: plan.buildProducts.compactMap { $0 as? Build.ProductBuildDescription }.map{ ($0.product.name, $0) })
142145
self.targetMap = try Dictionary(throwingUniqueKeysWithValues: plan.targetMap.map{ ($0.0.name, $0.1) })
143146
}
144147

145-
func checkTargetsCount(_ count: Int, file: StaticString = #file, line: UInt = #line) {
148+
public func checkTargetsCount(_ count: Int, file: StaticString = #file, line: UInt = #line) {
146149
XCTAssertEqual(plan.targetMap.count, count, file: file, line: line)
147150
}
148151

149-
func checkProductsCount(_ count: Int, file: StaticString = #file, line: UInt = #line) {
152+
public func checkProductsCount(_ count: Int, file: StaticString = #file, line: UInt = #line) {
150153
XCTAssertEqual(plan.productMap.count, count, file: file, line: line)
151154
}
152155

153-
func target(for name: String) throws -> TargetBuildDescription {
156+
public func target(for name: String) throws -> TargetBuildDescription {
154157
guard let target = targetMap[name] else {
155158
throw BuildError.error("Target \(name) not found.")
156159
}
157160
return target
158161
}
159162

160-
func buildProduct(for name: String) throws -> Build.ProductBuildDescription {
163+
public func buildProduct(for name: String) throws -> Build.ProductBuildDescription {
161164
guard let product = productMap[name] else {
162165
// <rdar://problem/30162871> Display the thrown error on macOS
163166
throw BuildError.error("Product \(name) not found.")
@@ -167,7 +170,7 @@ struct BuildPlanResult {
167170
}
168171

169172
extension TargetBuildDescription {
170-
func swiftTarget() throws -> SwiftTargetBuildDescription {
173+
public func swiftTarget() throws -> SwiftTargetBuildDescription {
171174
switch self {
172175
case .swift(let target):
173176
return target
@@ -176,7 +179,7 @@ extension TargetBuildDescription {
176179
}
177180
}
178181

179-
func clangTarget() throws -> ClangTargetBuildDescription {
182+
public func clangTarget() throws -> ClangTargetBuildDescription {
180183
switch self {
181184
case .clang(let target):
182185
return target
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
15+
/*private*/ import struct Basics.AbsolutePath
16+
/*private*/ import func Basics.resolveSymlinks
17+
// FIXME: should not import this module
18+
import Build
19+
// FIXME: should be internal imports
20+
import PackageGraph
21+
/*private*/ import SPMBuildCore
22+
23+
public protocol BuildTarget {
24+
var sources: [URL] { get }
25+
26+
func compileArguments(for fileURL: URL) throws -> [String]
27+
}
28+
29+
extension ClangTargetBuildDescription: BuildTarget {
30+
public var sources: [URL] {
31+
return (try? compilePaths().map { URL(fileURLWithPath: $0.source.pathString) }) ?? []
32+
}
33+
34+
public func compileArguments(for fileURL: URL) throws -> [String] {
35+
let filePath = try resolveSymlinks(try AbsolutePath(validating: fileURL.path))
36+
return try self.emitCommandLine(for: filePath)
37+
}
38+
}
39+
40+
private struct WrappedSwiftTargetBuildDescription: BuildTarget {
41+
private let description: SwiftTargetBuildDescription
42+
43+
init(description: SwiftTargetBuildDescription) {
44+
self.description = description
45+
}
46+
47+
var sources: [URL] {
48+
return description.sources.map { URL(fileURLWithPath: $0.pathString) }
49+
}
50+
51+
func compileArguments(for fileURL: URL) throws -> [String] {
52+
// Note: we ignore the `fileURL` here as the expectation is that we get a commandline for the entire target in case of Swift.
53+
return try description.emitCommandLine(scanInvocation: false)
54+
}
55+
}
56+
57+
public struct BuildDescription {
58+
private let buildPlan: Build.BuildPlan
59+
60+
// FIXME: should not use `BuildPlan` in the public interface
61+
public init(buildPlan: Build.BuildPlan) {
62+
self.buildPlan = buildPlan
63+
}
64+
65+
// FIXME: should not use `ResolvedTarget` in the public interface
66+
public func getBuildTarget(for target: ResolvedTarget) -> BuildTarget? {
67+
if let description = buildPlan.targetMap[target] {
68+
switch description {
69+
case .clang(let description):
70+
return description
71+
case .swift(let description):
72+
return WrappedSwiftTargetBuildDescription(description: description)
73+
}
74+
} else {
75+
if target.type == .plugin, let package = self.buildPlan.graph.package(for: target) {
76+
return PluginTargetBuildDescription(target: target, toolsVersion: package.manifest.toolsVersion)
77+
}
78+
return nil
79+
}
80+
}
81+
}

0 commit comments

Comments
 (0)