Skip to content

Cache lld binaries to avoid redundant rebuilds #35

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions Sources/GeneratorEngine/Cache/CacheKeyProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ public protocol CacheKeyProtocol {

extension Bool: CacheKeyProtocol {
public func hash(with hashFunction: inout some HashFunction) {
"Swift.Bool".hash(with: &hashFunction)
String(reflecting: Self.self).hash(with: &hashFunction)
hashFunction.update(data: self ? [1] : [0])
}
}

extension Int: CacheKeyProtocol {
public func hash(with hashFunction: inout some HashFunction) {
"Swift.Int".hash(with: &hashFunction)
String(reflecting: Self.self).hash(with: &hashFunction)
withUnsafeBytes(of: self) {
hashFunction.update(bufferPointer: $0)
}
Expand All @@ -40,7 +40,7 @@ extension Int: CacheKeyProtocol {

extension String: CacheKeyProtocol {
public func hash(with hashFunction: inout some HashFunction) {
var t = "Swift.String"
var t = String(reflecting: Self.self)
t.withUTF8 {
hashFunction.update(bufferPointer: .init($0))
}
Expand All @@ -53,21 +53,28 @@ extension String: CacheKeyProtocol {

extension FilePath: CacheKeyProtocol {
public func hash(with hashFunction: inout some HashFunction) {
"SystemPackage.FilePath".hash(with: &hashFunction)
String(reflecting: Self.self).hash(with: &hashFunction)
self.string.hash(with: &hashFunction)
}
}

extension FilePath.Component: CacheKeyProtocol {
public func hash(with hashFunction: inout some HashFunction) {
String(reflecting: Self.self).hash(with: &hashFunction)
self.string.hash(with: &hashFunction)
}
}

extension URL: CacheKeyProtocol {
public func hash(with hashFunction: inout some HashFunction) {
"Foundation.URL".hash(with: &hashFunction)
String(reflecting: Self.self).hash(with: &hashFunction)
self.description.hash(with: &hashFunction)
}
}

extension Optional: CacheKeyProtocol where Wrapped: CacheKeyProtocol {
public func hash(with hashFunction: inout some HashFunction) {
"Swift.Optional".hash(with: &hashFunction)
String(reflecting: Self.self).hash(with: &hashFunction)
if let self {
true.hash(with: &hashFunction)
self.hash(with: &hashFunction)
Expand All @@ -77,5 +84,14 @@ extension Optional: CacheKeyProtocol where Wrapped: CacheKeyProtocol {
}
}

extension Array: CacheKeyProtocol where Element: CacheKeyProtocol {
public func hash(with hashFunction: inout some HashFunction) {
String(reflecting: Self.self).hash(with: &hashFunction)
for element in self {
element.hash(with: &hashFunction)
}
}
}

@attached(extension, conformances: CacheKeyProtocol, names: named(hash(with:)))
public macro CacheKey() = #externalMacro(module: "Macros", type: "CacheKeyMacro")
4 changes: 2 additions & 2 deletions Sources/GeneratorEngine/Cache/FileCacheRecord.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
import struct SystemPackage.FilePath

public struct FileCacheRecord: Sendable {
let path: FilePath
let hash: String
public let path: FilePath
public let hash: String
}

extension FileCacheRecord: Codable {
Expand Down
1 change: 1 addition & 0 deletions Sources/GeneratorEngine/Cache/SQLite.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import SystemPackage
import SystemSQLite

extension FilePath: @unchecked Sendable {}
extension FilePath.Component: @unchecked Sendable {}

/// A minimal SQLite wrapper.
public final class SQLite {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ struct DownloadableArtifacts: Sendable {
isPrebuilt: true
)

let targetArtifactsOS = ArtifactOS(targetTriple.os, versions)
self.targetSwift = .init(
remoteURL: versions.swiftDownloadURL(),
localPath: paths.artifactsCachePath
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,7 @@ import struct SystemPackage.FilePath
private let ubuntuAMD64Mirror = "http://gb.archive.ubuntu.com/ubuntu"
private let ubuntuARM64Mirror = "http://ports.ubuntu.com/ubuntu-ports"

private let byteCountFormatter = ByteCountFormatter()

@Query
struct DownloadQuery {
let artifact: DownloadableArtifacts.Item

func run(engine: Engine) async throws -> FilePath {
print("Downloading remote artifact not available in local cache: \(self.artifact.remoteURL)")
let stream = await engine.httpClient.streamDownloadProgress(for: self.artifact)
.removeDuplicates(by: didProgressChangeSignificantly)
.throttle(for: .seconds(1))

for try await item in stream {
report(progress: item.progress, for: item.artifact)
}
return self.artifact.localPath
}
}
let byteCountFormatter = ByteCountFormatter()

extension SwiftSDKGenerator {
func downloadArtifacts(_ client: HTTPClient) async throws {
Expand All @@ -57,9 +40,8 @@ extension SwiftSDKGenerator {

let results = try await withThrowingTaskGroup(of: FileCacheRecord.self) { group in
for item in self.downloadableArtifacts.allItems {
print(item.remoteURL)
group.addTask {
try await self.engine[DownloadQuery(artifact: item)]
try await self.engine[DownloadArtifactQuery(artifact: item)]
}
}

Expand All @@ -69,6 +51,11 @@ extension SwiftSDKGenerator {
}
return result
}

print("Using downloaded artifacts in these locations:")
for path in results.map(\.path) {
print(path)
}
}

func downloadUbuntuPackages(_ client: HTTPClient, requiredPackages: [String]) async throws {
Expand Down Expand Up @@ -211,40 +198,3 @@ extension HTTPClient {
return result
}
}

/// Checks whether two given progress value are different enough from each other. Used for filtering out progress
/// values in async streams with `removeDuplicates` operator.
/// - Parameters:
/// - previous: Preceding progress value in the stream.
/// - current: Currently processed progress value in the stream.
/// - Returns: `true` if `totalBytes` value is different by any amount or if `receivedBytes` is different by amount
/// larger than 1MiB. Returns `false` otherwise.
@Sendable
private func didProgressChangeSignificantly(
previous: ArtifactDownloadProgress,
current: ArtifactDownloadProgress
) -> Bool {
guard previous.progress.totalBytes == current.progress.totalBytes else {
return true
}

return current.progress.receivedBytes - previous.progress.receivedBytes > 1024 * 1024 * 1024
}

private func report(progress: FileDownloadDelegate.Progress, for artifact: DownloadableArtifacts.Item) {
if let total = progress.totalBytes {
print("""
\(artifact.remoteURL.lastPathComponent) \(
byteCountFormatter
.string(fromByteCount: Int64(progress.receivedBytes))
)/\(
byteCountFormatter
.string(fromByteCount: Int64(total))
)
""")
} else {
print(
"\(artifact.remoteURL.lastPathComponent) \(byteCountFormatter.string(fromByteCount: Int64(progress.receivedBytes)))"
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public extension SwiftSDKGenerator {
try await self.unpackTargetSwiftPackage()
}

try await self.unpackLLDLinker()
try await self.prepareLLDLinker()

try self.fixAbsoluteSymlinks()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
//
//===----------------------------------------------------------------------===//

import struct SystemPackage.FilePath

private let unusedDarwinPlatforms = [
"watchsimulator",
"iphonesimulator",
Expand Down Expand Up @@ -67,36 +69,43 @@ extension SwiftSDKGenerator {
}
}

func unpackLLDLinker() async throws {
func prepareLLDLinker() async throws {
logGenerationStep("Unpacking and copying `lld` linker...")
let downloadableArtifacts = self.downloadableArtifacts
let pathsConfiguration = self.pathsConfiguration
let targetOS = self.targetTriple.os

try await inTemporaryDirectory { fileSystem, tmpDir in
let llvmArtifact = downloadableArtifacts.hostLLVM
try await fileSystem.untar(
file: llvmArtifact.localPath,
into: tmpDir,
stripComponents: 1
)
let llvmArtifact = downloadableArtifacts.hostLLVM

let unpackedLLDPath = if llvmArtifact.isPrebuilt {
tmpDir.appending("bin/lld")
} else {
try await self.buildLLD(llvmSourcesDirectory: tmpDir)
}
let untarDestination = pathsConfiguration.artifactsCachePath.appending(
FilePath.Component(llvmArtifact.localPath.stem!)!.stem
)
try self.createDirectoryIfNeeded(at: untarDestination)
try await self.untar(
file: llvmArtifact.localPath,
into: untarDestination,
stripComponents: 1
)

let toolchainLLDPath = switch targetOS {
case .linux:
pathsConfiguration.toolchainBinDirPath.appending("ld.lld")
case .wasi:
pathsConfiguration.toolchainBinDirPath.appending("wasm-ld")
default:
fatalError()
}
let unpackedLLDPath = if llvmArtifact.isPrebuilt {
untarDestination.appending("bin/lld")
} else {
try await self.engine[CMakeBuildQuery(
sourcesDirectory: untarDestination,
outputBinarySubpath: ["bin", "lld"],
options: "-DLLVM_ENABLE_PROJECTS=lld -DLLVM_TARGETS_TO_BUILD=\(self.targetTriple.cpu.llvmTargetConventionName)"
)].path
}

try await fileSystem.copy(from: unpackedLLDPath, to: toolchainLLDPath)
let toolchainLLDPath = switch targetOS {
case .linux:
pathsConfiguration.toolchainBinDirPath.appending("ld.lld")
case .wasi:
pathsConfiguration.toolchainBinDirPath.appending("wasm-ld")
default:
fatalError()
}

try self.copy(from: unpackedLLDPath, to: toolchainLLDPath)
}
}
36 changes: 36 additions & 0 deletions Sources/SwiftSDKGenerator/Queries/CMakeBuildQuery.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2022-2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import GeneratorEngine
import struct SystemPackage.FilePath

@Query
struct CMakeBuildQuery {
let sourcesDirectory: FilePath
/// Path to the output binary relative to the CMake build directory.
let outputBinarySubpath: [FilePath.Component]
let options: String

func run(engine: Engine) async throws -> FilePath {
try await Shell.run(
"""
cmake -B build -G Ninja -S llvm -DCMAKE_BUILD_TYPE=Release \(self.options)
""",
currentDirectory: self.sourcesDirectory
)

let buildDirectory = self.sourcesDirectory.appending("build")
try await Shell.run("ninja", currentDirectory: buildDirectory)

return self.outputBinarySubpath.reduce(into: buildDirectory) { $0.append($1) }
Copy link
Contributor

@euanh euanh Nov 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to me to read more easily and produces the same result, but maybe I'm missing something subtle
.

Suggested change
return self.outputBinarySubpath.reduce(into: buildDirectory) { $0.append($1) }
return buildDirectory.appending(outputBinarySubpath.components)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was mostly about making it explicit that the query takes relative paths as arguments and not absolute ones.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was reading the docs for FilePath just now and I wondered if that was what you were intending to do, to make sure components such as bin/lld aren't mixed up with full paths.

Copy link
Contributor Author

@MaxDesiatov MaxDesiatov Nov 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a doc comment on that CMakeBuildQuery property explicitly mentioning relative paths, which I hope clarifies things a bit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thing that originally drew my attention here was that I found the reduce(into:) hard to read.

The following still seems clearer to me, and it seems to be be able to append [FilePath.Component] - presumably using appending<C>(_ components: C)
https://developer.apple.com/documentation/system/filepath/appending(_:)-60fwk

However it's just a style suggestion, and I think it's fine to merge this now so the generator branch doesn't accumulate too many PRs.

Suggested change
return self.outputBinarySubpath.reduce(into: buildDirectory) { $0.append($1) }
return buildDirectory.appending(self.outputBinarySubpath)

Copy link
Contributor Author

@MaxDesiatov MaxDesiatov Nov 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, thanks, I'll update this to make it more readable! I'd be happy to merge after that, but prefer to merge PRs separately, so I need a review on the engine PR at the start of this PR chain: #31

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. I wondered if you were going to merge them all back down into max/engine and keep that branch going for a while, but I see you have now addressed Johannes's comments.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I prefer merging PRs from a stack separately, especially as I stick to "Squash and merge" that allows to preserve history of each PR in separate commits.

}
}
Loading