Skip to content

Commit 8516cb5

Browse files
authored
remove resolution retry and removing package.resolved in resolution failure (#3853)
motivation: resolution retry + removing of package resolved in failure is difficult to justify changes: * remove the retry code from package resolution and "give up" after one attempt without mutating the package resolved file * fix the resolver to also compare locations so that resolved file with same identity but different location would work correctly * add and adjust tests
1 parent 9e0124e commit 8516cb5

File tree

4 files changed

+123
-81
lines changed

4 files changed

+123
-81
lines changed

Sources/PackageGraph/Pubgrub/PubgrubDependencyResolver.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1394,14 +1394,14 @@ private final class ContainerProvider {
13941394
/// Get the container for the given identifier, loading it if necessary.
13951395
func getContainer(for package: PackageReference, completion: @escaping (Result<PubGrubPackageContainer, Error>) -> Void) {
13961396
// Return the cached container, if available.
1397-
if let container = self.containersCache[package] {
1397+
if let container = self.containersCache[package], package.equalsIncludingLocation(container.package) {
13981398
return completion(.success(container))
13991399
}
14001400

14011401
if let prefetchSync = self.prefetches[package] {
14021402
// If this container is already being prefetched, wait for that to complete
14031403
prefetchSync.notify(queue: .sharedConcurrent) {
1404-
if let container = self.containersCache[package] {
1404+
if let container = self.containersCache[package], package.equalsIncludingLocation(container.package) {
14051405
// should be in the cache once prefetch completed
14061406
return completion(.success(container))
14071407
} else {

Sources/Workspace/ManagedDependency.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ extension Workspace {
8080
/// - unmanagedPath: A custom absolute path instead of the subpath.
8181
public func edited(subpath: RelativePath, unmanagedPath: AbsolutePath?) throws -> ManagedDependency {
8282
guard case .sourceControlCheckout = self.state else {
83-
throw InternalError("invalid depenedency state: \(self.state)")
83+
throw InternalError("invalid dependency state: \(self.state)")
8484
}
8585
return ManagedDependency(
8686
packageRef: self.packageRef,

Sources/Workspace/Workspace.swift

Lines changed: 5 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2400,8 +2400,6 @@ extension Workspace {
24002400
explicitProduct: String? = nil,
24012401
forceResolution: Bool,
24022402
constraints: [PackageContainerConstraint],
2403-
retryOnPackagePathMismatch: Bool = true,
2404-
resetPinsStoreOnFailure: Bool = true,
24052403
observabilityScope: ObservabilityScope
24062404
) throws -> DependencyManifests {
24072405
// Ensure the cache path exists and validate that edited dependencies.
@@ -2488,51 +2486,12 @@ extension Workspace {
24882486

24892487
// Update the pinsStore.
24902488
let updatedDependencyManifests = try self.loadDependencyManifests(root: graphRoot, observabilityScope: observabilityScope)
2491-
2492-
// If we still have required URLs, we probably cloned a wrong URL for
2493-
// some package dependency.
2494-
//
2495-
// This would usually happen when we're resolving from scratch and the
2496-
// resolved file has an outdated entry for a transitive dependency whose
2497-
// URL was changed. For e.g., the resolved file could refer to a dependency
2498-
// through a ssh url but its new reference is now changed to http.
2489+
// If we still have missing packages, something is fundamentally wrong with the resolution of the graph
24992490
let stillMissingPackages = updatedDependencyManifests.computePackages().missing
2500-
if !stillMissingPackages.isEmpty {
2501-
if retryOnPackagePathMismatch {
2502-
// Retry resolution which will most likely resolve correctly now since
2503-
// we have the manifest files of all the dependencies.
2504-
return try self.resolve(
2505-
root: root,
2506-
explicitProduct: explicitProduct,
2507-
forceResolution: forceResolution,
2508-
constraints: constraints,
2509-
retryOnPackagePathMismatch: false,
2510-
resetPinsStoreOnFailure: resetPinsStoreOnFailure,
2511-
observabilityScope: observabilityScope
2512-
)
2513-
} else if resetPinsStoreOnFailure, !pinsStore.pinsMap.isEmpty {
2514-
// If we weren't able to resolve properly even after a retry, it
2515-
// could mean that the dependency at fault has a different
2516-
// version of the manifest file which contains dependencies that
2517-
// have also changed their package references.
2518-
pinsStore.unpinAll()
2519-
try pinsStore.saveState(toolsVersion: rootManifestsMinimumToolsVersion)
2520-
// try again with pins reset
2521-
return try self.resolve(
2522-
root: root,
2523-
explicitProduct: explicitProduct,
2524-
forceResolution: forceResolution,
2525-
constraints: constraints,
2526-
retryOnPackagePathMismatch: false,
2527-
resetPinsStoreOnFailure: false,
2528-
observabilityScope: observabilityScope
2529-
)
2530-
} else {
2531-
// give up
2532-
let missing = stillMissingPackages.map{ $0.description }
2533-
observabilityScope.emit(error: "exhausted attempts to resolve the dependencies graph, with '\(missing.joined(separator: "', '"))' unresolved.")
2534-
return updatedDependencyManifests
2535-
}
2491+
guard stillMissingPackages.isEmpty else {
2492+
let missing = stillMissingPackages.map{ $0.description }
2493+
observabilityScope.emit(error: "exhausted attempts to resolve the dependencies graph, with '\(missing.joined(separator: "', '"))' unresolved.")
2494+
return updatedDependencyManifests
25362495
}
25372496

25382497
self.pinAll(

Tests/WorkspaceTests/WorkspaceTests.swift

Lines changed: 115 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3237,7 +3237,10 @@ final class WorkspaceTests: XCTestCase {
32373237
}
32383238
}
32393239

3240-
func testDependencySwitchWithSameIdentity() throws {
3240+
3241+
// Test that switching between two same local packages placed at
3242+
// different locations works correctly.
3243+
func testDependencySwitchLocalWithSameIdentity() throws {
32413244
let sandbox = AbsolutePath("/tmp/ws/")
32423245
let fs = InMemoryFileSystem()
32433246

@@ -3279,9 +3282,6 @@ final class WorkspaceTests: XCTestCase {
32793282
]
32803283
)
32813284

3282-
// Test that switching between two same local packages placed at
3283-
// different locations works correctly.
3284-
32853285
var deps: [MockDependency] = [
32863286
.fileSystem(path: "./Foo", products: .specific(["Foo"])),
32873287
]
@@ -3315,6 +3315,84 @@ final class WorkspaceTests: XCTestCase {
33153315
}
33163316
}
33173317

3318+
// Test that switching between two remote packages at
3319+
// different locations works correctly.
3320+
func testDependencySwitchRemoteWithSameIdentity() throws {
3321+
let sandbox = AbsolutePath("/tmp/ws/")
3322+
let fs = InMemoryFileSystem()
3323+
3324+
let workspace = try MockWorkspace(
3325+
sandbox: sandbox,
3326+
fs: fs,
3327+
roots: [
3328+
MockPackage(
3329+
name: "Root",
3330+
targets: [
3331+
MockTarget(name: "Root", dependencies: []),
3332+
],
3333+
products: [],
3334+
dependencies: []
3335+
),
3336+
],
3337+
packages: [
3338+
MockPackage(
3339+
name: "Foo",
3340+
url: "https://scm.com/org/foo",
3341+
targets: [
3342+
MockTarget(name: "Foo"),
3343+
],
3344+
products: [
3345+
MockProduct(name: "Foo", targets: ["Foo"]),
3346+
],
3347+
versions: ["1.0.0"]
3348+
),
3349+
MockPackage(
3350+
name: "Foo",
3351+
url: "https://scm.com/other/foo",
3352+
targets: [
3353+
MockTarget(name: "OtherFoo"),
3354+
],
3355+
products: [
3356+
MockProduct(name: "OtherFoo", targets: ["OtherFoo"]),
3357+
],
3358+
versions: ["1.1.0"]
3359+
),
3360+
]
3361+
)
3362+
3363+
var deps: [MockDependency] = [
3364+
.sourceControl(url: "https://scm.com/org/foo", requirement: .exact("1.0.0")),
3365+
]
3366+
try workspace.checkPackageGraph(roots: ["Root"], deps: deps) { graph, diagnostics in
3367+
XCTAssertNoDiagnostics(diagnostics)
3368+
PackageGraphTester(graph) { result in
3369+
result.check(roots: "Root")
3370+
result.check(packages: "Foo", "Root")
3371+
}
3372+
}
3373+
workspace.checkManagedDependencies { result in
3374+
result.check(dependency: "foo", at: .checkout(.version("1.0.0")))
3375+
}
3376+
do {
3377+
let ws = try workspace.getOrCreateWorkspace()
3378+
XCTAssertEqual(ws.state.dependencies[.plain("foo")]?.packageRef.locationString, "https://scm.com/org/foo")
3379+
}
3380+
3381+
deps = [
3382+
.sourceControl(url: "https://scm.com/other/foo", requirement: .exact("1.1.0"))
3383+
]
3384+
try workspace.checkPackageGraph(roots: ["Root"], deps: deps) { _, diagnostics in
3385+
XCTAssertNoDiagnostics(diagnostics)
3386+
}
3387+
workspace.checkManagedDependencies { result in
3388+
result.check(dependency: "foo", at: .checkout(.version("1.1.0")))
3389+
}
3390+
do {
3391+
let ws = try workspace.getOrCreateWorkspace()
3392+
XCTAssertEqual(ws.state.dependencies[.plain("foo")]?.packageRef.locationString, "https://scm.com/other/foo")
3393+
}
3394+
}
3395+
33183396
func testResolvedFileUpdate() throws {
33193397
let sandbox = AbsolutePath("/tmp/ws/")
33203398
let fs = InMemoryFileSystem()
@@ -3525,6 +3603,9 @@ final class WorkspaceTests: XCTestCase {
35253603
}
35263604
}
35273605

3606+
// In this test, we get into a state where an entry in the resolved
3607+
// file for a transitive dependency whose URL is later changed to
3608+
// something else, while keeping the same package identity.
35283609
func testTransitiveDependencySwitchWithSameIdentity() throws {
35293610
let sandbox = AbsolutePath("/tmp/ws/")
35303611
let fs = InMemoryFileSystem()
@@ -3536,44 +3617,59 @@ final class WorkspaceTests: XCTestCase {
35363617
MockPackage(
35373618
name: "Root",
35383619
targets: [
3539-
MockTarget(name: "Root", dependencies: ["Bar"]),
3620+
MockTarget(
3621+
name: "Root",
3622+
dependencies: [
3623+
.product(name: "Bar", package: "bar")
3624+
]),
35403625
],
35413626
products: [],
35423627
dependencies: [
3543-
.sourceControl(path: "./Bar", requirement: .upToNextMajor(from: "1.0.0")),
3628+
.sourceControl(url: "https://scm.com/org/bar", requirement: .upToNextMajor(from: "1.0.0")),
35443629
]
35453630
),
35463631
],
35473632
packages: [
35483633
MockPackage(
35493634
name: "Bar",
3635+
url: "https://scm.com/org/bar",
35503636
targets: [
3551-
MockTarget(name: "Bar", dependencies: ["Foo"]),
3637+
MockTarget(
3638+
name: "Bar",
3639+
dependencies: [
3640+
.product(name: "Foo", package: "foo")
3641+
]),
35523642
],
35533643
products: [
35543644
MockProduct(name: "Bar", targets: ["Bar"]),
35553645
],
35563646
dependencies: [
3557-
.sourceControl(path: "./Foo", requirement: .upToNextMajor(from: "1.0.0")),
3647+
.sourceControl(url: "https://scm.com/org/foo", requirement: .upToNextMajor(from: "1.0.0")),
35583648
],
35593649
versions: ["1.0.0"]
35603650
),
35613651
MockPackage(
35623652
name: "Bar",
3653+
url: "https://scm.com/org/bar",
35633654
targets: [
3564-
MockTarget(name: "Bar", dependencies: ["Nested/Foo"]),
3655+
MockTarget(
3656+
name: "Bar",
3657+
dependencies: [
3658+
.product(name: "OtherFoo", package: "foo")
3659+
]),
35653660
],
35663661
products: [
35673662
MockProduct(name: "Bar", targets: ["Bar"]),
35683663
],
35693664
dependencies: [
3570-
.sourceControl(path: "Nested/Foo", requirement: .upToNextMajor(from: "1.0.0")),
3665+
.sourceControl(url: "https://scm.com/other/foo", requirement: .upToNextMajor(from: "1.0.0")),
35713666
],
35723667
versions: ["1.1.0"],
35733668
toolsVersion: .v5
35743669
),
35753670
MockPackage(
35763671
name: "Foo",
3672+
url: "https://scm.com/org/foo",
35773673
targets: [
35783674
MockTarget(name: "Foo"),
35793675
],
@@ -3584,40 +3680,27 @@ final class WorkspaceTests: XCTestCase {
35843680
),
35853681
MockPackage(
35863682
name: "Foo",
3587-
path: "Nested/Foo",
3683+
url: "https://scm.com/other/foo",
35883684
targets: [
3589-
MockTarget(name: "Foo"),
3685+
MockTarget(name: "OtherFoo"),
35903686
],
35913687
products: [
3592-
MockProduct(name: "Nested/Foo", targets: ["Foo"]),
3688+
MockProduct(name: "OtherFoo", targets: ["OtherFoo"]),
35933689
],
35943690
versions: ["1.0.0"]
35953691
),
35963692
]
35973693
)
35983694

3599-
// In this test, we get into a state where add an entry in the resolved
3600-
// file for a transitive dependency whose URL is later changed to
3601-
// something else, while keeping the same package identity.
3602-
//
3603-
// This is normally detected during pins validation before the
3604-
// dependency resolution process even begins but if we're starting with
3605-
// a clean slate, we don't even know about the correct urls of the
3606-
// transitive dependencies. We will end up fetching the wrong
3607-
// dependency as we prefetch the pins. If we get into this case, it
3608-
// should kick off another dependency resolution operation which will
3609-
// have enough information to remove the invalid pins of transitive
3610-
// dependencies.
3611-
36123695
var deps: [MockDependency] = [
3613-
.sourceControl(path: "./Bar", requirement: .exact("1.0.0"), products: .specific(["Bar"])),
3696+
.sourceControl(url: "https://scm.com/org/bar", requirement: .exact("1.0.0")),
36143697
]
36153698
try workspace.checkPackageGraph(roots: ["Root"], deps: deps) { graph, diagnostics in
3699+
XCTAssertNoDiagnostics(diagnostics)
36163700
PackageGraphTester(graph) { result in
36173701
result.check(roots: "Root")
36183702
result.check(packages: "Bar", "Foo", "Root")
36193703
}
3620-
XCTAssertNoDiagnostics(diagnostics)
36213704
}
36223705
workspace.checkManagedDependencies { result in
36233706
result.check(dependency: "foo", at: .checkout(.version("1.0.0")))
@@ -3626,22 +3709,22 @@ final class WorkspaceTests: XCTestCase {
36263709

36273710
do {
36283711
let ws = try workspace.getOrCreateWorkspace()
3629-
XCTAssertEqual(ws.state.dependencies[.plain("foo")]?.packageRef.locationString, "/tmp/ws/pkgs/Foo")
3712+
XCTAssertEqual(ws.state.dependencies[.plain("foo")]?.packageRef.locationString, "https://scm.com/org/foo")
36303713
}
36313714

36323715
workspace.checkReset { diagnostics in
36333716
XCTAssertNoDiagnostics(diagnostics)
36343717
}
36353718

36363719
deps = [
3637-
.sourceControl(path: "./Bar", requirement: .exact("1.1.0"), products: .specific(["Bar"])),
3720+
.sourceControl(url: "https://scm.com/org/bar", requirement: .exact("1.1.0")),
36383721
]
36393722
try workspace.checkPackageGraph(roots: ["Root"], deps: deps) { graph, diagnostics in
3723+
XCTAssertNoDiagnostics(diagnostics)
36403724
PackageGraphTester(graph) { result in
36413725
result.check(roots: "Root")
36423726
result.check(packages: "Bar", "Foo", "Root")
36433727
}
3644-
XCTAssertNoDiagnostics(diagnostics)
36453728
}
36463729
workspace.checkManagedDependencies { result in
36473730
result.check(dependency: "foo", at: .checkout(.version("1.0.0")))
@@ -3650,7 +3733,7 @@ final class WorkspaceTests: XCTestCase {
36503733

36513734
do {
36523735
let ws = try workspace.getOrCreateWorkspace()
3653-
XCTAssertEqual(ws.state.dependencies[.plain("foo")]?.packageRef.locationString, "/tmp/ws/pkgs/Nested/Foo")
3736+
XCTAssertEqual(ws.state.dependencies[.plain("foo")]?.packageRef.locationString, "https://scm.com/other/foo")
36543737
}
36553738
}
36563739

0 commit comments

Comments
 (0)