@@ -56,21 +56,18 @@ extension Workspace {
5656 private let underlying : ManifestLoaderProtocol
5757 private let registryClient : RegistryClient
5858 private let transformationMode : TransformationMode
59-
60- private let cacheTTL = DispatchTimeInterval . seconds ( 300 ) // 5m
61- // private let identityLookupCache = ThreadSafeKeyValueStore<
62- // SourceControlURL,
63- // (result: Result<PackageIdentity?, Error>, expirationTime: DispatchTime)
64- // >()
59+ private let identityLookupCache : Workspace . IdentityLookupCache
6560
6661 init (
6762 underlying: ManifestLoaderProtocol ,
6863 registryClient: RegistryClient ,
69- transformationMode: TransformationMode
64+ transformationMode: TransformationMode ,
65+ identityLookupCache: Workspace . IdentityLookupCache
7066 ) {
7167 self . underlying = underlying
7268 self . registryClient = registryClient
7369 self . transformationMode = transformationMode
70+ self . identityLookupCache = identityLookupCache
7471 }
7572
7673 func load(
@@ -85,10 +82,6 @@ extension Workspace {
8582 fileSystem: any FileSystem ,
8683 observabilityScope: ObservabilityScope ,
8784 delegateQueue: DispatchQueue ,
88- identityLookupCache: ThreadSafeKeyValueStore <
89- SourceControlURL ,
90- ( result: Result < PackageIdentity ? , Error > , expirationTime: DispatchTime )
91- >
9285 ) async throws -> Manifest {
9386 let manifest = try await self . underlying. load (
9487 manifestPath: manifestPath,
@@ -101,13 +94,11 @@ extension Workspace {
10194 dependencyMapper: dependencyMapper,
10295 fileSystem: fileSystem,
10396 observabilityScope: observabilityScope,
104- delegateQueue: delegateQueue,
105- identityLookupCache: . init( )
97+ delegateQueue: delegateQueue
10698 )
10799 return try await self . transformSourceControlDependenciesToRegistry (
108100 manifest: manifest,
109101 transformationMode: transformationMode,
110- identityLookupCache: identityLookupCache,
111102 observabilityScope: observabilityScope
112103 )
113104 }
@@ -123,10 +114,6 @@ extension Workspace {
123114 private func transformSourceControlDependenciesToRegistry(
124115 manifest: Manifest ,
125116 transformationMode: TransformationMode ,
126- identityLookupCache: ThreadSafeKeyValueStore <
127- SourceControlURL ,
128- ( result: Result < PackageIdentity ? , Error > , expirationTime: DispatchTime )
129- > ,
130117 observabilityScope: ObservabilityScope
131118 ) async throws -> Manifest {
132119 var transformations = [ PackageDependency: PackageIdentity] ( )
@@ -138,7 +125,6 @@ extension Workspace {
138125 do {
139126 let identity = try await self . mapRegistryIdentity (
140127 url: url,
141- identityLookupCache: identityLookupCache,
142128 observabilityScope: observabilityScope
143129 )
144130 return ( dependency, identity)
@@ -343,37 +329,31 @@ extension Workspace {
343329
344330 private func mapRegistryIdentity(
345331 url: SourceControlURL ,
346- identityLookupCache: ThreadSafeKeyValueStore <
347- SourceControlURL ,
348- ( result:
349- Result < PackageIdentity ? , Error > ,
350- expirationTime: DispatchTime
351- ) > ,
352332 observabilityScope: ObservabilityScope
353333 ) async throws -> PackageIdentity ? {
354334 if let cached = identityLookupCache [ url] , cached. expirationTime > . now( ) {
355335 switch cached. result {
356336 case . success( let identity) :
357- return identity;
337+ return identity
338+ case . notApplicable:
339+ // scm package does not have a valid registry mapping
340+ return nil
358341 case . failure:
359342 // server error, do not try again
360343 return nil
361344 }
362345 }
363346
364347 do {
365- // TODO bp :
366- // - possibly propogate the resolvedFileStrategy here?
367- // - ensure that the cache is up to date
368348 let identities = try await self . registryClient. lookupIdentities (
369349 scmURL: url,
370350 observabilityScope: observabilityScope
371351 )
372352 let identity = identities. sorted ( ) . first
373- identityLookupCache [ url] = ( result : . success( identity) , expirationTime : . now ( ) + self . cacheTTL )
353+ identityLookupCache [ storing : url] = . success( identity)
374354 return identity
375355 } catch {
376- identityLookupCache [ url] = ( result : . failure( error) , expirationTime : . now ( ) + self . cacheTTL )
356+ identityLookupCache [ storing : url] = . failure( error)
377357 throw error
378358 }
379359 }
@@ -469,3 +449,71 @@ extension Workspace {
469449 try registryDownloadsManager. remove ( package : dependency. packageRef. identity)
470450 }
471451}
452+
453+ extension Workspace {
454+ public class IdentityLookupCache {
455+ public typealias Key = SourceControlURL
456+ public typealias Value = CacheResult
457+ public typealias CacheResult = ( result: IdentityMapResult < PackageIdentity ? , Error > , expirationTime: DispatchTime )
458+
459+
460+ public enum IdentityMapResult < Success, Failure> {
461+ /// Represents a successful mapping of a source control URL to its registry identity
462+ case success( Success )
463+ /// Represents a failure to retrieve an identity from the registry
464+ case failure( Failure )
465+ /// Represents a scenario wherein a scm package has no valid registry identity mapping
466+ case notApplicable
467+ }
468+
469+ /// Tracks the mapping of scm packages to their registry identities
470+ private let cache = ThreadSafeKeyValueStore < SourceControlURL , CacheResult > ( )
471+ private let cacheTTL = DispatchTimeInterval . seconds ( 300 ) // 5m
472+
473+ public subscript( key: Key ) -> CacheResult ? {
474+ get { self . cache [ key] }
475+ set { self . cache [ key] = newValue }
476+ }
477+
478+ public subscript( storing key: Key ) -> IdentityMapResult < PackageIdentity ? , Error > ? {
479+ get { self . cache [ key] ? . result }
480+ set {
481+ guard let newValue else {
482+ cache. removeValue ( forKey: key)
483+ return
484+ }
485+ self . cache [ key] = (
486+ result: newValue,
487+ expirationTime: . now( ) + self . cacheTTL
488+ )
489+ }
490+ }
491+
492+ /// Derives an identity lookup cache from the Package.resolved file if applicable.
493+ /// Asserts against the transformation mode set in the Workspace to determine how to store
494+ /// source control packages and their identity mappings.
495+ public func deriveCache( from resolvedPackages: ResolvedPackagesStore . ResolvedPackages , _ transformationMode: WorkspaceConfiguration . SourceControlToRegistryDependencyTransformation ) {
496+ guard transformationMode != . disabled else {
497+ return
498+ }
499+
500+ // First run, create a mapping between registry packages and their scm location, if applicable.
501+ for resolvedPackage in resolvedPackages. values {
502+ if case let . registry( id, . some( url) ) = resolvedPackage. packageRef. kind {
503+ self [ storing: url] = . success( id)
504+ }
505+ }
506+
507+ // Second pass; identify source control packages that may have had their
508+ // identifiers modified to that of a registry id
509+ if transformationMode == . swizzle {
510+ for resolvedPackage in resolvedPackages. values {
511+ if case . remoteSourceControl( let url) = resolvedPackage. packageRef. kind,
512+ self . cache [ url] == nil {
513+ self [ storing: url] = . notApplicable
514+ }
515+ }
516+ }
517+ }
518+ }
519+ }
0 commit comments