Skip to content

Commit 570b57d

Browse files
committed
[DependencyResolver] Add VersionAssignmentSet.merge.
1 parent abe8bae commit 570b57d

File tree

2 files changed

+72
-1
lines changed

2 files changed

+72
-1
lines changed

Sources/PackageGraph/DependencyResolver.swift

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ public protocol DependencyResolverDelegate {
162162
/// A bound version for a package within an assignment.
163163
//
164164
// FIXME: This should be nested, but cannot be currently.
165-
enum BoundVersion {
165+
enum BoundVersion: Equatable {
166166
/// The assignment should not include the package.
167167
///
168168
/// This is different from the absence of an assignment for a particular
@@ -173,6 +173,18 @@ enum BoundVersion {
173173
/// The version of the package to include.
174174
case version(Version)
175175
}
176+
func ==(_ lhs: BoundVersion, _ rhs: BoundVersion) -> Bool {
177+
switch (lhs, rhs) {
178+
case (.excluded, .excluded):
179+
return true
180+
case (.excluded, _):
181+
return false
182+
case (.version(let lhs), .version(let rhs)):
183+
return lhs == rhs
184+
case (.version, _):
185+
return false
186+
}
187+
}
176188

177189
/// A container for constraints for a set of packages.
178190
//
@@ -286,6 +298,43 @@ struct VersionAssignmentSet<C: PackageContainer> {
286298
}
287299
}
288300

301+
/// Merge in the bindings from the given `assignment`.
302+
///
303+
/// - Returns: False if the merge cannot be made (the assignments contain
304+
/// incompatible versions).
305+
mutating func merge(_ assignment: VersionAssignmentSet<Container>) -> Bool {
306+
// In order to protect the assignment set, we first have to test whether
307+
// the merged constraint sets are satisfiable.
308+
//
309+
// FIXME: Move to non-mutating methods with results, in order to have a
310+
// nice consistent API with `PackageContainerConstraintSet.merge`.
311+
//
312+
// FIXME: This is very inefficient; we should decide whether it is right
313+
// to handle it here or force the main resolver loop to handle the
314+
// discovery of this property.
315+
var mergedConstraints = constraints
316+
guard mergedConstraints.merge(assignment.constraints) else {
317+
return false
318+
}
319+
320+
// The induced constraints are satisfiable, so we *can* union the
321+
// assignments without breaking our internal invariant on
322+
// satisfiability.
323+
for entry in assignment.assignments.values {
324+
if let existing = self[entry.container] {
325+
if existing != entry.binding {
326+
// NOTE: We are returning here with the data structure
327+
// partially updated, which feels wrong. See FIXME above.
328+
return false
329+
}
330+
} else {
331+
self[entry.container] = entry.binding
332+
}
333+
}
334+
335+
return true
336+
}
337+
289338
/// The combined version constraints induced by the assignment.
290339
///
291340
/// This consists of the merged constraints which need to be satisfied on

Tests/PackageGraphTests/DependencyResolverTests.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,28 @@ class DependencyResolverTests: XCTestCase {
199199
assignment[a] = .version(v2)
200200
XCTAssertEqual(assignment.constraints, ["C": v1_0Range])
201201
XCTAssert(assignment.checkIfValidAndComplete())
202+
203+
// Check assignment merging.
204+
let d = MockPackageContainer(name: "D", dependenciesByVersion: [
205+
v1: [(container: "E", versionRequirement: v1Range)],
206+
v2: []])
207+
var assignment2 = VersionAssignmentSet<MockPackageContainer>()
208+
assignment2[d] = .version(v1)
209+
XCTAssertTrue(assignment.merge(assignment2))
210+
XCTAssertEqual(assignment.constraints, ["C": v1_0Range, "E": v1Range])
211+
212+
// Check merger of an assignment with incompatible constraints.
213+
let d2 = MockPackageContainer(name: "D2", dependenciesByVersion: [
214+
v1: [(container: "E", versionRequirement: v2Range)]])
215+
var assignment3 = VersionAssignmentSet<MockPackageContainer>()
216+
assignment3[d2] = .version(v1)
217+
XCTAssertFalse(assignment.merge(assignment3))
218+
XCTAssertEqual(assignment.constraints, ["C": v1_0Range, "E": v1Range])
219+
220+
// Check merger of an incompatible assignment.
221+
var assignment4 = VersionAssignmentSet<MockPackageContainer>()
222+
assignment4[d] = .version(v2)
223+
XCTAssertFalse(assignment.merge(assignment4))
202224
}
203225

204226
static var allTests = [

0 commit comments

Comments
 (0)