Skip to content

Commit 1628832

Browse files
committed
add DependencyResolverDelegate to track events from the dependency resolver
motivation: * significant part of pacakge resolution is spent in the dependency resolver which does not emit events we can use to tell the user what is happening * a follow up PR would use these new events to emit inforamtion when package depedencies are resolved, e.g when running swift package update chnages: * define DependencyResolverDelegate * replace logging in PubgrubDependencyResolver with calling the relevant delegate methods * add TracingDependencyResolverDelegate which back-fills the existing logging behavior in PubgrubDependencyResolver * adjust call-sites open issues: * is this the right API shape? the delegate methods are 1:1 mapping to PubgrubDependencyResolver logging which may or may not be the right API * add tests once API is stable
1 parent d4a488b commit 1628832

File tree

5 files changed

+104
-56
lines changed

5 files changed

+104
-56
lines changed

Sources/PackageGraph/DependencyResolver.swift

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,16 @@
1111
import TSCBasic
1212
import PackageModel
1313

14+
public protocol DependencyResolver {
15+
typealias Binding = (container: PackageReference, binding: BoundVersion, products: ProductFilter)
16+
typealias Delegate = DependencyResolverDelegate
17+
}
18+
1419
public enum DependencyResolverError: Error, Equatable {
1520
/// A revision-based dependency contains a local package dependency.
1621
case revisionDependencyContainsLocalPackage(dependency: String, localPackage: String)
1722
}
1823

