Skip to content

Commit 8beea8a

Browse files
authored
improve and relax the validation of explicit dependency names (#3687)
motivation: follow up work to relaxing the requirement to set an explicit name on dependency, this relaxes the requirement that such name (if/when set) must match the manifest's name attribute, which we want to deprecate over time changes: * change the validation flow to ignore the name set in the manifest, instead manage an in-memory list of names used for target based package lookup which suffices * remove a few tests that are no longer needed given the relaxed requirment * fix a couple of tests that were always wrong but previous validation did not catch
1 parent bed2934 commit 8beea8a

File tree

7 files changed

+59
-320
lines changed

7 files changed

+59
-320
lines changed

Sources/PackageGraph/PackageGraph+Loading.swift

Lines changed: 42 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,12 @@ private func createResolvedPackages(
205205
return nil
206206
}
207207
let isAllowedToVendUnsafeProducts = unsafeAllowedPackages.contains{ $0.location == package.manifest.packageLocation }
208+
let allowedToOverride = rootManifestSet.contains(node.manifest)
208209
return ResolvedPackageBuilder(
209210
package,
210211
productFilter: node.productFilter,
211-
isAllowedToVendUnsafeProducts: isAllowedToVendUnsafeProducts
212+
isAllowedToVendUnsafeProducts: isAllowedToVendUnsafeProducts,
213+
allowedToOverride: allowedToOverride
212214
)
213215
}
214216

@@ -218,21 +220,15 @@ private func createResolvedPackages(
218220
return ($0.package.identity, $0)
219221
}
220222

