@@ -89,9 +89,6 @@ public actor BuildSystemManager {
89
89
/// The set of watched files, along with their main file and language.
90
90
var watchedFiles : [ DocumentURI : ( mainFile: DocumentURI , language: Language ) ] = [ : ]
91
91
92
- /// Statuses for each main file, containing build settings from the build systems.
93
- var mainFileStatuses : [ DocumentURI : MainFileStatus ] = [ : ]
94
-
95
92
/// The underlying primary build system.
96
93
let buildSystem : BuildSystem ?
97
94
@@ -148,16 +145,7 @@ extension BuildSystemManager {
148
145
self . mainFilesProvider = mainFilesProvider
149
146
}
150
147
151
- /// Get the build settings for the given document, assuming it has the given
152
- /// language.
153
- ///
154
- /// Returns `nil` if no build settings are available in the build system and
155
- /// no fallback build settings can be computed.
156
- ///
157
- /// `isFallback` is `true` if the build settings couldn't be computed and
158
- /// fallback settings are used. These fallback settings are most likely not
159
- /// correct and provide limited semantic functionality.
160
- public func buildSettings(
148
+ private func buildSettings(
161
149
for document: DocumentURI ,
162
150
language: Language
163
151
) async -> ( buildSettings: FileBuildSettings , isFallback: Bool ) ? {
@@ -183,6 +171,28 @@ extension BuildSystemManager {
183
171
}
184
172
}
185
173
174
+ /// Returns the build settings for the given document.
175
+ ///
176
+ /// If the document doesn't have builds settings by itself, eg. because it is
177
+ /// a C header file, the build settings will be inferred from the primary main
178
+ /// file of the document. In practice this means that we will compute the build
179
+ /// settings of a C file that includes the header and replace any file
180
+ /// references to that C file in the build settings by the header file.
181
+ public func buildSettingsInferredFromMainFile(
182
+ for document: DocumentURI ,
183
+ language: Language
184
+ ) async -> ( buildSettings: FileBuildSettings , isFallback: Bool ) ? {
185
+ if let mainFile = mainFilesProvider? . mainFilesContainingFile ( document) . first {
186
+ if let mainFileBuildSettings = await buildSettings ( for: mainFile, language: language) {
187
+ return (
188
+ buildSettings: mainFileBuildSettings. buildSettings. patching ( newFile: document. pseudoPath, originalFile: mainFile. pseudoPath) ,
189
+ isFallback: mainFileBuildSettings. isFallback
190
+ )
191
+ }
192
+ }
193
+ return await buildSettings ( for: document, language: language)
194
+ }
195
+
186
196
public func registerForChangeNotifications( for uri: DocumentURI , language: Language ) async {
187
197
log ( " registerForChangeNotifications( \( uri. pseudoPath) ) " )
188
198
let mainFile : DocumentURI
@@ -195,13 +205,8 @@ extension BuildSystemManager {
195
205
self . watchedFiles [ uri] = ( mainFile, language)
196
206
}
197
207
198
- let newStatus = await self . cachedStatusOrRegisterForSettings ( for: mainFile, language: language)
199
-
200
- if let mainChange = newStatus. buildSettingsChange,
201
- let delegate = self . _delegate {
202
- let change = self . convert ( change: mainChange, ofMainFile: mainFile, to: uri)
203
- await delegate. fileBuildSettingsChanged ( [ uri: change] )
204
- }
208
+ await buildSystem? . registerForChangeNotifications ( for: mainFile, language: language)
209
+ fallbackBuildSystem? . registerForChangeNotifications ( for: mainFile, language: language)
205
210
}
206
211
207
212
/// Return settings for `file` based on the `change` settings for `mainFile`.
@@ -222,116 +227,6 @@ extension BuildSystemManager {
222
227
}
223
228
}
224
229
225
- /// Handle a request for `FileBuildSettings` on `mainFile`.
226
- ///
227
- /// Updates and returns the new `MainFileStatus` for `mainFile`.
228
- func cachedStatusOrRegisterForSettings(
229
- for mainFile: DocumentURI ,
230
- language: Language
231
- ) async -> MainFileStatus {
232
- // If we already have a status for the main file, use that.
233
- // Don't update any existing timeout.
234
- if let status = self . mainFileStatuses [ mainFile] {
235
- return status
236
- }
237
- // This is a new `mainFile` that we need to handle. We need to fetch the
238
- // build settings.
239
- let newStatus : MainFileStatus
240
- if let buildSystem = self . buildSystem {
241
- // Register the timeout if it's applicable.
242
- if let fallback = self . fallbackBuildSystem {
243
- Task {
244
- try await Task . sleep ( nanoseconds: UInt64 ( self . fallbackSettingsTimeout. nanoseconds ( ) !) )
245
- await self . handleFallbackTimer ( for: mainFile, language: language, fallback)
246
- }
247
- }
248
-
249
- // Intentionally register with the `BuildSystem` after setting the fallback to allow for
250
- // testing of the fallback system triggering before the `BuildSystem` can reply (e.g. if a
251
- // fallback time of 0 is specified).
252
- await buildSystem. registerForChangeNotifications ( for: mainFile, language: language)
253
-
254
-
255
- newStatus = . waiting
256
- } else if let fallback = self . fallbackBuildSystem {
257
- // Only have a fallback build system. We consider it be a primary build
258
- // system that functions synchronously.
259
- if let settings = fallback. buildSettings ( for: mainFile, language: language) {
260
- newStatus = . primary( settings)
261
- } else {
262
- newStatus = . unsupported
263
- }
264
- } else { // Don't have any build systems.
265
- newStatus = . unsupported
266
- }
267
-
268
- if let status = self . mainFileStatuses [ mainFile] {
269
- // Since we await above, another call to `cachedStatusOrRegisterForSettings`
270
- // might have set the main file status of `mainFile`. If this race happened,
271
- // return the value set by the concurrently executing function. This is safe
272
- // since all calls from this function are either side-effect free or
273
- // idempotent.
274
- return status
275
- }
276
-
277
- self . mainFileStatuses [ mainFile] = newStatus
278
- return newStatus
279
- }
280
-
281
- /// Update and notify our delegate for the given main file changes if they are
282
- /// convertible into `FileBuildSettingsChange`.
283
- func updateAndNotifyStatuses( changes: [ DocumentURI : MainFileStatus ] ) async {
284
- var changedWatchedFiles = [ DocumentURI: FileBuildSettingsChange] ( )
285
- for (mainFile, status) in changes {
286
- let watches = self . watchedFiles. filter { $1. mainFile == mainFile }
287
- guard !watches. isEmpty else {
288
- // Possible notification after the file was unregistered. Ignore.
289
- continue
290
- }
291
- let prevStatus = self . mainFileStatuses [ mainFile]
292
- self . mainFileStatuses [ mainFile] = status
293
-
294
- // It's possible that the command line arguments didn't change
295
- // (waitingFallback --> fallback), in that case we don't need to report a change.
296
- // If we were waiting though, we need to emit an initial change.
297
- guard prevStatus == . waiting || status. buildSettings != prevStatus? . buildSettings else {
298
- continue
299
- }
300
- if let change = status. buildSettingsChange {
301
- for watch in watches {
302
- let newChange =
303
- self . convert ( change: change, ofMainFile: mainFile, to: watch. key)
304
- changedWatchedFiles [ watch. key] = newChange
305
- }
306
- }
307
- }
308
-
309
- if !changedWatchedFiles. isEmpty, let delegate = self . _delegate {
310
- await delegate. fileBuildSettingsChanged ( changedWatchedFiles)
311
- }
312
- }
313
-
314
- /// Handle the fallback timer firing for a given `mainFile`.
315
- ///
316
- /// Since this doesn't occur immediately it's possible that the `mainFile` is
317
- /// no longer referenced or is referenced by multiple watched files.
318
- func handleFallbackTimer(
319
- for mainFile: DocumentURI ,
320
- language: Language ,
321
- _ fallback: FallbackBuildSystem
322
- ) async {
323
- // There won't be a current status if it's unreferenced by any watched file.
324
- // Similarly, if the status isn't `waiting` then there's nothing to do.
325
- guard let status = self . mainFileStatuses [ mainFile] , status == . waiting else {
326
- return
327
- }
328
- if let settings = fallback. buildSettings ( for: mainFile, language: language) {
329
- await self . updateAndNotifyStatuses ( changes: [ mainFile: . waitingUsingFallback( settings) ] )
330
- } else {
331
- // Keep the status as waiting.
332
- }
333
- }
334
-
335
230
public func unregisterForChangeNotifications( for uri: DocumentURI ) async {
336
231
guard let mainFile = self . watchedFiles [ uri] ? . mainFile else {
337
232
log ( " Unbalanced calls for registerForChangeNotifications and unregisterForChangeNotifications " , level: . warning)
@@ -347,7 +242,6 @@ extension BuildSystemManager {
347
242
if !self . watchedFiles. values. lazy. map ( { $0. mainFile } ) . contains ( mainFile) {
348
243
// This was the last reference to the main file. Remove it.
349
244
await self . buildSystem? . unregisterForChangeNotifications ( for: mainFile)
350
- self . mainFileStatuses [ mainFile] = nil
351
245
}
352
246
}
353
247
@@ -361,41 +255,20 @@ extension BuildSystemManager {
361
255
362
256
extension BuildSystemManager : BuildSystemDelegate {
363
257
// FIXME: (async) Make this method isolated once `BuildSystemDelegate` has ben asyncified
364
- public nonisolated func fileBuildSettingsChanged( _ changes: [ DocumentURI : FileBuildSettingsChange ] ) {
258
+ public nonisolated func fileBuildSettingsChanged( _ changes: Set < DocumentURI > ) {
365
259
Task {
366
260
await fileBuildSettingsChangedImpl ( changes)
367
261
}
368
262
}
369
263
370
- public func fileBuildSettingsChangedImpl( _ changes: [ DocumentURI : FileBuildSettingsChange ] ) async {
371
- let statusChanges : [ DocumentURI : MainFileStatus ] =
372
- changes. reduce ( into: [ : ] ) { ( result, entry) in
373
- let mainFile = entry. key
374
- let settingsChange = entry. value
375
- let watches = self . watchedFiles. filter { $1. mainFile == mainFile }
376
- guard let firstWatch = watches. first else {
377
- // Possible notification after the file was unregistered. Ignore.
378
- return
379
- }
380
- let newStatus : MainFileStatus
381
-
382
- if let newSettings = settingsChange. newSettings {
383
- newStatus = settingsChange. isFallback ? . fallback( newSettings) : . primary( newSettings)
384
- } else if let fallback = self . fallbackBuildSystem {
385
- // FIXME: we need to stop threading the language everywhere, or we need the build system
386
- // itself to pass it in here. Or alternatively cache the fallback settings/language earlier?
387
- let language = firstWatch. value. language
388
- if let settings = fallback. buildSettings ( for: mainFile, language: language) {
389
- newStatus = . fallback( settings)
390
- } else {
391
- newStatus = . unsupported
392
- }
393
- } else {
394
- newStatus = . unsupported
395
- }
396
- result [ mainFile] = newStatus
264
+ public func fileBuildSettingsChangedImpl( _ changedFiles: Set < DocumentURI > ) async {
265
+ let changedWatchedFiles = changedFiles. flatMap ( { mainFile in
266
+ self . watchedFiles. filter { $1. mainFile == mainFile } . keys
267
+ } )
268
+
269
+ if !changedWatchedFiles. isEmpty, let delegate = self . _delegate {
270
+ await delegate. fileBuildSettingsChanged ( Set ( changedWatchedFiles) )
397
271
}
398
- await self . updateAndNotifyStatuses ( changes: statusChanges)
399
272
}
400
273
401
274
// FIXME: (async) Make this method isolated once `BuildSystemDelegate` has ben asyncified
@@ -461,7 +334,7 @@ extension BuildSystemManager: MainFilesDelegate {
461
334
public func mainFilesChangedImpl( ) async {
462
335
let origWatched = self . watchedFiles
463
336
self . watchedFiles = [ : ]
464
- var buildSettingsChanges = [ DocumentURI: FileBuildSettingsChange ] ( )
337
+ var buildSettingsChanges = Set < DocumentURI > ( )
465
338
466
339
for (uri, state) in origWatched {
467
340
let mainFiles = self . _mainFilesProvider? . mainFilesContainingFile ( uri) ?? [ ]
@@ -474,12 +347,7 @@ extension BuildSystemManager: MainFilesDelegate {
474
347
log ( " main file for ' \( uri) ' changed old: ' \( state. mainFile) ' -> new: ' \( newMainFile) ' " , level: . info)
475
348
await self . checkUnreferencedMainFile ( state. mainFile)
476
349
477
- let newStatus = await self . cachedStatusOrRegisterForSettings (
478
- for: newMainFile, language: language)
479
- if let change = newStatus. buildSettingsChange {
480
- let newChange = self . convert ( change: change, ofMainFile: newMainFile, to: uri)
481
- buildSettingsChanges [ uri] = newChange
482
- }
350
+ buildSettingsChanges. insert ( uri)
483
351
}
484
352
}
485
353
@@ -495,11 +363,6 @@ extension BuildSystemManager {
495
363
public func _cachedMainFile( for uri: DocumentURI ) -> DocumentURI ? {
496
364
watchedFiles [ uri] ? . mainFile
497
365
}
498
-
499
- /// *For Testing* Returns the main file used for `uri`, if this is a registered file.
500
- public func _cachedMainFileSettings( for uri: DocumentURI ) -> FileBuildSettings ? ? {
501
- mainFileStatuses [ uri] ? . buildSettings
502
- }
503
366
}
504
367
505
368
/// Choose a new main file for the given uri, preferring to use a previous main file if still
0 commit comments