@@ -30,6 +30,7 @@ public class RegistryPackageContainer: PackageContainer {
30
30
private var toolsVersionsCache = ThreadSafeKeyValueStore < Version , ToolsVersion > ( )
31
31
private var validToolsVersionsCache = ThreadSafeKeyValueStore < Version , Bool > ( )
32
32
private var manifestsCache = ThreadSafeKeyValueStore < Version , Manifest > ( )
33
+ private var availableManifestsCache = ThreadSafeKeyValueStore < Version , ( manifests: [ String : ( toolsVersion: ToolsVersion , content: String ? ) ] , fileSystem: FileSystem ) > ( )
33
34
34
35
public init (
35
36
package : PackageReference ,
@@ -65,24 +66,10 @@ public class RegistryPackageContainer: PackageContainer {
65
66
66
67
public func toolsVersion( for version: Version ) throws -> ToolsVersion {
67
68
try self . toolsVersionsCache. memoize ( version) {
68
- let manifests = try temp_await {
69
- self . registryClient. getAvailableManifests (
70
- package : self . package . identity,
71
- version: version,
72
- observabilityScope: self . observabilityScope,
73
- callbackQueue: . sharedConcurrent,
74
- completion: $0
75
- )
76
- }
77
-
78
- // ToolsVersionLoader is designed to scan files to decide which is the best tools-version
79
- // as such, this writes a fake manifest based on the information returned by the registry
80
- // with only the header line which is all that is needed by ToolsVersionLoader
81
- let fileSystem = InMemoryFileSystem ( )
82
- for manifest in manifests {
83
- try fileSystem. writeFileContents ( AbsolutePath . root. appending ( component: manifest. key) , string: " // swift-tools-version: \( manifest. value) " )
69
+ let result = try temp_await {
70
+ self . getAvailableManifestsFilesystem ( version: version, completion: $0)
84
71
}
85
- return try self . toolsVersionLoader. load ( at: . root, fileSystem: fileSystem)
72
+ return try self . toolsVersionLoader. load ( at: . root, fileSystem: result . fileSystem)
86
73
}
87
74
}
88
75
@@ -125,95 +112,124 @@ public class RegistryPackageContainer: PackageContainer {
125
112
return self . package
126
113
}
127
114
128
- private func loadManifest( version: Version ) throws -> Manifest {
115
+ // internal for testing
116
+ internal func loadManifest( version: Version ) throws -> Manifest {
129
117
return try self . manifestsCache. memoize ( version) {
130
118
try temp_await {
131
- loadManifest ( version: version, completion: $0)
119
+ self . loadManifest ( version: version, completion: $0)
132
120
}
133
121
}
134
122
}
135
123
136
- // FIXME: make this DRYer with toolsVersion(for:)
137
124
private func loadManifest( version: Version , completion: @escaping ( Result < Manifest , Error > ) -> Void ) {
138
- self . registryClient. getAvailableManifests (
139
- package : self . package . identity,
140
- version: version,
141
- observabilityScope: self . observabilityScope,
142
- callbackQueue: . sharedConcurrent
143
- ) { result in
125
+ self . getAvailableManifestsFilesystem ( version: version) { result in
144
126
switch result {
145
127
case . failure( let error) :
146
128
return completion ( . failure( error) )
147
- case . success( let manifests ) :
129
+ case . success( let result ) :
148
130
do {
149
- let fileSystem = InMemoryFileSystem ( )
131
+ let manifests = result. manifests
132
+ let fileSystem = result. fileSystem
133
+
150
134
// first, decide the tools-version we should use
151
- // ToolsVersionLoader is designed to scan files to decide which is the best tools-version
152
- // as such, this writes a fake manifest based on the information returned by the registry
153
- // with only the header line which is all that is needed by ToolsVersionLoader
154
- for manifest in manifests {
155
- try fileSystem. writeFileContents ( AbsolutePath . root. appending ( component: manifest. key) , string: " // swift-tools-version: \( manifest. value) " )
156
- }
157
- guard let mainToolsVersion = manifests. first ( where: { $0. key == Manifest . filename } ) ? . value else {
135
+ let preferredToolsVersion = try self . toolsVersionLoader. load ( at: . root, fileSystem: fileSystem)
136
+ // validate preferred the tools version is compatible with the current toolchain
137
+ try preferredToolsVersion. validateToolsVersion (
138
+ self . currentToolsVersion,
139
+ packageIdentity: self . package . identity
140
+ )
141
+ // load the manifest content
142
+ guard let defaultManifestToolsVersion = manifests. first ( where: { $0. key == Manifest . filename } ) ? . value. toolsVersion else {
158
143
throw StringError ( " Could not find the ' \( Manifest . filename) ' file for ' \( self . package . identity) ' ' \( version) ' " )
159
144
}
160
- let preferredToolsVersion = try self . toolsVersionLoader. load ( at: . root, fileSystem: fileSystem)
161
- let customToolsVersion = preferredToolsVersion != mainToolsVersion ? preferredToolsVersion : nil
162
-
163
- // now that we know the tools version we need, fetch the manifest content
164
- self . registryClient. getManifestContent (
165
- package : self . package . identity,
166
- version: version,
167
- customToolsVersion: customToolsVersion,
168
- observabilityScope: self . observabilityScope,
169
- callbackQueue: . sharedConcurrent
170
- ) { result in
171
- switch result {
172
- case . failure( let error) :
173
- return completion ( . failure( error) )
174
- case . success( let manifestContent) :
175
- do {
176
- // replace the fake manifest with the real manifest content
177
- let filename : String
178
- if let toolsVersion = customToolsVersion {
179
- filename = Manifest . basename + " @swift- \( toolsVersion) .swift "
180
- } else {
181
- filename = Manifest . filename
182
- }
183
- try fileSystem. writeFileContents ( AbsolutePath . root. appending ( component: filename) , string: manifestContent)
184
-
185
- // validate the tools version.
186
- try preferredToolsVersion. validateToolsVersion (
187
- self . currentToolsVersion,
188
- packageIdentity: self . package . identity
189
- )
190
-
191
- // finally, load the manifest
192
- self . manifestLoader. load (
193
- at: . root,
194
- packageIdentity: self . package . identity,
195
- packageKind: self . package . kind,
196
- packageLocation: self . package . locationString,
197
- version: version,
198
- revision: nil ,
199
- toolsVersion: preferredToolsVersion,
200
- identityResolver: self . identityResolver,
201
- fileSystem: fileSystem,
202
- observabilityScope: self . observabilityScope,
203
- on: . sharedConcurrent,
204
- completion: completion
205
- )
206
- } catch {
145
+ if preferredToolsVersion == defaultManifestToolsVersion {
146
+ // default tools version - we already have the content on disk from getAvailableManifestsFileSystem()
147
+ self . manifestLoader. load (
148
+ at: . root,
149
+ packageIdentity: self . package . identity,
150
+ packageKind: self . package . kind,
151
+ packageLocation: self . package . locationString,
152
+ version: version,
153
+ revision: nil ,
154
+ toolsVersion: preferredToolsVersion,
155
+ identityResolver: self . identityResolver,
156
+ fileSystem: result. fileSystem,
157
+ observabilityScope: self . observabilityScope,
158
+ on: . sharedConcurrent,
159
+ completion: completion
160
+ )
161
+ } else {
162
+ // custom tools-version, we need to fetch the content from the server
163
+ self . registryClient. getManifestContent (
164
+ package : self . package . identity,
165
+ version: version,
166
+ customToolsVersion: preferredToolsVersion,
167
+ observabilityScope: self . observabilityScope,
168
+ callbackQueue: . sharedConcurrent
169
+ ) { result in
170
+ switch result {
171
+ case . failure( let error) :
207
172
return completion ( . failure( error) )
173
+ case . success( let manifestContent) :
174
+ do {
175
+ // replace the fake manifest with the real manifest content
176
+ let manifestPath = AbsolutePath . root. appending ( component: Manifest . basename + " @swift- \( preferredToolsVersion) .swift " )
177
+ try fileSystem. removeFileTree ( manifestPath)
178
+ try fileSystem. writeFileContents ( manifestPath, string: manifestContent)
179
+ // finally, load the manifest
180
+ self . manifestLoader. load (
181
+ at: . root,
182
+ packageIdentity: self . package . identity,
183
+ packageKind: self . package . kind,
184
+ packageLocation: self . package . locationString,
185
+ version: version,
186
+ revision: nil ,
187
+ toolsVersion: preferredToolsVersion,
188
+ identityResolver: self . identityResolver,
189
+ fileSystem: fileSystem,
190
+ observabilityScope: self . observabilityScope,
191
+ on: . sharedConcurrent,
192
+ completion: completion
193
+ )
194
+ } catch {
195
+ return completion ( . failure( error) )
196
+ }
208
197
}
209
- }
198
+ }
210
199
}
211
200
} catch {
212
201
return completion ( . failure( error) )
213
202
}
214
203
}
215
204
}
216
205
}
206
+
207
+ private func getAvailableManifestsFilesystem( version: Version , completion: @escaping ( Result < ( manifests: [ String : ( toolsVersion: ToolsVersion , content: String ? ) ] , fileSystem: FileSystem ) , Error > ) -> Void ) {
208
+ // try cached first
209
+ if let availableManifests = self . availableManifestsCache [ version] {
210
+ return completion ( . success( availableManifests) )
211
+ }
212
+ // get from server
213
+ self . registryClient. getAvailableManifests (
214
+ package : self . package . identity,
215
+ version: version,
216
+ observabilityScope: self . observabilityScope,
217
+ callbackQueue: . sharedConcurrent
218
+ ) { result in
219
+ completion ( result. tryMap { manifests in
220
+ // ToolsVersionLoader is designed to scan files to decide which is the best tools-version
221
+ // as such, this writes a fake manifest based on the information returned by the registry
222
+ // with only the header line which is all that is needed by ToolsVersionLoader
223
+ let fileSystem = InMemoryFileSystem ( )
224
+ for manifest in manifests {
225
+ let content = manifest. value. content ?? " // swift-tools-version: \( manifest. value. toolsVersion) "
226
+ try fileSystem. writeFileContents ( AbsolutePath . root. appending ( component: manifest. key) , string: content)
227
+ }
228
+ self . availableManifestsCache [ version] = ( manifests: manifests, fileSystem: fileSystem)
229
+ return ( manifests: manifests, fileSystem: fileSystem)
230
+ } )
231
+ }
232
+ }
217
233
}
218
234
219
235
// MARK: - CustomStringConvertible
0 commit comments