Skip to content

Commit c54f8d9

Browse files
committed
[Workspace] Resolve with no pins if resolve with valid pins fails
1 parent f4a477d commit c54f8d9

File tree

2 files changed

+85
-27
lines changed

2 files changed

+85
-27
lines changed

Sources/Workspace/Workspace.swift

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -376,27 +376,22 @@ extension Workspace {
376376
return diagnostics.emit(error)
377377
}
378378

379-
// Compute the checkout state.
380-
//
381-
// We use a dummy revision in case of version and branch because we
382-
// might not know the needed revision at this point.
383-
let checkoutState: CheckoutState
379+
// Compute the custom or extra constraint we need to impose.
380+
let requirement: RepositoryPackageConstraint.Requirement
384381
if let version = version {
385-
checkoutState = CheckoutState(revision: Revision(identifier: ""), version: version)
382+
requirement = .versionSet(.exact(version))
386383
} else if let branch = branch {
387-
checkoutState = CheckoutState(revision: Revision(identifier: ""), branch: branch)
384+
requirement = .revision(branch)
388385
} else if let revision = revision {
389-
checkoutState = CheckoutState(revision: Revision(identifier: revision))
386+
requirement = .revision(revision)
390387
} else {
391-
checkoutState = currentState
388+
requirement = currentState.requirement()
392389
}
393-
394-
// Create a pin with above checkout state.
395-
let pin = PinsStore.Pin(
396-
package: dependency.name, repository: dependency.repository, state: checkoutState)
390+
let constraint = RepositoryPackageConstraint(
391+
container: dependency.repository, requirement: requirement)
397392

398393
// Run the resolution.
399-
_resolve(root: root, extraPins: [pin], diagnostics: diagnostics)
394+
_resolve(root: root, extraConstraints: [constraint], diagnostics: diagnostics)
400395
}
401396