221-
// in case packages have same manifest name this map can miss packages which will lead to missing product errors
222-
// our plan is to deprecate the use of manifest + dependency explicit name in target dependency lookup and instead lean 100% on identity
223-
// which means this map would go away too
224-
let packageMapByNameForTargetDependencyResolutionOnly = packageBuilders.reduce(into: [String: ResolvedPackageBuilder](), { partial, item in
225-
partial[item.package.manifestName] = item
226-
})
227-
228223
// Scan and validate the dependencies
229224
for packageBuilder in packageBuilders {
230225
let package = packageBuilder.package
231226

232227
var dependencies = [ResolvedPackageBuilder]()
228+
var dependenciesByNameForTargetDependencyResolution = [String: ResolvedPackageBuilder]()
229+
233230
// Establish the manifest-declared package dependencies.
234231
package.manifest.dependenciesRequired(for: packageBuilder.productFilter).forEach { dependency in
235-
let dependencyIdentity = dependency.identity
236232
// FIXME: change this validation logic to use identity instead of location
237233
let dependencyLocation: String
238234
switch dependency {
@@ -245,57 +241,57 @@ private func createResolvedPackages(
245241
fatalError("registry based dependencies not implemented yet")
246242
}
247243

248-
// Use the package name to lookup the dependency. The package name will be present in packages with tools version >= 5.2.
249-
if let explicitDependencyName = dependency.explicitNameForTargetDependencyResolutionOnly, let resolvedPackage = packageMapByNameForTargetDependencyResolutionOnly[explicitDependencyName] {
244+
// Otherwise, look it up by its identity.
245+
if let resolvedPackage = packageMapByIdentity[dependency.identity] {
246+
// check if this resolved package already listed in the dependencies
247+
// this means that the dependencies share the same identity
248+
// FIXME: this works but the way we find out about this is based on a side effect, need to improve it
250249
guard !dependencies.contains(resolvedPackage) else {
251-
// check if this resolvedPackage already listed in the dependencies
252-
// this means that the dependencies share the same name
253-
// FIXME: this works but the way we find out about this is based on a side effect, need to improve it when working on identity
254-
let error = PackageGraphError.dependencyAlreadySatisfiedByName(
250+
let error = PackageGraphError.dependencyAlreadySatisfiedByIdentifier(
255251
package: package.identity.description,
256252
dependencyLocation: dependencyLocation,
257253
otherDependencyURL: resolvedPackage.package.manifest.packageLocation,
258-
name: explicitDependencyName)
254+
identity: dependency.identity)
259255
return diagnostics.emit(error, location: package.diagnosticLocation)
260256
}
261-
return dependencies.append(resolvedPackage)
262-
}
263257

264-
// Otherwise, look it up by its identity.
265-
if let resolvedPackage = packageMapByIdentity[dependencyIdentity] {
266-
// check if this resolvedPackage already listed in the dependencies
267-
// this means that the dependencies share the same identity
268-
// FIXME: this works but the way we find out about this is based on a side effect, need to improve it when working on identity
269-
guard !dependencies.contains(resolvedPackage) else {
258+
// check if the resolved package url is the same as the dependency one
259+
// if not, this means that the dependencies share the same identity
260+
// FIXME: this works but the way we find out about this is based on a side effect, need to improve it
261+
if resolvedPackage.package.manifest.packageLocation != dependencyLocation && !resolvedPackage.allowedToOverride {
270262
let error = PackageGraphError.dependencyAlreadySatisfiedByIdentifier(
271263
package: package.identity.description,
272264
dependencyLocation: dependencyLocation,
273265
otherDependencyURL: resolvedPackage.package.manifest.packageLocation,
274-
identity: dependencyIdentity)
266+
identity: dependency.identity)
275267
return diagnostics.emit(error, location: package.diagnosticLocation)
276268
}
277-
// check that the explicit package dependency name matches the package name.
278-
if let explicitDependencyName = dependency.explicitNameForTargetDependencyResolutionOnly, resolvedPackage.package.manifestName != explicitDependencyName {
279-
// check if this resolvedPackage url is the same as the dependency one
280-
// if not, this means that the dependencies share the same identity
281-
// FIXME: this works but the way we find out about this is based on a side effect, need to improve it when working on identity
282-
if resolvedPackage.package.manifest.packageLocation != dependencyLocation {
283-
let error = PackageGraphError.dependencyAlreadySatisfiedByIdentifier(
284-
package: package.identity.description,
285-
dependencyLocation: dependencyLocation,
286-
otherDependencyURL: resolvedPackage.package.manifest.packageLocation,
287-
identity: dependencyIdentity)
288-
return diagnostics.emit(error, location: package.diagnosticLocation)
289-
} else {
290-
let error = PackageGraphError.incorrectPackageDependencyName(
269+
270+
// checks if two dependencies have the same explicit name which can cause target based dependency package lookup issue
271+
if let explicitDependencyName = dependency.explicitNameForTargetDependencyResolutionOnly {
272+
if let previouslyResolvedPackage = dependenciesByNameForTargetDependencyResolution[explicitDependencyName] {
273+
let error = PackageGraphError.dependencyAlreadySatisfiedByName(
291274
package: package.identity.description,
292-
dependencyName: explicitDependencyName,
293275
dependencyLocation: dependencyLocation,
294-
resolvedPackageManifestName: resolvedPackage.package.manifestName,
295-
resolvedPackageURL: resolvedPackage.package.manifest.packageLocation)
276+
otherDependencyURL: previouslyResolvedPackage.package.manifest.packageLocation,
277+
name: explicitDependencyName)
296278
return diagnostics.emit(error, location: package.diagnosticLocation)
297279
}
298280
}
281+
282+
// checks if two dependencies have the same implicit (identity based) name which can cause target based dependency package lookup issue
283+
if let previouslyResolvedPackage = dependenciesByNameForTargetDependencyResolution[dependency.identity.description] {
284+
let error = PackageGraphError.dependencyAlreadySatisfiedByName(
285+
package: package.identity.description,
286+
dependencyLocation: dependencyLocation,
287+
otherDependencyURL: previouslyResolvedPackage.package.manifest.packageLocation,
288+
name: dependency.identity.description)
289+
return diagnostics.emit(error, location: package.diagnosticLocation)
290+
}
291+
292+
let nameForTargetDependencyResolution = dependency.explicitNameForTargetDependencyResolutionOnly ?? dependency.identity.description
293+
dependenciesByNameForTargetDependencyResolution[nameForTargetDependencyResolution] = resolvedPackage
294+
299295
dependencies.append(resolvedPackage)
300296
}
301297
}
@@ -588,10 +584,13 @@ private final class ResolvedPackageBuilder: ResolvedBuilder<ResolvedPackage> {
588584

589585
let isAllowedToVendUnsafeProducts: Bool
590586

591-
init(_ package: Package, productFilter: ProductFilter, isAllowedToVendUnsafeProducts: Bool) {
587+
let allowedToOverride: Bool
588+
589+
init(_ package: Package, productFilter: ProductFilter, isAllowedToVendUnsafeProducts: Bool, allowedToOverride: Bool) {
592590
self.package = package
593591
self.productFilter = productFilter
594592
self.isAllowedToVendUnsafeProducts = isAllowedToVendUnsafeProducts
593+
self.allowedToOverride = allowedToOverride
595594
}
596595

597596
override func constructImpl() throws -> ResolvedPackage {

Sources/PackageGraph/PackageGraph.swift

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@ enum PackageGraphError: Swift.Error {
2121
/// The product dependency not found.
2222
case productDependencyNotFound(package: String, targetName: String, dependencyProductName: String, dependencyPackageName: String?, dependencyProductInDecl: Bool)
2323

24-
/// The package dependency name does not match the package name.
25-
case incorrectPackageDependencyName(package: String, dependencyName: String, dependencyLocation: String, resolvedPackageManifestName: String, resolvedPackageURL: String)
26-
2724
/// The package dependency already satisfied by a different dependency package
2825
case dependencyAlreadySatisfiedByIdentifier(package: String, dependencyLocation: String, otherDependencyURL: String, identity: PackageIdentity)
2926

@@ -58,7 +55,7 @@ public struct PackageGraph {
5855

5956
/// Returns all the targets in the graph, regardless if they are reachable from the root targets or not.
6057
public let allTargets: Set<ResolvedTarget>
61-
58+
6259
/// Returns all the products in the graph, regardless if they are reachable from the root targets or not.
6360
public let allProducts: Set<ResolvedProduct>
6461

@@ -79,17 +76,18 @@ public struct PackageGraph {
7976
return rootPackages.contains(package)
8077
}
8178

79+
private let targetsToPackages: [ResolvedTarget: ResolvedPackage]
8280
/// Returns the package that contains the target, or nil if the target isn't in the graph.
8381
public func package(for target: ResolvedTarget) -> ResolvedPackage? {
8482
return self.targetsToPackages[target]
8583
}
86-
private let targetsToPackages: [ResolvedTarget: ResolvedPackage]
8784

88-
/// Returns the package that contains the product, or nil if the product isn't in the graph.
89-
public func package(for product: ResolvedProduct) -> ResolvedPackage? {
90-
return self.productsToPackages[product]
91-
}
85+
9286
private let productsToPackages: [ResolvedProduct: ResolvedPackage]
87+
/// Returns the package that contains the product, or nil if the product isn't in the graph.
88+
public func package(for product: ResolvedProduct) -> ResolvedPackage? {
89+
return self.productsToPackages[product]
90+
}
9391

9492
/// All root and root dependency packages provided as input to the graph.
9593
public let inputPackages: [ResolvedPackage]
@@ -189,32 +187,26 @@ extension PackageGraphError: CustomStringConvertible {
189187

190188
case .cycleDetected(let cycle):
191189
return "cyclic dependency declaration found: " +
192-
(cycle.path + cycle.cycle).map({ $0.name }).joined(separator: " -> ") +
193-
" -> " + cycle.cycle[0].name
190+
(cycle.path + cycle.cycle).map({ $0.name }).joined(separator: " -> ") +
191+
" -> " + cycle.cycle[0].name
194192

195193
case .productDependencyNotFound(let package, let targetName, let dependencyProductName, let dependencyPackageName, let dependencyProductInDecl):
196194
if dependencyProductInDecl {
197195
return "product '\(dependencyProductName)' is declared in the same package '\(package)' and can't be used as a dependency for target '\(targetName)'."
198196
} else {
199197
return "product '\(dependencyProductName)' required by package '\(package)' target '\(targetName)' \(dependencyPackageName.map{ "not found in package '\($0)'" } ?? "not found")."
200198
}
201-
case .incorrectPackageDependencyName(let package, let dependencyName, let dependencyURL, let resolvedPackageManifestName, let resolvedPackageURL):
202-
return """
203-
'\(package)' dependency on '\(dependencyURL)' has an explicit name '\(dependencyName)' which does not match the \
204-
name '\(resolvedPackageManifestName)' set for '\(resolvedPackageURL)'
205-
"""
206-
207199
case .dependencyAlreadySatisfiedByIdentifier(let package, let dependencyURL, let otherDependencyURL, let identity):
208200
return "'\(package)' dependency on '\(dependencyURL)' conflicts with dependency on '\(otherDependencyURL)' which has the same identity '\(identity)'"
209201

210202
case .dependencyAlreadySatisfiedByName(let package, let dependencyURL, let otherDependencyURL, let name):
211203
return "'\(package)' dependency on '\(dependencyURL)' conflicts with dependency on '\(otherDependencyURL)' which has the same explicit name '\(name)'"
212204

213205
case .productDependencyMissingPackage(
214-
let productName,
215-
let targetName,
216-
let packageIdentifier
217-
):
206+
let productName,
207+
let targetName,
208+
let packageIdentifier
209+
):
218210

219211
let solution = """
220212
reference the package in the target dependency with '.product(name: "\(productName)", package: \

Sources/PackageGraph/ResolvedPackage.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public final class ResolvedPackage: ObjectIdentifierProtocol {
3535

3636
/// The name of the package as entered in the manifest.
3737
/// This should rarely be used beyond presentation purposes
38+
//@available(*, deprecated)
3839
public var manifestName: String {
3940
return self.underlyingPackage.manifestName
4041
}

Sources/PackageModel/Package.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public final class Package: ObjectIdentifierProtocol, Encodable {
6060

6161
/// The name of the package as entered in the manifest.
6262
/// This should rarely be used beyond presentation purposes
63+
//@available(*, deprecated)
6364
public var manifestName: String {
6465
return manifest.name
6566
}

Tests/BuildTests/BuildPlanTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1020,7 +1020,7 @@ final class BuildPlanTests: XCTestCase {
10201020
packageKind: .root,
10211021
packageLocation: "/Pkg",
10221022
dependencies: [
1023-
.scm(location: "Clibgit", requirement: .upToNextMajor(from: "1.0.0"))
1023+
.scm(location: "/Clibgit", requirement: .upToNextMajor(from: "1.0.0"))
10241024
],
10251025
targets: [
10261026
TargetDescription(name: "exe", dependencies: []),

Tests/PackageGraphTests/PackageGraphTests.swift

Lines changed: 0 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,51 +1108,6 @@ class PackageGraphTests: XCTestCase {
11081108
}
11091109
}
11101110

1111-
func testInvalidExplicitPackageDependencyName() throws {
1112-
let fs = InMemoryFileSystem(emptyFiles:
1113-
"/Foo/Sources/Foo/foo.swift",
1114-
"/Bar/Sources/Baar/bar.swift"
1115-
)
1116-
1117-
let diagnostics = DiagnosticsEngine()
1118-
_ = try loadPackageGraph(fs: fs, diagnostics: diagnostics,
1119-
manifests: [
1120-
Manifest.createV4Manifest(
1121-
name: "Foo",
1122-
path: "/Foo",
1123-
packageKind: .root,
1124-
packageLocation: "/Foo",
1125-
dependencies: [
1126-
.scm(deprecatedName: "Baar", location: "/Bar", requirement: .upToNextMajor(from: "1.0.0")),
1127-
],
1128-
targets: [
1129-
TargetDescription(name: "Foo", dependencies: ["Baar"]),
1130-
]),
1131-
Manifest.createV4Manifest(
1132-
name: "Bar",
1133-
path: "/Bar",
1134-
packageKind: .local,
1135-
packageLocation: "/Bar",
1136-
products: [
1137-
ProductDescription(name: "Baar", type: .library(.automatic), targets: ["Baar"])
1138-
],
1139-
targets: [
1140-
TargetDescription(name: "Baar"),
1141-
]),
1142-
]
1143-
)
1144-
1145-
DiagnosticsEngineTester(diagnostics, ignoreNotes: true) { result in
1146-
result.check(
1147-
diagnostic: """
1148-
'foo' dependency on '/Bar' has an explicit name 'Baar' which does not match the name 'Bar' set for '/Bar'
1149-
""",
1150-
behavior: .error,
1151-
location: "'Foo' /Foo"
1152-
)
1153-
}
1154-
}
1155-
11561111
func testConditionalTargetDependency() throws {
11571112
let fs = InMemoryFileSystem(emptyFiles:
11581113
"/Foo/Sources/Foo/source.swift",
@@ -1885,71 +1840,6 @@ class PackageGraphTests: XCTestCase {
18851840
XCTAssert(diagnostics.diagnostics.isEmpty, "\(diagnostics.diagnostics)")
18861841
}
18871842
}
1888-
1889-
func testTargetDependencies_Post52_ManifestNameNotMatchedWithURL() throws {
1890-
let fs = InMemoryFileSystem(emptyFiles:
1891-
"/Foo/Sources/Foo/foo.swift",
1892-
"/Bar/Sources/Bar/bar.swift"
1893-
)
1894-
1895-
let manifests = try [
1896-
Manifest.createManifest(
1897-
name: "Foo",
1898-
path: "/Foo",
1899-
packageKind: .root,
1900-
packageLocation: "/Foo",
1901-
v: .v5_2,
1902-
dependencies: [
1903-
.scm(deprecatedName: "Bar", location: "/Bar", requirement: .upToNextMajor(from: "1.0.0")),
1904-
],
1905-
targets: [
1906-
TargetDescription(name: "Foo", dependencies: ["ProductBar"]),
1907-
]),
1908-
Manifest.createManifest(
1909-
name: "Some-Bar",
1910-
path: "/Bar",
1911-
packageKind: .local,
1912-
packageLocation: "/Bar",
1913-
v: .v5_2,
1914-
products: [
1915-
ProductDescription(name: "ProductBar", type: .library(.automatic), targets: ["Bar"])
1916-
],
1917-
targets: [
1918-
TargetDescription(name: "Bar"),
1919-
]),
1920-
]
1921-
1922-
do {
1923-
let diagnostics = DiagnosticsEngine()
1924-
_ = try loadPackageGraph(fs: fs, diagnostics: diagnostics, manifests: manifests)
1925-
DiagnosticsEngineTester(diagnostics, ignoreNotes: true) { result in
1926-
result.check(
1927-
diagnostic: """
1928-
'foo' dependency on '/Bar' has an explicit name 'Bar' which does not match the name 'Some-Bar' set for '/Bar'
1929-
""",
1930-
behavior: .error,
1931-
location: "'Foo' /Foo"
1932-
)
1933-
}
1934-
}
1935-
1936-
// fix it
1937-
1938-
do {
1939-
let fixedManifests = [
1940-
try manifests[0].withDependencies([
1941-
.scm(deprecatedName: "Some-Bar", location: "/Bar", requirement: .upToNextMajor(from: "1.0.0")),
1942-
]).withTargets([
1943-
TargetDescription(name: "Foo", dependencies: [.product(name: "ProductBar", package: "Some-Bar")]),
1944-
]),
1945-
manifests[1] // same
1946-
]
1947-
1948-
let diagnostics = DiagnosticsEngine()
1949-
_ = try loadPackageGraph(fs: fs, diagnostics: diagnostics, manifests: fixedManifests)
1950-
XCTAssert(diagnostics.diagnostics.isEmpty, "\(diagnostics.diagnostics)")
1951-
}
1952-
}
19531843
}
19541844

19551845

0 commit comments

Comments
 (0)