19-
public class DependencyResolver {
20-
public typealias Binding = (container: PackageReference, binding: BoundVersion, products: ProductFilter)
21-
}
22-
2324
extension DependencyResolverError: CustomStringConvertible {
2425
public var description: String {
2526
switch self {
@@ -28,3 +29,71 @@ extension DependencyResolverError: CustomStringConvertible {
2829
}
2930
}
3031
}
32+
33+
public protocol DependencyResolverDelegate {
34+
func willResolve(term: Term)
35+
func didResolve(term: Term, version: Version)
36+
37+
func derived(term: Term)
38+
func conflict(conflict: Incompatibility)
39+
func satisfied(term: Term, by: Assignment, incompatibility: Incompatibility)
40+
func partiallySatisfied(term: Term, by: Assignment, incompatibility: Incompatibility, difference: Term)
41+
func failedToResolve(incompatibility: Incompatibility)
42+
func computed(bindings: [DependencyResolver.Binding])
43+
}
44+
45+
public struct TracingDependencyResolverDelegate: DependencyResolverDelegate {
46+
private let stream: OutputByteStream
47+
48+
public init (path: AbsolutePath) throws {
49+
self.stream = try LocalFileOutputByteStream(path, closeOnDeinit: true, buffered: false)
50+
}
51+
52+
public init (stream: OutputByteStream) {
53+
self.stream = stream
54+
}
55+
56+
public func willResolve(term: Term) {
57+
self.log("resolving: \(term.node.package.location)")
58+
}
59+
60+
public func didResolve(term: Term, version: Version) {
61+
self.log("resolved: \(term.node.package.location) @ \(version)")
62+
}
63+
64+
public func derived(term: Term) {
65+
self.log("derived: \(term.node.package.location)")
66+
}
67+
68+
public func conflict(conflict: Incompatibility) {
69+
self.log("conflict: \(conflict)")
70+
}
71+
72+
public func failedToResolve(incompatibility: Incompatibility) {
73+
self.log("failed to resolve: \(incompatibility)")
74+
}
75+
76+
public func satisfied(term: Term, by assignment: Assignment, incompatibility: Incompatibility) {
77+
log("CR: \(term) is satisfied by \(assignment)")
78+
log("CR: which is caused by \(assignment.cause?.description ?? "")")
79+
log("CR: new incompatibility \(incompatibility)")
80+
}
81+
82+
public func partiallySatisfied(term: Term, by assignment: Assignment, incompatibility: Incompatibility, difference: Term) {
83+
log("CR: \(term) is partially satisfied by \(assignment)")
84+
log("CR: which is caused by \(assignment.cause?.description ?? "")")
85+
log("CR: new incompatibility \(incompatibility)")
86+
}
87+
88+
public func computed(bindings: [DependencyResolver.Binding]) {
89+
self.log("solved:")
90+
for (container, binding, _) in bindings {
91+
self.log("\(container) \(binding)")
92+
}
93+
}
94+
95+
private func log(_ message: String) {
96+
self.stream <<< message <<< "\n"
97+
self.stream.flush()
98+
}
99+
}

Sources/PackageGraph/Pubgrub/PartialSolution.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public struct PartialSolution {
2929

3030
/// Union of all negative assignments for a package.
3131
///
32-
/// Only present if a package has no postive assignment.
32+
/// Only present if a package has no positive assignment.
3333
public private(set) var _negative: [DependencyResolutionNode: Term] = [:]
3434

3535
/// The current decision level.

Sources/PackageGraph/Pubgrub/PubgrubDependencyResolver.swift

Lines changed: 22 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -111,40 +111,22 @@ public struct PubgrubDependencyResolver {
111111
/// Should resolver prefetch the containers.
112112
private let isPrefetchingEnabled: Bool
113113

114-
// Log stream
115-
private let traceStream: OutputByteStream?
114+
/// Resolver delegate
115+
private let delegate: DependencyResolverDelegate?
116116

117117
public init(
118118
provider: PackageContainerProvider,
119119
pinsMap: PinsStore.PinsMap = [:],
120120
isPrefetchingEnabled: Bool = false,
121121
skipUpdate: Bool = false,
122-
traceFile: AbsolutePath? = nil,
123-
traceStream: OutputByteStream? = nil
122+
delegate: DependencyResolverDelegate? = nil
124123
) {
125124
self.packageContainerProvider = provider
126125
self.pinsMap = pinsMap
127126
self.isPrefetchingEnabled = isPrefetchingEnabled
128127
self.skipUpdate = skipUpdate
129-
if let stream = traceStream {
130-
self.traceStream = stream
131-
} else {
132-
self.traceStream = traceFile.flatMap { file in
133-
// FIXME: Emit a warning if this fails.
134-
try? LocalFileOutputByteStream(file, closeOnDeinit: true, buffered: false)
135-
}
136-
}
137128
self.provider = ContainerProvider(provider: self.packageContainerProvider, skipUpdate: self.skipUpdate, pinsMap: self.pinsMap)
138-
}
139-
140-
public init(
141-
provider: PackageContainerProvider,
142-
pinsMap: PinsStore.PinsMap = [:],
143-
isPrefetchingEnabled: Bool = false,
144-
skipUpdate: Bool = false,
145-
traceFile: AbsolutePath? = nil
146-
) {
147-
self.init(provider: provider, pinsMap: pinsMap, isPrefetchingEnabled: isPrefetchingEnabled, skipUpdate: skipUpdate, traceFile: traceFile, traceStream: nil)
129+
self.delegate = delegate
148130
}
149131

150132
/// Execute the resolution algorithm to find a valid assignment of versions.
@@ -256,7 +238,7 @@ public struct PubgrubDependencyResolver {
256238
finalAssignments.append((identifier, override.version, override.products))
257239
}
258240

259-
self.log(finalAssignments)
241+
self.delegate?.computed(bindings: finalAssignments)
260242

261243
return (finalAssignments, state)
262244
}
@@ -517,7 +499,8 @@ public struct PubgrubDependencyResolver {
517499
return .conflict
518500
}
519501

520-
log("derived: \(unsatisfiedTerm.inverse)")
502+
//log("derived: \(unsatisfiedTerm.inverse)")
503+
self.delegate?.derived(term: unsatisfiedTerm.inverse)
521504
state.derive(unsatisfiedTerm.inverse, cause: incompatibility)
522505

523506
return .almostSatisfied(node: unsatisfiedTerm.node)
@@ -527,7 +510,7 @@ public struct PubgrubDependencyResolver {
527510
// https://github.com/dart-lang/pub/tree/master/doc/solver.md#conflict-resolution
528511
// https://github.com/dart-lang/pub/blob/master/lib/src/solver/version_solver.dart#L201
529512
internal func resolve(state: State, conflict: Incompatibility) throws -> Incompatibility {
530-
log("conflict: \(conflict)")
513+
self.delegate?.conflict(conflict: conflict)
531514

532515
var incompatibility = conflict
533516
var createdIncompatibility = false
@@ -594,12 +577,17 @@ public struct PubgrubDependencyResolver {
594577
)
595578
createdIncompatibility = true
596579

597-
log("CR: \(mostRecentTerm?.description ?? "") is\(difference != nil ? " partially" : "") satisfied by \(_mostRecentSatisfier)")
598-
log("CR: which is caused by \(_mostRecentSatisfier.cause?.description ?? "")")
599-
log("CR: new incompatibility \(incompatibility)")
580+
if let term = mostRecentTerm {
581+
if let diff = difference {
582+
self.delegate?.partiallySatisfied(term: term, by: _mostRecentSatisfier, incompatibility: incompatibility, difference: diff)
583+
} else {
584+
self.delegate?.satisfied(term: term, by: _mostRecentSatisfier, incompatibility: incompatibility)
585+
}
586+
}
600587
}
601588

602-
log("failed: \(incompatibility)")
589+
//log("failed: \(incompatibility)")
590+
self.delegate?.failedToResolve(incompatibility: incompatibility)
603591
throw PubgrubError._unresolvable(incompatibility, state.incompatibilities)
604592
}
605593

@@ -649,8 +637,10 @@ public struct PubgrubDependencyResolver {
649637
let counts = try result.get()
650638
// forced unwraps safe since we are testing for count and errors above
651639
let pkgTerm = undecided.min { counts[$0]! < counts[$1]! }!
640+
self.delegate?.willResolve(term: pkgTerm)
652641
// at this point the container is cached
653642
let container = try self.provider.getCachedContainer(for: pkgTerm.node.package)
643+
654644
// Get the best available version for this package.
655645
guard let version = try container.getBestAvailableVersion(for: pkgTerm) else {
656646
state.addIncompatibility(try Incompatibility(pkgTerm, root: state.root, cause: .noAvailableVersion), at: .decisionMaking)
@@ -670,7 +660,7 @@ public struct PubgrubDependencyResolver {
670660
// Add the incompatibility to our partial solution.
671661
state.addIncompatibility(incompatibility, at: .decisionMaking)
672662

673-
// Check if this incompatibility will statisfy the solution.
663+
// Check if this incompatibility will satisfy the solution.
674664
haveConflict = haveConflict || incompatibility.terms.allSatisfy {
675665
// We only need to check if the terms other than this package
676666
// are satisfied because we _know_ that the terms matching
@@ -682,7 +672,8 @@ public struct PubgrubDependencyResolver {
682672

683673
// Decide this version if there was no conflict with its dependencies.
684674
if !haveConflict {
685-
self.log("decision: \(pkgTerm.node.package)@\(version)")
675+
//self.log("decision: \(pkgTerm.node.package)@\(version)")
676+
self.delegate?.didResolve(term: pkgTerm, version: version)
686677
state.decide(pkgTerm.node, at: version)
687678
}
688679

@@ -692,20 +683,6 @@ public struct PubgrubDependencyResolver {
692683
}
693684
}
694685
}
695-
696-
private func log(_ assignments: [(container: PackageReference, binding: BoundVersion, products: ProductFilter)]) {
697-
log("solved:")
698-
for (container, binding, _) in assignments {
699-
log("\(container) \(binding)")
700-
}
701-
}
702-
703-
private func log(_ message: String) {
704-
if let traceStream = traceStream {
705-
traceStream <<< message <<< "\n"
706-
traceStream.flush()
707-
}
708-
}
709686
}
710687

711688
private struct DiagnosticReportBuilder {

Sources/Workspace/Workspace.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ extension Workspace {
580580
}
581581

582582
// Resolve the dependencies.
583-
let resolver = self.createResolver(pinsMap: pinsMap)
583+
let resolver = try self.createResolver(pinsMap: pinsMap)
584584
self.activeResolver = resolver
585585

586586
let updateResults = resolveDependencies(
@@ -1924,7 +1924,7 @@ extension Workspace {
19241924
constraints += try graphRoot.constraints() + extraConstraints
19251925

19261926
// Perform dependency resolution.
1927-
let resolver = createResolver(pinsMap: pinsStore.pinsMap)
1927+
let resolver = try createResolver(pinsMap: pinsStore.pinsMap)
19281928
self.activeResolver = resolver
19291929

19301930
let result = resolveDependencies(
@@ -2285,14 +2285,15 @@ extension Workspace {
22852285
}
22862286

22872287
/// Creates resolver for the workspace.
2288-
fileprivate func createResolver(pinsMap: PinsStore.PinsMap) -> PubgrubDependencyResolver {
2289-
let traceFile = enableResolverTrace ? self.dataPath.appending(components: "resolver.trace") : nil
2288+
fileprivate func createResolver(pinsMap: PinsStore.PinsMap) throws -> PubgrubDependencyResolver {
2289+
let delegate = self.enableResolverTrace ? try TracingDependencyResolverDelegate(path: self.dataPath.appending(components: "resolver.trace")) : nil
22902290

22912291
return PubgrubDependencyResolver(
22922292
provider: containerProvider,
22932293
pinsMap: pinsMap,
22942294
isPrefetchingEnabled: isResolverPrefetchingEnabled,
2295-
skipUpdate: skipUpdate, traceFile: traceFile
2295+
skipUpdate: skipUpdate,
2296+
delegate: delegate
22962297
)
22972298
}
22982299

Tests/PackageGraphTests/PubgrubTests.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2152,7 +2152,8 @@ class DependencyGraphBuilder {
21522152
self.references = [:]
21532153
}
21542154
let provider = MockProvider(containers: self.containers.values.map { $0 })
2155-
return PubgrubDependencyResolver(provider :provider, pinsMap: pinsMap, traceStream: log ? stdoutStream : nil)
2155+
let delegate = log ? TracingDependencyResolverDelegate(stream: stdoutStream) : nil
2156+
return PubgrubDependencyResolver(provider :provider, pinsMap: pinsMap, delegate: delegate)
21562157
}
21572158
}
21582159

0 commit comments

Comments
 (0)