@@ -24,6 +24,18 @@ import struct TSCBasic.ByteString
2424import struct TSCBasic. SHA256
2525import Workspace
2626
27+ // Format for the .zip.json files.
28+ struct Artifact : Codable {
29+ var platform : Workspace . PrebuiltsManifest . Platform
30+ var checksum : String
31+ var libraryName : String ?
32+ var products : [ String ] ?
33+ var includePath : [ RelativePath ] ?
34+ var cModules : [ String ] ? // deprecated, includePath is the way forward
35+ var swiftVersion : String ?
36+ }
37+
38+ // The master list of repos and their versions
2739struct PrebuiltRepos : Codable {
2840 let url : URL
2941 let versions : [ Version ]
@@ -118,12 +130,12 @@ struct BuildPrebuilts: AsyncParsableCommand {
118130 @Option ( name: . customLong( " cert-chain-path " ) , help: " Path to a certificate (DER encoded) in the chain. The certificate used for signing must be first and the root certificate last. " )
119131 var certChainPathStrs : [ String ] = [ ]
120132
133+ @Option ( help: . hidden)
134+ var prebuiltsUrl : String = " https://download.swift.org/prebuilts "
135+
121136 @Flag ( help: . hidden)
122137 var testSigning : Bool = false
123138
124- @Flag ( name: . customLong( " include-path " ) , help: " Add includePath to manifest " )
125- var addIncludePath : Bool = false
126-
127139 func validate( ) throws {
128140 if sign && !testSigning {
129141 guard privateKeyPathStr != nil else {
@@ -241,8 +253,6 @@ struct BuildPrebuilts: AsyncParsableCommand {
241253 }
242254 try packageContents. write ( to: packageFile. asURL, atomically: true , encoding: . utf8)
243255
244- var newLibraries : [ Workspace . PrebuiltsManifest . Library ] = [ ]
245-
246256 // Build
247257 for library in version. manifest. libraries {
248258 let cModules = libraryTargets [ library. name] ? . compactMap ( { $0 as? ClangModule } ) ?? [ ]
@@ -303,32 +313,29 @@ struct BuildPrebuilts: AsyncParsableCommand {
303313 let contents = try ByteString ( Data ( contentsOf: zipFile. asURL) )
304314#endif
305315
316+ // Manifest fragment for the zip file
306317 let checksum = SHA256 ( ) . hash ( contents) . hexadecimalRepresentation
307- let artifact : Workspace . PrebuiltsManifest . Library . Artifact =
308- . init( platform: platform, checksum: checksum)
318+ let artifact = Artifact (
319+ platform: platform,
320+ checksum: checksum,
321+ libraryName: library. name,
322+ products: library. products,
323+ includePath: cModules. map ( { $0. includeDir. relative ( to: repoDir ) } ) ,
324+ cModules: cModules. map ( \. name) ,
325+ swiftVersion: swiftVersion
326+ )
309327
310328 let artifactJsonFile = versionDir. appending ( " \( swiftVersion) - \( library. name) - \( platform) .zip.json " )
311329 try fileSystem. writeFileContents ( artifactJsonFile, data: encoder. encode ( artifact) )
312330
331+ // Clean up
313332 try fileSystem. removeFileTree ( libDir)
314333 try fileSystem. removeFileTree ( modulesDir)
315334 try fileSystem. removeFileTree ( includesDir)
316335 }
317336
318- let newLibrary = Workspace . PrebuiltsManifest. Library (
319- name: library. name,
320- products: library. products,
321- cModules: cModules. map ( { $0. name } ) ,
322- includePath: addIncludePath ? cModules. map ( { $0. includeDir. relative ( to: repoDir ) } ) : nil ,
323- )
324- newLibraries. append ( newLibrary)
325-
326337 try await shell ( " git restore . " , cwd: repoDir)
327338 }
328-
329- let manifest = Workspace . PrebuiltsManifest ( libraries: newLibraries)
330- let manifestFile = versionDir. appending ( " \( swiftVersion) -prebuilts.json " )
331- try fileSystem. writeFileContents ( manifestFile, data: encoder. encode ( manifest) )
332339 }
333340 }
334341
@@ -342,31 +349,127 @@ struct BuildPrebuilts: AsyncParsableCommand {
342349 encoder. outputFormatting = . prettyPrinted
343350 let decoder = JSONDecoder ( )
344351
352+ let httpClient = HTTPClient ( )
353+
345354 guard let swiftVersion = try computeSwiftVersion ( ) else {
346355 print ( " Unable to determine swift compiler version " )
347- return
356+ _exit ( 1 )
348357 }
349358
350359 for repo in prebuiltRepos {
351360 let prebuiltDir = stageDir. appending ( repo. url. lastPathComponent)
352361 for version in repo. versions {
353362 let versionDir = prebuiltDir. appending ( version. tag)
354- let prebuiltsFile = versionDir. appending ( " \( swiftVersion) -prebuilts.json " )
355- let manifestFile = versionDir. appending ( " \( swiftVersion) -manifest.json " )
356-
357- // Load generated manifest
358- let manifestContents : Data = try fileSystem. readFileContents ( prebuiltsFile)
359- var manifest = try decoder. decode ( Workspace . PrebuiltsManifest. self, from: manifestContents)
360- manifest. libraries = try manifest. libraries. map ( {
361- var library = $0
362- library. artifacts = try fileSystem. getDirectoryContents ( versionDir)
363- . filter ( { $0. hasSuffix ( " .zip.json " ) } )
364- . compactMap ( {
365- let data : Data = try fileSystem. readFileContents ( versionDir. appending ( $0) )
366- return try ? decoder. decode ( Workspace . PrebuiltsManifest. Library. Artifact. self, from: data)
367- } )
368- return library
369- } )
363+
364+ // Load artifacts
365+ let artifacts = try fileSystem. getDirectoryContents ( versionDir)
366+ . filter ( { $0. hasSuffix ( " .zip.json " ) } )
367+ . map {
368+ let data : Data = try fileSystem. readFileContents ( versionDir. appending ( $0) )
369+ var artifact = try decoder. decode ( Artifact . self, from: data)
370+ if artifact. swiftVersion == nil || artifact. libraryName == nil {
371+ let regex = try Regex ( #"(.+)-([^-]+)-[^-]+.zip.json"# )
372+ if let match = try regex. firstMatch ( in: $0) ,
373+ match. count > 2 ,
374+ let swiftVersion = match [ 1 ] . substring,
375+ let libraryName = match [ 2 ] . substring
376+ {
377+ artifact. swiftVersion = . init( swiftVersion)
378+ artifact. libraryName = . init( libraryName)
379+ }
380+ }
381+ return artifact
382+ }
383+
384+ // Fetch manifests for requested swift versions
385+ let swiftVersions : Set < String > = . init( artifacts. compactMap ( \. swiftVersion) )
386+ var manifests : [ String : Workspace . PrebuiltsManifest ] = [ : ]
387+ for swiftVersion in swiftVersions {
388+ let manifestFile = " \( swiftVersion) -manifest.json "
389+ let destination = versionDir. appending ( component: manifestFile)
390+ if fileSystem. exists ( destination) {
391+ let signedManifest = try decoder. decode (
392+ path: destination,
393+ fileSystem: fileSystem,
394+ as: Workspace . SignedPrebuiltsManifest. self
395+ )
396+ manifests [ swiftVersion] = signedManifest. manifest
397+ } else {
398+ let manifestURL = URL ( string: prebuiltsUrl) ? . appending ( components: repo. url. lastPathComponent, version. tag, manifestFile)
399+ guard let manifestURL else {
400+ print ( " Invalid URL \( prebuiltsUrl) " )
401+ _exit ( 1 )
402+ }
403+
404+ var headers = HTTPClientHeaders ( )
405+ headers. add ( name: " Accept " , value: " application/json " )
406+ var request = HTTPClient . Request. download (
407+ url: manifestURL,
408+ headers: headers,
409+ fileSystem: fileSystem,
410+ destination: destination
411+ )
412+ request. options. retryStrategy = . exponentialBackoff(
413+ maxAttempts: 3 ,
414+ baseDelay: . milliseconds( 50 )
415+ )
416+ request. options. validResponseCodes = [ 200 ]
417+
418+ do {
419+ _ = try await httpClient. execute ( request) { _, _ in }
420+ } catch {
421+ manifests [ swiftVersion] = . init( )
422+ continue
423+ }
424+
425+ let signedManifest = try decoder. decode (
426+ path: destination,
427+ fileSystem: fileSystem,
428+ as: Workspace . SignedPrebuiltsManifest. self
429+ )
430+
431+ manifests [ swiftVersion] = signedManifest. manifest
432+ }
433+ }
434+
435+ // Merge in the artifacts
436+ for artifact in artifacts {
437+ let swiftVersion = artifact. swiftVersion ?? swiftVersion
438+ var manifest = manifests [ swiftVersion, default: version. manifest]
439+ let libraryName = artifact. libraryName ?? manifest. libraries [ 0 ] . name
440+ var library = manifest. libraries. first ( where: { $0. name == libraryName } ) ?? . init( name: libraryName)
441+ var newArtifacts = library. artifacts ?? [ ]
442+
443+ if let products = artifact. products {
444+ library. products = products
445+ }
446+
447+ if let includePath = artifact. includePath {
448+ library. includePath = includePath
449+ }
450+
451+ if let cModules = artifact. cModules {
452+ library. cModules = cModules
453+ }
454+
455+ if let index = newArtifacts. firstIndex ( where: { $0. platform == artifact. platform } ) {
456+ var oldArtifact = newArtifacts [ index]
457+ oldArtifact. checksum = artifact. checksum
458+ newArtifacts [ index] = oldArtifact
459+ } else {
460+ newArtifacts. append ( . init( platform: artifact. platform, checksum: artifact. checksum) )
461+ }
462+
463+ library. artifacts = newArtifacts
464+
465+ if let index = manifest. libraries. firstIndex ( where: { $0. name == libraryName } ) {
466+ manifest. libraries [ index] = library
467+ } else {
468+ manifest. libraries. append ( library)
469+ }
470+
471+ manifests [ swiftVersion] = manifest
472+ }
370473
371474 if testSigning {
372475 // Use SwiftPM's test certificate chain and private key for testing
@@ -401,19 +504,21 @@ struct BuildPrebuilts: AsyncParsableCommand {
401504 observabilityScope: ObservabilitySystem { _, diagnostic in print ( diagnostic) } . topScope
402505 )
403506
404- let signature = try await signer. sign (
405- manifest: manifest,
406- certChainPaths: certChainPaths,
407- certPrivateKeyPath: privateKeyPath,
408- fileSystem: fileSystem
409- )
410-
411- let signedManifest = Workspace . SignedPrebuiltsManifest ( manifest: manifest, signature: signature)
412- try encoder. encode ( signedManifest) . write ( to: manifestFile. asURL)
507+ for (swiftVersion, manifest) in manifests where !manifest. libraries. flatMap ( { $0. artifacts ?? [ ] } ) . isEmpty {
508+ let signature = try await signer. sign (
509+ manifest: manifest,
510+ certChainPaths: certChainPaths,
511+ certPrivateKeyPath: privateKeyPath,
512+ fileSystem: fileSystem
513+ )
514+
515+ let signedManifest = Workspace . SignedPrebuiltsManifest ( manifest: manifest, signature: signature)
516+ let manifestFile = versionDir. appending ( component: " \( swiftVersion) -manifest.json " )
517+ try encoder. encode ( signedManifest) . write ( to: manifestFile. asURL)
518+ }
413519 }
414520 }
415521 }
416-
417522 }
418523
419524 func canBuild( _ platform: Workspace . PrebuiltsManifest . Platform ) -> Bool {
0 commit comments