Skip to content

Commit c3ecb9a

Browse files
committed
Add BSP request to get the output file paths of a target
1 parent 2a05d94 commit c3ecb9a

14 files changed

+241
-24
lines changed

Contributor Documentation/BSP Extensions.md

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@ Definition is the same as in LSP.
1414

1515
```ts
1616
export interface SourceKitInitializeBuildResponseData {
17+
/** The path at which SourceKit-LSP can store its index database, aggregating data from `indexStorePath`.
18+
* This should point to a directory that can be exclusively managed by SourceKit-LSP. Its exact location can be arbitrary. */
19+
indexDatabasePath?: string;
20+
1721
/** The directory to which the index store is written during compilation, ie. the path passed to `-index-store-path`
1822
* for `swiftc` or `clang` invocations **/
1923
indexStorePath?: string;
2024

21-
/** The path at which SourceKit-LSP can store its index database, aggregating data from `indexStorePath`.
22-
* This should point to a directory that can be exclusively managed by SourceKit-LSP. Its exact location can be arbitrary. */
23-
indexDatabasePath?: string;
25+
/** Whether the server implements the `buildTarget/outputPaths` request. */
26+
outputPathsProvider?: bool;
2427

2528
/** Whether the build server supports the `buildTarget/prepare` request */
2629
prepareProvider?: bool;
@@ -43,6 +46,35 @@ If `data` contains a string value for the `workDoneProgressTitle` key, then the
4346

4447
`changes` can be `null` to indicate that all targets have changed.
4548

49+
## `buildTarget/outputPaths`
50+
51+
For all the source files in this target, the output paths that are used during indexing, ie. the `-index-unit-output-path` for the file, if it is specified in the compiler arguments or the file that is passed as `-o`, if `-index-unit-output-path` is not specified.
52+
53+
server communicates during the initialize handshake whether this method is supported or not by setting `outputPathsProvider: true` in `SourceKitInitializeBuildResponseData`.
54+
55+
- method: `buildTarget/outputPaths`
56+
- params: `OutputPathsParams`
57+
- result: `OutputPathsResult`
58+
59+
```ts
60+
export interface BuildTargetOutputPathsParams {
61+
/** A list of build targets to get the output paths for. */
62+
targets: BuildTargetIdentifier[];
63+
}
64+
65+
export interface BuildTargetOutputPathsItem {
66+
/** The target these output file paths are for. */
67+
target: BuildTargetIdentifier;
68+
69+
/** The output paths for all source files in this target. */
70+
outputPaths: string[];
71+
}
72+
73+
export interface BuildTargetOutputPathsResult {
74+
items: BuildTargetOutputPathsItem[];
75+
}
76+
```
77+
4678
## `buildTarget/prepare`
4779

4880
The prepare build target request is sent from the client to the server to prepare the given list of build targets for editor functionality.
@@ -53,6 +85,7 @@ The server communicates during the initialize handshake whether this method is s
5385

5486
- method: `buildTarget/prepare`
5587
- params: `PrepareParams`
88+
- result: `void`
5689

5790
```ts
5891
export interface PrepareParams {

Sources/BuildServerProtocol/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ add_library(BuildServerProtocol STATIC
22
Messages.swift
33

44
Messages/BuildShutdownRequest.swift
5+
Messages/BuildTargetOutputPathsRequest.swift
56
Messages/BuildTargetPrepareRequest.swift
67
Messages/BuildTargetSourcesRequest.swift
78
Messages/InitializeBuildRequest.swift

Sources/BuildServerProtocol/Messages.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import LanguageServerProtocol
1818

1919
fileprivate let requestTypes: [_RequestType.Type] = [
2020
BuildShutdownRequest.self,
21+
BuildTargetOutputPathsRequest.self,
2122
BuildTargetPrepareRequest.self,
2223
BuildTargetSourcesRequest.self,
2324
CreateWorkDoneProgressRequest.self,
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#if compiler(>=6)
14+
public import LanguageServerProtocol
15+
#else
16+
import LanguageServerProtocol
17+
#endif
18+
19+
/// For all the source files in this target, the output paths that are used during indexing, ie. the
20+
/// `-index-unit-output-path` for the file, if it is specified in the compiler arguments or the file that is passed as
21+
/// `-o`, if `-index-unit-output-path` is not specified.
22+
public struct BuildTargetOutputPathsRequest: RequestType, Equatable, Hashable {
23+
public static let method: String = "buildTarget/outputPaths"
24+
25+
public typealias Response = BuildTargetOutputPathsResponse
26+
27+
/// A list of build targets to get the output paths for.
28+
public var targets: [BuildTargetIdentifier]
29+
30+
public init(targets: [BuildTargetIdentifier]) {
31+
self.targets = targets
32+
}
33+
}
34+
35+
public struct BuildTargetOutputPathsItem: Codable, Sendable {
36+
/// The target these output file paths are for.
37+
public var target: BuildTargetIdentifier
38+
39+
/// The output paths for all source files in this target.
40+
public var outputPaths: [String]
41+
42+
public init(target: BuildTargetIdentifier, outputPaths: [String]) {
43+
self.target = target
44+
self.outputPaths = outputPaths
45+
}
46+
}
47+
48+
public struct BuildTargetOutputPathsResponse: ResponseType {
49+
public var items: [BuildTargetOutputPathsItem]
50+
51+
public init(items: [BuildTargetOutputPathsItem]) {
52+
self.items = items
53+
}
54+
}

Sources/BuildServerProtocol/Messages/InitializeBuildRequest.swift

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,15 +273,19 @@ public struct SourceKitInitializeBuildResponseData: LSPAnyCodable, Codable, Send
273273
/// The path at which SourceKit-LSP can store its index database, aggregating data from `indexStorePath`
274274
public var indexStorePath: String?
275275

276-
/// The files to watch for changes.
277-
public var watchers: [FileSystemWatcher]?
276+
/// Whether the server implements the `buildTarget/outputPaths` request.
277+
public var outputPathsProvider: Bool?
278278

279279
/// Whether the build server supports the `buildTarget/prepare` request.
280280
public var prepareProvider: Bool?
281281

282282
/// Whether the server implements the `textDocument/sourceKitOptions` request.
283283
public var sourceKitOptionsProvider: Bool?
284284

285+
/// The files to watch for changes.
286+
public var watchers: [FileSystemWatcher]?
287+
288+
@available(*, deprecated, message: "Use initializer with alphabetical order of parameters")
285289
public init(
286290
indexDatabasePath: String? = nil,
287291
indexStorePath: String? = nil,
@@ -296,6 +300,22 @@ public struct SourceKitInitializeBuildResponseData: LSPAnyCodable, Codable, Send
296300
self.sourceKitOptionsProvider = sourceKitOptionsProvider
297301
}
298302

303+
public init(
304+
indexDatabasePath: String? = nil,
305+
indexStorePath: String? = nil,
306+
outputPathsProvider: Bool? = nil,
307+
prepareProvider: Bool? = nil,
308+
sourceKitOptionsProvider: Bool? = nil,
309+
watchers: [FileSystemWatcher]? = nil
310+
) {
311+
self.indexDatabasePath = indexDatabasePath
312+
self.indexStorePath = indexStorePath
313+
self.outputPathsProvider = outputPathsProvider
314+
self.prepareProvider = prepareProvider
315+
self.sourceKitOptionsProvider = sourceKitOptionsProvider
316+
self.watchers = watchers
317+
}
318+
299319
public init?(fromLSPDictionary dictionary: [String: LanguageServerProtocol.LSPAny]) {
300320
if case .string(let indexDatabasePath) = dictionary[CodingKeys.indexDatabasePath.stringValue] {
301321
self.indexDatabasePath = indexDatabasePath

Sources/BuildSystemIntegration/BuildSystemManager.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,8 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
379379

380380
private var cachedTargetSources = RequestCache<BuildTargetSourcesRequest>()
381381

382+
private var cachedTargetOutputPaths = RequestCache<BuildTargetOutputPathsRequest>()
383+
382384
/// `SourceFilesAndDirectories` is a global property that only gets reset when the build targets change and thus
383385
/// has no real key.
384386
private struct SourceFilesAndDirectoriesKey: Hashable {}
@@ -624,6 +626,13 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
624626
}
625627
return !updatedTargets.intersection(cacheKey.targets).isEmpty
626628
}
629+
self.cachedTargetOutputPaths.clear(isolation: self) { cacheKey in
630+
guard let updatedTargets else {
631+
// All targets might have changed
632+
return true
633+
}
634+
return !updatedTargets.intersection(cacheKey.targets).isEmpty
635+
}
627636
self.cachedSourceFilesAndDirectories.clearAll(isolation: self)
628637

629638
await delegate?.buildTargetsChanged(notification.changes)
@@ -1158,6 +1167,32 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
11581167
return response.items
11591168
}
11601169

1170+
package func outputPath(from targets: Set<BuildTargetIdentifier>) async throws -> [String] {
1171+
guard let buildSystemAdapter = await buildSystemAdapterAfterInitialized, !targets.isEmpty else {
1172+
return []
1173+
}
1174+
1175+
let request = BuildTargetOutputPathsRequest(targets: targets.sorted { $0.uri.stringValue < $1.uri.stringValue })
1176+
1177+
// If we have a cached request for a superset of the targets, serve the result from that cache entry.
1178+
let fromSuperset = await orLog("Getting output paths from superset request") {
1179+
try await cachedTargetOutputPaths.getDerived(
1180+
isolation: self,
1181+
request,
1182+
canReuseKey: { targets.isSubset(of: $0.targets) },
1183+
transform: { BuildTargetOutputPathsResponse(items: $0.items.filter { targets.contains($0.target) }) }
1184+
)
1185+
}
1186+
if let fromSuperset {
1187+
return fromSuperset.items.flatMap(\.outputPaths)
1188+
}
1189+
1190+
let response = try await cachedTargetOutputPaths.get(request, isolation: self) { request in
1191+
try await buildSystemAdapter.send(request)
1192+
}
1193+
return response.items.flatMap(\.outputPaths)
1194+
}
1195+
11611196
/// Returns all source files in the project.
11621197
///
11631198
/// - SeeAlso: Comment in `sourceFilesAndDirectories` for a definition of what `buildable` means.

Sources/BuildSystemIntegration/BuildSystemMessageDependencyTracker.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ package enum BuildSystemMessageDependencyTracker: QueueBasedMessageHandlerDepend
9090
switch request {
9191
case is BuildShutdownRequest:
9292
self = .stateChange
93+
case is BuildTargetOutputPathsRequest:
94+
self = .stateRead
9395
case is BuildTargetPrepareRequest:
9496
self = .stateRead
9597
case is BuildTargetSourcesRequest:

Sources/BuildSystemIntegration/BuiltInBuildSystem.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,19 @@ package protocol BuiltInBuildSystem: AnyObject, Sendable {
4444
/// The path to put the index database, if any.
4545
var indexDatabasePath: URL? { get async }
4646

47-
/// Whether the build system is capable of preparing a target for indexing, ie. if the `prepare` methods has been
48-
/// implemented.
49-
var supportsPreparation: Bool { get }
47+
/// Whether the build system is capable of preparing a target for indexing and determining the output paths for the
48+
/// target, ie. whether the `prepare` and `buildTargetOutputPaths` methods have been implemented.
49+
var supportsPreparationAndOutputPaths: Bool { get }
5050

5151
/// Returns all targets in the build system
5252
func buildTargets(request: WorkspaceBuildTargetsRequest) async throws -> WorkspaceBuildTargetsResponse
5353

5454
/// Returns all the source files in the given targets
5555
func buildTargetSources(request: BuildTargetSourcesRequest) async throws -> BuildTargetSourcesResponse
5656

57+
/// Returns all the output paths for the source files in the given targets.
58+
func buildTargetOutputPaths(request: BuildTargetOutputPathsRequest) async throws -> BuildTargetOutputPathsResponse
59+
5760
/// Called when files in the project change.
5861
func didChangeWatchedFiles(notification: OnWatchedFilesDidChangeNotification) async
5962

Sources/BuildSystemIntegration/BuiltInBuildSystemAdapter.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,10 @@ actor BuiltInBuildSystemAdapter: QueueBasedMessageHandler {
100100
indexStorePath: await orLog("getting index store file path") {
101101
try await underlyingBuildSystem.indexStorePath?.filePath
102102
},
103-
watchers: await underlyingBuildSystem.fileWatchers,
104-
prepareProvider: underlyingBuildSystem.supportsPreparation,
105-
sourceKitOptionsProvider: true
103+
outputPathsProvider: underlyingBuildSystem.supportsPreparationAndOutputPaths,
104+
prepareProvider: underlyingBuildSystem.supportsPreparationAndOutputPaths,
105+
sourceKitOptionsProvider: true,
106+
watchers: await underlyingBuildSystem.fileWatchers
106107
).encodeToLSPAny()
107108
)
108109
}
@@ -130,6 +131,8 @@ actor BuiltInBuildSystemAdapter: QueueBasedMessageHandler {
130131
switch request {
131132
case let request as RequestAndReply<BuildShutdownRequest>:
132133
await request.reply { VoidResponse() }
134+
case let request as RequestAndReply<BuildTargetOutputPathsRequest>:
135+
await request.reply { try await underlyingBuildSystem.buildTargetOutputPaths(request: request.params) }
133136
case let request as RequestAndReply<BuildTargetPrepareRequest>:
134137
await request.reply { try await underlyingBuildSystem.prepare(request: request.params) }
135138
case let request as RequestAndReply<BuildTargetSourcesRequest>:

Sources/BuildSystemIntegration/FixedCompilationDatabaseBuildSystem.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ package actor FixedCompilationDatabaseBuildSystem: BuiltInBuildSystem {
6666
indexStorePath?.deletingLastPathComponent().appendingPathComponent("IndexDatabase")
6767
}
6868

69-
package nonisolated var supportsPreparation: Bool { false }
69+
package nonisolated var supportsPreparationAndOutputPaths: Bool { false }
7070

7171
private static func parseCompileFlags(at configPath: URL) throws -> [String] {
7272
let fileContents: String = try String(contentsOf: configPath, encoding: .utf8)
@@ -121,7 +121,13 @@ package actor FixedCompilationDatabaseBuildSystem: BuiltInBuildSystem {
121121
}
122122

123123
package func prepare(request: BuildTargetPrepareRequest) async throws -> VoidResponse {
124-
throw PrepareNotSupportedError()
124+
throw ResponseError.methodNotFound(BuildTargetPrepareRequest.method)
125+
}
126+
127+
package func buildTargetOutputPaths(
128+
request: BuildTargetOutputPathsRequest
129+
) async throws -> BuildTargetOutputPathsResponse {
130+
throw ResponseError.methodNotFound(BuildTargetOutputPathsRequest.method)
125131
}
126132

127133
package func sourceKitOptions(

0 commit comments

Comments
 (0)