@@ -24,6 +24,18 @@ import struct TSCBasic.ByteString
24
24
import struct TSCBasic. SHA256
25
25
import Workspace
26
26
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
27
39
struct PrebuiltRepos : Codable {
28
40
let url : URL
29
41
let versions : [ Version ]
@@ -118,12 +130,12 @@ struct BuildPrebuilts: AsyncParsableCommand {
118
130
@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. " )
119
131
var certChainPathStrs : [ String ] = [ ]
120
132
133
+ @Option ( help: . hidden)
134
+ var prebuiltsUrl : String = " https://download.swift.org/prebuilts "
135
+
121
136
@Flag ( help: . hidden)
122
137
var testSigning : Bool = false
123
138
124
- @Flag ( name: . customLong( " include-path " ) , help: " Add includePath to manifest " )
125
- var addIncludePath : Bool = false
126
-
127
139
func validate( ) throws {
128
140
if sign && !testSigning {
129
141
guard privateKeyPathStr != nil else {
@@ -241,8 +253,6 @@ struct BuildPrebuilts: AsyncParsableCommand {
241
253
}
242
254
try packageContents. write ( to: packageFile. asURL, atomically: true , encoding: . utf8)
243
255
244
- var newLibraries : [ Workspace . PrebuiltsManifest . Library ] = [ ]
245
-
246
256
// Build
247
257
for library in version. manifest. libraries {
248
258
let cModules = libraryTargets [ library. name] ? . compactMap ( { $0 as? ClangModule } ) ?? [ ]
@@ -303,32 +313,29 @@ struct BuildPrebuilts: AsyncParsableCommand {
303
313
let contents = try ByteString ( Data ( contentsOf: zipFile. asURL) )
304
314
#endif
305
315
316
+ // Manifest fragment for the zip file
306
317
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
+ )
309
327
310
328
let artifactJsonFile = versionDir. appending ( " \( swiftVersion) - \( library. name) - \( platform) .zip.json " )
311
329
try fileSystem. writeFileContents ( artifactJsonFile, data: encoder. encode ( artifact) )
312
330
331
+ // Clean up
313
332
try fileSystem. removeFileTree ( libDir)
314
333
try fileSystem. removeFileTree ( modulesDir)
315
334
try fileSystem. removeFileTree ( includesDir)
316
335
}
317
336
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
-
326
337
try await shell ( " git restore . " , cwd: repoDir)
327
338
}
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) )
332
339
}
333
340
}
334
341
@@ -342,31 +349,127 @@ struct BuildPrebuilts: AsyncParsableCommand {
342
349
encoder. outputFormatting = . prettyPrinted
343
350
let decoder = JSONDecoder ( )
344
351
352
+ let httpClient = HTTPClient ( )
353
+
345
354
guard let swiftVersion = try computeSwiftVersion ( ) else {
346
355
print ( " Unable to determine swift compiler version " )
347
- return
356
+ _exit ( 1 )
348
357
}
349
358
350
359
for repo in prebuiltRepos {
351
360
let prebuiltDir = stageDir. appending ( repo. url. lastPathComponent)
352
361
for version in repo. versions {
353
362
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
+ }
370
473
371
474
if testSigning {
372
475
// Use SwiftPM's test certificate chain and private key for testing
@@ -401,19 +504,21 @@ struct BuildPrebuilts: AsyncParsableCommand {
401
504
observabilityScope: ObservabilitySystem { _, diagnostic in print ( diagnostic) } . topScope
402
505
)
403
506
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
+ }
413
519
}
414
520
}
415
521
}
416
-
417
522
}
418
523
419
524
func canBuild( _ platform: Workspace . PrebuiltsManifest . Platform ) -> Bool {
0 commit comments