Skip to content
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

Improve DescriptionPackage.resolveBuildProducts() performance #134

Merged
Merged
Changes from 3 commits
Commits
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
96 changes: 60 additions & 36 deletions Sources/ScipioKit/DescriptionPackage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,52 @@ struct DescriptionPackage {
}

extension DescriptionPackage {
func resolveBuildProducts() throws -> [BuildProduct] {
let resolver = BuildProductsResolver(descriptionPackage: self)
return try resolver.resolveBuildProducts()
}
}

struct BuildProduct: Hashable, Sendable {
var package: ResolvedPackage
var target: ScipioResolvedModule

var frameworkName: String {
"\(target.name.packageNamed()).xcframework"
}

var binaryTarget: ScipioBinaryModule? {
target.underlying as? ScipioBinaryModule
}

func hash(into hasher: inout Hasher) {
// Important: Relevant for swift-6.0+ toolchain versions. For the versions below
// this change has no effect as SwiftPM provides its own proper `Hashable`
// implementations for both `ResolvedPackage` and `ResolvedTarget`.
//
// We cannot directly use `ResolvedModule.id` here as `id` also includes `BuildTriple`.
// The reason for this is that `ResolvedModule.buildTriple` is parent-dependent; more
// specifically, the same `ResolvedModule` will have a different build triple depending
// on whether it is in a root or dependency position.
// For more context, see `ResolvedModule.updateBuildTriplesOfDependencies`.
//
// At the same time, build triples remain irrelevant for the `Scipio` use case where the
// build product must be the same regardless of the triple. Meanwhile, the target name and
// package identity remain relevant and unambiguously identify the build product.
hasher.combine(target.name)
hasher.combine(package.identity)
}
}


private final class BuildProductsResolver {
private var buildProductsCache: [BuildProduct: Set<BuildProduct>] = [:]
let descriptionPackage: DescriptionPackage

init(descriptionPackage: DescriptionPackage) {
self.descriptionPackage = descriptionPackage
}

func resolveBuildProducts() throws -> [BuildProduct] {
let targetsToBuild = try targetsToBuild()
var products = try targetsToBuild.flatMap(resolveBuildProduct(from:))
Expand Down Expand Up @@ -181,7 +227,7 @@ extension DescriptionPackage {
}
} catch {
switch error {
case GraphError.unexpectedCycle: throw Error.cycleDetected
case GraphError.unexpectedCycle: throw DescriptionPackage.Error.cycleDetected
default: throw error
}
}
Expand All @@ -190,7 +236,7 @@ extension DescriptionPackage {
}

private func targetsToBuild() throws -> [ScipioResolvedModule] {
switch mode {
switch descriptionPackage.mode {
case .createPackage:
// In create mode, all products should be built
// In future update, users will be enable to specify products want to build
Expand All @@ -214,8 +260,8 @@ extension DescriptionPackage {
}

private func fetchRootPackage() throws -> ResolvedPackage {
guard let rootPackage = graph.rootPackages.first else {
throw Error.packageNotDefined
guard let rootPackage = descriptionPackage.graph.rootPackages.first else {
throw DescriptionPackage.Error.packageNotDefined
}
return rootPackage
}
Expand All @@ -229,7 +275,7 @@ extension DescriptionPackage {
.flatMap(buildProducts(from:)))
#endif

switch mode {
switch descriptionPackage.mode {
case .createPackage:
// In create mode, rootTarget should be built
let rootTargetProducts = try buildProducts(from: rootTarget)
Expand All @@ -241,47 +287,25 @@ extension DescriptionPackage {
}

private func buildProducts(from target: ScipioResolvedModule) throws -> Set<BuildProduct> {
guard let package = graph.package(for: target) else {
guard let package = descriptionPackage.graph.package(for: target) else {
return []
}

let rootTargetProduct = BuildProduct(package: package, target: target)

if let buildProducts = buildProductsCache[rootTargetProduct] {
return buildProducts
}

#if compiler(>=6.0)
let dependencyProducts = try target.recursiveDependencies().compactMap(\.module).flatMap(buildProducts(from:))
#else
let dependencyProducts = try target.recursiveDependencies().compactMap(\.target).flatMap(buildProducts(from:))
#endif
return Set([rootTargetProduct] + dependencyProducts)
}
}

struct BuildProduct: Hashable, Sendable {
var package: ResolvedPackage
var target: ScipioResolvedModule

var frameworkName: String {
"\(target.name.packageNamed()).xcframework"
}

var binaryTarget: ScipioBinaryModule? {
target.underlying as? ScipioBinaryModule
}
let buildProducts = Set([rootTargetProduct] + dependencyProducts)
buildProductsCache.updateValue(buildProducts, forKey: rootTargetProduct)

func hash(into hasher: inout Hasher) {
// Important: Relevant for swift-6.0+ toolchain versions. For the versions below
// this change has no effect as SwiftPM provides its own proper `Hashable`
// implementations for both `ResolvedPackage` and `ResolvedTarget`.
//
// We cannot directly use `ResolvedModule.id` here as `id` also includes `BuildTriple`.
// The reason for this is that `ResolvedModule.buildTriple` is parent-dependent; more
// specifically, the same `ResolvedModule` will have a different build triple depending
// on whether it is in a root or dependency position.
// For more context, see `ResolvedModule.updateBuildTriplesOfDependencies`.
//
// At the same time, build triples remain irrelevant for the `Scipio` use case where the
// build product must be the same regardless of the triple. Meanwhile, the target name and
// package identity remain relevant and unambiguously identify the build product.
hasher.combine(target.name)
hasher.combine(package.identity)
return buildProducts
}
}
Loading