@@ -482,8 +482,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
482
482
self . filesDependenciesUpdatedDebouncer = Debouncer (
483
483
debounceDuration: . milliseconds( 500 ) ,
484
484
combineResults: { $0. union ( $1) } ,
485
- makeCall: {
486
- [ weak self] ( filesWithUpdatedDependencies) in
485
+ makeCall: { [ weak self] ( filesWithUpdatedDependencies) in
487
486
guard let self, let delegate = await self . delegate else {
488
487
logger. fault ( " Not calling filesDependenciesUpdated because no delegate exists in SwiftPMBuildServer " )
489
488
return
@@ -502,8 +501,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
502
501
self . filesBuildSettingsChangedDebouncer = Debouncer (
503
502
debounceDuration: . milliseconds( 20 ) ,
504
503
combineResults: { $0. union ( $1) } ,
505
- makeCall: {
506
- [ weak self] ( filesWithChangedBuildSettings) in
504
+ makeCall: { [ weak self] ( filesWithChangedBuildSettings) in
507
505
guard let self, let delegate = await self . delegate else {
508
506
logger. fault ( " Not calling fileBuildSettingsChanged because no delegate exists in SwiftPMBuildServer " )
509
507
return
@@ -670,31 +668,76 @@ package actor BuildServerManager: QueueBasedMessageHandler {
670
668
}
671
669
672
670
private func didChangeBuildTarget( notification: OnBuildTargetDidChangeNotification ) async {
673
- let updatedTargets : Set < BuildTargetIdentifier > ? =
671
+ let changedTargets : Set < BuildTargetIdentifier > ? =
674
672
if let changes = notification. changes {
675
673
Set ( changes. map ( \. target) )
676
674
} else {
677
675
nil
678
676
}
679
- self . cachedAdjustedSourceKitOptions. clear ( isolation: self ) { cacheKey in
680
- guard let updatedTargets else {
681
- // All targets might have changed
682
- return true
677
+ await self . buildTargetsDidChange ( . didChangeBuildTargets( changedTargets: changedTargets) )
678
+ }
679
+
680
+ private enum BuildTargetsChange {
681
+ case didChangeBuildTargets( changedTargets: Set < BuildTargetIdentifier > ? )
682
+ case buildTargetsReceivedResultAfterTimeout(
683
+ request: WorkspaceBuildTargetsRequest ,
684
+ newResult: [ BuildTargetIdentifier : BuildTargetInfo ]
685
+ )
686
+ case sourceFilesReceivedResultAfterTimeout(
687
+ request: BuildTargetSourcesRequest ,
688
+ newResult: BuildTargetSourcesResponse
689
+ )
690
+ }
691
+
692
+ /// Update the cached state in `BuildServerManager` because new data was received from the BSP server.
693
+ ///
694
+ /// This handles a few seemingly unrelated reasons to ensure that we think about which caches to invalidate in the
695
+ /// other scenarios as well, when making changes in here.
696
+ private func buildTargetsDidChange( _ stateChange: BuildTargetsChange ) async {
697
+ let changedTargets : Set < BuildTargetIdentifier > ?
698
+
699
+ switch stateChange {
700
+ case . didChangeBuildTargets( let changedTargetsValue) :
701
+ changedTargets = changedTargetsValue
702
+ self . cachedAdjustedSourceKitOptions. clear ( isolation: self ) { cacheKey in
703
+ guard let changedTargets else {
704
+ // All targets might have changed
705
+ return true
706
+ }
707
+ return changedTargets. contains ( cacheKey. target)
683
708
}
684
- return updatedTargets . contains ( cacheKey . target )
685
- }
686
- self . cachedBuildTargets . clearAll ( isolation : self )
687
- self . cachedTargetSources . clear ( isolation : self ) { cacheKey in
688
- guard let updatedTargets else {
689
- // All targets might have changed
690
- return true
709
+ self . cachedBuildTargets . clearAll ( isolation : self )
710
+ self . cachedTargetSources . clear ( isolation : self ) { cacheKey in
711
+ guard let changedTargets else {
712
+ // All targets might have changed
713
+ return true
714
+ }
715
+ return !changedTargets . intersection ( cacheKey . targets ) . isEmpty
691
716
}
692
- return !updatedTargets. intersection ( cacheKey. targets) . isEmpty
693
- }
717
+ case . buildTargetsReceivedResultAfterTimeout( let request, let newResult) :
718
+ changedTargets = nil
719
+
720
+ // Caches not invalidated:
721
+ // - cachedAdjustedSourceKitOptions: We would not have requested SourceKit options for targets that we didn't
722
+ // know about. Even if we did, the build server now telling us about the target should not change the options of
723
+ // the file within the target
724
+ // - cachedTargetSources: Similar to cachedAdjustedSourceKitOptions, we would not have requested sources for
725
+ // targets that we didn't know about and if we did, they wouldn't be affected
726
+ self . cachedBuildTargets. set ( request, to: newResult)
727
+ case . sourceFilesReceivedResultAfterTimeout( let request, let newResult) :
728
+ changedTargets = Set ( request. targets)
729
+
730
+ // Caches not invalidated:
731
+ // - cachedAdjustedSourceKitOptions: Same as for buildTargetsReceivedResultAfterTimeout.
732
+ // - cachedBuildTargets: Getting a result for the source files in a target doesn't change anything about the
733
+ // target's existence.
734
+ self . cachedTargetSources. set ( request, to: newResult)
735
+ }
736
+ // Clear caches that capture global state and are affected by all changes
694
737
self . cachedSourceFilesAndDirectories. clearAll ( isolation: self )
695
738
self . scheduleRecomputeCopyFileMap ( )
696
739
697
- await delegate? . buildTargetsChanged ( notification . changes )
740
+ await delegate? . buildTargetsChanged ( changedTargets )
698
741
await filesBuildSettingsChangedDebouncer. scheduleCall ( Set ( watchedFiles. keys) )
699
742
}
700
743
@@ -898,6 +941,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
898
941
previousUpdateTask? . cancel ( )
899
942
await orLog ( " Re-computing copy file map " ) {
900
943
let sourceFilesAndDirectories = try await self . sourceFilesAndDirectories ( )
944
+ try Task . checkCancellation ( )
901
945
var copiedFileMap : [ DocumentURI : DocumentURI ] = [ : ]
902
946
for (file, fileInfo) in sourceFilesAndDirectories. files {
903
947
for copyDestination in fileInfo. copyDestinations {
@@ -1024,7 +1068,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
1024
1068
if fallbackAfterTimeout {
1025
1069
try await withTimeout ( options. buildSettingsTimeoutOrDefault) {
1026
1070
return try await self . buildSettingsFromBuildServer ( for: document, in: target, language: language)
1027
- } resultReceivedAfterTimeout: {
1071
+ } resultReceivedAfterTimeout: { _ in
1028
1072
await self . filesBuildSettingsChangedDebouncer. scheduleCall ( [ document] )
1029
1073
}
1030
1074
} else {
@@ -1082,7 +1126,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
1082
1126
} else {
1083
1127
try await withTimeout ( options. buildSettingsTimeoutOrDefault) {
1084
1128
await self . canonicalTarget ( for: mainFile)
1085
- } resultReceivedAfterTimeout: {
1129
+ } resultReceivedAfterTimeout: { _ in
1086
1130
await self . filesBuildSettingsChangedDebouncer. scheduleCall ( [ document] )
1087
1131
}
1088
1132
}
@@ -1236,28 +1280,39 @@ package actor BuildServerManager: QueueBasedMessageHandler {
1236
1280
1237
1281
let request = WorkspaceBuildTargetsRequest ( )
1238
1282
let result = try await cachedBuildTargets. get ( request, isolation: self ) { request in
1239
- let buildTargets = try await buildServerAdapter. send ( request) . targets
1240
- let ( depths, dependents) = await self . targetDepthsAndDependents ( for: buildTargets)
1241
- var result : [ BuildTargetIdentifier : BuildTargetInfo ] = [ : ]
1242
- result. reserveCapacity ( buildTargets. count)
1243
- for buildTarget in buildTargets {
1244
- guard result [ buildTarget. id] == nil else {
1245
- logger. error ( " Found two targets with the same ID \( buildTarget. id) " )
1246
- continue
1247
- }
1248
- let depth : Int
1249
- if let d = depths [ buildTarget. id] {
1250
- depth = d
1251
- } else {
1252
- logger. fault ( " Did not compute depth for target \( buildTarget. id) " )
1253
- depth = 0
1283
+ let result = try await withTimeout ( self . options. buildServerWorkspaceRequestsTimeoutOrDefault) {
1284
+ let buildTargets = try await buildServerAdapter. send ( request) . targets
1285
+ let ( depths, dependents) = await self . targetDepthsAndDependents ( for: buildTargets)
1286
+ var result : [ BuildTargetIdentifier : BuildTargetInfo ] = [ : ]
1287
+ result. reserveCapacity ( buildTargets. count)
1288
+ for buildTarget in buildTargets {
1289
+ guard result [ buildTarget. id] == nil else {
1290
+ logger. error ( " Found two targets with the same ID \( buildTarget. id) " )
1291
+ continue
1292
+ }
1293
+ let depth : Int
1294
+ if let d = depths [ buildTarget. id] {
1295
+ depth = d
1296
+ } else {
1297
+ logger. fault ( " Did not compute depth for target \( buildTarget. id) " )
1298
+ depth = 0
1299
+ }
1300
+ result [ buildTarget. id] = BuildTargetInfo (
1301
+ target: buildTarget,
1302
+ depth: depth,
1303
+ dependents: dependents [ buildTarget. id] ?? [ ]
1304
+ )
1254
1305
}
1255
- result [ buildTarget . id ] = BuildTargetInfo (
1256
- target : buildTarget ,
1257
- depth : depth ,
1258
- dependents : dependents [ buildTarget . id ] ?? [ ]
1306
+ return result
1307
+ } resultReceivedAfterTimeout : { newResult in
1308
+ await self . buildTargetsDidChange (
1309
+ . buildTargetsReceivedResultAfterTimeout ( request : request , newResult : newResult )
1259
1310
)
1260
1311
}
1312
+ guard let result else {
1313
+ logger. error ( " Failed to get targets of workspace within timeout " )
1314
+ return [ : ]
1315
+ }
1261
1316
return result
1262
1317
}
1263
1318
return result
@@ -1290,7 +1345,11 @@ package actor BuildServerManager: QueueBasedMessageHandler {
1290
1345
}
1291
1346
1292
1347
let response = try await cachedTargetSources. get ( request, isolation: self ) { request in
1293
- try await buildServerAdapter. send ( request)
1348
+ try await withTimeout ( self . options. buildServerWorkspaceRequestsTimeoutOrDefault) {
1349
+ return try await buildServerAdapter. send ( request)
1350
+ } resultReceivedAfterTimeout: { newResult in
1351
+ await self . buildTargetsDidChange ( . sourceFilesReceivedResultAfterTimeout( request: request, newResult: newResult) )
1352
+ } ?? BuildTargetSourcesResponse ( items: [ ] )
1294
1353
}
1295
1354
return response. items
1296
1355
}
0 commit comments