Skip to content

Fix Identity Resolution #3871

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 3 commits into from
Nov 19, 2021
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
25 changes: 20 additions & 5 deletions Sources/PackageModel/Manifest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,12 @@ public final class Manifest {
#if ENABLE_TARGET_BASED_DEPENDENCY_RESOLUTION
// If we have already calculated it, returned the cached value.
if let dependencies = self._requiredDependencies[productFilter] {
return self.dependencies
return dependencies
} else {
let targets = self.targetsRequired(for: productFilter)
let dependencies = self.dependenciesRequired(for: targets, keepUnused: productFilter == .everything)
self._requiredDependencies[productFilter] = dependencies
return self.dependencies
return dependencies
}
#else
guard toolsVersion >= .v5_2 && !packageKind.isRoot else {
Expand Down Expand Up @@ -300,12 +300,27 @@ public final class Manifest {
return nil
}

return packageDependency(referencedBy: packageName)
}
private func packageDependency(
referencedBy packageName: String
) -> PackageDependency? {
return self.dependencies.first(where: {
// rdar://80594761 make sure validation is case insensitive
$0.nameForTargetDependencyResolutionOnly.lowercased() == packageName.lowercased()
})
}

/// Returns the package identity referred to by a target dependency string.
///
/// This first checks if any declared package names (from 5.2) match.
/// If none is found, it is assumed that the string is the package identity itself
/// (although it may actually be a dangling reference diagnosed later).
private func packageIdentity(referencedBy packageName: String) -> PackageIdentity {
return packageDependency(referencedBy: packageName)?.identity
?? .plain(packageName)
}

/// Registers a required product with a particular dependency if possible, or registers it as unknown.
///
/// - Parameters:
Expand All @@ -324,7 +339,7 @@ public final class Manifest {
if let package = package { // ≥ 5.2
if !register(
product: product,
inPackage: .plain(package),
inPackage: packageIdentity(referencedBy: package),
registry: &registry.known,
availablePackages: availablePackages) {
// This is an invalid manifest condition diagnosed later. (No such package.)
Expand All @@ -347,7 +362,7 @@ public final class Manifest {
// If a by‐name entry is a product, it must be in a package of the same name.
if !register(
product: product,
inPackage: .plain(product),
inPackage: packageIdentity(referencedBy: product),
registry: &registry.known,
availablePackages: availablePackages) {
// If it doesn’t match a package, it should be a target, not a product.
Expand Down Expand Up @@ -381,7 +396,7 @@ public final class Manifest {
if let package = package {
if !register(
product: name,
inPackage: .plain(package),
inPackage: packageIdentity(referencedBy: package),
registry: &registry.known,
availablePackages: availablePackages) {
// Invalid, diagnosed later; see the dependency version of this method.
Expand Down
4 changes: 2 additions & 2 deletions Tests/BuildTests/BuildPlanTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1482,9 +1482,9 @@ final class BuildPlanTests: XCTestCase {

#if ENABLE_TARGET_BASED_DEPENDENCY_RESOLUTION
XCTAssertEqual(observability.diagnostics.count, 1)
let firstDiagnostic = observability.diagnostics.first.map({ $0.message.text })
let firstDiagnostic = observability.diagnostics.first.map({ $0.message })
XCTAssert(
firstDiagnostic == "dependency 'C' is not used by any target",
firstDiagnostic == "dependency 'c' is not used by any target",
"Unexpected diagnostic: " + (firstDiagnostic ?? "[none]")
)
#endif
Expand Down
37 changes: 37 additions & 0 deletions Tests/PackageGraphTests/PackageGraphTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1855,6 +1855,43 @@ class PackageGraphTests: XCTestCase {
XCTAssertNoDiagnostics(observability.diagnostics)
}
}

// test backwards compatibility 5.2 < 5.4
// TODO: remove this when we remove explicit dependency name
func testTargetDependencies_Post52_AliasFindsIdentity() throws {
let manifest = Manifest.createRootManifest(
name: "Package",
path: .init("/Package"),
toolsVersion: .v5_2,
dependencies: [
.localSourceControl(
deprecatedName: "Alias",
path: .init("/Identity"),
requirement: .upToNextMajor(from: "1.0.0")
),
.localSourceControl(
path: .init("/Unrelated"),
requirement: .upToNextMajor(from: "1.0.0")
)
],
targets: [
try TargetDescription(
name: "Target",
dependencies: [
.product(name: "Product", package: "Alias"),
.product(name: "Unrelated", package: "Unrelated")
]
),
])
// Make sure aliases are found properly and do not fall back to pre‐5.2 behaviour, leaking across onto other dependencies.
let required = manifest.dependenciesRequired(for: .everything)
let unrelated = try XCTUnwrap(required.first(where: { $0.nameForTargetDependencyResolutionOnly == "Unrelated" }))
let requestedProducts = unrelated.productFilter
#if ENABLE_TARGET_BASED_DEPENDENCY_RESOLUTION
// Unrelated should not have been asked for Product, because it should know Product comes from Identity.
XCTAssertFalse(requestedProducts.contains("Product"), "Product requests are leaking.")
#endif
}
}


Expand Down
6 changes: 3 additions & 3 deletions Tests/PackageModelTests/ManifestTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,9 @@ class ManifestTests: XCTestCase {
)

#if ENABLE_TARGET_BASED_DEPENDENCY_RESOLUTION
XCTAssertEqual(manifest.dependenciesRequired(for: .specific(["Foo"])).map({ $0.name }).sorted(), [
"Bar1", // Foo → Foo1 → Bar1
"Bar2", // Foo → Foo1 → Foo2 → Bar2
XCTAssertEqual(manifest.dependenciesRequired(for: .specific(["Foo"])).map({ $0.identity.description }).sorted(), [
"bar1", // Foo → Foo1 → Bar1
"bar2", // Foo → Foo1 → Foo2 → Bar2
// (Bar3 is unreachable.)
])
#endif
Expand Down
8 changes: 2 additions & 6 deletions Tests/WorkspaceTests/WorkspaceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2051,13 +2051,11 @@ final class WorkspaceTests: XCTestCase {
]
)

#if ENABLE_TARGET_BASED_DEPENDENCY_RESOLUTION
try workspace.checkPackageGraph(roots: ["Root"]) { _, diagnostics in
testDiagnostics(diagnostics) { result in
result.check(diagnostic: .contains("Foo[Foo] 1.0.0..<2.0.0"), severity: .error)
result.check(diagnostic: .contains("'foo' 1.0.0..<2.0.0"), severity: .error)
}
}
#endif
}

func testToolsVersionRootPackages() throws {
Expand Down Expand Up @@ -2922,11 +2920,9 @@ final class WorkspaceTests: XCTestCase {
result.check(packages: "Foo")
result.check(targets: "Foo")
}
#if ENABLE_TARGET_BASED_DEPENDENCY_RESOLUTION
testDiagnostics(diagnostics) { result in
result.check(diagnostic: .contains("Bar[Bar] {1.0.0..<1.5.0, 1.5.1..<2.0.0} is forbidden"), severity: .error)
result.check(diagnostic: .contains("'bar' {1.0.0..<1.5.0, 1.5.1..<2.0.0} cannot be used"), severity: .error)
}
#endif
}
}

Expand Down