@@ -175,7 +175,7 @@ package final actor SemanticIndexManager {
175
175
176
176
/// The tasks to generate the build graph (resolving package dependencies, generating the build description,
177
177
/// ...) and to schedule indexing of modified tasks.
178
- private var scheduleIndexingTasks : [ UUID : Task < Void , Never > ] = [ : ]
178
+ private var buildGraphGenerationTasks : [ UUID : Task < Void , Never > ] = [ : ]
179
179
180
180
private let preparationUpToDateTracker = UpToDateTracker < BuildTargetIdentifier > ( )
181
181
@@ -225,7 +225,7 @@ package final actor SemanticIndexManager {
225
225
if inProgressPreparationTasks. values. contains ( where: { $0. purpose == . forEditorFunctionality } ) {
226
226
return . preparingFileForEditorFunctionality
227
227
}
228
- if !scheduleIndexingTasks . isEmpty {
228
+ if !buildGraphGenerationTasks . isEmpty {
229
229
return . schedulingIndexing
230
230
}
231
231
let preparationTasks = inProgressPreparationTasks. mapValues { inProgressTask in
@@ -270,46 +270,37 @@ package final actor SemanticIndexManager {
270
270
/// Regenerate the build graph (also resolving package dependencies) and then index all the source files known to the
271
271
/// build system that don't currently have a unit with a timestamp that matches the mtime of the file.
272
272
///
273
- /// If `filesToIndex` is `nil`, all files in the build system with out-of-date units are indexed.
274
- ///
275
- /// If `ensureAllUnitsRegisteredInIndex` is `true`, ensure that all units are registered in the index before
276
- /// triggering the indexing. This is a costly operation since it iterates through all the unit files on the file
273
+ /// This is a costly operation since it iterates through all the unit files on the file
277
274
/// system but if existing unit files are not known to the index, we might re-index those files even if they are
278
275
/// up-to-date. Generally this should be set to `true` during the initial indexing (in which case we might be need to
279
276
/// build the indexstore-db) and `false` for all subsequent indexing.
280
- package func scheduleBuildGraphGenerationAndBackgroundIndexAllFiles(
281
- filesToIndex: [ DocumentURI ] ? ,
282
- ensureAllUnitsRegisteredInIndex: Bool ,
283
- indexFilesWithUpToDateUnit: Bool
284
- ) async {
277
+ package func scheduleBuildGraphGenerationAndBackgroundIndexAllFiles( indexFilesWithUpToDateUnit: Bool ) async {
285
278
let taskId = UUID ( )
286
279
let generateBuildGraphTask = Task ( priority: . low) {
287
280
await withLoggingSubsystemAndScope ( subsystem: indexLoggingSubsystem, scope: " build-graph-generation " ) {
288
281
await hooks. buildGraphGenerationDidStart ? ( )
289
282
await self . buildSystemManager. waitForUpToDateBuildGraph ( )
290
- if ensureAllUnitsRegisteredInIndex {
291
- index. pollForUnitChangesAndWait ( )
292
- }
283
+ // Polling for unit changes is a costly operation since it iterates through all the unit files on the file
284
+ // system but if existing unit files are not known to the index, we might re-index those files even if they are
285
+ // up-to-date. This operation is worth the cost during initial indexing and during the manual re-index command.
286
+ index. pollForUnitChangesAndWait ( )
293
287
await hooks. buildGraphGenerationDidFinish ? ( )
294
288
// TODO: Ideally this would be a type like any Collection<DocumentURI> & Sendable but that doesn't work due to
295
289
// https://github.com/swiftlang/swift/issues/75602
296
290
let filesToIndex : [ DocumentURI ] =
297
- if let filesToIndex {
298
- filesToIndex
299
- } else {
300
- await orLog ( " Getting files to index " ) {
301
- try await self . buildSystemManager. buildableSourceFiles ( ) . sorted { $0. stringValue < $1. stringValue }
302
- } ?? [ ]
303
- }
304
- _ = await self . scheduleIndexing (
291
+ await orLog ( " Getting files to index " ) {
292
+ try await self . buildSystemManager. buildableSourceFiles ( ) . sorted { $0. stringValue < $1. stringValue }
293
+ } ?? [ ]
294
+ await self . scheduleIndexing (
305
295
of: filesToIndex,
296
+ waitForBuildGraphGenerationTasks: false ,
306
297
indexFilesWithUpToDateUnit: indexFilesWithUpToDateUnit,
307
298
priority: . low
308
299
)
309
- scheduleIndexingTasks [ taskId] = nil
300
+ buildGraphGenerationTasks [ taskId] = nil
310
301
}
311
302
}
312
- scheduleIndexingTasks [ taskId] = generateBuildGraphTask
303
+ buildGraphGenerationTasks [ taskId] = generateBuildGraphTask
313
304
indexProgressStatusDidChange ( )
314
305
}
315
306
@@ -318,15 +309,11 @@ package final actor SemanticIndexManager {
318
309
package func scheduleReindex( ) async {
319
310
await indexStoreUpToDateTracker. markAllKnownOutOfDate ( )
320
311
await preparationUpToDateTracker. markAllKnownOutOfDate ( )
321
- await scheduleBuildGraphGenerationAndBackgroundIndexAllFiles (
322
- filesToIndex: nil ,
323
- ensureAllUnitsRegisteredInIndex: false ,
324
- indexFilesWithUpToDateUnit: true
325
- )
312
+ await scheduleBuildGraphGenerationAndBackgroundIndexAllFiles ( indexFilesWithUpToDateUnit: true )
326
313
}
327
314
328
315
private func waitForBuildGraphGenerationTasks( ) async {
329
- await scheduleIndexingTasks . values. concurrentForEach { await $0. value }
316
+ await buildGraphGenerationTasks . values. concurrentForEach { await $0. value }
330
317
}
331
318
332
319
/// Wait for all in-progress index tasks to finish.
@@ -358,86 +345,98 @@ package final actor SemanticIndexManager {
358
345
logger. info (
359
346
" Waiting for up-to-date index for \( uris. map { $0. fileURL? . lastPathComponent ?? $0. stringValue } . joined ( separator: " , " ) ) "
360
347
)
361
- // If there's a build graph update in progress wait for that to finish so we can discover new files in the build
362
- // system.
363
- await waitForBuildGraphGenerationTasks ( )
364
348
365
349
// Create a new index task for the files that aren't up-to-date. The newly scheduled index tasks will
366
350
// - Wait for the existing index operations to finish if they have the same number of files.
367
351
// - Reschedule the background index task in favor of an index task with fewer source files.
368
- await self . scheduleIndexing ( of: uris, indexFilesWithUpToDateUnit: false , priority: nil ) . value
352
+ await self . scheduleIndexing (
353
+ of: uris,
354
+ waitForBuildGraphGenerationTasks: true ,
355
+ indexFilesWithUpToDateUnit: false ,
356
+ priority: nil
357
+ ) . value
369
358
index. pollForUnitChangesAndWait ( )
370
359
logger. debug ( " Done waiting for up-to-date index " )
371
360
}
372
361
373
- package func filesDidChange( _ events: [ FileEvent ] ) async {
374
- // We only re-index the files that were changed and don't re-index any of their dependencies. See the
375
- // `Documentation/Files_To_Reindex.md` file.
376
- let changedFiles = events. map ( \. uri)
377
- await indexStoreUpToDateTracker. markOutOfDate ( changedFiles)
378
-
379
- // Preparation tracking should be per file. For now consider any non-known-language change as having to re-prepare
380
- // the target itself so that we re-prepare potential input files to plugins.
381
- // https://github.com/swiftlang/sourcekit-lsp/issues/1975
382
- var outOfDateTargets = Set < BuildTargetIdentifier > ( )
383
- for file in changedFiles {
384
- let changedTargets = await buildSystemManager. targets ( for: file)
385
- if Language ( inferredFromFileExtension: file) == nil {
386
- outOfDateTargets. formUnion ( changedTargets)
387
- }
388
-
389
- let dependentTargets = await buildSystemManager. targets ( dependingOn: changedTargets)
390
- outOfDateTargets. formUnion ( dependentTargets)
391
- }
392
- if !outOfDateTargets. isEmpty {
393
- logger. info (
394
- """
395
- Marking dependent targets as out-of-date: \
396
- \( String ( outOfDateTargets. map ( \. uri. stringValue) . joined ( separator: " , " ) ) )
397
- """
398
- )
399
- await preparationUpToDateTracker. markOutOfDate ( outOfDateTargets)
400
- }
401
-
402
- await scheduleBuildGraphGenerationAndBackgroundIndexAllFiles (
403
- filesToIndex: changedFiles,
404
- ensureAllUnitsRegisteredInIndex: false ,
405
- indexFilesWithUpToDateUnit: false
406
- )
407
- }
408
-
409
- package func buildTargetsChanged( _ changes: [ BuildTargetEvent ] ? ) async {
410
- let targets = changes? . map ( \. target)
362
+ package func filesDidChange( _ events: [ FileEvent ] ) {
363
+ // We don't care about the order of file change events, so we can execute the changes in a task, allowing further
364
+ // file change handling to continue before we have scheduled the file re-indexing.
365
+ Task {
366
+ // We only re-index the files that were changed and don't re-index any of their dependencies. See the
367
+ // `Documentation/Files_To_Reindex.md` file.
368
+ let changedFiles = events. map ( \. uri)
369
+ await indexStoreUpToDateTracker. markOutOfDate ( changedFiles)
370
+
371
+ // Preparation tracking should be per file. For now consider any non-known-language change as having to re-prepare
372
+ // the target itself so that we re-prepare potential input files to plugins.
373
+ // https://github.com/swiftlang/sourcekit-lsp/issues/1975
374
+ var outOfDateTargets = Set < BuildTargetIdentifier > ( )
375
+ for file in changedFiles {
376
+ let changedTargets = await buildSystemManager. targets ( for: file)
377
+ if Language ( inferredFromFileExtension: file) == nil {
378
+ outOfDateTargets. formUnion ( changedTargets)
379
+ }
411
380
412
- if let targets {
413
- var targetsAndDependencies = targets
414
- targetsAndDependencies += await buildSystemManager . targets ( dependingOn : Set ( targets ) )
415
- if !targetsAndDependencies . isEmpty {
381
+ let dependentTargets = await buildSystemManager . targets ( dependingOn : changedTargets )
382
+ outOfDateTargets . formUnion ( dependentTargets )
383
+ }
384
+ if !outOfDateTargets . isEmpty {
416
385
logger. info (
417
386
"""
418
387
Marking dependent targets as out-of-date: \
419
- \( String ( targetsAndDependencies . map ( \. uri. stringValue) . joined ( separator: " , " ) ) )
388
+ \( String ( outOfDateTargets . map ( \. uri. stringValue) . joined ( separator: " , " ) ) )
420
389
"""
421
390
)
422
- await preparationUpToDateTracker. markOutOfDate ( targetsAndDependencies )
391
+ await preparationUpToDateTracker. markOutOfDate ( outOfDateTargets )
423
392
}
424
- } else {
425
- await preparationUpToDateTracker. markAllKnownOutOfDate ( )
426
- }
427
393
428
- await orLog ( " Scheduling re-indexing of changed targets " ) {
429
- var sourceFiles = try await self . buildSystemManager. sourceFiles ( includeNonBuildableFiles: false )
430
- if let targets {
431
- sourceFiles = sourceFiles. filter { !$0. value. targets. isDisjoint ( with: targets) }
432
- }
433
- _ = await scheduleIndexing (
434
- of: sourceFiles. keys,
394
+ await scheduleIndexing (
395
+ of: changedFiles,
396
+ waitForBuildGraphGenerationTasks: true ,
435
397
indexFilesWithUpToDateUnit: false ,
436
398
priority: . low
437
399
)
438
400
}
439
401
}
440
402
403
+ package func buildTargetsChanged( _ changes: [ BuildTargetEvent ] ? ) async {
404
+ // We don't care about the order of target change events, so we can execute the changes in a task, allowing further
405
+ // target change handling to continue.
406
+ Task {
407
+ let targets = changes? . map ( \. target)
408
+
409
+ if let targets {
410
+ var targetsAndDependencies = targets
411
+ targetsAndDependencies += await buildSystemManager. targets ( dependingOn: Set ( targets) )
412
+ if !targetsAndDependencies. isEmpty {
413
+ logger. info (
414
+ """
415
+ Marking dependent targets as out-of-date: \
416
+ \( String ( targetsAndDependencies. map ( \. uri. stringValue) . joined ( separator: " , " ) ) )
417
+ """
418
+ )
419
+ await preparationUpToDateTracker. markOutOfDate ( targetsAndDependencies)
420
+ }
421
+ } else {
422
+ await preparationUpToDateTracker. markAllKnownOutOfDate ( )
423
+ }
424
+
425
+ await orLog ( " Scheduling re-indexing of changed targets " ) {
426
+ var sourceFiles = try await self . buildSystemManager. sourceFiles ( includeNonBuildableFiles: false )
427
+ if let targets {
428
+ sourceFiles = sourceFiles. filter { !$0. value. targets. isDisjoint ( with: targets) }
429
+ }
430
+ await scheduleIndexing (
431
+ of: sourceFiles. keys,
432
+ waitForBuildGraphGenerationTasks: true ,
433
+ indexFilesWithUpToDateUnit: false ,
434
+ priority: . low
435
+ )
436
+ }
437
+ }
438
+ }
439
+
441
440
/// Returns the files that should be indexed to get up-to-date index information for the given files.
442
441
///
443
442
/// If `files` contains a header file, this will return a `FileToIndex` that re-indexes a main file which includes the
@@ -696,12 +695,23 @@ package final actor SemanticIndexManager {
696
695
/// known to be up-to-date based on `indexStoreUpToDateTracker` or the unit timestamp will not be re-indexed unless
697
696
/// `indexFilesWithUpToDateUnit` is `true`.
698
697
///
698
+ /// If `waitForBuildGraphGenerationTasks` is `true` and there are any tasks in progress that wait for an up-to-date
699
+ /// build graph, wait for those to finish. This is helpful so to avoid the following: We receive a build target change
700
+ /// notification before the initial background indexing has finished. If indexstore-db hasn't been initialized with
701
+ /// `pollForUnitChangesAndWait` yet, we might not know that the changed targets' files' index is actually up-to-date
702
+ /// and would thus schedule an unnecessary re-index of the file.
703
+ ///
699
704
/// The returned task finishes when all files are indexed.
705
+ @discardableResult
700
706
package func scheduleIndexing(
701
707
of files: some Collection < DocumentURI > & Sendable ,
708
+ waitForBuildGraphGenerationTasks: Bool ,
702
709
indexFilesWithUpToDateUnit: Bool ,
703
710
priority: TaskPriority ?
704
711
) async -> Task < Void , Never > {
712
+ if waitForBuildGraphGenerationTasks {
713
+ await self . waitForBuildGraphGenerationTasks ( )
714
+ }
705
715
// Perform a quick initial check to whether the files is up-to-date, in which case we don't need to schedule a
706
716
// prepare and index operation at all.
707
717
// We will check the up-to-date status again in `IndexTaskDescription.execute`. This ensures that if we schedule
0 commit comments