Skip to content

Commit 5d47358

Browse files
authored
Merge pull request #1762 from ahoppen/build-settings-timeout
2 parents 4cbf2de + 5bae73f commit 5d47358

26 files changed

+346
-80
lines changed

Documentation/Configuration File.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ The structure of the file is currently not guaranteed to be stable. Options may
3131
- `cxxCompilerFlags: string[]`: Extra arguments passed to the compiler for C++ files
3232
- `swiftCompilerFlags: string[]`: Extra arguments passed to the compiler for Swift files
3333
- `sdk: string`: The SDK to use for fallback arguments. Default is to infer the SDK using `xcrun`.
34+
- `buildSettingsTimeout: int`: Number of milliseconds to wait for build settings from the build system before using fallback build settings.
3435
- `clangdOptions: string[]`: Extra command line arguments passed to `clangd` when launching it
3536
- `index`: Dictionary with the following keys, defining options related to indexing
3637
- `indexStorePath: string`: Directory in which a separate compilation stores the index store. By default, inferred from the build system.

Sources/BuildSystemIntegration/BuildSystemManager.swift

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ private extension BuildSystemKind {
139139
private static func createBuiltInBuildSystemAdapter(
140140
projectRoot: AbsolutePath,
141141
messagesToSourceKitLSPHandler: any MessageHandler,
142+
buildSystemTestHooks: BuildSystemTestHooks,
142143
_ createBuildSystem: @Sendable (_ connectionToSourceKitLSP: any Connection) async throws -> BuiltInBuildSystem?
143144
) async -> BuildSystemAdapter? {
144145
let connectionToSourceKitLSP = LocalConnection(
@@ -156,7 +157,8 @@ private extension BuildSystemKind {
156157
logger.log("Created \(type(of: buildSystem), privacy: .public) at \(projectRoot.pathString)")
157158
let buildSystemAdapter = BuiltInBuildSystemAdapter(
158159
underlyingBuildSystem: buildSystem,
159-
connectionToSourceKitLSP: connectionToSourceKitLSP
160+
connectionToSourceKitLSP: connectionToSourceKitLSP,
161+
buildSystemTestHooks: buildSystemTestHooks
160162
)
161163
let connectionToBuildSystem = LocalConnection(
162164
receiverName: "\(type(of: buildSystem)) for \(projectRoot.asURL.lastPathComponent)"
@@ -190,7 +192,8 @@ private extension BuildSystemKind {
190192
case .compilationDatabase(projectRoot: let projectRoot):
191193
return await Self.createBuiltInBuildSystemAdapter(
192194
projectRoot: projectRoot,
193-
messagesToSourceKitLSPHandler: messagesToSourceKitLSPHandler
195+
messagesToSourceKitLSPHandler: messagesToSourceKitLSPHandler,
196+
buildSystemTestHooks: testHooks
194197
) { connectionToSourceKitLSP in
195198
CompilationDatabaseBuildSystem(
196199
projectRoot: projectRoot,
@@ -203,7 +206,8 @@ private extension BuildSystemKind {
203206
case .swiftPM(projectRoot: let projectRoot):
204207
return await Self.createBuiltInBuildSystemAdapter(
205208
projectRoot: projectRoot,
206-
messagesToSourceKitLSPHandler: messagesToSourceKitLSPHandler
209+
messagesToSourceKitLSPHandler: messagesToSourceKitLSPHandler,
210+
buildSystemTestHooks: testHooks
207211
) { connectionToSourceKitLSP in
208212
try await SwiftPMBuildSystem(
209213
projectRoot: projectRoot,
@@ -216,7 +220,8 @@ private extension BuildSystemKind {
216220
case .testBuildSystem(projectRoot: let projectRoot):
217221
return await Self.createBuiltInBuildSystemAdapter(
218222
projectRoot: projectRoot,
219-
messagesToSourceKitLSPHandler: messagesToSourceKitLSPHandler
223+
messagesToSourceKitLSPHandler: messagesToSourceKitLSPHandler,
224+
buildSystemTestHooks: testHooks
220225
) { connectionToSourceKitLSP in
221226
TestBuildSystem(projectRoot: projectRoot, connectionToSourceKitLSP: connectionToSourceKitLSP)
222227
}
@@ -411,7 +416,8 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
411416
)
412417
let adapter = BuiltInBuildSystemAdapter(
413418
underlyingBuildSystem: legacyBuildServer,
414-
connectionToSourceKitLSP: legacyBuildServer.connectionToSourceKitLSP
419+
connectionToSourceKitLSP: legacyBuildServer.connectionToSourceKitLSP,
420+
buildSystemTestHooks: buildSystemTestHooks
415421
)
416422
let connectionToBuildSystem = LocalConnection(receiverName: "Legacy BSP server")
417423
connectionToBuildSystem.start(handler: adapter)
@@ -672,7 +678,12 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
672678
/// Returns the target's module name as parsed from the `BuildTargetIdentifier`'s compiler arguments.
673679
package func moduleName(for document: DocumentURI, in target: BuildTargetIdentifier) async -> String? {
674680
guard let language = await self.defaultLanguage(for: document, in: target),
675-
let buildSettings = await buildSettings(for: document, in: target, language: language)
681+
let buildSettings = await buildSettings(
682+
for: document,
683+
in: target,
684+
language: language,
685+
fallbackAfterTimeout: false
686+
)
676687
else {
677688
return nil
678689
}
@@ -715,11 +726,6 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
715726
language: language
716727
)
717728

718-
// TODO: We should only wait `fallbackSettingsTimeout` for build settings
719-
// and return fallback afterwards.
720-
// For now, this should be fine because all build systems return
721-
// very quickly from `settings(for:language:)`.
722-
// https://github.com/apple/sourcekit-lsp/issues/1181
723729
let response = try await cachedSourceKitOptions.get(request, isolation: self) { request in
724730
try await buildSystemAdapter.send(request)
725731
}
@@ -740,19 +746,30 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
740746
/// Only call this method if it is known that `document` is a main file. Prefer `buildSettingsInferredFromMainFile`
741747
/// otherwise. If `document` is a header file, this will most likely return fallback settings because header files
742748
/// don't have build settings by themselves.
749+
///
750+
/// If `fallbackAfterTimeout` is true fallback build settings will be returned if no build settings can be found in
751+
/// `SourceKitLSPOptions.buildSettingsTimeoutOrDefault`.
743752
package func buildSettings(
744753
for document: DocumentURI,
745754
in target: BuildTargetIdentifier?,
746-
language: Language
755+
language: Language,
756+
fallbackAfterTimeout: Bool
747757
) async -> FileBuildSettings? {
748-
do {
749-
if let target,
750-
let buildSettings = try await buildSettingsFromBuildSystem(for: document, in: target, language: language)
751-
{
752-
return buildSettings
758+
if let target {
759+
let buildSettingsFromBuildSystem = await orLog("Getting build settings") {
760+
if fallbackAfterTimeout {
761+
try await withTimeout(options.buildSettingsTimeoutOrDefault) {
762+
return try await self.buildSettingsFromBuildSystem(for: document, in: target, language: language)
763+
} resultReceivedAfterTimeout: {
764+
await self.delegate?.fileBuildSettingsChanged([document])
765+
}
766+
} else {
767+
try await self.buildSettingsFromBuildSystem(for: document, in: target, language: language)
768+
}
769+
}
770+
if let buildSettingsFromBuildSystem {
771+
return buildSettingsFromBuildSystem
753772
}
754-
} catch {
755-
logger.error("Getting build settings failed: \(error.forLogging)")
756773
}
757774

758775
guard
@@ -780,14 +797,27 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
780797
/// by the header file.
781798
package func buildSettingsInferredFromMainFile(
782799
for document: DocumentURI,
783-
language: Language
800+
language: Language,
801+
fallbackAfterTimeout: Bool
784802
) async -> FileBuildSettings? {
785803
func mainFileAndSettings(
786804
basedOn document: DocumentURI
787805
) async -> (mainFile: DocumentURI, settings: FileBuildSettings)? {
788806
let mainFile = await self.mainFile(for: document, language: language)
789-
let target = await canonicalTarget(for: mainFile)
790-
guard let settings = await buildSettings(for: mainFile, in: target, language: language) else {
807+
let settings = await orLog("Getting build settings") {
808+
let target = try await withTimeout(options.buildSettingsTimeoutOrDefault) {
809+
await self.canonicalTarget(for: mainFile)
810+
} resultReceivedAfterTimeout: {
811+
await self.delegate?.fileBuildSettingsChanged([document])
812+
}
813+
return await self.buildSettings(
814+
for: mainFile,
815+
in: target,
816+
language: language,
817+
fallbackAfterTimeout: fallbackAfterTimeout
818+
)
819+
}
820+
guard let settings else {
791821
return nil
792822
}
793823
return (mainFile, settings)

Sources/BuildSystemIntegration/BuildSystemTestHooks.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,25 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
#if compiler(>=6)
14+
package import LanguageServerProtocol
15+
#else
16+
import LanguageServerProtocol
17+
#endif
18+
1319
package struct BuildSystemTestHooks: Sendable {
1420
package var swiftPMTestHooks: SwiftPMTestHooks
1521

16-
package init(swiftPMTestHooks: SwiftPMTestHooks = SwiftPMTestHooks()) {
22+
/// A hook that will be executed before a request is handled by a `BuiltInBuildSystem`.
23+
///
24+
/// This allows requests to be artificially delayed.
25+
package var handleRequest: (@Sendable (any RequestType) async -> Void)?
26+
27+
package init(
28+
swiftPMTestHooks: SwiftPMTestHooks = SwiftPMTestHooks(),
29+
handleRequest: (@Sendable (any RequestType) async -> Void)? = nil
30+
) {
1731
self.swiftPMTestHooks = swiftPMTestHooks
32+
self.handleRequest = handleRequest
1833
}
1934
}

Sources/BuildSystemIntegration/BuiltInBuildSystemAdapter.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ actor BuiltInBuildSystemAdapter: QueueBasedMessageHandler {
5959
/// The connection with which messages are sent to `BuildSystemManager`.
6060
private let connectionToSourceKitLSP: LocalConnection
6161

62+
private let buildSystemTestHooks: BuildSystemTestHooks
63+
6264
/// If the underlying build system is a `TestBuildSystem`, return it. Otherwise, `nil`
6365
///
6466
/// - Important: For testing purposes only.
@@ -70,10 +72,12 @@ actor BuiltInBuildSystemAdapter: QueueBasedMessageHandler {
7072
/// from the build system to SourceKit-LSP.
7173
init(
7274
underlyingBuildSystem: BuiltInBuildSystem,
73-
connectionToSourceKitLSP: LocalConnection
75+
connectionToSourceKitLSP: LocalConnection,
76+
buildSystemTestHooks: BuildSystemTestHooks
7477
) {
7578
self.underlyingBuildSystem = underlyingBuildSystem
7679
self.connectionToSourceKitLSP = connectionToSourceKitLSP
80+
self.buildSystemTestHooks = buildSystemTestHooks
7781
}
7882

7983
deinit {
@@ -116,6 +120,7 @@ actor BuiltInBuildSystemAdapter: QueueBasedMessageHandler {
116120
reply: @Sendable @escaping (LSPResult<Request.Response>) -> Void
117121
) async {
118122
let request = RequestAndReply(request, reply: reply)
123+
await buildSystemTestHooks.handleRequest?(request.params)
119124
switch request {
120125
case let request as RequestAndReply<BuildShutdownRequest>:
121126
await request.reply { VoidResponse() }

Sources/BuildSystemIntegration/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ add_library(BuildSystemIntegration STATIC
1414
ExternalBuildSystemAdapter.swift
1515
FallbackBuildSettings.swift
1616
FileBuildSettings.swift
17-
Language+InferredFromFileExtension.swift
1817
LegacyBuildServerBuildSystem.swift
1918
MainFilesProvider.swift
2019
PathPrefixMapping.swift

Sources/SKOptions/SourceKitLSPOptions.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,13 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable {
250250
set { fallbackBuildSystem = newValue }
251251
}
252252

253+
/// Number of milliseconds to wait for build settings from the build system before using fallback build settings.
254+
public var buildSettingsTimeout: Int?
255+
public var buildSettingsTimeoutOrDefault: Duration {
256+
// The default timeout of 500ms was chosen arbitrarily without any measurements.
257+
get { .milliseconds(buildSettingsTimeout ?? 500) }
258+
}
259+
253260
public var clangdOptions: [String]?
254261

255262
private var index: IndexOptions?

Sources/SKSupport/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ add_library(SKSupport STATIC
88
DocumentURI+CustomLogStringConvertible.swift
99
DocumentURI+symlinkTarget.swift
1010
FileSystem.swift
11+
Language+InferredFromFileExtension.swift
1112
LineTable.swift
1213
LocalConnection.swift
1314
Process+Run.swift

Sources/BuildSystemIntegration/Language+InferredFromFileExtension.swift renamed to Sources/SKSupport/Language+InferredFromFileExtension.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,16 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
#if compiler(>=6)
14+
import Foundation
15+
package import LanguageServerProtocol
16+
#else
1317
import Foundation
1418
import LanguageServerProtocol
19+
#endif
1520

1621
extension Language {
17-
init?(inferredFromFileExtension uri: DocumentURI) {
22+
package init?(inferredFromFileExtension uri: DocumentURI) {
1823
// URL.pathExtension is only set for file URLs but we want to also infer a file extension for non-file URLs like
1924
// untitled:file.cpp
2025
let pathExtension = uri.fileURL?.pathExtension ?? (uri.pseudoPath as NSString).pathExtension

Sources/SKTestSupport/WrappedSemaphore.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,16 @@ package struct WrappedSemaphore: Sendable {
5656
}
5757

5858
/// Wait for a signal and emit an XCTFail if the semaphore is not signaled within `timeout`.
59-
package func waitOrXCTFail(timeout: DispatchTime = DispatchTime.now() + .seconds(Int(defaultTimeout))) {
59+
package func waitOrXCTFail(
60+
timeout: DispatchTime = DispatchTime.now() + .seconds(Int(defaultTimeout)),
61+
file: StaticString = #filePath,
62+
line: UInt = #line
63+
) {
6064
switch self.wait(timeout: timeout) {
6165
case .success:
6266
break
6367
case .timedOut:
64-
XCTFail("\(name) timed out")
68+
XCTFail("\(name) timed out", file: file, line: line)
6569
}
6670
}
6771
}

Sources/SemanticIndex/UpdateIndexStoreTaskDescription.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,12 @@ package struct UpdateIndexStoreTaskDescription: IndexTaskDescription {
254254
logger.error("Not indexing \(file.forLogging) because its language could not be determined")
255255
return
256256
}
257-
let buildSettings = await buildSystemManager.buildSettings(for: file.mainFile, in: target, language: language)
257+
let buildSettings = await buildSystemManager.buildSettings(
258+
for: file.mainFile,
259+
in: target,
260+
language: language,
261+
fallbackAfterTimeout: false
262+
)
258263
guard let buildSettings else {
259264
logger.error("Not indexing \(file.forLogging) because it has no compiler arguments")
260265
return

0 commit comments

Comments
 (0)