Skip to content

Use topologicalSort with Identifiable on ResolvedTarget #7211

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 2 commits into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions Sources/PackageGraph/GraphLoadingNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,7 @@ extension GraphLoadingNode: CustomStringConvertible {
}
}
}

extension GraphLoadingNode: Identifiable {
public var id: PackageIdentity { self.identity }
}
1 change: 0 additions & 1 deletion Sources/PackageGraph/PackageGraph+Loading.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import OrderedCollections
import PackageLoading
import PackageModel

import func TSCBasic.topologicalSort
import func TSCBasic.bestMatch

extension PackageGraph {
Expand Down
22 changes: 10 additions & 12 deletions Sources/PackageGraph/Resolution/ResolvedTarget.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@

import PackageModel

import func TSCBasic.topologicalSort

/// Represents a fully resolved target. All the dependencies for this target are also stored as resolved.
public struct ResolvedTarget: Hashable {
/// Represents dependency of a resolved target.
Expand Down Expand Up @@ -72,7 +70,7 @@ public struct ResolvedTarget: Hashable {

/// The name of this target.
public var name: String {
return underlying.name
self.underlying.name
}

/// Returns dependencies which satisfy the input build environment, based on their conditions.
Expand All @@ -84,49 +82,49 @@ public struct ResolvedTarget: Hashable {

/// Returns the recursive dependencies, across the whole package-graph.
public func recursiveDependencies() throws -> [Dependency] {
return try TSCBasic.topologicalSort(self.dependencies) { $0.dependencies }
try topologicalSort(self.dependencies) { $0.dependencies }
}

/// Returns the recursive target dependencies, across the whole package-graph.
public func recursiveTargetDependencies() throws -> [ResolvedTarget] {
return try TSCBasic.topologicalSort(self.dependencies) { $0.dependencies }.compactMap { $0.target }
try topologicalSort(self.dependencies) { $0.dependencies }.compactMap { $0.target }
}

/// Returns the recursive dependencies, across the whole package-graph, which satisfy the input build environment,
/// based on their conditions.
/// - Parameters:
/// - environment: The build environment to use to filter dependencies on.
public func recursiveDependencies(satisfying environment: BuildEnvironment) throws -> [Dependency] {
return try TSCBasic.topologicalSort(dependencies(satisfying: environment)) { dependency in
return dependency.dependencies.filter { $0.satisfies(environment) }
try topologicalSort(dependencies(satisfying: environment)) { dependency in
dependency.dependencies.filter { $0.satisfies(environment) }
}
}

/// The language-level target name.
public var c99name: String {
return underlying.c99name
self.underlying.c99name
}

/// Module aliases for dependencies of this target. The key is an
/// original target name and the value is a new unique name mapped
/// to the name of its .swiftmodule binary.
public var moduleAliases: [String: String]? {
return underlying.moduleAliases
self.underlying.moduleAliases
}

/// Allows access to package symbols from other targets in the package
public var packageAccess: Bool {
return underlying.packageAccess
self.underlying.packageAccess
}

/// The "type" of target.
public var type: Target.Kind {
return underlying.type
self.underlying.type
}

/// The sources for the target.
public var sources: Sources {
return underlying.sources
self.underlying.sources
}

let packageIdentity: PackageIdentity
Expand Down
42 changes: 42 additions & 0 deletions Sources/SPMTestSupport/ResolvedTarget+Mock.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2014-2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import PackageGraph
import PackageModel

extension ResolvedTarget {
Copy link
Contributor Author

@MaxDesiatov MaxDesiatov Dec 20, 2023

Choose a reason for hiding this comment

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

Moved from ResolvedTargetTests for reuse in perf tests.

public static func mock(
packageIdentity: PackageIdentity,
name: String,
deps: ResolvedTarget...,
conditions: [PackageCondition] = []
) -> ResolvedTarget {
ResolvedTarget(
packageIdentity: packageIdentity,
underlying: SwiftTarget(
name: name,
type: .library,
path: .root,
sources: Sources(paths: [], root: "/"),
dependencies: [],
packageAccess: false,
swiftVersion: .v4,
usesUnsafeFlags: false
),
dependencies: deps.map { .target($0, conditions: conditions) },
defaultLocalization: nil,
supportedPlatforms: [],
platformVersionProvider: .init(implementation: .minimumDeploymentTargetDefault)
)
}
}

20 changes: 19 additions & 1 deletion Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2014-2017 Apple Inc. and the Swift project authors
// Copyright (c) 2014-2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
Expand Down Expand Up @@ -161,4 +161,22 @@ final class PackageGraphPerfTests: XCTestCasePerf {
}
}
}

func testRecursiveDependencies() throws {
var resolvedTarget = ResolvedTarget.mock(packageIdentity: "pkg", name: "t0")
for i in 1..<1000 {
resolvedTarget = ResolvedTarget.mock(packageIdentity: "pkg", name: "t\(i)", deps: resolvedTarget)
}

let N = 10
measure {
do {
for _ in 0..<N {
_ = try resolvedTarget.recursiveTargetDependencies()
}
} catch {
XCTFail("Loading package graph is not expected to fail in this test.")
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2014-2018 Apple Inc. and the Swift project authors
// Copyright (c) 2014-2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
Expand All @@ -14,84 +14,58 @@ import XCTest

import PackageGraph
@testable import PackageModel
import SPMTestSupport

extension ResolvedTarget {
fileprivate init(
packageIdentity: String,
name: String,
deps: ResolvedTarget...,
conditions: [PackageCondition] = []
) {
self.init(
packageIdentity: .init(packageIdentity),
underlying: SwiftTarget(
name: name,
type: .library,
path: .root,
sources: Sources(paths: [], root: "/"),
dependencies: [],
packageAccess: false,
swiftVersion: .v4,
usesUnsafeFlags: false
),
dependencies: deps.map { .target($0, conditions: conditions) },
defaultLocalization: nil,
supportedPlatforms: [],
platformVersionProvider: .init(implementation: .minimumDeploymentTargetDefault)
)
}
}

class TargetDependencyTests: XCTestCase {
final class ResolvedTargetDependencyTests: XCTestCase {
func test1() throws {
let t1 = ResolvedTarget(packageIdentity: "pkg", name: "t1")
let t2 = ResolvedTarget(packageIdentity: "pkg", name: "t2", deps: t1)
let t3 = ResolvedTarget(packageIdentity: "pkg", name: "t3", deps: t2)
let t1 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t1")
let t2 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t2", deps: t1)
let t3 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t3", deps: t2)

XCTAssertEqual(try t3.recursiveTargetDependencies(), [t2, t1])
XCTAssertEqual(try t2.recursiveTargetDependencies(), [t1])
}

func test2() throws {
let t1 = ResolvedTarget(packageIdentity: "pkg", name: "t1")
let t2 = ResolvedTarget(packageIdentity: "pkg", name: "t2", deps: t1)
let t3 = ResolvedTarget(packageIdentity: "pkg", name: "t3", deps: t2, t1)
let t4 = ResolvedTarget(packageIdentity: "pkg", name: "t4", deps: t2, t3, t1)
let t1 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t1")
let t2 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t2", deps: t1)
let t3 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t3", deps: t2, t1)
let t4 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t4", deps: t2, t3, t1)

XCTAssertEqual(try t4.recursiveTargetDependencies(), [t3, t2, t1])
XCTAssertEqual(try t3.recursiveTargetDependencies(), [t2, t1])
XCTAssertEqual(try t2.recursiveTargetDependencies(), [t1])
}

func test3() throws {
let t1 = ResolvedTarget(packageIdentity: "pkg", name: "t1")
let t2 = ResolvedTarget(packageIdentity: "pkg", name: "t2", deps: t1)
let t3 = ResolvedTarget(packageIdentity: "pkg", name: "t3", deps: t2, t1)
let t4 = ResolvedTarget(packageIdentity: "pkg", name: "t4", deps: t1, t2, t3)
let t1 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t1")
let t2 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t2", deps: t1)
let t3 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t3", deps: t2, t1)
let t4 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t4", deps: t1, t2, t3)

XCTAssertEqual(try t4.recursiveTargetDependencies(), [t3, t2, t1])
XCTAssertEqual(try t3.recursiveTargetDependencies(), [t2, t1])
XCTAssertEqual(try t2.recursiveTargetDependencies(), [t1])
}

func test4() throws {
let t1 = ResolvedTarget(packageIdentity: "pkg", name: "t1")
let t2 = ResolvedTarget(packageIdentity: "pkg", name: "t2", deps: t1)
let t3 = ResolvedTarget(packageIdentity: "pkg", name: "t3", deps: t2)
let t4 = ResolvedTarget(packageIdentity: "pkg", name: "t4", deps: t3)
let t1 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t1")
let t2 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t2", deps: t1)
let t3 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t3", deps: t2)
let t4 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t4", deps: t3)

XCTAssertEqual(try t4.recursiveTargetDependencies(), [t3, t2, t1])
XCTAssertEqual(try t3.recursiveTargetDependencies(), [t2, t1])
XCTAssertEqual(try t2.recursiveTargetDependencies(), [t1])
}

func test5() throws {
let t1 = ResolvedTarget(packageIdentity: "pkg", name: "t1")
let t2 = ResolvedTarget(packageIdentity: "pkg", name: "t2", deps: t1)
let t3 = ResolvedTarget(packageIdentity: "pkg", name: "t3", deps: t2)
let t4 = ResolvedTarget(packageIdentity: "pkg", name: "t4", deps: t3)
let t5 = ResolvedTarget(packageIdentity: "pkg", name: "t5", deps: t2)
let t6 = ResolvedTarget(packageIdentity: "pkg", name: "t6", deps: t5, t4)
let t1 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t1")
let t2 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t2", deps: t1)
let t3 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t3", deps: t2)
let t4 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t4", deps: t3)
let t5 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t5", deps: t2)
let t6 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t6", deps: t5, t4)

// precise order is not important, but it is important that the following are true
let t6rd = try t6.recursiveTargetDependencies()
Expand All @@ -108,12 +82,12 @@ class TargetDependencyTests: XCTestCase {
}

func test6() throws {
let t1 = ResolvedTarget(packageIdentity: "pkg", name: "t1")
let t2 = ResolvedTarget(packageIdentity: "pkg", name: "t2", deps: t1)
let t3 = ResolvedTarget(packageIdentity: "pkg", name: "t3", deps: t2)
let t4 = ResolvedTarget(packageIdentity: "pkg", name: "t4", deps: t3)
let t5 = ResolvedTarget(packageIdentity: "pkg", name: "t5", deps: t2)
let t6 = ResolvedTarget(
let t1 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t1")
let t2 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t2", deps: t1)
let t3 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t3", deps: t2)
let t4 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t4", deps: t3)
let t5 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t5", deps: t2)
let t6 = ResolvedTarget.mock(
packageIdentity: "pkg",
name: "t6",
deps: t4,
Expand All @@ -135,10 +109,10 @@ class TargetDependencyTests: XCTestCase {
}

func testConditions() throws {
let t1 = ResolvedTarget(packageIdentity: "pkg", name: "t1")
let t2 = ResolvedTarget(packageIdentity: "pkg", name: "t2", deps: t1)
let t2NoConditions = ResolvedTarget(packageIdentity: "pkg", name: "t2", deps: t1)
let t2WithConditions = ResolvedTarget(
let t1 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t1")
let t2 = ResolvedTarget.mock(packageIdentity: "pkg", name: "t2", deps: t1)
let t2NoConditions = ResolvedTarget.mock(packageIdentity: "pkg", name: "t2", deps: t1)
let t2WithConditions = ResolvedTarget.mock(
packageIdentity: "pkg",
name: "t2",
deps: t1,
Expand Down