@@ -21,6 +21,7 @@ import SwiftExtensions
21
21
import ToolchainRegistry
22
22
23
23
import struct TSCBasic. AbsolutePath
24
+ import struct TSCBasic. RelativePath
24
25
import func TSCBasic. resolveSymlinks
25
26
26
27
fileprivate typealias RequestCache < Request: RequestType & Hashable > = Cache < Request , Request . Response >
@@ -91,6 +92,112 @@ fileprivate extension DocumentURI {
91
92
}
92
93
}
93
94
95
+ fileprivate extension InitializeBuildResponse {
96
+ var sourceKitData : SourceKitInitializeBuildResponseData ? {
97
+ guard dataKind == nil || dataKind == . sourceKit else {
98
+ return nil
99
+ }
100
+ guard case . dictionary( let data) = data else {
101
+ return nil
102
+ }
103
+ return SourceKitInitializeBuildResponseData ( fromLSPDictionary: data)
104
+ }
105
+ }
106
+
107
+ /// A build system adapter is responsible for receiving messages from the `BuildSystemManager` and forwarding them to
108
+ /// the build system. For built-in build systems, this means that we need to translate the BSP messages to methods in
109
+ /// the `BuiltInBuildSystem` protocol. For external (aka. out-of-process, aka. BSP servers) build systems, this means
110
+ /// that we need to manage the external build system's lifetime.
111
+ private enum BuildSystemAdapter {
112
+ case builtIn( BuiltInBuildSystemAdapter )
113
+ case external( ExternalBuildSystemAdapter )
114
+ }
115
+
116
+ private extension BuildSystemKind {
117
+ private static func createBuiltInBuildSystemAdapter(
118
+ projectRoot: AbsolutePath ,
119
+ messagesToSourceKitLSPHandler: any MessageHandler ,
120
+ _ createBuildSystem: @Sendable ( _ connectionToSourceKitLSP: any Connection ) async throws -> BuiltInBuildSystem ?
121
+ ) async -> ( buildSystemAdapter: BuildSystemAdapter , connectionToBuildSystem: any Connection ) ? {
122
+ let connectionToSourceKitLSP = LocalConnection ( receiverName: " BuildSystemManager " )
123
+ connectionToSourceKitLSP. start ( handler: messagesToSourceKitLSPHandler)
124
+
125
+ let buildSystem = await orLog ( " Creating build system " ) {
126
+ try await createBuildSystem ( connectionToSourceKitLSP)
127
+ }
128
+ guard let buildSystem else {
129
+ logger. log ( " Failed to create build system at \( projectRoot. pathString) " )
130
+ return nil
131
+ }
132
+ logger. log ( " Created \( type ( of: buildSystem) , privacy: . public) at \( projectRoot. pathString) " )
133
+ let buildSystemAdapter = BuiltInBuildSystemAdapter (
134
+ underlyingBuildSystem: buildSystem,
135
+ connectionToSourceKitLSP: connectionToSourceKitLSP
136
+ )
137
+ let connectionToBuildSystem = LocalConnection ( receiverName: " Build system " )
138
+ connectionToBuildSystem. start ( handler: buildSystemAdapter)
139
+ return ( . builtIn( buildSystemAdapter) , connectionToBuildSystem)
140
+ }
141
+
142
+ /// Create a `BuildSystemAdapter` that manages a build system of this kind and return a connection that can be used
143
+ /// to send messages to the build system.
144
+ func createBuildSystemAdapter(
145
+ toolchainRegistry: ToolchainRegistry ,
146
+ options: SourceKitLSPOptions ,
147
+ buildSystemTestHooks testHooks: BuildSystemTestHooks ,
148
+ messagesToSourceKitLSPHandler: any MessageHandler
149
+ ) async -> ( buildSystemAdapter: BuildSystemAdapter , connectionToBuildSystem: any Connection ) ? {
150
+ switch self {
151
+ case . buildServer( projectRoot: let projectRoot) :
152
+ let buildSystem = await orLog ( " Creating external build system " ) {
153
+ try await ExternalBuildSystemAdapter (
154
+ projectRoot: projectRoot,
155
+ messagesToSourceKitLSPHandler: messagesToSourceKitLSPHandler
156
+ )
157
+ }
158
+ guard let buildSystem else {
159
+ logger. log ( " Failed to create external build system at \( projectRoot. pathString) " )
160
+ return nil
161
+ }
162
+ logger. log ( " Created external build server at \( projectRoot. pathString) " )
163
+ return ( . external( buildSystem) , buildSystem. connectionToBuildServer)
164
+ case . compilationDatabase( projectRoot: let projectRoot) :
165
+ return await Self . createBuiltInBuildSystemAdapter (
166
+ projectRoot: projectRoot,
167
+ messagesToSourceKitLSPHandler: messagesToSourceKitLSPHandler
168
+ ) { connectionToSourceKitLSP in
169
+ CompilationDatabaseBuildSystem (
170
+ projectRoot: projectRoot,
171
+ searchPaths: ( options. compilationDatabaseOrDefault. searchPaths ?? [ ] ) . compactMap {
172
+ try ? RelativePath ( validating: $0)
173
+ } ,
174
+ connectionToSourceKitLSP: connectionToSourceKitLSP
175
+ )
176
+ }
177
+ case . swiftPM( projectRoot: let projectRoot) :
178
+ return await Self . createBuiltInBuildSystemAdapter (
179
+ projectRoot: projectRoot,
180
+ messagesToSourceKitLSPHandler: messagesToSourceKitLSPHandler
181
+ ) { connectionToSourceKitLSP in
182
+ try await SwiftPMBuildSystem (
183
+ projectRoot: projectRoot,
184
+ toolchainRegistry: toolchainRegistry,
185
+ options: options,
186
+ connectionToSourceKitLSP: connectionToSourceKitLSP,
187
+ testHooks: testHooks. swiftPMTestHooks
188
+ )
189
+ }
190
+ case . testBuildSystem( projectRoot: let projectRoot) :
191
+ return await Self . createBuiltInBuildSystemAdapter (
192
+ projectRoot: projectRoot,
193
+ messagesToSourceKitLSPHandler: messagesToSourceKitLSPHandler
194
+ ) { connectionToSourceKitLSP in
195
+ TestBuildSystem ( projectRoot: projectRoot, connectionToSourceKitLSP: connectionToSourceKitLSP)
196
+ }
197
+ }
198
+ }
199
+ }
200
+
94
201
/// Entry point for all build system queries.
95
202
package actor BuildSystemManager : QueueBasedMessageHandler {
96
203
package static let signpostLoggingCategory : String = " build-system-manager-message-handling "
@@ -110,11 +217,6 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
110
217
/// get `fileBuildSettingsChanged` and `filesDependenciesUpdated` callbacks.
111
218
private var watchedFiles : [ DocumentURI : ( mainFile: DocumentURI , language: Language ) ] = [ : ]
112
219
113
- /// The underlying primary build system.
114
- ///
115
- /// - Important: The only time this should be modified is in the initializer. Afterwards, it must be constant.
116
- private var buildSystem : BuiltInBuildSystemAdapter ?
117
-
118
220
/// The connection through which the `BuildSystemManager` can send requests to the build system.
119
221
///
120
222
/// Access to this property should generally go through the non-underscored version, which waits until the build
@@ -131,12 +233,19 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
131
233
}
132
234
}
133
235
236
+ /// The build system adapter that is used to answer build system queries.
237
+ private var buildSystemAdapter : BuildSystemAdapter ?
238
+
134
239
/// If the underlying build system is a `TestBuildSystem`, return it. Otherwise, `nil`
135
240
///
136
241
/// - Important: For testing purposes only.
137
242
package var testBuildSystem : TestBuildSystem ? {
138
243
get async {
139
- return await buildSystem? . testBuildSystem
244
+ switch buildSystemAdapter {
245
+ case . builtIn( let builtInBuildSystemAdapter) : return await builtInBuildSystemAdapter. testBuildSystem
246
+ case . external: return nil
247
+ case nil : return nil
248
+ }
140
249
}
141
250
}
142
251
@@ -205,16 +314,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
205
314
/// The `SourceKitInitializeBuildResponseData` received from the `build/initialize` request, if any.
206
315
package var initializationData : SourceKitInitializeBuildResponseData ? {
207
316
get async {
208
- guard let initializeResult = await initializeResult. value else {
209
- return nil
210
- }
211
- guard initializeResult. dataKind == nil || initializeResult. dataKind == . sourceKit else {
212
- return nil
213
- }
214
- guard case . dictionary( let data) = initializeResult. data else {
215
- return nil
216
- }
217
- return SourceKitInitializeBuildResponseData ( fromLSPDictionary: data)
317
+ return await initializeResult. value? . sourceKitData
218
318
}
219
319
}
220
320
@@ -227,20 +327,15 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
227
327
self . toolchainRegistry = toolchainRegistry
228
328
self . options = options
229
329
self . projectRoot = buildSystemKind? . projectRoot
230
- self . buildSystem = await BuiltInBuildSystemAdapter (
231
- buildSystemKind: buildSystemKind,
330
+ let buildSystemAdapterAndConnection = await buildSystemKind? . createBuildSystemAdapter (
232
331
toolchainRegistry: toolchainRegistry,
233
332
options: options,
234
333
buildSystemTestHooks: buildSystemTestHooks,
235
334
messagesToSourceKitLSPHandler: WeakMessageHandler ( self )
236
335
)
237
- if let buildSystem {
238
- let connectionToBuildSystem = LocalConnection ( receiverName: " Build system " )
239
- connectionToBuildSystem. start ( handler: buildSystem)
240
- self . _connectionToBuildSystem = connectionToBuildSystem
241
- } else {
242
- self . _connectionToBuildSystem = nil
243
- }
336
+ self . buildSystemAdapter = buildSystemAdapterAndConnection? . buildSystemAdapter
337
+ self . _connectionToBuildSystem = buildSystemAdapterAndConnection? . connectionToBuildSystem
338
+
244
339
// The debounce duration of 500ms was chosen arbitrarily without any measurements.
245
340
self . filesDependenciesUpdatedDebouncer = Debouncer (
246
341
debounceDuration: . milliseconds( 500 ) ,
@@ -278,6 +373,27 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
278
373
)
279
374
)
280
375
}
376
+ if let initializeResponse, !( initializeResponse. sourceKitData? . sourceKitOptionsProvider ?? false ) ,
377
+ case . external( let externalBuildSystemAdapter) = buildSystemAdapter
378
+ {
379
+ // The BSP server does not support the pull-based settings model. Inject a `LegacyBuildServerBuildSystem` that
380
+ // offers the pull-based model to `BuildSystemManager` and uses the push-based model to get build settings from
381
+ // the build server.
382
+ logger. log ( " Launched a legacy BSP server. Using push-based build settings model. " )
383
+ let legacyBuildServer = await LegacyBuildServerBuildSystem (
384
+ projectRoot: buildSystemKind. projectRoot,
385
+ initializationData: initializeResponse,
386
+ externalBuildSystemAdapter
387
+ )
388
+ let adapter = BuiltInBuildSystemAdapter (
389
+ underlyingBuildSystem: legacyBuildServer,
390
+ connectionToSourceKitLSP: legacyBuildServer. connectionToSourceKitLSP
391
+ )
392
+ self . buildSystemAdapter = . builtIn( adapter)
393
+ let connectionToBuildSystem = LocalConnection ( receiverName: " Legacy BSP server " )
394
+ connectionToBuildSystem. start ( handler: adapter)
395
+ self . _connectionToBuildSystem = connectionToBuildSystem
396
+ }
281
397
_connectionToBuildSystem. send ( OnBuildInitializedNotification ( ) )
282
398
return initializeResponse
283
399
}
@@ -782,7 +898,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
782
898
}
783
899
784
900
package func sourceFiles( in targets: Set < BuildTargetIdentifier > ) async throws -> [ SourcesItem ] {
785
- guard let connectionToBuildSystem = await connectionToBuildSystem else {
901
+ guard let connectionToBuildSystem = await connectionToBuildSystem, !targets . isEmpty else {
786
902
return [ ]
787
903
}
788
904
0 commit comments