From 3f9dedd663051d2ce336844cefff4f2269076acb Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 28 Aug 2025 16:42:29 +0200 Subject: [PATCH] If a BSP server fails to initialize, show the error message to the user MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the BSP server fails to initialize, it can’t give us any compiler arguments and we won’t be able to provide semantic functionality. Show the message to the user so they can do something about fixing it instead having to dig into SourceKit-LSP logs. --- .../BuildServerManager.swift | 33 ++++++++++++++----- .../ExternalBuildServerTests.swift | 33 +++++++++++++++++++ 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/Sources/BuildServerIntegration/BuildServerManager.swift b/Sources/BuildServerIntegration/BuildServerManager.swift index 0f648180..9827493e 100644 --- a/Sources/BuildServerIntegration/BuildServerManager.swift +++ b/Sources/BuildServerIntegration/BuildServerManager.swift @@ -329,8 +329,10 @@ package actor BuildServerManager: QueueBasedMessageHandler { /// preferred over `buildServerAdapter` because no messages must be sent to the build server before initialization /// finishes. private var buildServerAdapterAfterInitialized: BuildServerAdapter? { - get async { - _ = await initializeResult.value + get async throws { + guard await initializeResult.value != nil else { + throw ResponseError.unknown("Build server failed to initialize") + } return buildServerAdapter } } @@ -505,8 +507,9 @@ package actor BuildServerManager: QueueBasedMessageHandler { logger.fault("If we have a connectionToBuildServer, we must have had a buildServerSpec") return nil } - let initializeResponse = await orLog("Initializing build server") { - try await buildServerAdapter.send( + let initializeResponse: InitializeBuildResponse? + do { + initializeResponse = try await buildServerAdapter.send( InitializeBuildRequest( displayName: "SourceKit-LSP", version: "", @@ -515,7 +518,19 @@ package actor BuildServerManager: QueueBasedMessageHandler { capabilities: BuildClientCapabilities(languageIds: [.c, .cpp, .objective_c, .objective_cpp, .swift]) ) ) + } catch { + initializeResponse = nil + let errorMessage: String + if let error = error as? ResponseError { + errorMessage = error.message + } else { + errorMessage = "\(error)" + } + connectionToClient.send( + ShowMessageNotification(type: .error, message: "Failed to initialize build server: \(errorMessage)") + ) } + if let initializeResponse, !(initializeResponse.sourceKitData?.sourceKitOptionsProvider ?? false), case .external(let externalBuildServerAdapter) = buildServerAdapter { @@ -559,7 +574,7 @@ package actor BuildServerManager: QueueBasedMessageHandler { package func shutdown() async { // Clear any pending work done progresses from the build server. self.workDoneProgressManagers.removeAll() - guard let buildServerAdapter = await self.buildServerAdapterAfterInitialized else { + guard let buildServerAdapter = try? await self.buildServerAdapterAfterInitialized else { return } await orLog("Sending shutdown request to build server") { @@ -898,7 +913,7 @@ package actor BuildServerManager: QueueBasedMessageHandler { in target: BuildTargetIdentifier, language: Language ) async throws -> FileBuildSettings? { - guard let buildServerAdapter = await buildServerAdapterAfterInitialized else { + guard let buildServerAdapter = try await buildServerAdapterAfterInitialized else { return nil } let request = TextDocumentSourceKitOptionsRequest( @@ -1158,7 +1173,7 @@ package actor BuildServerManager: QueueBasedMessageHandler { } private func buildTargets() async throws -> [BuildTargetIdentifier: BuildTargetInfo] { - guard let buildServerAdapter = await buildServerAdapterAfterInitialized else { + guard let buildServerAdapter = try await buildServerAdapterAfterInitialized else { return [:] } @@ -1198,7 +1213,7 @@ package actor BuildServerManager: QueueBasedMessageHandler { } package func sourceFiles(in targets: Set) async throws -> [SourcesItem] { - guard let buildServerAdapter = await buildServerAdapterAfterInitialized, !targets.isEmpty else { + guard let buildServerAdapter = try await buildServerAdapterAfterInitialized, !targets.isEmpty else { return [] } @@ -1414,7 +1429,7 @@ package actor BuildServerManager: QueueBasedMessageHandler { // MARK: Informing BuildSererManager about changes package func filesDidChange(_ events: [FileEvent]) async { - if let buildServerAdapter = await buildServerAdapterAfterInitialized { + if let buildServerAdapter = try? await buildServerAdapterAfterInitialized { await buildServerAdapter.send(OnWatchedFilesDidChangeNotification(changes: events)) } diff --git a/Tests/BuildServerIntegrationTests/ExternalBuildServerTests.swift b/Tests/BuildServerIntegrationTests/ExternalBuildServerTests.swift index e933cef1..c12fef2a 100644 --- a/Tests/BuildServerIntegrationTests/ExternalBuildServerTests.swift +++ b/Tests/BuildServerIntegrationTests/ExternalBuildServerTests.swift @@ -647,4 +647,37 @@ final class ExternalBuildServerTests: XCTestCase { } try await fulfillmentOfOrThrow(preparationFinished) } + + func testBuildServerFailsToInitialize() async throws { + actor BuildServer: CustomBuildServer { + let inProgressRequestsTracker = CustomBuildServerInProgressRequestTracker() + + init(projectRoot: URL, connectionToSourceKitLSP: any Connection) {} + + func initializeBuildRequest(_ request: InitializeBuildRequest) async throws -> InitializeBuildResponse { + throw ResponseError.unknown("Initialization failed with bad error") + } + + func buildTargetSourcesRequest(_ request: BuildTargetSourcesRequest) async throws -> BuildTargetSourcesResponse { + throw ResponseError.unknown("Not expected to get called") + } + + func textDocumentSourceKitOptionsRequest( + _ request: TextDocumentSourceKitOptionsRequest + ) async throws -> TextDocumentSourceKitOptionsResponse? { + throw ResponseError.unknown("Not expected to get called") + } + } + + let project = try await CustomBuildServerTestProject( + files: [ + "Test.swift": """ + func 1️⃣myTestFunc() {} + """ + ], + buildServer: BuildServer.self + ) + let message = try await project.testClient.nextNotification(ofType: ShowMessageNotification.self) + assertContains(message.message, "Initialization failed with bad error") + } }