Skip to content

Commit f4e015b

Browse files
authored
Merge pull request #1988 from ahoppen/sourcekitoptions
Add an experimental request to return the build settings that SourceKit-LSP uses to process a file
2 parents dae74db + 9496b49 commit f4e015b

22 files changed

+668
-36
lines changed

Contributor Documentation/BSP Extensions.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@ export interface TextDocumentSourceKitOptionsResult {
120120

121121
/** The working directory for the compile command. */
122122
workingDirectory?: string;
123+
124+
/** Additional data that will not be interpreted by SourceKit-LSP but made available to clients in the
125+
* `workspace/_sourceKitOptions` LSP requests. */
126+
data?: LSPAny;
123127
}
124128
```
125129

Contributor Documentation/LSP Extensions.md

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,102 @@ export interface SetOptionsParams {
506506
}
507507
```
508508
509+
## `workspace/_sourceKitOptions`
510+
511+
New request from the client to the server to retrieve the compiler arguments that SourceKit-LSP uses to process the document.
512+
513+
This request does not require the document to be opened in SourceKit-LSP. This is also why it has the `workspace/` instead of the `textDocument/` prefix.
514+
515+
> [!IMPORTANT]
516+
> This request is experimental, guarded behind the `sourcekit-options-request` experimental feature, and may be modified or removed in future versions of SourceKit-LSP without notice. Do not rely on it.
517+
518+
519+
- params: `SourceKitOptionsRequest`
520+
- result: `SourceKitOptionsResult`
521+
522+
```ts
523+
export interface SourceKitOptionsRequest {
524+
/**
525+
* The document to get options for
526+
*/
527+
textDocument: TextDocumentIdentifier;
528+
529+
/**
530+
* If specified, explicitly request the compiler arguments when interpreting the document in the context of the given
531+
* target.
532+
*
533+
* The target URI must match the URI that is used by the BSP server to identify the target. This option thus only
534+
* makes sense to specify if the client also controls the BSP server.
535+
*
536+
* When this is `null`, SourceKit-LSP returns the compiler arguments it uses when the the document is opened in the
537+
* client, ie. it infers a canonical target for the document.
538+
*/
539+
target?: DocumentURI;
540+
541+
/**
542+
* Whether SourceKit-LSP should ensure that the document's target is prepared before returning build settings.
543+
*
544+
* There is a tradeoff whether the target should be prepared: Preparing a target may take significant time but if the
545+
* target is not prepared, the build settings might eg. refer to modules that haven't been built yet.
546+
*/
547+
prepareTarget: bool;
548+
549+
/**
550+
* If set to `true` and build settings could not be determined within a timeout (see `buildSettingsTimeout` in the
551+
* SourceKit-LSP configuration file), this request returns fallback build settings.
552+
*
553+
* If set to `true` the request only finishes when build settings were provided by the build system.
554+
*/
555+
allowFallbackSettings: bool
556+
}
557+
558+
/**
559+
* The kind of options that were returned by the `workspace/_sourceKitOptions` request, ie. whether they are fallback
560+
* options or the real compiler options for the file.
561+
*/
562+
export namespace SourceKitOptionsKind {
563+
/**
564+
* The SourceKit options are known to SourceKit-LSP and returned them.
565+
*/
566+
export const normal = "normal"
567+
568+
/**
569+
* SourceKit-LSP was unable to determine the build settings for this file and synthesized fallback settings.
570+
*/
571+
export const fallback = "fallback"
572+
}
573+
574+
export interface SourceKitOptionsResult {
575+
/**
576+
* The compiler options required for the requested file.
577+
*/
578+
compilerArguments: string[];
579+
580+
/**
581+
* The working directory for the compile command.
582+
*/
583+
workingDirectory?: string;
584+
585+
/**
586+
* Whether SourceKit-LSP was able to determine the build settings or synthesized fallback settings.
587+
*/
588+
kind: SourceKitOptionsKind;
589+
590+
/**
591+
* - `true` If the request requested the file's target to be prepared and the target needed preparing
592+
* - `false` If the request requested the file's target to be prepared and the target was up to date
593+
* - `nil`: If the request did not request the file's target to be prepared or the target could not be prepared for
594+
* other reasons
595+
*/
596+
didPrepareTarget?: bool
597+
598+
/**
599+
* Additional data that the BSP server returned in the `textDocument/sourceKitOptions` BSP request. This data is not
600+
* interpreted by SourceKit-LSP.
601+
*/
602+
data?: LSPAny
603+
}
604+
```
509605
510606
## `workspace/getReferenceDocument`
511607

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")[]`: Experimental features that are enabled.
56+
- `experimentalFeatures: ("on-type-formatting"|"set-options-request"|"sourcekit-options-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/BuildServerProtocol/Messages/TextDocumentSourceKitOptionsRequest.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,13 @@ public struct TextDocumentSourceKitOptionsResponse: ResponseType, Hashable {
5353
/// The working directory for the compile command.
5454
public var workingDirectory: String?
5555

56-
public init(compilerArguments: [String], workingDirectory: String? = nil) {
56+
/// Additional data that will not be interpreted by SourceKit-LSP but made available to clients in the
57+
/// `workspace/_sourceKitOptions` LSP requests.
58+
public var data: LSPAny?
59+
60+
public init(compilerArguments: [String], workingDirectory: String? = nil, data: LSPAny? = nil) {
5761
self.compilerArguments = compilerArguments
5862
self.workingDirectory = workingDirectory
63+
self.data = data
5964
}
6065
}

Sources/BuildSystemIntegration/BuildSystemManager.swift

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,8 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
809809
return FileBuildSettings(
810810
compilerArguments: response.compilerArguments,
811811
workingDirectory: response.workingDirectory,
812+
language: language,
813+
data: response.data,
812814
isFallback: false
813815
)
814816
}
@@ -869,25 +871,49 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
869871
/// be inferred from the primary main file of the document. In practice this means that we will compute the build
870872
/// settings of a C file that includes the header and replace any file references to that C file in the build settings
871873
/// by the header file.
874+
///
875+
/// When a target is passed in, the build settings for the document, interpreted as part of that target, are returned,
876+
/// otherwise a canonical target is inferred for the source file.
877+
///
878+
/// If no language is passed, this method tries to infer the language of the document from the build system. If that
879+
/// fails, it returns `nil`.
872880
package func buildSettingsInferredFromMainFile(
873881
for document: DocumentURI,
874-
language: Language,
882+
target explicitlyRequestedTarget: BuildTargetIdentifier? = nil,
883+
language: Language?,
875884
fallbackAfterTimeout: Bool
876885
) async -> FileBuildSettings? {
877886
func mainFileAndSettings(
878887
basedOn document: DocumentURI
879888
) async -> (mainFile: DocumentURI, settings: FileBuildSettings)? {
880889
let mainFile = await self.mainFile(for: document, language: language)
881-
let settings = await orLog("Getting build settings") {
882-
let target = try await withTimeout(options.buildSettingsTimeoutOrDefault) {
883-
await self.canonicalTarget(for: mainFile)
884-
} resultReceivedAfterTimeout: {
885-
await self.delegate?.fileBuildSettingsChanged([document])
890+
let settings: FileBuildSettings? = await orLog("Getting build settings") {
891+
let target =
892+
if let explicitlyRequestedTarget {
893+
explicitlyRequestedTarget
894+
} else {
895+
try await withTimeout(options.buildSettingsTimeoutOrDefault) {
896+
await self.canonicalTarget(for: mainFile)
897+
} resultReceivedAfterTimeout: {
898+
await self.delegate?.fileBuildSettingsChanged([document])
899+
}
900+
}
901+
var languageForFile: Language
902+
if let language {
903+
languageForFile = language
904+
} else if let target, let language = await self.defaultLanguage(for: mainFile, in: target) {
905+
languageForFile = language
906+
} else if let language = Language(inferredFromFileExtension: mainFile) {
907+
languageForFile = language
908+
} else {
909+
// We don't know the language as which to interpret the document, so we can't ask the build system for its
910+
// settings.
911+
return nil
886912
}
887913
return await self.buildSettings(
888914
for: mainFile,
889915
in: target,
890-
language: language,
916+
language: languageForFile,
891917
fallbackAfterTimeout: fallbackAfterTimeout
892918
)
893919
}
@@ -1153,10 +1179,12 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
11531179

11541180
/// Return the main file that should be used to get build settings for `uri`.
11551181
///
1156-
/// For Swift or normal C files, this will be the file itself. For header
1157-
/// files, we pick a main file that includes the header since header files
1158-
/// don't have build settings by themselves.
1159-
package func mainFile(for uri: DocumentURI, language: Language, useCache: Bool = true) async -> DocumentURI {
1182+
/// For Swift or normal C files, this will be the file itself. For header files, we pick a main file that includes the
1183+
/// header since header files don't have build settings by themselves.
1184+
///
1185+
/// `language` is a hint of the document's language to speed up the `main` file lookup. Passing `nil` if the language
1186+
/// is unknown should always be safe.
1187+
package func mainFile(for uri: DocumentURI, language: Language?, useCache: Bool = true) async -> DocumentURI {
11601188
if language == .swift {
11611189
// Swift doesn't have main files. Skip the main file provider query.
11621190
return uri
@@ -1351,7 +1379,9 @@ fileprivate extension TextDocumentSourceKitOptionsResponse {
13511379

13521380
result += supplementalClangIndexingArgs.flatMap { ["-Xcc", $0] }
13531381

1354-
return TextDocumentSourceKitOptionsResponse(compilerArguments: result, workingDirectory: workingDirectory)
1382+
var adjusted = self
1383+
adjusted.compilerArguments = result
1384+
return adjusted
13551385
}
13561386

13571387
/// Adjust compiler arguments that were created for building to compiler arguments that should be used for indexing
@@ -1411,7 +1441,10 @@ fileprivate extension TextDocumentSourceKitOptionsResponse {
14111441
result.append(
14121442
"-fsyntax-only"
14131443
)
1414-
return TextDocumentSourceKitOptionsResponse(compilerArguments: result, workingDirectory: workingDirectory)
1444+
1445+
var adjusted = self
1446+
adjusted.compilerArguments = result
1447+
return adjusted
14151448
}
14161449
}
14171450

Sources/BuildSystemIntegration/FallbackBuildSettings.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ package func fallbackBuildSettings(
4949
default:
5050
return nil
5151
}
52-
return FileBuildSettings(compilerArguments: args, workingDirectory: nil, isFallback: true)
52+
return FileBuildSettings(compilerArguments: args, workingDirectory: nil, language: language, isFallback: true)
5353
}
5454

5555
private func fallbackBuildSettingsSwift(

Sources/BuildSystemIntegration/FileBuildSettings.swift

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,14 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import Foundation
14-
import LanguageServerProtocol
1514
import LanguageServerProtocolExtensions
1615

16+
#if compiler(>=6)
17+
package import LanguageServerProtocol
18+
#else
19+
import LanguageServerProtocol
20+
#endif
21+
1722
/// Build settings for a single file.
1823
///
1924
/// Encapsulates all the settings needed to compile a single file, including the compiler arguments
@@ -26,12 +31,28 @@ package struct FileBuildSettings: Equatable, Sendable {
2631
/// The working directory to resolve any relative paths in `compilerArguments`.
2732
package var workingDirectory: String? = nil
2833

34+
/// The language that the document was interpreted as, and which implies the compiler to which the build settings
35+
/// would be passed.
36+
package var language: Language
37+
38+
/// Additional data about the build settings that was received from the BSP server, will not be interpreted by
39+
/// SourceKit-LSP but returned to clients in the `workspace/_sourceKitOptions` LSP request.
40+
package var data: LSPAny?
41+
2942
/// Whether the build settings were computed from a real build system or whether they are synthesized fallback arguments while the build system is still busy computing build settings.
3043
package var isFallback: Bool
3144

32-
package init(compilerArguments: [String], workingDirectory: String? = nil, isFallback: Bool = false) {
45+
package init(
46+
compilerArguments: [String],
47+
workingDirectory: String? = nil,
48+
language: Language,
49+
data: LSPAny? = nil,
50+
isFallback: Bool = false
51+
) {
3352
self.compilerArguments = compilerArguments
3453
self.workingDirectory = workingDirectory
54+
self.language = language
55+
self.data = data
3556
self.isFallback = isFallback
3657
}
3758

@@ -65,6 +86,8 @@ package struct FileBuildSettings: Equatable, Sendable {
6586
return FileBuildSettings(
6687
compilerArguments: arguments,
6788
workingDirectory: self.workingDirectory,
89+
language: self.language,
90+
data: self.data,
6891
isFallback: self.isFallback
6992
)
7093
}

Sources/BuildSystemIntegration/SwiftPMBuildSystem.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -610,7 +610,8 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
610610
// with the `.cpp` file.
611611
let buildSettings = FileBuildSettings(
612612
compilerArguments: try await compilerArguments(for: DocumentURI(substituteFile), in: swiftPMTarget),
613-
workingDirectory: try projectRoot.filePath
613+
workingDirectory: try projectRoot.filePath,
614+
language: request.language
614615
).patching(newFile: DocumentURI(try path.asURL.realpath), originalFile: DocumentURI(substituteFile))
615616
return TextDocumentSourceKitOptionsResponse(
616617
compilerArguments: buildSettings.compilerArguments,

Sources/LanguageServerProtocol/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ add_library(LanguageServerProtocol STATIC
8181
Requests/ShowMessageRequest.swift
8282
Requests/ShutdownRequest.swift
8383
Requests/SignatureHelpRequest.swift
84+
Requests/SourceKitOptionsRequest.swift
8485
Requests/SymbolInfoRequest.swift
8586
Requests/TriggerReindexRequest.swift
8687
Requests/TypeDefinitionRequest.swift

Sources/LanguageServerProtocol/Messages.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ public let builtinRequests: [_RequestType.Type] = [
7272
ShowMessageRequest.self,
7373
ShutdownRequest.self,
7474
SignatureHelpRequest.self,
75+
SourceKitOptionsRequest.self,
7576
SymbolInfoRequest.self,
7677
TriggerReindexRequest.self,
7778
TypeDefinitionRequest.self,

0 commit comments

Comments
 (0)