@@ -211,7 +211,11 @@ public class Workspace {
211
211
/// The package container provider.
212
212
fileprivate let containerProvider : RepositoryPackageContainerProvider
213
213
214
+ /// The http client used for downloading binary artifacts.
215
+ fileprivate let httpClient : HTTPClient
216
+
214
217
/// The downloader used for downloading binary artifacts.
218
+ // FIXME: consolidate HttpClient and Downloader as they provide same functionality
215
219
fileprivate let downloader : Downloader
216
220
217
221
fileprivate let netrcFilePath : AbsolutePath ?
@@ -267,6 +271,7 @@ public class Workspace {
267
271
fileSystem: FileSystem = localFileSystem,
268
272
repositoryProvider: RepositoryProvider = GitRepositoryProvider ( ) ,
269
273
identityResolver: IdentityResolver ? = nil ,
274
+ httpClient: HTTPClient ? = nil ,
270
275
downloader: Downloader = FoundationDownloader ( ) ,
271
276
netrcFilePath: AbsolutePath ? = nil ,
272
277
archiver: Archiver = ZipArchiver ( ) ,
@@ -286,6 +291,7 @@ public class Workspace {
286
291
self . currentToolsVersion = currentToolsVersion
287
292
self . toolsVersionLoader = toolsVersionLoader
288
293
self . downloader = downloader
294
+ self . httpClient = httpClient ?? HTTPClient ( )
289
295
self . netrcFilePath = netrcFilePath
290
296
self . archiver = archiver
291
297
@@ -771,7 +777,7 @@ extension Workspace {
771
777
772
778
return diagnostics. wrap {
773
779
let contents = try fileSystem. readFileContents ( path)
774
- return checksumAlgorithm. hash ( contents) . hexadecimalRepresentation
780
+ return self . checksumAlgorithm. hash ( contents) . hexadecimalRepresentation
775
781
} ?? " "
776
782
}
777
783
}
@@ -1546,7 +1552,7 @@ extension Workspace {
1546
1552
targetName: target. name,
1547
1553
path: absolutePath)
1548
1554
)
1549
- } else if let url = target. url, let checksum = target. checksum {
1555
+ } else if let url = target. url. flatMap ( URL . init ( string : ) ) , let checksum = target. checksum {
1550
1556
remoteArtifacts. append (
1551
1557
. init(
1552
1558
packageRef: packageRef,
@@ -1566,6 +1572,7 @@ extension Workspace {
1566
1572
private func download( _ artifacts: [ RemoteArtifact ] , diagnostics: DiagnosticsEngine ) throws -> [ ManagedArtifact ] {
1567
1573
let group = DispatchGroup ( )
1568
1574
let tempDiagnostics = DiagnosticsEngine ( )
1575
+ let result = ThreadSafeArrayStore < ManagedArtifact > ( )
1569
1576
1570
1577
var authProvider : AuthorizationProviding ? = nil
1571
1578
var didDownloadAnyArtifact = false
@@ -1577,15 +1584,70 @@ extension Workspace {
1577
1584
}
1578
1585
#endif
1579
1586
1580
- let result = ThreadSafeArrayStore < ManagedArtifact > ( )
1587
+ // zip files to download
1588
+ // stored in a thread-safe way as we may fetch more from "ari" files
1589
+ let zipArtifacts = ThreadSafeArrayStore < RemoteArtifact > ( artifacts. filter { $0. url. pathExtension. lowercased ( ) == " zip " } )
1581
1590
1582
- for artifact in artifacts {
1591
+ // fetch and parse "ari" files, if any
1592
+ let hostToolchain = try UserToolchain ( destination: . hostDestination( ) )
1593
+ let jsonDecoder = JSONDecoder . makeWithDefaults ( )
1594
+ let indexFiles = artifacts. filter { $0. url. pathExtension. lowercased ( ) == " ari " }
1595
+ for indexFile in indexFiles {
1583
1596
group. enter ( )
1584
- defer { group. leave ( ) }
1597
+ var request = HTTPClient . Request ( method: . get, url: indexFile. url)
1598
+ request. options. validResponseCodes = [ 200 ]
1599
+ request. options. authorizationProvider = authProvider? . authorization ( for: )
1600
+ self . httpClient. execute ( request) { result in
1601
+ defer { group. leave ( ) }
1585
1602
1586
- guard let parsedURL = URL ( string: artifact. url) else {
1587
- throw StringError ( " invalid url \( artifact. url) " )
1603
+ do {
1604
+ switch result {
1605
+ case . failure( let error) :
1606
+ throw error
1607
+ case . success( let response) :
1608
+ guard let body = response. body else {
1609
+ throw StringError ( " Body is empty " )
1610
+ }
1611
+ // FIXME: would be nice if checksumAlgorithm.hash took Data directly
1612
+ let bodyChecksum = self . checksumAlgorithm. hash ( ByteString ( body) ) . hexadecimalRepresentation
1613
+ guard bodyChecksum == indexFile. checksum else {
1614
+ throw StringError ( " checksum of downloaded artifact of binary target ' \( indexFile. targetName) ' ( \( bodyChecksum) ) does not match checksum specified by the manifest ( \( indexFile. checksum ) ) " )
1615
+ }
1616
+ let metadata = try jsonDecoder. decode ( ArchiveIndexFile . self, from: body)
1617
+ // FIXME: this filter needs to become more sophisticated
1618
+ guard let supportedArchive = metadata. archives. first ( where: { $0. fileName. lowercased ( ) . hasSuffix ( " .zip " ) && $0. supportedTriples. contains ( hostToolchain. triple) } ) else {
1619
+ throw StringError ( " No supported archive was found for ' \( hostToolchain. triple) ' " )
1620
+ }
1621
+ // add relevant archive
1622
+ zipArtifacts. append (
1623
+ RemoteArtifact (
1624
+ packageRef: indexFile. packageRef,
1625
+ targetName: indexFile. targetName,
1626
+ url: indexFile. url. deletingLastPathComponent ( ) . appendingPathComponent ( supportedArchive. fileName) ,
1627
+ checksum: supportedArchive. checksum)
1628
+ )
1629
+ }
1630
+ } catch {
1631
+ tempDiagnostics. emit ( . error( " failed retrieving ' \( indexFile. url) ': \( error) " ) )
1632
+ }
1588
1633
}
1634
+ }
1635
+
1636
+ // wait for all "ari" files to be processed
1637
+ group. wait ( )
1638
+
1639
+ // no reason to continue if we already ran into issues
1640
+ if tempDiagnostics. hasErrors {
1641
+ // collect all diagnostics
1642
+ diagnostics. append ( contentsOf: tempDiagnostics)
1643
+ throw Diagnostics . fatalError
1644
+ }
1645
+
1646
+ // finally download zip files, if any
1647
+ didDownloadAnyArtifact = zipArtifacts. count > 0
1648
+ for artifact in ( zipArtifacts. map { $0 } ) {
1649
+ group. enter ( )
1650
+ defer { group. leave ( ) }
1589
1651
1590
1652
let parentDirectory = self . artifactsPath. appending ( component: artifact. packageRef. name)
1591
1653
@@ -1596,18 +1658,16 @@ extension Workspace {
1596
1658
continue
1597
1659
}
1598
1660
1599
- let archivePath = parentDirectory. appending ( component: parsedURL. lastPathComponent)
1600
-
1601
- didDownloadAnyArtifact = true
1661
+ let archivePath = parentDirectory. appending ( component: artifact. url. lastPathComponent)
1602
1662
1603
1663
group. enter ( )
1604
- downloader. downloadFile (
1605
- at: parsedURL ,
1664
+ self . downloader. downloadFile (
1665
+ at: artifact . url ,
1606
1666
to: archivePath,
1607
1667
withAuthorizationProvider: authProvider,
1608
1668
progress: { bytesDownloaded, totalBytesToDownload in
1609
1669
self . delegate? . downloadingBinaryArtifact (
1610
- from: artifact. url,
1670
+ from: artifact. url. absoluteString ,
1611
1671
bytesDownloaded: bytesDownloaded,
1612
1672
totalBytesToDownload: totalBytesToDownload)
1613
1673
} ,
@@ -1643,21 +1703,21 @@ extension Workspace {
1643
1703
. remote(
1644
1704
packageRef: artifact. packageRef,
1645
1705
targetName: artifact. targetName,
1646
- url: artifact. url,
1706
+ url: artifact. url. absoluteString ,
1647
1707
checksum: artifact. checksum,
1648
1708
path: artifactPath
1649
1709
)
1650
1710
)
1651
1711
case . failure( let error) :
1652
1712
let reason = ( error as? LocalizedError ) ? . errorDescription ?? error. localizedDescription
1653
- tempDiagnostics. emit ( . artifactFailedExtraction( targetName: artifact. targetName, reason: reason) )
1713
+ tempDiagnostics. emit ( . artifactFailedExtraction( artifactURL : artifact . url , targetName: artifact. targetName, reason: reason) )
1654
1714
}
1655
1715
1656
1716
tempDiagnostics. wrap { try self . fileSystem. removeFileTree ( archivePath) }
1657
1717
} )
1658
1718
case . failure( let error) :
1659
1719
let reason = error. errorDescription ?? error. localizedDescription
1660
- tempDiagnostics. emit ( . artifactFailedDownload( targetName: artifact. targetName, reason: reason) )
1720
+ tempDiagnostics. emit ( . artifactFailedDownload( artifactURL : artifact . url , targetName: artifact. targetName, reason: reason) )
1661
1721
}
1662
1722
} )
1663
1723
}
@@ -1668,9 +1728,8 @@ extension Workspace {
1668
1728
delegate? . didDownloadBinaryArtifacts ( )
1669
1729
}
1670
1730
1671
- for diagnostic in tempDiagnostics. diagnostics {
1672
- diagnostics. emit ( diagnostic. message, location: diagnostic. location)
1673
- }
1731
+ // collect all diagnostics
1732
+ diagnostics. append ( contentsOf: tempDiagnostics)
1674
1733
1675
1734
return result. map { $0 }
1676
1735
}
@@ -2567,10 +2626,34 @@ public final class LoadableResult<Value> {
2567
2626
private struct RemoteArtifact {
2568
2627
let packageRef : PackageReference
2569
2628
let targetName : String
2570
- let url : String
2629
+ let url : Foundation . URL
2571
2630
let checksum : String
2572
2631
}
2573
2632
2633
+ private struct ArchiveIndexFile : Decodable {
2634
+ let schemaVersion : String
2635
+ let archives : [ Archive ]
2636
+
2637
+ struct Archive : Decodable {
2638
+ let fileName : String
2639
+ let checksum : String
2640
+ let supportedTriples : [ Triple ]
2641
+
2642
+ enum CodingKeys : String , CodingKey {
2643
+ case fileName
2644
+ case checksum
2645
+ case supportedTriples
2646
+ }
2647
+
2648
+ public init ( from decoder: Decoder ) throws {
2649
+ let container = try decoder. container ( keyedBy: CodingKeys . self)
2650
+ self . fileName = try container. decode ( String . self, forKey: . fileName)
2651
+ self . checksum = try container. decode ( String . self, forKey: . checksum)
2652
+ self . supportedTriples = try container. decode ( [ String ] . self, forKey: . supportedTriples) . map ( Triple . init)
2653
+ }
2654
+ }
2655
+ }
2656
+
2574
2657
private extension ManagedArtifact {
2575
2658
var originURL : String ? {
2576
2659
switch self . source {
@@ -2598,3 +2681,11 @@ private extension PackageDependencyDescription {
2598
2681
}
2599
2682
}
2600
2683
}
2684
+
2685
+ private extension DiagnosticsEngine {
2686
+ func append( contentsOf other: DiagnosticsEngine ) {
2687
+ for diagnostic in other. diagnostics {
2688
+ self . emit ( diagnostic. message, location: diagnostic. location)
2689
+ }
2690
+ }
2691
+ }
0 commit comments