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 1 commit
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
127 changes: 74 additions & 53 deletions Sources/ScipioKit/DescriptionPackage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,58 @@ 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: ResolvedTarget

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

var binaryTarget: BinaryTarget? {
target.underlyingTarget as? BinaryTarget
}

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 visitedTargets: Set<ResolvedTarget> = []
Ryu0118 marked this conversation as resolved.
Show resolved Hide resolved
let descriptionPackage: DescriptionPackage

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

func resolveBuildProducts() throws -> [BuildProduct] {
let targetsToBuild = try targetsToBuild()
var products = try targetsToBuild.flatMap(resolveBuildProduct(from:))

visitedTargets.removeAll()

let productMap: [String: BuildProduct] = Dictionary(products.map { ($0.target.name, $0) }) { $1 }
func resolvedTargetToBuildProduct(_ target: ResolvedTarget) -> BuildProduct {
guard let product = productMap[target.name] else {
Expand All @@ -174,41 +222,18 @@ extension DescriptionPackage {
}
} catch {
switch error {
case GraphError.unexpectedCycle: throw Error.cycleDetected
case GraphError.unexpectedCycle: throw DescriptionPackage.Error.cycleDetected
default: throw error
}
}

return products.reversed()
}

private func targetsToBuild() throws -> Set<ResolvedTarget> {
switch mode {
case .createPackage:
// In create mode, all products should be built
// In future update, users will be enable to specify products want to build
let rootPackage = try fetchRootPackage()
let productNamesToBuild = rootPackage.manifest.products.map { $0.name }
let productsToBuild = rootPackage.products.filter { productNamesToBuild.contains($0.name) }
return Set(productsToBuild.flatMap(\.targets))
case .prepareDependencies:
// In prepare mode, all targets should be built
// In future update, users will be enable to specify targets want to build
return Set(try fetchRootPackage().targets)
}
}

private func fetchRootPackage() throws -> ResolvedPackage {
guard let rootPackage = graph.rootPackages.first else {
throw Error.packageNotDefined
}
return rootPackage
}

private func resolveBuildProduct(from rootTarget: ResolvedTarget) throws -> Set<BuildProduct> {
let dependencyProducts = Set(try rootTarget.recursiveTargetDependencies().flatMap(buildProducts(from:)))

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

private func buildProducts(from target: ResolvedTarget) throws -> Set<BuildProduct> {
guard let package = graph.package(for: target) else {
guard let package = descriptionPackage.graph.package(for: target),
!visitedTargets.contains(target)
else {
return []
}
Ryu0118 marked this conversation as resolved.
Show resolved Hide resolved

visitedTargets.insert(target)

let rootTargetProduct = BuildProduct(package: package, target: target)
let dependencyProducts = try target.recursiveDependencies().compactMap(\.target).flatMap(buildProducts(from:))
return Set([rootTargetProduct] + dependencyProducts)
}
}

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

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

var binaryTarget: BinaryTarget? {
target.underlyingTarget as? BinaryTarget
private func targetsToBuild() throws -> Set<ResolvedTarget> {
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
let rootPackage = try fetchRootPackage()
let productNamesToBuild = rootPackage.manifest.products.map { $0.name }
let productsToBuild = rootPackage.products.filter { productNamesToBuild.contains($0.name) }
return Set(productsToBuild.flatMap(\.targets))
case .prepareDependencies:
// In prepare mode, all targets should be built
// In future update, users will be enable to specify targets want to build
return Set(try fetchRootPackage().targets)
}
}

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 func fetchRootPackage() throws -> ResolvedPackage {
guard let rootPackage = descriptionPackage.graph.rootPackages.first else {
throw DescriptionPackage.Error.packageNotDefined
}
return rootPackage
}
}
Loading