@@ -21,6 +21,7 @@ import SwiftExtensions
2121import ToolchainRegistry
2222
2323import struct TSCBasic. AbsolutePath
24+ import struct TSCBasic. RelativePath
2425import func TSCBasic. resolveSymlinks
2526
2627fileprivate typealias RequestCache < Request: RequestType & Hashable > = Cache < Request , Request . Response >
@@ -91,6 +92,112 @@ fileprivate extension DocumentURI {
9192 }
9293}
9394
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+
94201/// Entry point for all build system queries.
95202package actor BuildSystemManager : QueueBasedMessageHandler {
96203 package static let signpostLoggingCategory : String = " build-system-manager-message-handling "
@@ -110,11 +217,6 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
110217 /// get `fileBuildSettingsChanged` and `filesDependenciesUpdated` callbacks.
111218 private var watchedFiles : [ DocumentURI : ( mainFile: DocumentURI , language: Language ) ] = [ : ]
112219
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-
118220 /// The connection through which the `BuildSystemManager` can send requests to the build system.
119221 ///
120222 /// 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 {
131233 }
132234 }
133235
236+ /// The build system adapter that is used to answer build system queries.
237+ private var buildSystemAdapter : BuildSystemAdapter ?
238+
134239 /// If the underlying build system is a `TestBuildSystem`, return it. Otherwise, `nil`
135240 ///
136241 /// - Important: For testing purposes only.
137242 package var testBuildSystem : TestBuildSystem ? {
138243 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+ }
140249 }
141250 }
142251
@@ -205,16 +314,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
205314 /// The `SourceKitInitializeBuildResponseData` received from the `build/initialize` request, if any.
206315 package var initializationData : SourceKitInitializeBuildResponseData ? {
207316 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
218318 }
219319 }
220320
@@ -227,20 +327,15 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
227327 self . toolchainRegistry = toolchainRegistry
228328 self . options = options
229329 self . projectRoot = buildSystemKind? . projectRoot
230- self . buildSystem = await BuiltInBuildSystemAdapter (
231- buildSystemKind: buildSystemKind,
330+ let buildSystemAdapterAndConnection = await buildSystemKind? . createBuildSystemAdapter (
232331 toolchainRegistry: toolchainRegistry,
233332 options: options,
234333 buildSystemTestHooks: buildSystemTestHooks,
235334 messagesToSourceKitLSPHandler: WeakMessageHandler ( self )
236335 )
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+
244339 // The debounce duration of 500ms was chosen arbitrarily without any measurements.
245340 self . filesDependenciesUpdatedDebouncer = Debouncer (
246341 debounceDuration: . milliseconds( 500 ) ,
@@ -278,6 +373,27 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
278373 )
279374 )
280375 }
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+ }
281397 _connectionToBuildSystem. send ( OnBuildInitializedNotification ( ) )
282398 return initializeResponse
283399 }
@@ -782,7 +898,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
782898 }
783899
784900 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 {
786902 return [ ]
787903 }
788904
0 commit comments