@@ -100,12 +100,25 @@ struct FrameworkProducer {
100100 // no-op
101101 targetGraph. remove ( valid)
102102 } else {
103- let restored = await restoreAllAvailableCachesIfNeeded (
103+ let restoredSetsToSourceStorage = await restoreAllAvailableCachesIfNeeded (
104104 availableTargets: targets. subtracting ( valid) ,
105105 to: storagesWithConsumer,
106106 cacheSystem: cacheSystem
107107 )
108- let skipTargets = valid. union ( restored)
108+
109+ let allRestoredTargets = restoredSetsToSourceStorage. keys. reduce ( into: Set < CacheSystem . CacheTarget > ( ) ) { result, targetSet in
110+ result. formUnion ( targetSet)
111+ }
112+
113+ if !allRestoredTargets. isEmpty {
114+ await shareRestoredCachesToProducers (
115+ allRestoredTargets,
116+ restoredSetsToSourceStorage: restoredSetsToSourceStorage,
117+ cacheSystem: cacheSystem
118+ )
119+ }
120+
121+ let skipTargets = valid. union ( allRestoredTargets)
109122 targetGraph. remove ( skipTargets)
110123 }
111124 dependencyGraphToBuild = targetGraph
@@ -137,7 +150,7 @@ struct FrameworkProducer {
137150 availableTargets: Set < CacheSystem . CacheTarget > ,
138151 cacheSystem: CacheSystem
139152 ) async -> Set < CacheSystem . CacheTarget > {
140- let chunked = availableTargets. chunks ( ofCount: CacheSystem . defaultParalellNumber )
153+ let chunked = availableTargets. chunks ( ofCount: CacheSystem . defaultParallelNumber )
141154
142155 var validFrameworks : Set < CacheSystem . CacheTarget > = [ ]
143156 for chunk in chunked {
@@ -147,7 +160,7 @@ struct FrameworkProducer {
147160 do {
148161 let product = target. buildProduct
149162 let frameworkName = product. frameworkName
150- let outputPath = outputDir. appendingPathComponent ( frameworkName)
163+ let outputPath = outputDir. appending ( component : frameworkName)
151164 let exists = fileSystem. exists ( outputPath)
152165 guard exists else { return nil }
153166
@@ -184,9 +197,9 @@ struct FrameworkProducer {
184197 availableTargets: Set < CacheSystem . CacheTarget > ,
185198 to storages: [ any CacheStorage ] ,
186199 cacheSystem: CacheSystem
187- ) async -> Set < CacheSystem . CacheTarget > {
200+ ) async -> [ Set < CacheSystem . CacheTarget > : any CacheStorage ] {
188201 var remainingTargets = availableTargets
189- var restored : Set < CacheSystem . CacheTarget > = [ ]
202+ var restoredSetsToSourceStorage : [ Set < CacheSystem . CacheTarget > : any CacheStorage ] = [ : ]
190203
191204 for index in storages. indices {
192205 let storage = storages [ index]
@@ -209,7 +222,11 @@ struct FrameworkProducer {
209222 from: storage,
210223 cacheSystem: cacheSystem
211224 )
212- restored. formUnion ( restoredPerStorage)
225+
226+ // Record which storage restored which set of targets
227+ if !restoredPerStorage. isEmpty {
228+ restoredSetsToSourceStorage [ restoredPerStorage] = storage
229+ }
213230
214231 logger. info (
215232 " ⏸️ Restoration finished with cache storage: \( logSuffix) " ,
@@ -224,15 +241,15 @@ struct FrameworkProducer {
224241 }
225242
226243 logger. info ( " ⏹️ Restoration finished " , metadata: . color( . green) )
227- return restored
244+ return restoredSetsToSourceStorage
228245 }
229246
230247 private func restoreCaches(
231248 for targets: Set < CacheSystem . CacheTarget > ,
232249 from cacheStorage: any CacheStorage ,
233250 cacheSystem: CacheSystem
234251 ) async -> Set < CacheSystem . CacheTarget > {
235- let chunked = targets. chunks ( ofCount: cacheStorage. parallelNumber ?? CacheSystem . defaultParalellNumber )
252+ let chunked = targets. chunks ( ofCount: cacheStorage. parallelNumber ?? CacheSystem . defaultParallelNumber )
236253
237254 var restored : Set < CacheSystem . CacheTarget > = [ ]
238255 for chunk in chunked {
@@ -369,6 +386,89 @@ struct FrameworkProducer {
369386 logger. warning ( " ⚠️ Could not create VersionFile. This framework will not be cached. " , metadata: . color( . yellow) )
370387 }
371388 }
389+
390+ private func shareRestoredCachesToProducers(
391+ _ allRestoredTargets: Set < CacheSystem . CacheTarget > ,
392+ restoredSetsToSourceStorage: [ Set < CacheSystem . CacheTarget > : any CacheStorage ] ,
393+ cacheSystem: CacheSystem
394+ ) async {
395+ let storagesWithProducer = cachePolicies. storages ( for: . producer)
396+ guard !storagesWithProducer. isEmpty else { return }
397+
398+ logger. info (
399+ " 🔄 Sharing \( allRestoredTargets. count) restored framework(s) to other cache storages " ,
400+ metadata: . color( . blue)
401+ )
402+
403+ for storage in storagesWithProducer {
404+ // Filter targets to exclude those that were restored from this storage
405+ var targetsToShare = allRestoredTargets
406+
407+ // Remove targets that were restored from this storage
408+ for (restoredSet, sourceStorage) in restoredSetsToSourceStorage where areStoragesEqual ( sourceStorage, storage) {
409+ targetsToShare. subtract ( restoredSet)
410+ }
411+
412+ if !targetsToShare. isEmpty {
413+ await shareCachesToStorage ( targetsToShare, to: storage, cacheSystem: cacheSystem)
414+ }
415+ }
416+
417+ logger. info ( " ⏹️ Sharing to other cache storages finished " , metadata: . color( . green) )
418+ }
419+
420+ private func areStoragesEqual( _ lhs: any CacheStorage , _ rhs: any CacheStorage ) -> Bool {
421+ // ProjectCacheStorage instances are always considered the same
422+ if lhs is ProjectCacheStorage && rhs is ProjectCacheStorage {
423+ return true
424+ }
425+
426+ // For LocalDiskCacheStorage (value type), use Equatable comparison
427+ if let lhsLocal = lhs as? LocalDiskCacheStorage ,
428+ let rhsLocal = rhs as? LocalDiskCacheStorage {
429+ return lhsLocal == rhsLocal
430+ }
431+
432+ // For reference types (actors, classes), use ObjectIdentifier comparison
433+ return ObjectIdentifier ( lhs as AnyObject ) == ObjectIdentifier ( rhs as AnyObject )
434+ }
435+
436+ private func shareCachesToStorage(
437+ _ targets: Set < CacheSystem . CacheTarget > ,
438+ to storage: any CacheStorage ,
439+ cacheSystem: CacheSystem
440+ ) async {
441+ let chunked = targets. chunks ( ofCount: storage. parallelNumber ?? CacheSystem . defaultParallelNumber)
442+
443+ for chunk in chunked {
444+ await withTaskGroup ( of: Void . self) { group in
445+ for target in chunk {
446+ group. addTask {
447+ do {
448+ let cacheKey = try await cacheSystem. calculateCacheKey ( of: target)
449+ let hasCache = try await storage. existsValidCache ( for: cacheKey)
450+ guard !hasCache else { return }
451+
452+ let frameworkName = target. buildProduct. frameworkName
453+ let frameworkPath = outputDir. appending ( component: frameworkName)
454+
455+ logger. info (
456+ " 🔄 Share \( frameworkName) to cache storage: \( storage. displayName) " ,
457+ metadata: . color( . blue)
458+ )
459+ try await storage. cacheFramework ( frameworkPath, for: cacheKey)
460+ } catch {
461+ logger. warning (
462+ " ⚠️ Failed to share cache to \( storage. displayName) : \( error. localizedDescription) " ,
463+ metadata: . color( . yellow)
464+ )
465+ }
466+ }
467+ }
468+ await group. waitForAll ( )
469+ }
470+ }
471+ }
372472}
373473
374474extension [ Runner . Options . CachePolicy ] {
0 commit comments