Skip to content

Restore Target‐Based Resolution #3006

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 7 commits into from
Nov 10, 2020
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
19 changes: 19 additions & 0 deletions Fixtures/DependencyResolution/Regressions/SRP/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// swift-tools-version:4.0

import PackageDescription

let package = Package(
name: "SRP",
products: [
.library(name: "SRP", targets: ["SRP"]),
],
dependencies: [
.package(url: "https://github.com/IBM-Swift/BlueCryptor.git", from: "0.8.16"),
.package(url: "https://github.com/lorentey/BigInt.git", from: "3.0.0"),
],
targets: [
.target(name: "SRP", dependencies: ["Cryptor", "BigInt"], path: "Sources"),
.testTarget(name: "SRPTests", dependencies: ["Cryptor", "SRP"]),
],
swiftLanguageVersions: [4]
)
1 change: 1 addition & 0 deletions Fixtures/DependencyResolution/Regressions/SRP/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Reduced from https://github.com/Bouke/SRP/tree/b166838d4cf9218933c03151d62e50772460f95e, which was tripping rdar://problem/65284674.
Empty file.
11 changes: 6 additions & 5 deletions Sources/PackageGraph/RepositoryPackageContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public class RepositoryPackageContainer: BasePackageContainer, CustomStringConve
let _reversedVersions: [Version]

/// The cached dependency information.
private var dependenciesCache: [String: (Manifest, [RepositoryPackageConstraint])] = [:]
private var dependenciesCache: [String: [ProductFilter: (Manifest, [RepositoryPackageConstraint])]] = [:]
private var dependenciesCacheLock = Lock()