402397
/// Cleans the build artefacts from workspace data.
@@ -886,14 +881,14 @@ extension Workspace {
886881

887882
/// Implementation of resolve(root:diagnostics:).
888883
///
889-
/// The extra pins will override the pin of the same package in the
890-
/// pinsStore. It is useful in situations where a requirement is being
884+
/// The extra constraints will be added to the main requirements.
885+
/// It is useful in situations where a requirement is being
891886
/// imposed outside of manifest and pins file. E.g., when using a command
892887
/// like `$ swift package resolve foo --version 1.0.0`.
893888
@discardableResult
894889
fileprivate func _resolve(
895890
root: WorkspaceRoot,
896-
extraPins: [PinsStore.Pin] = [],
891+
extraConstraints: [RepositoryPackageConstraint] = [],
897892
diagnostics: DiagnosticsEngine
898893
) -> DependencyManifests {
899894

@@ -918,17 +913,18 @@ extension Workspace {
918913
// The pins to use in case we need to run the resolution.
919914
var validPins = pinsStore.createConstraints()
920915

921-
// Compute if we need to run the resolver.
916+
// Compute if we need to run the resolver. We always run the resolver if
917+
// there are extra constraints.
922918
if missingURLs.isEmpty {
923919
// Use root constraints, dependency manifest constraints and extra
924-
// pins to compute if a new resolution is required.
925-
let dependencies = graphRoot.constraints + currentManifests.dependencyConstraints()
926-
extraPins.forEach(pinsStore.add)
920+
// constraints to compute if a new resolution is required.
921+
let dependencies = graphRoot.constraints + currentManifests.dependencyConstraints() + extraConstraints
927922

928923
let result = isResolutionRequired(dependencies: dependencies, pinsStore: pinsStore)
929924

930-
// If we don't need resolution, just validate pinsStore and return.
931-
if !result.resolve {
925+
// If we don't need resolution and there are no extra constraints,
926+
// just validate pinsStore and return.
927+
if !result.resolve && extraConstraints.isEmpty {
932928
validatePinsStore(with: diagnostics)
933929
return currentManifests
934930
}
@@ -938,13 +934,23 @@ extension Workspace {
938934

939935
// Create the constraints.
940936
var constraints = [RepositoryPackageConstraint]()
941-
constraints += graphRoot.constraints
937+
constraints += graphRoot.constraints + extraConstraints
942938
constraints += currentManifests.editedPackagesConstraints()
943939

944940
// Perform dependency resolution.
945-
let result = resolveDependencies(dependencies: constraints, pins: validPins, diagnostics: diagnostics)
946-
guard !diagnostics.hasErrors else {
947-
return currentManifests
941+
let resolverDiagnostics = DiagnosticsEngine()
942+
var result = resolveDependencies(dependencies: constraints, pins: validPins, diagnostics: resolverDiagnostics)
943+
944+
// If we fail, we just try again without any pins because the pins might
945+
// be completely incompatible.
946+
//
947+
// FIXME: We should only do this if resolver emits "unresolvable" error.
948+
// FIXME: We should merge the engine in case we get no errors but warnings or notes.
949+
if resolverDiagnostics.hasErrors {
950+
result = resolveDependencies(dependencies: constraints, pins: [], diagnostics: diagnostics)
951+
guard !diagnostics.hasErrors else {
952+
return currentManifests
953+
}
948954
}
949955

950956
// Update the checkouts with dependency resolution result.

Tests/WorkspaceTests/WorkspaceTests.swift

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1897,6 +1897,57 @@ final class WorkspaceTests: XCTestCase {
18971897
}
18981898
}
18991899

1900+
func testCanResolveWithIncompatiblePins() throws {
1901+
let v2: Version = "2.0.0"
1902+
let path = AbsolutePath("/RootPkg")
1903+
let fs = InMemoryFileSystem()
1904+
1905+
let manifestGraph = try MockManifestGraph(at: path,
1906+
rootDeps: [],
1907+
packages: [
1908+
MockPackage("A", version: v1, dependencies: [
1909+
MockDependency("AA", version: v1)
1910+
]),
1911+
MockPackage("A", version: "1.0.1", dependencies: [
1912+
MockDependency("AA", version: v2)
1913+
]),
1914+
MockPackage("AA", version: v1),
1915+
MockPackage("AA", version: v2),
1916+
],
1917+
fs: fs)
1918+
let provider = manifestGraph.repoProvider!
1919+
try provider.specifierMap[manifestGraph.repo("A")]!.tag(name: "1.0.1")
1920+
try provider.specifierMap[manifestGraph.repo("AA")]!.tag(name: "2.0.0")
1921+
1922+
let delegate = TestWorkspaceDelegate()
1923+
1924+
let workspace = Workspace.createWith(
1925+
rootPackage: path,
1926+
manifestLoader: manifestGraph.manifestLoader,
1927+
delegate: delegate,
1928+
fileSystem: fs,
1929+
repositoryProvider: provider)
1930+
1931+
var root = WorkspaceRoot(packages: [], dependencies: [
1932+
.init(url: "/RootPkg/A", requirement: .exact(v1.asPD4Version), location: "A"),
1933+
])
1934+
1935+
let diagnostics = DiagnosticsEngine()
1936+
workspace.resolve(root: root, diagnostics: diagnostics)
1937+
XCTAssertFalse(diagnostics.hasErrors)
1938+
1939+
// This pin will become unresolvable when A is at 1.0.1.
1940+
// We should still be able to resolve in that case.
1941+
XCTAssertEqual(try workspace.pinsStore.load().pinsMap["AA"]?.state.version, v1)
1942+
1943+
root = WorkspaceRoot(packages: [], dependencies: [
1944+
.init(url: "/RootPkg/A", requirement: .exact("1.0.1"), location: "A"),
1945+
])
1946+
workspace.resolve(root: root, diagnostics: diagnostics)
1947+
XCTAssertFalse(diagnostics.hasErrors)
1948+
XCTAssertEqual(try workspace.pinsStore.load().pinsMap["AA"]?.state.version, v2)
1949+
}
1950+
19001951
static var allTests = [
19011952
("testBasics", testBasics),
19021953
("testBranchAndRevision", testBranchAndRevision),
@@ -1913,6 +1964,7 @@ final class WorkspaceTests: XCTestCase {
19131964
("testUpdate", testUpdate),
19141965
("testUneditDependency", testUneditDependency),
19151966
("testCleanAndReset", testCleanAndReset),
1967+
("testCanResolveWithIncompatiblePins", testCanResolveWithIncompatiblePins),
19161968
("testMultipleRootPackages", testMultipleRootPackages),
19171969
("testWarnings", testWarnings),
19181970
("testDependencyResolutionWithEdit", testDependencyResolutionWithEdit),

0 commit comments

Comments
 (0)