Skip to content

Commit 1002fbb

Browse files
committed
[DependencyResolver] Support Equatable on version assignment sets.
- This also splits out the top-level resolve function which returns an assignment so it can be used by tests.
1 parent 1c16b69 commit 1002fbb

File tree

2 files changed

+46
-21
lines changed

2 files changed

+46
-21
lines changed

Sources/PackageGraph/DependencyResolver.swift

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -323,25 +323,32 @@ struct PackageContainerConstraintSet<C: PackageContainer>: Collection {
323323
/// `constraints`, but this invariant is not explicitly enforced.
324324
//
325325
// FIXME: Actually make efficient.
326-
struct VersionAssignmentSet<C: PackageContainer>: Sequence {
326+
struct VersionAssignmentSet<C: PackageContainer>: Equatable, Sequence {
327327
typealias Container = C
328328
typealias Identifier = Container.Identifier
329329

330330
/// The assignment records.
331331
//
332332
// FIXME: Does it really make sense to key on the identifier here. Should we
333333
// require referential equality of containers and use that to simplify?
334-
private var assignments: [Identifier: (container: Container, binding: BoundVersion)]
334+
fileprivate var assignments: [Identifier: (container: Container, binding: BoundVersion)]
335335

336336
/// Create an empty assignment.
337337
init() {
338338
assignments = [:]
339339
}
340340

341+
/// The assignment for the given container `identifier.
342+
subscript(identifier: Identifier) -> BoundVersion? {
343+
get {
344+
return assignments[identifier]?.binding
345+
}
346+
}
347+
341348
/// The assignment for the given `container`.
342349
subscript(container: Container) -> BoundVersion? {
343350
get {
344-
return assignments[container.identifier]?.binding
351+
return self[container.identifier]
345352
}
346353
set {
347354
// We disallow deletion.
@@ -481,6 +488,18 @@ struct VersionAssignmentSet<C: PackageContainer>: Sequence {
481488
}
482489
}
483490
}
491+
func ==<C: PackageContainer>(lhs: VersionAssignmentSet<C>, rhs: VersionAssignmentSet<C>) -> Bool {
492+
if lhs.assignments.count != rhs.assignments.count { return false }
493+
for (container, lhsBinding) in lhs {
494+
switch rhs[container] {
495+
case let rhsBinding? where lhsBinding == rhsBinding:
496+
continue
497+
default:
498+
return false
499+
}
500+
}
501+
return true
502+
}
484503

485504
/// A general purpose package dependency resolver.
486505
///
@@ -567,19 +586,29 @@ public class DependencyResolver<
567586
/// - Returns: A satisfying assignment of containers and versions.
568587
/// - Throws: DependencyResolverError, or errors from the underlying package provider.
569588
public func resolve(constraints: [Constraint]) throws -> [(container: Identifier, version: Version)] {
589+
return try resolveAssignment(constraints: constraints).map { (container, binding) in
590+
guard case .version(let version) = binding else {
591+
fatalError("unexpected exclude binding")
592+
}
593+
return (container: container.identifier, version: version)
594+
}
595+
}
596+
597+
/// Execute the resolution algorithm to find a valid assignment of versions.
598+
///
599+
/// - Parameters:
600+
/// - constraints: The contraints to solve. It is legal to supply multiple
601+
/// constraints for the same container identifier.
602+
/// - Returns: A satisfying assignment of containers and versions.
603+
/// - Throws: DependencyResolverError, or errors from the underlying package provider.
604+
func resolveAssignment(constraints: [Constraint]) throws -> AssignmentSet {
570605
// Create an assignment for the input constraints.
571606
guard let assignment = try merge(
572607
constraints: constraints, into: AssignmentSet(),
573608
subjectTo: ConstraintSet(), excluding: [:]) else {
574609
throw DependencyResolverError.unsatisfiable
575610
}
576-
577-
return assignment.map { (container, binding) in
578-
guard case .version(let version) = binding else {
579-
fatalError("unexpected exclude binding")
580-
}
581-
return (container: container.identifier, version: version)
582-
}
611+
return assignment
583612
}
584613

585614
/// Resolve an individual container dependency tree.

Tests/PackageGraphTests/DependencyResolverTests.swift

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ private class MockResolverDelegate: DependencyResolverDelegate {
7575
}
7676

7777
private typealias MockDependencyResolver = DependencyResolver<MockPackagesProvider, MockResolverDelegate>
78+
private typealias MockVersionAssignmentSet = VersionAssignmentSet<MockPackageContainer>
7879

7980
// Some handy ranges.
8081
//
@@ -172,7 +173,7 @@ class DependencyResolverTests: XCTestCase {
172173
let c = MockPackageContainer(name: "C", dependenciesByVersion: [
173174
v1: []])
174175

175-
var assignment = VersionAssignmentSet<MockPackageContainer>()
176+
var assignment = MockVersionAssignmentSet()
176177
XCTAssertEqual(assignment.constraints, [:])
177178
XCTAssert(assignment.isValid(binding: .version(v2), for: b))
178179
// An empty assignment is valid.
@@ -210,7 +211,7 @@ class DependencyResolverTests: XCTestCase {
210211
let d = MockPackageContainer(name: "D", dependenciesByVersion: [
211212
v1: [(container: "E", versionRequirement: v1Range)],
212213
v2: []])
213-
var assignment2 = VersionAssignmentSet<MockPackageContainer>()
214+
var assignment2 = MockVersionAssignmentSet()
214215
assignment2[d] = .version(v1)
215216
if let mergedAssignment = assignment.merging(assignment2) {
216217
assignment = mergedAssignment
@@ -222,12 +223,12 @@ class DependencyResolverTests: XCTestCase {
222223
// Check merger of an assignment with incompatible constraints.
223224
let d2 = MockPackageContainer(name: "D2", dependenciesByVersion: [
224225
v1: [(container: "E", versionRequirement: v2Range)]])
225-
var assignment3 = VersionAssignmentSet<MockPackageContainer>()
226+
var assignment3 = MockVersionAssignmentSet()
226227
assignment3[d2] = .version(v1)
227228
XCTAssertEqual(assignment.merging(assignment3), nil)
228229

229230
// Check merger of an incompatible assignment.
230-
var assignment4 = VersionAssignmentSet<MockPackageContainer>()
231+
var assignment4 = MockVersionAssignmentSet()
231232
assignment4[d] = .version(v2)
232233
XCTAssertEqual(assignment.merging(assignment4), nil)
233234
}
@@ -410,14 +411,11 @@ where C.Identifier == String
410411

411412
private func XCTAssertEqual<C: PackageContainer>(
412413
_ assignment: VersionAssignmentSet<C>?,
413-
_ expected: [String: Version]?,
414+
_ expected: [String: Version],
414415
file: StaticString = #file, line: UInt = #line)
415416
where C.Identifier == String
416417
{
417418
if let assignment = assignment {
418-
guard let expected = expected else {
419-
return XCTFail("unexpected satisfying assignment (expected failure): \(assignment)", file: file, line: line)
420-
}
421419
var actual = [String: Version]()
422420
for (container, binding) in assignment {
423421
guard case .version(let version) = binding else {
@@ -427,9 +425,7 @@ where C.Identifier == String
427425
}
428426
XCTAssertEqual(actual, expected, file: file, line: line)
429427
} else {
430-
if let expected = expected {
431-
return XCTFail("unexpected missing assignment, expected: \(expected)", file: file, line: line)
432-
}
428+
return XCTFail("unexpected missing assignment, expected: \(expected)", file: file, line: line)
433429
}
434430
}
435431

0 commit comments

Comments
 (0)