Skip to content

add delegate to track events from the dependency resolver #3420

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 73 additions & 4 deletions Sources/PackageGraph/DependencyResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@
import TSCBasic
import PackageModel

public protocol DependencyResolver {
typealias Binding = (package: PackageReference, binding: BoundVersion, products: ProductFilter)
typealias Delegate = DependencyResolverDelegate
}

public enum DependencyResolverError: Error, Equatable {
/// A revision-based dependency contains a local package dependency.
case revisionDependencyContainsLocalPackage(dependency: String, localPackage: String)
}

public class DependencyResolver {
public typealias Binding = (container: PackageReference, binding: BoundVersion, products: ProductFilter)
}

extension DependencyResolverError: CustomStringConvertible {
public var description: String {
switch self {
Expand All @@ -28,3 +29,71 @@ extension DependencyResolverError: CustomStringConvertible {
}
}
}

public protocol DependencyResolverDelegate {
func willResolve(term: Term)
func didResolve(term: Term, version: Version)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^^ we need for sure


func derived(term: Term)
func conflict(conflict: Incompatibility)
func satisfied(term: Term, by: Assignment, incompatibility: Incompatibility)
func partiallySatisfied(term: Term, by: Assignment, incompatibility: Incompatibility, difference: Term)
func failedToResolve(incompatibility: Incompatibility)
func computed(bindings: [DependencyResolver.Binding])
Copy link
Contributor Author

@tomerd tomerd Apr 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^^ are more questionable and mirror the current logging behavior of pubgrub resolver

}

public struct TracingDependencyResolverDelegate: DependencyResolverDelegate {
private let stream: OutputByteStream

public init (path: AbsolutePath) throws {
self.stream = try LocalFileOutputByteStream(path, closeOnDeinit: true, buffered: false)
}

public init (stream: OutputByteStream) {
self.stream = stream
}

public func willResolve(term: Term) {
self.log("resolving: \(term.node.package.location)")
}

public func didResolve(term: Term, version: Version) {
self.log("resolved: \(term.node.package.location) @ \(version)")
}

public func derived(term: Term) {
self.log("derived: \(term.node.package.location)")
}

public func conflict(conflict: Incompatibility) {
self.log("conflict: \(conflict)")
}

public func failedToResolve(incompatibility: Incompatibility) {
self.log("failed to resolve: \(incompatibility)")
}

public func satisfied(term: Term, by assignment: Assignment, incompatibility: Incompatibility) {
log("CR: \(term) is satisfied by \(assignment)")
log("CR: which is caused by \(assignment.cause?.description ?? "")")
log("CR: new incompatibility \(incompatibility)")
}

public func partiallySatisfied(term: Term, by assignment: Assignment, incompatibility: Incompatibility, difference: Term) {
log("CR: \(term) is partially satisfied by \(assignment)")
log("CR: which is caused by \(assignment.cause?.description ?? "")")
log("CR: new incompatibility \(incompatibility)")
}

public func computed(bindings: [DependencyResolver.Binding]) {
self.log("solved:")
for (container, binding, _) in bindings {
self.log("\(container) \(binding)")
}
}

private func log(_ message: String) {
self.stream <<< message <<< "\n"
self.stream.flush()
}
}
2 changes: 1 addition & 1 deletion Sources/PackageGraph/Pubgrub/PartialSolution.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public struct PartialSolution {

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

/// The current decision level.
Expand Down
66 changes: 20 additions & 46 deletions Sources/PackageGraph/Pubgrub/PubgrubDependencyResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,40 +111,22 @@ public struct PubgrubDependencyResolver {
/// Should resolver prefetch the containers.
private let isPrefetchingEnabled: Bool

// Log stream
private let traceStream: OutputByteStream?
/// Resolver delegate
private let delegate: DependencyResolverDelegate?

public init(
provider: PackageContainerProvider,
pinsMap: PinsStore.PinsMap = [:],
isPrefetchingEnabled: Bool = false,
skipUpdate: Bool = false,
traceFile: AbsolutePath? = nil,
traceStream: OutputByteStream? = nil
delegate: DependencyResolverDelegate? = nil
) {
self.packageContainerProvider = provider
self.pinsMap = pinsMap
self.isPrefetchingEnabled = isPrefetchingEnabled
self.skipUpdate = skipUpdate
if let stream = traceStream {
self.traceStream = stream
} else {
self.traceStream = traceFile.flatMap { file in
// FIXME: Emit a warning if this fails.
try? LocalFileOutputByteStream(file, closeOnDeinit: true, buffered: false)
}
}
self.provider = ContainerProvider(provider: self.packageContainerProvider, skipUpdate: self.skipUpdate, pinsMap: self.pinsMap)
}

public init(
provider: PackageContainerProvider,
pinsMap: PinsStore.PinsMap = [:],
isPrefetchingEnabled: Bool = false,
skipUpdate: Bool = false,
traceFile: AbsolutePath? = nil
) {
self.init(provider: provider, pinsMap: pinsMap, isPrefetchingEnabled: isPrefetchingEnabled, skipUpdate: skipUpdate, traceFile: traceFile, traceStream: nil)
self.delegate = delegate
}

/// Execute the resolution algorithm to find a valid assignment of versions.
Expand Down Expand Up @@ -245,7 +227,7 @@ public struct PubgrubDependencyResolver {
var finalAssignments: [DependencyResolver.Binding]
= flattenedAssignments.keys.sorted(by: { $0.name < $1.name }).map { package in
let details = flattenedAssignments[package]!
return (container: package, binding: details.binding, products: details.products)
return (package: package, binding: details.binding, products: details.products)
}

// Add overriden packages to the result.
Expand All @@ -256,7 +238,7 @@ public struct PubgrubDependencyResolver {
finalAssignments.append((identifier, override.version, override.products))
}

self.log(finalAssignments)
self.delegate?.computed(bindings: finalAssignments)

return (finalAssignments, state)
}
Expand Down Expand Up @@ -517,7 +499,7 @@ public struct PubgrubDependencyResolver {
return .conflict
}

log("derived: \(unsatisfiedTerm.inverse)")
self.delegate?.derived(term: unsatisfiedTerm.inverse)
state.derive(unsatisfiedTerm.inverse, cause: incompatibility)

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

var incompatibility = conflict
var createdIncompatibility = false
Expand Down Expand Up @@ -594,12 +576,16 @@ public struct PubgrubDependencyResolver {
)
createdIncompatibility = true

log("CR: \(mostRecentTerm?.description ?? "") is\(difference != nil ? " partially" : "") satisfied by \(_mostRecentSatisfier)")
log("CR: which is caused by \(_mostRecentSatisfier.cause?.description ?? "")")
log("CR: new incompatibility \(incompatibility)")
if let term = mostRecentTerm {
if let diff = difference {
self.delegate?.partiallySatisfied(term: term, by: _mostRecentSatisfier, incompatibility: incompatibility, difference: diff)
} else {
self.delegate?.satisfied(term: term, by: _mostRecentSatisfier, incompatibility: incompatibility)
}
}
}

log("failed: \(incompatibility)")
self.delegate?.failedToResolve(incompatibility: incompatibility)
throw PubgrubError._unresolvable(incompatibility, state.incompatibilities)
}

Expand Down Expand Up @@ -649,8 +635,10 @@ public struct PubgrubDependencyResolver {
let counts = try result.get()
// forced unwraps safe since we are testing for count and errors above
let pkgTerm = undecided.min { counts[$0]! < counts[$1]! }!
self.delegate?.willResolve(term: pkgTerm)
// at this point the container is cached
let container = try self.provider.getCachedContainer(for: pkgTerm.node.package)

// Get the best available version for this package.
guard let version = try container.getBestAvailableVersion(for: pkgTerm) else {
state.addIncompatibility(try Incompatibility(pkgTerm, root: state.root, cause: .noAvailableVersion), at: .decisionMaking)
Expand All @@ -670,7 +658,7 @@ public struct PubgrubDependencyResolver {
// Add the incompatibility to our partial solution.
state.addIncompatibility(incompatibility, at: .decisionMaking)

// Check if this incompatibility will statisfy the solution.
// Check if this incompatibility will satisfy the solution.
haveConflict = haveConflict || incompatibility.terms.allSatisfy {
// We only need to check if the terms other than this package
// are satisfied because we _know_ that the terms matching
Expand All @@ -682,7 +670,7 @@ public struct PubgrubDependencyResolver {

// Decide this version if there was no conflict with its dependencies.
if !haveConflict {
self.log("decision: \(pkgTerm.node.package)@\(version)")
self.delegate?.didResolve(term: pkgTerm, version: version)
state.decide(pkgTerm.node, at: version)
}

Expand All @@ -692,20 +680,6 @@ public struct PubgrubDependencyResolver {
}
}
}

private func log(_ assignments: [(container: PackageReference, binding: BoundVersion, products: ProductFilter)]) {
log("solved:")
for (container, binding, _) in assignments {
log("\(container) \(binding)")
}
}

private func log(_ message: String) {
if let traceStream = traceStream {
traceStream <<< message <<< "\n"
traceStream.flush()
}
}
}

private struct DiagnosticReportBuilder {
Expand Down
13 changes: 7 additions & 6 deletions Sources/Workspace/Workspace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,7 @@ extension Workspace {
}

// Resolve the dependencies.
let resolver = self.createResolver(pinsMap: pinsMap)
let resolver = try self.createResolver(pinsMap: pinsMap)
self.activeResolver = resolver

let updateResults = resolveDependencies(
Expand Down Expand Up @@ -1947,7 +1947,7 @@ extension Workspace {
constraints += try graphRoot.constraints() + extraConstraints

// Perform dependency resolution.
let resolver = createResolver(pinsMap: pinsStore.pinsMap)
let resolver = try createResolver(pinsMap: pinsStore.pinsMap)
self.activeResolver = resolver

let result = resolveDependencies(
Expand Down Expand Up @@ -2308,14 +2308,15 @@ extension Workspace {
}

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

return PubgrubDependencyResolver(
provider: containerProvider,
pinsMap: pinsMap,
isPrefetchingEnabled: isResolverPrefetchingEnabled,
skipUpdate: skipUpdate, traceFile: traceFile
skipUpdate: skipUpdate,
delegate: delegate
)
}

Expand All @@ -2324,7 +2325,7 @@ extension Workspace {
resolver: PubgrubDependencyResolver,
constraints: [PackageContainerConstraint],
diagnostics: DiagnosticsEngine
) -> [(container: PackageReference, binding: BoundVersion, products: ProductFilter)] {
) -> [(package: PackageReference, binding: BoundVersion, products: ProductFilter)] {

os_signpost(.begin, log: .swiftpm, name: SignpostName.resolution)
let result = resolver.solve(constraints: constraints)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class DependencyResolverRealWorldPerfTests: XCTestCasePerf {
XCTFail("Unexpected result")
return nil
}
return ($0.container, version)
return ($0.package, version)
}
graph.checkResult(result)
case .failure:
Expand Down
Loading