Skip to content

Commit 5c60d1d

Browse files
committed
Add a new test project type that uses a custom build server
This allows us to more easily test behavior for build servers that have different behavior than SwiftPM and compile commands without having to implement the build server in Python.
1 parent 375f9e5 commit 5c60d1d

File tree

11 files changed

+292
-194
lines changed

11 files changed

+292
-194
lines changed

Sources/BuildSystemIntegration/BuildSystemManager.swift

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,9 @@ private extension BuildSystemSpec {
179179
_ createBuildSystem: @Sendable (_ connectionToSourceKitLSP: any Connection) async throws -> BuiltInBuildSystem?
180180
) async -> BuildSystemAdapter? {
181181
let connectionToSourceKitLSP = LocalConnection(
182-
receiverName: "BuildSystemManager for \(projectRoot.lastPathComponent)"
182+
receiverName: "BuildSystemManager for \(projectRoot.lastPathComponent)",
183+
handler: messagesToSourceKitLSPHandler
183184
)
184-
connectionToSourceKitLSP.start(handler: messagesToSourceKitLSPHandler)
185185

186186
let buildSystem = await orLog("Creating build system") {
187187
try await createBuildSystem(connectionToSourceKitLSP)
@@ -197,9 +197,9 @@ private extension BuildSystemSpec {
197197
buildSystemHooks: buildSystemHooks
198198
)
199199
let connectionToBuildSystem = LocalConnection(
200-
receiverName: "\(type(of: buildSystem)) for \(projectRoot.lastPathComponent)"
200+
receiverName: "\(type(of: buildSystem)) for \(projectRoot.lastPathComponent)",
201+
handler: buildSystemAdapter
201202
)
202-
connectionToBuildSystem.start(handler: buildSystemAdapter)
203203
return .builtIn(buildSystemAdapter, connectionToBuildSystem: connectionToBuildSystem)
204204
}
205205

@@ -266,9 +266,9 @@ private extension BuildSystemSpec {
266266
#endif
267267
case .injected(let injector):
268268
let connectionToSourceKitLSP = LocalConnection(
269-
receiverName: "BuildSystemManager for \(projectRoot.lastPathComponent)"
269+
receiverName: "BuildSystemManager for \(projectRoot.lastPathComponent)",
270+
handler: messagesToSourceKitLSPHandler
270271
)
271-
connectionToSourceKitLSP.start(handler: messagesToSourceKitLSPHandler)
272272
return .injected(
273273
await injector(projectRoot, connectionToSourceKitLSP)
274274
)
@@ -510,8 +510,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
510510
connectionToSourceKitLSP: legacyBuildServer.connectionToSourceKitLSP,
511511
buildSystemHooks: buildSystemHooks
512512
)
513-
let connectionToBuildSystem = LocalConnection(receiverName: "Legacy BSP server")
514-
connectionToBuildSystem.start(handler: adapter)
513+
let connectionToBuildSystem = LocalConnection(receiverName: "Legacy BSP server", handler: adapter)
515514
self.buildSystemAdapter = .builtIn(adapter, connectionToBuildSystem: connectionToBuildSystem)
516515
}
517516
Task {

Sources/BuildSystemIntegration/CMakeLists.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ add_library(BuildSystemIntegration STATIC
1919
LegacyBuildServerBuildSystem.swift
2020
MainFilesProvider.swift
2121
SplitShellCommand.swift
22-
SwiftPMBuildSystem.swift
23-
TestBuildSystem.swift)
22+
SwiftPMBuildSystem.swift)
2423
set_target_properties(BuildSystemIntegration PROPERTIES
2524
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})
2625
target_link_libraries(BuildSystemIntegration PUBLIC

Sources/BuildSystemIntegration/LegacyBuildServerBuildSystem.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,10 @@ actor LegacyBuildServerBuildSystem: MessageHandler, BuiltInBuildSystem {
7373
self.configPath = configPath
7474
self.indexDatabasePath = nil
7575
self.indexStorePath = nil
76-
self.connectionToSourceKitLSP = LocalConnection(receiverName: "BuildSystemManager")
77-
await self.connectionToSourceKitLSP.start(handler: externalBuildSystemAdapter.messagesToSourceKitLSPHandler)
76+
self.connectionToSourceKitLSP = LocalConnection(
77+
receiverName: "BuildSystemManager",
78+
handler: await externalBuildSystemAdapter.messagesToSourceKitLSPHandler
79+
)
7880
await externalBuildSystemAdapter.changeMessageToSourceKitLSPHandler(to: self)
7981
self.buildServer = await externalBuildSystemAdapter.connectionToBuildServer
8082
}

Sources/LanguageServerProtocolExtensions/LocalConnection.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ package final class LocalConnection: Connection, Sendable {
5656
self.name = receiverName
5757
}
5858

59+
package convenience init(receiverName: String, handler: MessageHandler) {
60+
self.init(receiverName: receiverName)
61+
self.start(handler: handler)
62+
}
63+
5964
deinit {
6065
queue.sync {
6166
if state != .closed {

Sources/BuildSystemIntegration/TestBuildSystem.swift renamed to Sources/SKTestSupport/CustomBuildServerTestProject.swift

Lines changed: 97 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,53 +2,61 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
99
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
import Foundation
13+
import BuildSystemIntegration
1414
import LanguageServerProtocolExtensions
1515
import SKLogging
16-
import SKOptions
16+
import SourceKitLSP
17+
import SwiftExtensions
1718
import ToolchainRegistry
19+
import XCTest
1820

1921
#if compiler(>=6)
2022
package import BuildServerProtocol
23+
package import Foundation
2124
package import LanguageServerProtocol
25+
package import SKOptions
2226
#else
2327
import BuildServerProtocol
28+
import Foundation
2429
import LanguageServerProtocol
30+
import SKOptions
2531
#endif
2632

27-
/// Build system to be used for testing BuildSystem and BuildSystemDelegate functionality with SourceKitLSPServer
28-
/// and other components.
29-
package actor TestBuildSystem: MessageHandler {
30-
private let connectionToSourceKitLSP: any Connection
31-
32-
/// Build settings by file.
33-
private var buildSettingsByFile: [DocumentURI: TextDocumentSourceKitOptionsResponse] = [:]
34-
35-
package func setBuildSettings(for uri: DocumentURI, to buildSettings: TextDocumentSourceKitOptionsResponse?) {
36-
buildSettingsByFile[uri] = buildSettings
37-
connectionToSourceKitLSP.send(OnBuildTargetDidChangeNotification(changes: nil))
38-
}
33+
// MARK: - CustomBuildServer
3934

40-
private let initializeData: SourceKitInitializeBuildResponseData
35+
/// A build server that can be injected into `CustomBuildServerTestProject`.
36+
package protocol CustomBuildServer: MessageHandler {
37+
init(projectRoot: URL, connectionToSourceKitLSP: any Connection)
4138

42-
package init(
43-
initializeData: SourceKitInitializeBuildResponseData = SourceKitInitializeBuildResponseData(
44-
sourceKitOptionsProvider: true
45-
),
46-
connectionToSourceKitLSP: any Connection
47-
) {
48-
self.initializeData = initializeData
49-
self.connectionToSourceKitLSP = connectionToSourceKitLSP
50-
}
39+
func initializeBuildRequest(_ request: InitializeBuildRequest) async throws -> InitializeBuildResponse
40+
func onBuildInitialized(_ notification: OnBuildInitializedNotification) throws
41+
func buildShutdown(_ request: BuildShutdownRequest) async throws -> VoidResponse
42+
func onBuildExit(_ notification: OnBuildExitNotification) throws
43+
func workspaceBuildTargetsRequest(
44+
_ request: WorkspaceBuildTargetsRequest
45+
) async throws -> WorkspaceBuildTargetsResponse
46+
func buildTargetSourcesRequest(_ request: BuildTargetSourcesRequest) async throws -> BuildTargetSourcesResponse
47+
func textDocumentSourceKitOptionsRequest(
48+
_ request: TextDocumentSourceKitOptionsRequest
49+
) async throws -> TextDocumentSourceKitOptionsResponse?
50+
func prepareTarget(_ request: BuildTargetPrepareRequest) async throws -> VoidResponse
51+
func waitForBuildSystemUpdates(request: WorkspaceWaitForBuildSystemUpdatesRequest) async -> VoidResponse
52+
nonisolated func onWatchedFilesDidChange(_ notification: OnWatchedFilesDidChangeNotification) throws
53+
func workspaceWaitForBuildSystemUpdatesRequest(
54+
_ request: WorkspaceWaitForBuildSystemUpdatesRequest
55+
) async throws -> VoidResponse
56+
nonisolated func cancelRequest(_ notification: CancelRequestNotification) throws
57+
}
5158

59+
extension CustomBuildServer {
5260
package nonisolated func handle(_ notification: some NotificationType) {
5361
do {
5462
switch notification {
@@ -64,7 +72,7 @@ package actor TestBuildSystem: MessageHandler {
6472
throw ResponseError.methodNotFound(type(of: notification).method)
6573
}
6674
} catch {
67-
logger.error("Error while handling BSP notification")
75+
logger.error("Error while handling BSP notification: \(error.forLogging)")
6876
}
6977
}
7078

@@ -102,10 +110,18 @@ package actor TestBuildSystem: MessageHandler {
102110
reply(.failure(ResponseError.methodNotFound(type(of: request).method)))
103111
}
104112
}
113+
}
105114

106-
func initializeBuildRequest(_ request: InitializeBuildRequest) async throws -> InitializeBuildResponse {
107-
return InitializeBuildResponse(
108-
displayName: "TestBuildSystem",
115+
package extension CustomBuildServer {
116+
// MARK: Helper functions for the implementation of BSP methods
117+
118+
func initializationResponse(
119+
initializeData: SourceKitInitializeBuildResponseData = SourceKitInitializeBuildResponseData(
120+
sourceKitOptionsProvider: true
121+
)
122+
) -> InitializeBuildResponse {
123+
InitializeBuildResponse(
124+
displayName: "\(type(of: self))",
109125
version: "",
110126
bspVersion: "2.2.0",
111127
capabilities: BuildServerCapabilities(),
@@ -114,17 +130,25 @@ package actor TestBuildSystem: MessageHandler {
114130
)
115131
}
116132

117-
nonisolated func onBuildInitialized(_ notification: OnBuildInitializedNotification) throws {
118-
// Nothing to do
133+
func dummyTargetSourcesResponse(_ files: some Sequence<DocumentURI>) -> BuildTargetSourcesResponse {
134+
return BuildTargetSourcesResponse(items: [
135+
SourcesItem(target: .dummy, sources: files.map { SourceItem(uri: $0, kind: .file, generated: false) })
136+
])
119137
}
120138

139+
// MARK: Default implementation for all build server methods that usually don't need customization.
140+
141+
func initializeBuildRequest(_ request: InitializeBuildRequest) async throws -> InitializeBuildResponse {
142+
return initializationResponse()
143+
}
144+
145+
nonisolated func onBuildInitialized(_ notification: OnBuildInitializedNotification) throws {}
146+
121147
func buildShutdown(_ request: BuildShutdownRequest) async throws -> VoidResponse {
122148
return VoidResponse()
123149
}
124150

125-
nonisolated func onBuildExit(_ notification: OnBuildExitNotification) throws {
126-
// Nothing to do
127-
}
151+
nonisolated func onBuildExit(_ notification: OnBuildExitNotification) throws {}
128152

129153
func workspaceBuildTargetsRequest(
130154
_ request: WorkspaceBuildTargetsRequest
@@ -142,32 +166,15 @@ package actor TestBuildSystem: MessageHandler {
142166
])
143167
}
144168

145-
func buildTargetSourcesRequest(_ request: BuildTargetSourcesRequest) async throws -> BuildTargetSourcesResponse {
146-
return BuildTargetSourcesResponse(items: [
147-
SourcesItem(
148-
target: .dummy,
149-
sources: buildSettingsByFile.keys.map { SourceItem(uri: $0, kind: .file, generated: false) }
150-
)
151-
])
152-
}
153-
154-
func textDocumentSourceKitOptionsRequest(
155-
_ request: TextDocumentSourceKitOptionsRequest
156-
) async throws -> TextDocumentSourceKitOptionsResponse? {
157-
return buildSettingsByFile[request.textDocument.uri]
158-
}
159-
160169
func prepareTarget(_ request: BuildTargetPrepareRequest) async throws -> VoidResponse {
161170
return VoidResponse()
162171
}
163172

164-
package func waitForBuildSystemUpdates(request: WorkspaceWaitForBuildSystemUpdatesRequest) async -> VoidResponse {
173+
func waitForBuildSystemUpdates(request: WorkspaceWaitForBuildSystemUpdatesRequest) async -> VoidResponse {
165174
return VoidResponse()
166175
}
167176

168-
nonisolated func onWatchedFilesDidChange(_ notification: OnWatchedFilesDidChangeNotification) throws {
169-
// Not watching any files
170-
}
177+
nonisolated func onWatchedFilesDidChange(_ notification: OnWatchedFilesDidChangeNotification) throws {}
171178

172179
func workspaceWaitForBuildSystemUpdatesRequest(
173180
_ request: WorkspaceWaitForBuildSystemUpdatesRequest
@@ -177,3 +184,40 @@ package actor TestBuildSystem: MessageHandler {
177184

178185
nonisolated func cancelRequest(_ notification: CancelRequestNotification) throws {}
179186
}
187+
188+
// MARK: - CustomBuildServerTestProject
189+
190+
/// A test project that launches a custom build server in-process.
191+
///
192+
/// In contrast to `ExternalBuildServerTestProject`, the custom build system runs in-process and is implemented in
193+
/// Swift.
194+
package final class CustomBuildServerTestProject<BuildServer: CustomBuildServer>: MultiFileTestProject {
195+
private let buildServerBox = ThreadSafeBox<BuildServer?>(initialValue: nil)
196+
197+
package init(
198+
files: [RelativeFileLocation: String],
199+
buildServer buildServerType: BuildServer.Type,
200+
options: SourceKitLSPOptions? = nil,
201+
enableBackgroundIndexing: Bool = false,
202+
testName: String = #function
203+
) async throws {
204+
let hooks: Hooks = Hooks(
205+
buildSystemHooks: BuildSystemHooks(injectBuildServer: { [buildServerBox] projectRoot, connectionToSourceKitLSP in
206+
let buildServer = BuildServer(projectRoot: projectRoot, connectionToSourceKitLSP: connectionToSourceKitLSP)
207+
buildServerBox.value = buildServer
208+
return LocalConnection(receiverName: "TestBuildSystem", handler: buildServer)
209+
})
210+
)
211+
try await super.init(
212+
files: files,
213+
options: options,
214+
hooks: hooks,
215+
enableBackgroundIndexing: enableBackgroundIndexing,
216+
testName: testName
217+
)
218+
}
219+
220+
package func buildServer(file: StaticString = #filePath, line: UInt = #line) throws -> BuildServer {
221+
try XCTUnwrap(buildServerBox.value, "Accessing build server before it has been created", file: file, line: line)
222+
}
223+
}

Sources/SKTestSupport/BuildServerTestProject.swift renamed to Sources/SKTestSupport/ExternalBuildServerTestProject.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ private let skTestSupportInputsDirectory: URL = {
5959
///
6060
/// The build server can contain `$SDK_ARGS`, which will replaced by `"-sdk", "/path/to/sdk"` on macOS and by an empty
6161
/// string on all other platforms.
62-
package class BuildServerTestProject: MultiFileTestProject {
62+
package class ExternalBuildServerTestProject: MultiFileTestProject {
6363
package init(
6464
files: [RelativeFileLocation: String],
6565
buildServerConfigLocation: RelativeFileLocation = ".bsp/sourcekit-lsp.json",

0 commit comments

Comments
 (0)