Skip to content

Commit 1ef53e8

Browse files
authored
Cache DownloadableArtifacts with GeneratorEngine (#34)
New `GeneratorEngine` allows us to cache all items in `DownloadableArtifacts` in a generic way and to allow redundant re-downloads for artifacts that don't have their checksums hardcoded (e.g. LLVM sources for `lld`). This now allows us to delete all hardcoded checksums and also to cache other artifacts in the future, like `lld` binary and even whole artifact bundles.
1 parent e7a283a commit 1ef53e8

File tree

12 files changed

+116
-184
lines changed

12 files changed

+116
-184
lines changed

Package.resolved

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@
113113
"kind" : "remoteSourceControl",
114114
"location" : "https://github.com/apple/swift-syntax.git",
115115
"state" : {
116-
"revision" : "ffa3cd6fc2aa62adbedd31d3efaf7c0d86a9f029",
117-
"version" : "509.0.1"
116+
"revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036",
117+
"version" : "509.0.2"
118118
}
119119
},
120120
{

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ let package = Package(
2626
.package(url: "https://github.com/apple/swift-nio.git", from: "2.58.0"),
2727
.package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.19.0"),
2828
.package(url: "https://github.com/apple/swift-log.git", from: "1.5.3"),
29-
.package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.1"),
29+
.package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.2"),
3030
],
3131
targets: [
3232
// Targets are the basic building blocks of a package. A target can define a module or a test suite.

Sources/GeneratorCLI/GeneratorCLI.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ struct GeneratorCLI: AsyncParsableCommand {
8888
let linuxDistribution = try LinuxDistribution(name: linuxDistributionName, version: linuxDistributionVersion)
8989

9090
let elapsed = try await ContinuousClock().measure {
91-
try await SwiftSDKGenerator(
91+
let generator = try await SwiftSDKGenerator(
9292
hostCPUArchitecture: self.hostArch,
9393
targetCPUArchitecture: self.targetArch,
9494
swiftVersion: self.swiftVersion,
@@ -98,7 +98,13 @@ struct GeneratorCLI: AsyncParsableCommand {
9898
shouldUseDocker: self.withDocker,
9999
isVerbose: self.verbose
100100
)
101-
.generateBundle(shouldGenerateFromScratch: !self.incremental)
101+
do {
102+
try await generator.generateBundle(shouldGenerateFromScratch: !self.incremental)
103+
try await generator.shutDown()
104+
} catch {
105+
try await generator.shutDown()
106+
throw error
107+
}
102108
}
103109

104110
print("\nTime taken for this generator run: \(elapsed.intervalString).")

Sources/GeneratorEngine/Cache/FileCacheRecord.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import struct SystemPackage.FilePath
1414

15-
struct FileCacheRecord {
15+
public struct FileCacheRecord: Sendable {
1616
let path: FilePath
1717
let hash: String
1818
}
@@ -23,14 +23,14 @@ extension FileCacheRecord: Codable {
2323
case hash
2424
}
2525

26-
// FIXME: `Codable` on `FilePath` is broken
27-
init(from decoder: any Decoder) throws {
26+
// FIXME: `Codable` on `FilePath` is broken, thus all `Codable` types with `FilePath` properties need a custom impl.
27+
public init(from decoder: any Decoder) throws {
2828
let container = try decoder.container(keyedBy: CodingKeys.self)
2929
self.path = try FilePath(container.decode(String.self, forKey: .path))
3030
self.hash = try container.decode(String.self, forKey: .hash)
3131
}
3232

33-
func encode(to encoder: any Encoder) throws {
33+
public func encode(to encoder: any Encoder) throws {
3434
var container = encoder.container(keyedBy: CodingKeys.self)
3535
try container.encode(self.path.string, forKey: .path)
3636
try container.encode(self.hash, forKey: .hash)

Sources/GeneratorEngine/Engine.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ public actor Engine {
3232
/// `defer { engine.shutDown }` on the line that follows this initializer call.
3333
/// - Parameter fileSystem: Implementation of a file system this engine should use.
3434
/// - Parameter cacheLocation: Location of cache storage used by the engine.
35-
/// - Parameter httpClient: HTTP client to use in queries that need it.
3635
/// - Parameter logger: Logger to use during queries execution.
3736
public init(
3837
_ fileSystem: any FileSystem,
@@ -63,7 +62,7 @@ public actor Engine {
6362
/// Executes a given query if no cached result of it is available. Otherwise fetches the result from engine's cache.
6463
/// - Parameter query: A query value to execute.
6564
/// - Returns: A file path to query's result recorded in a file.
66-
public subscript(_ query: some QueryProtocol) -> FilePath {
65+
public subscript(_ query: some QueryProtocol) -> FileCacheRecord {
6766
get async throws {
6867
var hashFunction = SHA512()
6968
query.hash(with: &hashFunction)
@@ -78,7 +77,7 @@ public actor Engine {
7877

7978
if fileHash == fileRecord.hash {
8079
self.cacheHits += 1
81-
return fileRecord.path
80+
return fileRecord
8281
}
8382
}
8483

@@ -90,11 +89,12 @@ public actor Engine {
9089
try await $0.hash(with: &hashFunction)
9190
}
9291
let resultHash = hashFunction.finalize()
92+
let result = FileCacheRecord(path: resultPath, hash: resultHash.description)
9393

9494
// FIXME: update `SQLiteBackedCache` to store `resultHash` directly instead of relying on string conversions
95-
try self.resultsCache.set(key, to: FileCacheRecord(path: resultPath, hash: resultHash.description))
95+
try self.resultsCache.set(key, to: result)
9696

97-
return resultPath
97+
return result
9898
}
9999
}
100100
}

Sources/SwiftSDKGenerator/Artifacts/DownloadableArtifacts.swift

Lines changed: 13 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import struct Foundation.URL
14+
import GeneratorEngine
1415
import struct SystemPackage.FilePath
1516

1617
/// Information about the OS for which the artifact is built, if it's downloaded as prebuilt.
@@ -45,74 +46,26 @@ enum ArtifactOS: Hashable {
4546

4647
typealias CPUMapping = [Triple.CPU: String]
4748

48-
/// SHA256 hashes of binary LLVM artifacts known to the generator.
49-
private let knownLLVMBinariesVersions: [ArtifactOS: [String: CPUMapping]] = [
50-
.macOS: [
51-
"15.0.7": [
52-
Triple.CPU.arm64: "867c6afd41158c132ef05a8f1ddaecf476a26b91c85def8e124414f9a9ba188d",
53-
],
54-
"16.0.0": [
55-
Triple.CPU.arm64: "2041587b90626a4a87f0de14a5842c14c6c3374f42c8ed12726ef017416409d9",
56-
],
57-
"16.0.1": [
58-
Triple.CPU.arm64: "cb487fa991f047dc79ae36430cbb9ef14621c1262075373955b1d97215c75879",
59-
],
60-
"16.0.4": [
61-
Triple.CPU.arm64: "429b8061d620108fee636313df55a0602ea0d14458c6d3873989e6b130a074bd",
62-
],
63-
"16.0.5": [
64-
Triple.CPU.arm64: "1aed0787417dd915f0101503ce1d2719c8820a2c92d4a517bfc4044f72035bcc",
65-
],
66-
],
67-
]
68-
69-
/// SHA256 hashes of binary Swift artifacts known to the generator.
70-
private let knownSwiftBinariesVersions: [ArtifactOS: [String: CPUMapping]] = [
71-
.linux(.ubuntu(.jammy)): [
72-
"5.7.3-RELEASE": [
73-
.arm64: "75003d5a995292ae3f858b767fbb89bc3edee99488f4574468a0e44341aec55b",
74-
],
75-
"5.8-RELEASE": [
76-
.arm64: "12ea2df36f9af0aefa74f0989009683600978f62223e7dd73b627c90c7fe9273",
77-
],
78-
"5.9-RELEASE": [
79-
.arm64: "30b289e02f7e03c380744ea97fdf0e96985dff504b0f09de23e098fdaf6513f3",
80-
.x86_64: "bca015e9d727ca39385d7e5b5399f46302d54a02218d40d1c3063662ffc6b42f",
81-
],
82-
],
83-
.macOS: [
84-
"5.7.3-RELEASE": [
85-
.arm64: "ba3516845eb8f4469a8bb06a273687f05791187324a3843996af32a73a2a687d",
86-
.x86_64: "ba3516845eb8f4469a8bb06a273687f05791187324a3843996af32a73a2a687d",
87-
],
88-
"5.8-RELEASE": [
89-
.arm64: "9b6cc56993652ca222c86a2d6b7b66abbd50bb92cc526efc2b23d47d40002097",
90-
.x86_64: "9b6cc56993652ca222c86a2d6b7b66abbd50bb92cc526efc2b23d47d40002097",
91-
],
92-
"5.9-RELEASE": [
93-
.arm64: "3cf7a4b2f3efcfcb4fef42b6588a7b1c54f7b0f2d0a479f41c3e1620b045f48e",
94-
.x86_64: "3cf7a4b2f3efcfcb4fef42b6588a7b1c54f7b0f2d0a479f41c3e1620b045f48e",
95-
],
96-
],
97-
]
98-
99-
private let knownLLVMSourcesVersions: [String: String] = [
100-
"16.0.5": "37f540124b9cfd4680666e649f557077f9937c9178489cea285a672e714b2863",
101-
]
102-
103-
public struct DownloadableArtifacts: Sendable {
104-
public struct Item: Sendable {
49+
struct DownloadableArtifacts: Sendable {
50+
@CacheKey
51+
struct Item: Sendable {
10552
let remoteURL: URL
10653
var localPath: FilePath
107-
let checksum: String?
10854
let isPrebuilt: Bool
10955
}
11056

11157
let hostSwift: Item
11258
private(set) var hostLLVM: Item
11359
let targetSwift: Item
11460

115-
let allItems: [Item]
61+
private let shouldUseDocker: Bool
62+
var allItems: [Item] {
63+
if self.shouldUseDocker {
64+
[self.hostSwift, self.hostLLVM]
65+
} else {
66+
[self.hostSwift, self.hostLLVM, self.targetSwift]
67+
}
68+
}
11669

11770
private let versions: VersionsConfiguration
11871
private let paths: PathsConfiguration
@@ -136,7 +89,6 @@ public struct DownloadableArtifacts: Sendable {
13689
),
13790
localPath: paths.artifactsCachePath
13891
.appending("host_swift_\(versions.swiftVersion)_\(hostTriple).pkg"),
139-
checksum: knownSwiftBinariesVersions[hostArtifactsOS]?[versions.swiftVersion]?[hostTriple.cpu],
14092
isPrebuilt: true
14193
)
14294

@@ -152,7 +104,6 @@ public struct DownloadableArtifacts: Sendable {
152104
)!,
153105
localPath: paths.artifactsCachePath
154106
.appending("host_llvm_\(versions.lldVersion)_\(hostTriple).tar.xz"),
155-
checksum: knownLLVMBinariesVersions[hostArtifactsOS]?[versions.lldVersion]?[hostTriple.cpu],
156107
isPrebuilt: true
157108
)
158109

@@ -161,15 +112,10 @@ public struct DownloadableArtifacts: Sendable {
161112
remoteURL: versions.swiftDownloadURL(),
162113
localPath: paths.artifactsCachePath
163114
.appending("target_swift_\(versions.swiftVersion)_\(targetTriple).tar.gz"),
164-
checksum: knownSwiftBinariesVersions[targetArtifactsOS]?[versions.swiftVersion]?[targetTriple.cpu],
165115
isPrebuilt: true
166116
)
167117

168-
self.allItems = if shouldUseDocker {
169-
[self.hostSwift, self.hostLLVM]
170-
} else {
171-
[self.hostSwift, self.hostLLVM, self.targetSwift]
172-
}
118+
self.shouldUseDocker = shouldUseDocker
173119
}
174120

175121
mutating func useLLVMSources() {
@@ -185,7 +131,6 @@ public struct DownloadableArtifacts: Sendable {
185131
)!,
186132
localPath: self.paths.artifactsCachePath
187133
.appending("llvm_\(self.versions.lldVersion).src.tar.xz"),
188-
checksum: knownLLVMSourcesVersions[self.versions.lldVersion],
189134
isPrebuilt: false
190135
)
191136
}

Sources/SwiftSDKGenerator/Generator/SwiftSDKGenerator+Download.swift

Lines changed: 34 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,36 @@
1212

1313
import AsyncAlgorithms
1414
import AsyncHTTPClient
15+
import GeneratorEngine
1516
import RegexBuilder
1617

1718
import class Foundation.ByteCountFormatter
1819
import struct Foundation.URL
1920

21+
import struct SystemPackage.FilePath
22+
2023
private let ubuntuAMD64Mirror = "http://gb.archive.ubuntu.com/ubuntu"
2124
private let ubuntuARM64Mirror = "http://ports.ubuntu.com/ubuntu-ports"
2225

2326
private let byteCountFormatter = ByteCountFormatter()
2427

28+
@Query
29+
struct DownloadQuery {
30+
let artifact: DownloadableArtifacts.Item
31+
32+
func run(engine: Engine) async throws -> FilePath {
33+
print("Downloading remote artifact not available in local cache: \(self.artifact.remoteURL)")
34+
let stream = await engine.httpClient.streamDownloadProgress(for: self.artifact)
35+
.removeDuplicates(by: didProgressChangeSignificantly)
36+
.throttle(for: .seconds(1))
37+
38+
for try await item in stream {
39+
report(progress: item.progress, for: item.artifact)
40+
}
41+
return self.artifact.localPath
42+
}
43+
}
44+
2545
extension SwiftSDKGenerator {
2646
func downloadArtifacts(_ client: HTTPClient) async throws {
2747
logGenerationStep("Downloading required toolchain packages...")
@@ -35,42 +55,19 @@ extension SwiftSDKGenerator {
3555
downloadableArtifacts.useLLVMSources()
3656
}
3757

38-
let hostSwiftProgressStream = client.streamDownloadProgress(for: downloadableArtifacts.hostSwift)
39-
.removeDuplicates(by: didProgressChangeSignificantly)
40-
let hostLLVMProgressStream = client.streamDownloadProgress(for: downloadableArtifacts.hostLLVM)
41-
.removeDuplicates(by: didProgressChangeSignificantly)
42-
43-
print("Using these URLs for downloads:")
44-
45-
for artifact in downloadableArtifacts.allItems {
46-
print(artifact.remoteURL)
47-
}
48-
49-
// FIXME: some code duplication is necessary due to https://github.com/apple/swift-async-algorithms/issues/226
50-
if shouldUseDocker {
51-
let stream = combineLatest(hostSwiftProgressStream, hostLLVMProgressStream)
52-
.throttle(for: .seconds(1))
53-
54-
for try await (swiftProgress, llvmProgress) in stream {
55-
report(progress: swiftProgress, for: downloadableArtifacts.hostSwift)
56-
report(progress: llvmProgress, for: downloadableArtifacts.hostLLVM)
58+
let results = try await withThrowingTaskGroup(of: FileCacheRecord.self) { group in
59+
for item in self.downloadableArtifacts.allItems {
60+
print(item.remoteURL)
61+
group.addTask {
62+
try await self.engine[DownloadQuery(artifact: item)]
63+
}
5764
}
58-
} else {
59-
let targetSwiftProgressStream = client.streamDownloadProgress(for: downloadableArtifacts.targetSwift)
60-
.removeDuplicates(by: didProgressChangeSignificantly)
61-
62-
let stream = combineLatest(
63-
hostSwiftProgressStream,
64-
hostLLVMProgressStream,
65-
targetSwiftProgressStream
66-
)
67-
.throttle(for: .seconds(1))
6865

69-
for try await (hostSwiftProgress, hostLLVMProgress, targetSwiftProgress) in stream {
70-
report(progress: hostSwiftProgress, for: downloadableArtifacts.hostSwift)
71-
report(progress: hostLLVMProgress, for: downloadableArtifacts.hostLLVM)
72-
report(progress: targetSwiftProgress, for: downloadableArtifacts.targetSwift)
66+
var result = [FileCacheRecord]()
67+
for try await file in group {
68+
result.append(file)
7369
}
70+
return result
7471
}
7572
}
7673

@@ -224,14 +221,14 @@ extension HTTPClient {
224221
/// larger than 1MiB. Returns `false` otherwise.
225222
@Sendable
226223
private func didProgressChangeSignificantly(
227-
previous: FileDownloadDelegate.Progress,
228-
current: FileDownloadDelegate.Progress
224+
previous: ArtifactDownloadProgress,
225+
current: ArtifactDownloadProgress
229226
) -> Bool {
230-
guard previous.totalBytes == current.totalBytes else {
227+
guard previous.progress.totalBytes == current.progress.totalBytes else {
231228
return true
232229
}
233230

234-
return current.receivedBytes - previous.receivedBytes > 1024 * 1024 * 1024
231+
return current.progress.receivedBytes - previous.progress.receivedBytes > 1024 * 1024 * 1024
235232
}
236233

237234
private func report(progress: FileDownloadDelegate.Progress, for artifact: DownloadableArtifacts.Item) {

0 commit comments

Comments
 (0)