init(
Expand Down Expand Up @@ -157,7 +157,7 @@ public class RepositoryPackageContainer: BasePackageContainer, CustomStringConve

public override func getDependencies(at version: Version, productFilter: ProductFilter) throws -> [RepositoryPackageConstraint] {
do {
return try cachedDependencies(forIdentifier: version.description) {
return try cachedDependencies(forIdentifier: version.description, productFilter: productFilter) {
let tag = knownVersions[version]!
let revision = try repository.resolveRevision(tag: tag)
return try getDependencies(at: revision, version: version, productFilter: productFilter)
Expand All @@ -170,7 +170,7 @@ public class RepositoryPackageContainer: BasePackageContainer, CustomStringConve

public override func getDependencies(at revision: String, productFilter: ProductFilter) throws -> [RepositoryPackageConstraint] {
do {
return try cachedDependencies(forIdentifier: revision) {
return try cachedDependencies(forIdentifier: revision, productFilter: productFilter) {
// resolve the revision identifier and return its dependencies.
let revision = try repository.resolveRevision(identifier: revision)
return try getDependencies(at: revision, productFilter: productFilter)
Expand Down Expand Up @@ -203,14 +203,15 @@ public class RepositoryPackageContainer: BasePackageContainer, CustomStringConve

private func cachedDependencies(
forIdentifier identifier: String,
productFilter: ProductFilter,
getDependencies: () throws -> (Manifest, [RepositoryPackageConstraint])
) throws -> (Manifest, [RepositoryPackageConstraint]) {
return try dependenciesCacheLock.withLock {
if let result = dependenciesCache[identifier] {
if let result = dependenciesCache[identifier, default: [:]][productFilter] {
return result
}
let result = try getDependencies()
dependenciesCache[identifier] = result
dependenciesCache[identifier, default: [:]][productFilter] = result
return result
}
}
Expand Down
14 changes: 14 additions & 0 deletions Sources/SPMTestSupport/misc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,20 @@ public func executeSwiftRun(
return try SwiftPMProduct.SwiftRun.execute(args, packagePath: packagePath, env: env)
}

@discardableResult
public func executeSwiftPackage(
_ packagePath: AbsolutePath,
configuration: Configuration = .Debug,
extraArgs: [String] = [],
Xcc: [String] = [],
Xld: [String] = [],
Xswiftc: [String] = [],
env: [String: String]? = nil
) throws -> (stdout: String, stderr: String) {
let args = swiftArgs(configuration: configuration, extraArgs: extraArgs, Xcc: Xcc, Xld: Xld, Xswiftc: Xswiftc)
return try SwiftPMProduct.SwiftPackage.execute(args, packagePath: packagePath, env: env)
}

private func swiftArgs(
configuration: Configuration,
extraArgs: [String],
Expand Down
18 changes: 16 additions & 2 deletions Sources/Workspace/Workspace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1243,14 +1243,28 @@ extension Workspace {
var loadedManifests = [String: Manifest]()

// Compute the transitive closure of available dependencies.
let allManifests = try! topologicalSort(inputManifests.map({ KeyedPair(($0, ProductFilter.everything), key: $0.name)})) { node in
struct NameAndFilter: Hashable { // Just because a raw tuple cannot be hashable.
let name: String
let filter: ProductFilter
}
let allManifestsWithPossibleDuplicates = try! topologicalSort(inputManifests.map({ KeyedPair(($0, ProductFilter.everything), key: NameAndFilter(name: $0.name, filter: .everything)) })) { node in
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a bit surprised that topologicalSort() would return duplicates. That might be something to look into as well (not necessarily as part of this PR). I might be misreading what the underlying code does here, but is the change in this PR a workaround of something that should not have returned duplicates in the first place?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

topologicalSort() is working just fine. The duplicates here are because each node in the sort is a package–filter pair. (PackageA using ProductB ≠ PackageA using ProductC) Later, after the topological sort, the map operation drops the filter, keeping only the raw manifest. This removes the difference between some nodes. (PackageA = PackageA) Operations that follow are preconditioned such that each manifest should occur only once in the array.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, got it. Thanks for clarifying it!

return node.item.0.dependenciesRequired(for: node.item.1).compactMap({ dependency in
let url = config.mirroredURL(forURL: dependency.url)
let manifest = loadedManifests[url] ?? loadManifest(forURL: url, diagnostics: diagnostics)
loadedManifests[url] = manifest
return manifest.flatMap({ KeyedPair(($0, dependency.productFilter), key: $0.name) })
return manifest.flatMap({ KeyedPair(($0, dependency.productFilter), key: NameAndFilter(name: $0.name, filter: dependency.productFilter)) })
})
}
var deduplication: Set<String> = []
var allManifests: [KeyedPair<(Manifest, ProductFilter), NameAndFilter>] = []
for node in allManifestsWithPossibleDuplicates {
if deduplication.contains(node.item.0.name) {
continue // A duplicate.
} else {
allManifests.append(node)
deduplication.insert(node.item.0.name)
}
}

let allDependencyManifests = allManifests.map({ $0.item }).filter({ !root.manifests.contains($0.0) })
let deps = allDependencyManifests.map({ ($0, state.dependencies[forURL: $0.url]!, $1) })
Expand Down
15 changes: 15 additions & 0 deletions Tests/FunctionalTests/DependencyResolutionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,19 @@ class DependencyResolutionTests: XCTestCase {
XCTAssertEqual(output, "♣︎K\n♣︎Q\n♣︎J\n♣︎10\n♣︎9\n♣︎8\n♣︎7\n♣︎6\n♣︎5\n♣︎4\n")
}
}

func testRepositoryCacheDoesNotDerailResolution() throws {
// From rdar://problem/65284674
// RepositoryPackageContainer used to erroneously cache dependencies based only on version,
// storing the result of the first product filter and then continually returning it for other filters too.
// This lead to corrupt graph states.
guard Resources.havePD4Runtime else {
throw XCTSkip("PackageDescription v4 is unavailable; this test requires the compiler script instead of a self‐hosted build.")
}

fixture(name: "DependencyResolution/Regressions/SRP") { prefix in
let (output, error) = try executeSwiftPackage(prefix, extraArgs: ["resolve"])
XCTAssert(error.isEmpty, output + error)
}
}
}