Skip to content

Commit f92fa53

Browse files
authored
Merge pull request #2018 from ahoppen/output-paths-lsp-request
Add an experimental LSP request to return the output paths for all files in a target
2 parents 375f9e5 + 2a1fd10 commit f92fa53

File tree

11 files changed

+125
-4
lines changed

11 files changed

+125
-4
lines changed

Contributor Documentation/LSP Extensions.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,40 @@ export interface SourceKitOptionsResult {
678678
}
679679
```
680680
681+
## `workspace/_outputPaths`
682+
683+
New request from the client to the server to retrieve the output paths of a target (see the `buildTarget/outputPaths` BSP request).
684+
685+
This request will only succeed if the build server supports the `buildTarget/outputPaths` request.
686+
687+
> [!IMPORTANT]
688+
> This request is experimental, guarded behind the `output-paths-request` experimental feature, and may be modified or removed in future versions of SourceKit-LSP without notice. Do not rely on it.
689+
690+
691+
- params: `OutputPathsRequest`
692+
- result: `OutputPathsResult`
693+
694+
```ts
695+
export interface OutputPathsRequest {
696+
/**
697+
* The target whose output file paths to get.
698+
*/
699+
target: DocumentURI;
700+
701+
/**
702+
* The URI of the workspace to which the target belongs.
703+
*/
704+
workspace: DocumentURI;
705+
}
706+
707+
export interface OutputPathsResult {
708+
/**
709+
* The output paths for all source files in the target
710+
*/
711+
outputPaths: string[];
712+
}
713+
```
714+
681715
## `workspace/getReferenceDocument`
682716
683717
Request from the client to the server asking for contents of a URI having a custom scheme.

Documentation/Configuration File.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ The structure of the file is currently not guaranteed to be stable. Options may
5353
- `noLazy`: Prepare a target without generating object files but do not do lazy type checking and function body skipping. This uses SwiftPM's `--experimental-prepare-for-indexing-no-lazy` flag.
5454
- `enabled`: Prepare a target without generating object files.
5555
- `cancelTextDocumentRequestsOnEditAndClose: boolean`: Whether sending a `textDocument/didChange` or `textDocument/didClose` notification for a document should cancel all pending requests for that document.
56-
- `experimentalFeatures: ("on-type-formatting"|"set-options-request"|"sourcekit-options-request"|"is-indexing-request"|"structured-logs")[]`: Experimental features that are enabled.
56+
- `experimentalFeatures: ("on-type-formatting"|"set-options-request"|"sourcekit-options-request"|"is-indexing-request"|"structured-logs"|"output-paths-request")[]`: Experimental features that are enabled.
5757
- `swiftPublishDiagnosticsDebounceDuration: number`: The time that `SwiftLanguageService` should wait after an edit before starting to compute diagnostics and sending a `PublishDiagnosticsNotification`.
5858
- `workDoneProgressDebounceDuration: number`: When a task is started that should be displayed to the client as a work done progress, how many milliseconds to wait before actually starting the work done progress. This prevents flickering of the work done progress in the client for short-lived index tasks which end within this duration.
5959
- `sourcekitdRequestTimeout: number`: The maximum duration that a sourcekitd request should be allowed to execute before being declared as timed out. In general, editors should cancel requests that they are no longer interested in, but in case editors don't cancel requests, this ensures that a long-running non-cancelled request is not blocking sourcekitd and thus most semantic functionality. In particular, VS Code does not cancel the semantic tokens request, which can cause a long-running AST build that blocks sourcekitd.

Sources/LanguageServerProtocol/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ add_library(LanguageServerProtocol STATIC
7070
Requests/IsIndexingRequest.swift
7171
Requests/LinkedEditingRangeRequest.swift
7272
Requests/MonikersRequest.swift
73+
Requests/OutputPathsRequest.swift
7374
Requests/PeekDocumentsRequest.swift
7475
Requests/PollIndexRequest.swift
7576
Requests/PrepareRenameRequest.swift

Sources/LanguageServerProtocol/Messages.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public let builtinRequests: [_RequestType.Type] = [
6161
IsIndexingRequest.self,
6262
LinkedEditingRangeRequest.self,
6363
MonikersRequest.self,
64+
OutputPathsRequest.self,
6465
PeekDocumentsRequest.self,
6566
PollIndexRequest.self,
6667
PrepareRenameRequest.self,
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2025 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+
/// Request from the client to the server to retrieve the output paths of a target (see the `buildTarget/outputPaths`
14+
/// BSP request).
15+
///
16+
/// **(LSP Extension)**.
17+
public struct OutputPathsRequest: RequestType, Hashable {
18+
public static let method: String = "workspace/_outputPaths"
19+
public typealias Response = OutputPathsResponse
20+
21+
/// The target whose output file paths to get.
22+
public var target: DocumentURI
23+
24+
/// The URI of the workspace to which the target belongs.
25+
public var workspace: DocumentURI
26+
27+
public init(target: DocumentURI, workspace: DocumentURI) {
28+
self.target = target
29+
self.workspace = workspace
30+
}
31+
}
32+
public struct OutputPathsResponse: ResponseType, Hashable {
33+
/// The output paths for all source files in the target
34+
public var outputPaths: [String]
35+
36+
public init(outputPaths: [String]) {
37+
self.outputPaths = outputPaths
38+
}
39+
}

Sources/SKOptions/ExperimentalFeatures.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,7 @@ public enum ExperimentalFeature: String, Codable, Sendable, CaseIterable {
2828

2929
/// Indicate that the client can handle the experimental `structure` field in the `window/logMessage` notification.
3030
case structuredLogs = "structured-logs"
31+
32+
/// Enable the `workspace/_outputPaths` request.
33+
case outputPathsRequest = "output-paths-request"
3134
}

Sources/SourceKitLSP/MessageHandlingDependencyTracker.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ package enum MessageHandlingDependencyTracker: QueueBasedMessageHandlerDependenc
6363
case (.globalConfigurationChange, _): return true
6464
case (_, .globalConfigurationChange): return true
6565

66-
// globalDocumentState
66+
// workspaceRequest
6767
case (.workspaceRequest, .workspaceRequest): return false
6868
case (.documentUpdate, .workspaceRequest): return true
6969
case (.workspaceRequest, .documentUpdate): return true
@@ -208,6 +208,8 @@ package enum MessageHandlingDependencyTracker: QueueBasedMessageHandlerDependenc
208208
self = .freestanding
209209
case is IsIndexingRequest:
210210
self = .freestanding
211+
case is OutputPathsRequest:
212+
self = .freestanding
211213
case is PollIndexRequest:
212214
self = .globalConfigurationChange
213215
case is RenameRequest:

Sources/SourceKitLSP/SourceKitLSPServer.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,8 @@ extension SourceKitLSPServer: QueueBasedMessageHandler {
830830
await self.handleRequest(for: request, requestHandler: self.inlayHint)
831831
case let request as RequestAndReply<IsIndexingRequest>:
832832
await request.reply { try await self.isIndexing(request.params) }
833+
case let request as RequestAndReply<OutputPathsRequest>:
834+
await request.reply { try await outputPaths(request.params) }
833835
case let request as RequestAndReply<PollIndexRequest>:
834836
await request.reply { try await pollIndex(request.params) }
835837
case let request as RequestAndReply<PrepareRenameRequest>:
@@ -1559,6 +1561,22 @@ extension SourceKitLSPServer {
15591561
)
15601562
}
15611563

1564+
func outputPaths(_ request: OutputPathsRequest) async throws -> OutputPathsResponse {
1565+
guard options.hasExperimentalFeature(.outputPathsRequest) else {
1566+
throw ResponseError.unknown("\(OutputPathsRequest.method) is an experimental request")
1567+
}
1568+
guard let workspace = self.workspaces.first(where: { $0.rootUri == request.workspace }) else {
1569+
throw ResponseError.unknown("No workspace with URI \(request.workspace.forLogging) found")
1570+
}
1571+
guard await workspace.buildSystemManager.initializationData?.outputPathsProvider ?? false else {
1572+
throw ResponseError.unknown("Build server for \(request.workspace.forLogging) does not support output paths")
1573+
}
1574+
let outputPaths = try await workspace.buildSystemManager.outputPaths(in: [
1575+
BuildTargetIdentifier(uri: request.target)
1576+
])
1577+
return OutputPathsResponse(outputPaths: outputPaths)
1578+
}
1579+
15621580
// MARK: - Language features
15631581

15641582
func completion(

Sources/SourceKitLSP/Workspace.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ package final class Workspace: Sendable, BuildSystemManagerDelegate {
105105
}
106106

107107
/// The root directory of the workspace.
108+
///
109+
/// `nil` when SourceKit-LSP is launched without a workspace (ie. no workspace folder or rootURI).
108110
package let rootUri: DocumentURI?
109111

110112
/// Tracks dynamically registered server capabilities as well as the client's capabilities.

Tests/SourceKitLSPTests/WorkspaceTests.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1380,6 +1380,25 @@ final class WorkspaceTests: XCTestCase {
13801380
)
13811381
XCTAssert(options.compilerArguments.contains("-DHAVE_SETTINGS"))
13821382
}
1383+
1384+
func testOutputPaths() async throws {
1385+
let project = try await SwiftPMTestProject(
1386+
files: [
1387+
"FileA.swift": "",
1388+
"FileB.swift": "",
1389+
],
1390+
options: .testDefault(experimentalFeatures: [.outputPathsRequest]),
1391+
enableBackgroundIndexing: true
1392+
)
1393+
1394+
let outputPaths = try await project.testClient.send(
1395+
OutputPathsRequest(
1396+
target: BuildTargetIdentifier(target: "MyLibrary", destination: .target).uri,
1397+
workspace: DocumentURI(project.scratchDirectory)
1398+
)
1399+
)
1400+
XCTAssertEqual(outputPaths.outputPaths.map { $0.suffix(13) }.sorted(), ["FileA.swift.o", "FileB.swift.o"])
1401+
}
13831402
}
13841403

13851404
fileprivate let defaultSDKArgs: String = {

0 commit comments

Comments
 (0)