diff --git a/src/GraphType.jl b/src/GraphType.jl index 263fa67420e2f..8f49471ac8add 100644 --- a/src/GraphType.jl +++ b/src/GraphType.jl @@ -6,32 +6,56 @@ using ..Types import ..Types.uuid_julia import Pkg3.equalto -export Graph, add_reqs!, add_fixed!, simplify_graph! - -# This is used to keep track of dependency relations when propagating -# requirements, so as to emit useful information in case of unsatisfiable -# conditions. -# The `why` field is a Vector which keeps track of the requirements. Each -# entry is a Tuple of two elements: -# 1) the first element is the reason, and it can be either :fixed (for -# fixed packages), :explicit_requirement (for explicitly required packages), -# or a Tuple `(:constr_prop, p, backtrace_item)` (for requirements induced -# indirectly), where `p` is the package index and `backtrace_item` is -# another ResolveBacktraceItem. -# 2) the second element is a BitVector representing the requirement as a mask -# over the possible states of the package -mutable struct ResolveBacktraceItem - why::Vector{Any} - ResolveBacktraceItem() = new(Any[]) +export Graph, add_reqs!, add_fixed!, simplify_graph!, showlog + +# The ResolveLog is used to keep track of events that take place during the +# resolution process. We use one ResolveLogEntry per package, and record all events +# associated with that package. An event consists of two items: another +# entry representing another package's influence (or `nothing`) and +# a message for the log. +# +# Specialized functions called `log_event_[...]!` are used to store the +# various events. The events are also recorded in orded in a shared +# ResolveJournal, which is used to provide a plain chronological view. +# +# The `showlog` functions are used for display, and called to create messages +# in case of resolution errors. + +const ResolveJournal = Vector{Tuple{UUID,String}} + +mutable struct ResolveLogEntry + journal::ResolveJournal # shared with all other entries + pkg::UUID + header::String + events::Vector{Tuple{Any,String}} # here Any should ideally be Union{ResolveLogEntry,Void} + ResolveLogEntry(journal::ResolveJournal, pkg::UUID, msg::String = "") = new(journal, pkg, msg, []) end -function Base.push!(ritem::ResolveBacktraceItem, reason, versionmask) - push!(ritem.why, (reason, versionmask)) +function Base.push!(entry::ResolveLogEntry, reason::Tuple{Union{ResolveLogEntry,Void},String}) + push!(entry.events, reason) + entry.pkg ≠ uuid_julia && push!(entry.journal, (entry.pkg, reason[2])) + return entry +end + +# Note: the `init` field is used to keep track of all entries which were there +# at the beginning, since the `pool` can be pruned during the resolution process. +mutable struct ResolveLog + init::ResolveLogEntry + pool::Dict{UUID,ResolveLogEntry} + journal::Vector{Tuple{UUID,String}} + function ResolveLog() + journal = ResolveJournal() + return new(ResolveLogEntry(journal, uuid_julia), Dict(), journal) + end end # Installation state: either a version, or uninstalled const InstState = Union{VersionNumber,Void} + +# GraphData is basically a part of Graph that collects data structures useful +# for interfacing the internal abstract representation of Graph with the +# input/output (e.g. converts between package UUIDs and node numbers, etc.) mutable struct GraphData # packages list pkgs::Vector{UUID} @@ -56,8 +80,11 @@ mutable struct GraphData # pvers[p0][vdict[p0][vn]] = vn vdict::Vector{Dict{VersionNumber,Int}} + # UUID to names uuid_to_name::Dict{UUID,String} + # requirements and fixed packages, passed at initialization + # (TODO: these are probably useless?) reqs::Requires fixed::Dict{UUID,Fixed} @@ -72,6 +99,9 @@ mutable struct GraphData # states, keep track of other equivalent states eq_classes::Dict{UUID,Dict{InstState,Set{InstState}}} + # resolve log: keep track of the resolution process + rlog::ResolveLog + function GraphData( versions::Dict{UUID,Set{VersionNumber}}, deps::Dict{UUID,Dict{VersionRange,Dict{String,UUID}}}, @@ -94,13 +124,21 @@ mutable struct GraphData # generate vdict vdict = [Dict{VersionNumber,Int}(vn => i for (i,vn) in enumerate(pvers[p0])) for p0 = 1:np] + # nothing is pruned yet, of course pruned = Dict{UUID,VersionNumber}() # equivalence classes (at the beginning each state represents just itself) eq_vn(v0, p0) = (v0 == spp[p0] ? nothing : pvers[p0][v0]) eq_classes = Dict(pkgs[p0] => Dict(eq_vn(v0,p0) => Set([eq_vn(v0,p0)]) for v0 = 1:spp[p0]) for p0 = 1:np) - return new(pkgs, np, spp, pdict, pvers, vdict, uuid_to_name, reqs, fixed, pruned, eq_classes) + # the resolution log is actually initialized below + rlog = ResolveLog() + + data = new(pkgs, np, spp, pdict, pvers, vdict, uuid_to_name, reqs, fixed, pruned, eq_classes, rlog) + + init_log!(data) + + return data end end @@ -161,9 +199,6 @@ mutable struct Graph # states per package: same as in GraphData spp::Vector{Int} - # backtrace: keep track of the resolution process - bktrc::Vector{ResolveBacktraceItem} - # number of packages (all Vectors above have this length) np::Int @@ -180,7 +215,7 @@ mutable struct Graph extra_uuids ⊆ keys(versions) || error("unknown UUID found in reqs/fixed") # TODO? data = GraphData(versions, deps, compat, uuid_to_name, reqs, fixed) - pkgs, np, spp, pdict, pvers, vdict = data.pkgs, data.np, data.spp, data.pdict, data.pvers, data.vdict + pkgs, np, spp, pdict, pvers, vdict, rlog = data.pkgs, data.np, data.spp, data.pdict, data.pvers, data.vdict, data.rlog extended_deps = [[Dict{Int,BitVector}() for v0 = 1:(spp[p0]-1)] for p0 = 1:np] for p0 = 1:np, v0 = 1:(spp[p0]-1) @@ -265,9 +300,7 @@ mutable struct Graph req_inds = Set{Int}() fix_inds = Set{Int}() - bktrc = [ResolveBacktraceItem() for p0 = 1:np] - - graph = new(data, gadj, gmsk, gdir, gconstr, adjdict, req_inds, fix_inds, spp, bktrc, np) + graph = new(data, gadj, gmsk, gdir, gconstr, adjdict, req_inds, fix_inds, spp, np) _add_fixed!(graph, fixed) _add_reqs!(graph, reqs, :explicit_requirement) @@ -293,7 +326,6 @@ function _add_reqs!(graph::Graph, reqs::Requires, reason) gconstr = graph.gconstr spp = graph.spp req_inds = graph.req_inds - bktrc = graph.bktrc pdict = graph.data.pdict pvers = graph.data.pvers @@ -309,7 +341,7 @@ function _add_reqs!(graph::Graph, reqs::Requires, reason) old_constr = copy(gconstr[rp0]) gconstr[rp0] .&= new_constr reason ≡ :explicit_requirement && push!(req_inds, rp0) - old_constr ≠ gconstr[rp0] && push!(bktrc[rp0], reason, new_constr) + old_constr ≠ gconstr[rp0] && log_event_req!(graph, rp, rvs, reason) end return graph end @@ -326,7 +358,6 @@ function _add_fixed!(graph::Graph, fixed::Dict{UUID,Fixed}) gconstr = graph.gconstr spp = graph.spp fix_inds = graph.fix_inds - bktrc = graph.bktrc pdict = graph.data.pdict vdict = graph.data.vdict @@ -338,14 +369,15 @@ function _add_fixed!(graph::Graph, fixed::Dict{UUID,Fixed}) new_constr[fv0] = true gconstr[fp0] .&= new_constr push!(fix_inds, fp0) - push!(bktrc[fp0], :fixed, new_constr) - _add_reqs!(graph, fx.requires, (:constr_prop, fp0, bktrc[fp0])) + bkitem = log_event_fixed!(graph, fp, fx) + _add_reqs!(graph, fx.requires, (fp, bkitem)) end return graph end -Types.pkgID(p::UUID, graph::Graph) = pkgID(p, graph.data.uuid_to_name) -Types.pkgID(p0::Int, graph::Graph) = pkgID(graph.data.pkgs[p0], graph) +Types.pkgID(p::UUID, data::GraphData) = pkgID(p, data.uuid_to_name) +Types.pkgID(p0::Int, data::GraphData) = pkgID(data.pkgs[p0], data) +Types.pkgID(p, graph::Graph) = pkgID(p, graph.data) function check_consistency(graph::Graph) np = graph.np @@ -357,7 +389,6 @@ function check_consistency(graph::Graph) adjdict = graph.adjdict req_inds = graph.req_inds fix_inds = graph.fix_inds - bktrc = graph.bktrc data = graph.data pkgs = data.pkgs pdict = data.pdict @@ -365,9 +396,10 @@ function check_consistency(graph::Graph) vdict = data.vdict pruned = data.pruned eq_classes = data.eq_classes + rlog = data.rlog @assert np ≥ 0 - for x in [spp, gadj, gmsk, gdir, gconstr, adjdict, bktrc, pkgs, pdict, pvers, vdict] + for x in [spp, gadj, gmsk, gdir, gconstr, adjdict, rlog.pool, pkgs, pdict, pvers, vdict] @assert length(x) == np end for p0 = 1:np @@ -425,78 +457,320 @@ function check_consistency(graph::Graph) return true end -"Show the resolution backtrace for some package" -function showbacktrace(io::IO, graph::Graph, p0::Int) - _show(io, graph, p0, graph.bktrc[p0], "", Set{ResolveBacktraceItem}()) +function init_log!(data::GraphData) + np = data.np + pkgs = data.pkgs + pvers = data.pvers + rlog = data.rlog + for p0 = 1:np + p = pkgs[p0] + id = pkgID(p0, data) + versions = pvers[p0] + if isempty(versions) + msg = "$id has no known versions!" # This shouldn't happen? + else + msg = "possible versions are: $(VersionSpec(VersionRange.(versions))) or uninstalled" + end + first_entry = get!(rlog.pool, p) do; ResolveLogEntry(rlog.journal, p, "$id log:") end + + if p ≠ uuid_julia + push!(first_entry, (nothing, msg)) + push!(rlog.init, (first_entry, "")) + end + end + return data end -# Show a recursive tree with requirements applied to a package, either directly or indirectly -function _show(io::IO, graph::Graph, p0::Int, ritem::ResolveBacktraceItem, indent::String, seen::Set{ResolveBacktraceItem}) - id0 = pkgID(p0, graph) +function log_event_fixed!(graph::Graph, fp::UUID, fx::Fixed) + rlog = graph.data.rlog + id = pkgID(fp, graph) + msg = "$id is fixed to version $(fx.version)" + entry = rlog.pool[fp] + push!(entry, (nothing, msg)) + return entry +end + +function log_event_req!(graph::Graph, rp::UUID, rvs::VersionSpec, reason) + rlog = graph.data.rlog + gconstr = graph.gconstr + pdict = graph.data.pdict + pvers = graph.data.pvers + id = pkgID(rp, graph) + msg = "restricted to versions $rvs by " + if reason isa Symbol + @assert reason == :explicit_requirement + other_entry = nothing + msg *= "an explicit requirement, " + else + @assert reason isa Tuple{UUID,ResolveLogEntry} + other_p, other_entry = reason + if other_p == uuid_julia + msg *= "julia compatibility requirements, " + other_entry = nothing + else + other_id = pkgID(other_p, graph) + msg *= "$other_id, " + end + end + rp0 = pdict[rp] + @assert !gconstr[rp0][end] + if any(gconstr[rp0]) + msg *= "leaving only versions $(VersionSpec(VersionRange.(pvers[rp0][gconstr[rp0][1:(end-1)]])))" + else + msg *= "leaving no versions left" + end + entry = rlog.pool[rp] + push!(entry, (other_entry, msg)) + return entry +end + +function log_event_implicit_req!(graph::Graph, p1::Int, vmask::BitVector, p0::Int) + rlog = graph.data.rlog gconstr = graph.gconstr pkgs = graph.data.pkgs pvers = graph.data.pvers function vs_string(p0::Int, vmask::BitVector) - vns = Vector{Any}(pvers[p0][vmask[1:(end-1)]]) - vmask[end] && push!(vns, "uninstalled") - return join(string.(vns), ", ", " or ") - end - - l = length(ritem.why) - for (i,(w,vmask)) in enumerate(ritem.why) - print(io, indent, (i==l ? '└' : '├'), '─') - if w ≡ :fixed - @assert count(vmask) == 1 - println(io, "$id0 is fixed to version ", vs_string(p0, vmask)) - elseif w ≡ :explicit_requirement - @assert !vmask[end] - if any(vmask) - println(io, "an explicit requirement sets $id0 to versions: ", vs_string(p0, vmask)) - else - println(io, "an explicit requirement cannot be matched by any of the available versions of $id0") - end + if any(vmask[1:(end-1)]) + vns = string(VersionSpec(VersionRange.(pvers[p0][vmask[1:(end-1)]]))) + vmask[end] && (vns *= " or uninstalled") + elseif vmask[end] + vns = "uninstalled" else - @assert w isa Tuple{Symbol,Int,ResolveBacktraceItem} - @assert w[1] == :constr_prop - p1 = w[2] - if !is_current_julia(graph, p1) - id1 = pkgID(p1, graph) - otheritem = w[3] - if any(vmask) - println(io, "the only versions of $id0 compatible with $id1 (whose allowed versions are $(vs_string(p1, gconstr[p1])))\n", - indent, (i==l ? " " : "│ "),"are these: ", vs_string(p0, vmask)) - else - println(io, "no versions of $id0 are compatible with $id1 (whose allowed versions are $(vs_string(p1, gconstr[p1])))") - end - if otheritem ∈ seen - println(io, indent, (i==l ? " " : "│ "), "└─see above for $id1 backtrace") - continue - end - push!(seen, otheritem) - _show(io, graph, p1, otheritem, indent * (i==l ? " " : "│ "), seen) + vns = "no version" + end + return vns + end + + p = pkgs[p1] + id = pkgID(p, graph) + other_p, other_entry = pkgs[p0], rlog.pool[pkgs[p0]] + other_id = pkgID(other_p, graph) + if any(vmask) + msg = "restricted by " + if other_p == uuid_julia + msg *= "julia compatibility requirements " + other_entry = nothing # don't propagate the log + else + other_id = pkgID(other_p, graph) + msg *= "compatibility requirements with $other_id " + end + msg *= "to versions: $(vs_string(p1, vmask))" + if vmask ≠ gconstr[p1] + msg *= ", leaving " + if any(gconstr[p1]) + msg *= "only versions: $(vs_string(p1, gconstr[p1]))" else - if any(vmask) - println(io, "the only versions of $id0 compatible with julia v$VERSION are these: ", vs_string(p0, vmask)) - else - println(io, "no versions of $id0 are compatible with julia v$VERSION") - end + msg *= "no versions left" end end + else + msg = "found to have no compatible versions left with " + if other_p == uuid_julia + msg *= "julia" + other_entry = nothing # don't propagate the log + else + other_id = pkgID(other_p, graph) + msg *= "$other_id " + end + end + entry = rlog.pool[p] + push!(entry, (other_entry, msg)) + return entry +end + +function log_event_pruned!(graph::Graph, p0::Int, s0::Int) + rlog = graph.data.rlog + spp = graph.spp + pkgs = graph.data.pkgs + pvers = graph.data.pvers + + p = pkgs[p0] + id = pkgID(p, graph) + if s0 == spp[p0] + msg = "determined to be unneeded during graph pruning" + else + msg = "fixed during graph pruning to its only remaining available version, $(pvers[p0][s0])" + end + entry = rlog.pool[p] + push!(entry, (nothing, msg)) + return entry +end + +function log_event_greedysolved!(graph::Graph, p0::Int, s0::Int) + rlog = graph.data.rlog + spp = graph.spp + pkgs = graph.data.pkgs + pvers = graph.data.pvers + + p = pkgs[p0] + id = pkgID(p, graph) + if s0 == spp[p0] + msg = "determined to be unneeded by the solver" + else + if s0 == spp[p0] - 1 + msg = "set by the solver to its maximum version: $(pvers[p0][s0])" + else + msg = "set by the solver to the maximum version compatible with the constraints: $(pvers[p0][s0])" + end + end + entry = rlog.pool[p] + push!(entry, (nothing, msg)) + return entry +end + +function log_event_maxsumsolved!(graph::Graph, p0::Int, s0::Int, why::Symbol) + rlog = graph.data.rlog + spp = graph.spp + pkgs = graph.data.pkgs + pvers = graph.data.pvers + + p = pkgs[p0] + id = pkgID(p, graph) + if s0 == spp[p0] + @assert why == :uninst + msg = "determined to be unneeded by the solver" + else + @assert why == :constr + if s0 == spp[p0] - 1 + msg = "set by the solver to its maximum version: $(pvers[p0][s0])" + else + msg = "set by the solver version: $(pvers[p0][s0]) (version $(pvers[p0][s0+1]) would violate its constraints)" + end end + entry = rlog.pool[p] + push!(entry, (nothing, msg)) + return entry end -function is_current_julia(graph::Graph, p1::Int) +function log_event_maxsumsolved!(graph::Graph, p0::Int, s0::Int, p1::Int) + rlog = graph.data.rlog + spp = graph.spp + pkgs = graph.data.pkgs + pvers = graph.data.pvers + + p = pkgs[p0] + id = pkgID(p, graph) + other_id = pkgID(p1, graph) + @assert s0 ≠ spp[p0] + if s0 == spp[p0] - 1 + msg = "set by the solver to its maximum version: $(pvers[p0][s0]) (installation is required by $other_id)" + else + msg = "set by the solver version: $(pvers[p0][s0]) (version $(pvers[p0][s0+1]) would violate a dependecy relation with $other_id)" + end + other_entry = rlog.pool[pkgs[p1]] + entry = rlog.pool[p] + push!(entry, (other_entry, msg)) + return entry +end + +function log_event_eq_classes!(graph::Graph, p0::Int) + rlog = graph.data.rlog + spp = graph.spp gconstr = graph.gconstr - fix_inds = graph.fix_inds pkgs = graph.data.pkgs pvers = graph.data.pvers - (pkgs[p1] == uuid_julia && p1 ∈ fix_inds) || return false - jconstr = gconstr[p1] - return length(jconstr) == 2 && !jconstr[2] && pvers[p1][1] == VERSION + if any(gconstr[p0][1:(end-1)]) + vns = string(VersionSpec(VersionRange.(pvers[p0][gconstr[p0][1:(end-1)]]))) + gconstr[p0][end] && (vns *= " or uninstalled") + elseif gconstr[p0][end] + vns = "uninstalled" + else + vns = "no version" + end + + p = pkgs[p0] + id = pkgID(p, graph) + msg = "versions reduced by equivalence to: $vns" + entry = rlog.pool[p] + push!(entry, (nothing, msg)) + return entry end +""" +Show the full resolution log. The `view` keyword controls how the events are displayed/grouped: + + * `:plain` for a shallow view, grouped by package, alphabetically (the default) + * `:tree` for a tree view in which the log of a package is displayed as soon as it appears + in the process (the top-level is still grouped by package, alphabetically) + * `:chronological` for a flat view of all events in chronological order +""" +function showlog(io::IO, graph::Graph; view::Symbol = :plain) + view ∈ [:plain, :tree, :chronological] || throw(ArgumentError("the view argument should be `:plain`, `:tree` or `:chronological`")) + println(io, "Resolve log:") + view == :chronological && return showlogjournal(io, graph) + seen = ObjectIdDict() + recursive = (view == :tree) + initentries = [event[1] for event in graph.data.rlog.init.events] + for entry in sort!(initentries, by=(entry->pkgID(entry.pkg, graph))) + _show(io, graph, entry, "", seen, recursive) + recursive && (seen[entry] = true) + end +end + +function showlogjournal(io::IO, graph::Graph) + journal = graph.data.rlog.journal + padding = maximum(length(pkgID(p, graph)) for (p,_) in journal) + for (p, msg) in journal + println(io, ' ', rpad(pkgID(p, graph), padding), ": ", msg) + end +end + +""" +Show the resolution log for some package, and all the other packages that affected +it during resolution. The `view` option can be either `:plain` or `:tree` (works +the same as for `showlog(io, graph)`); the default is `:tree`. +""" +function showlog(io::IO, graph::Graph, p::UUID; view::Symbol = :tree) + view ∈ [:plain, :tree] || throw(ArgumentError("the view argument should be `:plain` or `:tree`")) + rlog = graph.data.rlog + if view == :tree + _show(io, graph, rlog.pool[p], "", ObjectIdDict(), true) + else + entries = ResolveLogEntry[rlog.pool[p]] + function getentries(entry) + for (other_entry,_) in entry.events + (other_entry ≡ nothing || other_entry ∈ entries) && continue + push!(entries, other_entry) + getentries(other_entry) + end + end + getentries(rlog.pool[p]) + for entry in entries + _show(io, graph, entry, "", ObjectIdDict(), false) + end + end +end + +# Show a recursive tree with requirements applied to a package, either directly or indirectly +function _show(io::IO, graph::Graph, entry::ResolveLogEntry, indent::String, seen::ObjectIdDict, recursive::Bool) + toplevel = isempty(indent) + firstglyph = toplevel ? "" : "└─" + pre = toplevel ? "" : " " + println(io, indent, firstglyph, entry.header) + l = length(entry.events) + for (i,(otheritem,msg)) in enumerate(entry.events) + if !isempty(msg) + print(io, indent * pre, (i==l ? '└' : '├'), '─') + println(io, msg) + newindent = indent * pre * (i==l ? " " : "│ ") + else + newindent = indent + end + otheritem ≡ nothing && continue + recursive || continue + if otheritem ∈ keys(seen) + println(io, newindent, "└─", otheritem.header, " see above") + continue + end + seen[otheritem] = true + _show(io, graph, otheritem, newindent, seen, recursive) + end +end + +is_julia(graph::Graph, p0::Int) = graph.data.pkgs[p0] == uuid_julia + "Check for contradictions in the constraints." function check_constraints(graph::Graph) np = graph.np @@ -504,12 +778,12 @@ function check_constraints(graph::Graph) pkgs = graph.data.pkgs pvers = graph.data.pvers - id(p0::Int) = pkgID(pkgs[p0], graph) + id(p0::Int) = pkgID(p0, graph) for p0 = 1:np any(gconstr[p0]) && continue err_msg = "Unsatisfiable requirements detected for package $(id(p0)):\n" - err_msg *= sprint(showbacktrace, graph, p0) + err_msg *= sprint(showlog, graph, pkgs[p0]) throw(PkgError(err_msg)) end return true @@ -518,18 +792,18 @@ end """ Propagates current constraints, determining new implicit constraints. Throws an error in case impossible requirements are detected, printing -a backtrace. +a log trace. """ function propagate_constraints!(graph::Graph) np = graph.np spp = graph.spp gadj = graph.gadj gmsk = graph.gmsk - bktrc = graph.bktrc gconstr = graph.gconstr adjdict = graph.adjdict pkgs = graph.data.pkgs pvers = graph.data.pvers + rlog = graph.data.rlog id(p0::Int) = pkgID(pkgs[p0], graph) @@ -542,7 +816,7 @@ function propagate_constraints!(graph::Graph) gconstr0 = gconstr[p0] for (j1,p1) in enumerate(gadj[p0]) # we don't propagate to julia (purely to have better error messages) - is_current_julia(graph, p1) && continue + pkgs[p1] == uuid_julia && continue msk = gmsk[p0][j1] # consider the sub-mask with only allowed versions of p0 @@ -560,11 +834,11 @@ function propagate_constraints!(graph::Graph) # previous ones, record it and propagate them next if gconstr1 ≠ old_gconstr1 push!(staged_next, p1) - push!(bktrc[p1], (:constr_prop, p0, bktrc[p0]), added_constr1) + log_event_implicit_req!(graph, p1, added_constr1, p0) end if !any(gconstr1) err_msg = "Unsatisfiable requirements detected for package $(id(p1)):\n" - err_msg *= sprint(showbacktrace, graph, p1) + err_msg *= sprint(showlog, graph, pkgs[p1]) throw(PkgError(err_msg)) end end @@ -636,10 +910,6 @@ function compute_eq_classes!(graph::Graph; verbose::Bool = false) """) end - # wipe out backtrace because it doesn't make sense now - # TODO: save it somehow? - graph.bktrc = [ResolveBacktraceItem() for p0 = 1:np] - @assert check_consistency(graph) return graph @@ -657,6 +927,7 @@ function build_eq_classes1!(graph::Graph, p0::Int) pvers = data.pvers vdict = data.vdict eq_classes = data.eq_classes + rlog = data.rlog # concatenate all the constraints; the columns of the # result encode the behavior of each version @@ -709,6 +980,9 @@ function build_eq_classes1!(graph::Graph, p0::Int) pvers[p0] = pvers[p0][repr_vers[1:(end-1)]] vdict[p0] = Dict(vn => i for (i,vn) in enumerate(pvers[p0])) + # put a record in the log + log_event_eq_classes!(graph, p0) + return end @@ -726,13 +1000,13 @@ function prune_graph!(graph::Graph; verbose::Bool = false) adjdict = graph.adjdict req_inds = graph.req_inds fix_inds = graph.fix_inds - bktrc = graph.bktrc data = graph.data pkgs = data.pkgs pdict = data.pdict pvers = data.pvers vdict = data.vdict pruned = data.pruned + rlog = data.rlog # We will remove all packages that only have one allowed state # (includes fixed packages and forbidden packages) @@ -768,7 +1042,8 @@ function prune_graph!(graph::Graph; verbose::Bool = false) # We don't record fixed packages p0 ∈ fix_inds && (@assert s0 ≠ spp[p0]; continue) p0 ∈ req_inds && @assert s0 ≠ spp[p0] - # We don't record packages that are not going to be installed + log_event_pruned!(graph, p0, s0) + # We don't record as pruned packages that are not going to be installed s0 == spp[p0] && continue @assert !haskey(pruned, pkgs[p0]) pruned[pkgs[p0]] = pvers[p0][s0] @@ -853,9 +1128,8 @@ function prune_graph!(graph::Graph; verbose::Bool = false) end new_gmsk = [[compute_gmsk(new_p0, new_j0) for new_j0 = 1:length(new_gadj[new_p0])] for new_p0 = 1:new_np] - # Clear out resolution backtrace - # TODO: save it somehow? - new_bktrc = [ResolveBacktraceItem() for new_p0 = 1:new_np] + # Reduce log pool (the other items are still reachable through rlog.init) + rlog.pool = Dict(p=>rlog.pool[p] for p in new_pkgs) # Done @@ -876,7 +1150,7 @@ function prune_graph!(graph::Graph; verbose::Bool = false) data.vdict = new_vdict # Notes: # * uuid_to_name, reqs, fixed, eq_classes are unchanged - # * pruned was updated in-place + # * pruned and rlog were updated in-place # Replace old structures with new ones graph.gadj = new_gadj @@ -887,7 +1161,6 @@ function prune_graph!(graph::Graph; verbose::Bool = false) graph.req_inds = new_req_inds graph.fix_inds = new_fix_inds graph.spp = new_spp - graph.bktrc = new_bktrc graph.np = new_np @assert check_consistency(graph) diff --git a/src/Resolve.jl b/src/Resolve.jl index ad856db077bf5..d4e7b6e46114c 100644 --- a/src/Resolve.jl +++ b/src/Resolve.jl @@ -9,7 +9,7 @@ using ..Types using ..GraphType using .MaxSum import ..Types: uuid_julia -import ..GraphType: is_current_julia +import ..GraphType: is_julia, log_event_greedysolved!, log_event_maxsumsolved! export resolve, sanity_check @@ -76,8 +76,8 @@ function sanity_check(graph::Graph, sources::Set{UUID} = Set{UUID}(); verbose::B id(p) = pkgID(p, graph) isempty(req_inds) || warn("sanity check called on a graph with non-empty requirements") - if !any(is_current_julia(graph, fp0) for fp0 in fix_inds) - warn("sanity check called on a graph without current julia requirement, adding it") + if !any(is_julia(graph, fp0) for fp0 in fix_inds) + warn("sanity check called on a graph without julia requirement, adding it") add_fixed!(graph, Dict(uuid_julia=>Fixed(VERSION))) end if length(fix_inds) ≠ 1 @@ -257,6 +257,10 @@ function greedysolver(graph::Graph) @assert verify_solution(sol, graph) + for p0 = 1:np + log_event_greedysolved!(graph, p0, sol[p0]) + end + return true, sol end @@ -297,16 +301,20 @@ function enforce_optimality!(sol::Vector{Int}, graph::Graph) gmsk = graph.gmsk gdir = graph.gdir gconstr = graph.gconstr + pkgs = graph.data.pkgs + + # keep a track for the log + why = Union{Symbol,Int}[0 for p0 = 1:np] restart = true while restart restart = false for p0 = 1:np s0 = sol[p0] - s0 == spp[p0] && continue # the package is not installed + s0 == spp[p0] && (why[p0] = :uninst; continue) # the package is not installed # check if bumping to the higher version would violate a constraint - gconstr[p0][s0+1] || continue + gconstr[p0][s0+1] || (why[p0] = :constr; continue) # check if bumping to the higher version would violate a constraint viol = false @@ -315,6 +323,7 @@ function enforce_optimality!(sol::Vector{Int}, graph::Graph) msk = gmsk[p0][j1] if !msk[s1, s0+1] viol = true + why[p0] = p1 break end end @@ -352,9 +361,14 @@ function enforce_optimality!(sol::Vector{Int}, graph::Graph) for p0 in find(uninst) sol[p0] = spp[p0] + why[p0] = :uninst end @assert verify_solution(sol, graph) + + for p0 = 1:np + log_event_maxsumsolved!(graph, p0, sol[p0], why[p0]) + end end end # module diff --git a/src/Types.jl b/src/Types.jl index 2f79b9947f005..465ea5b98e665 100644 --- a/src/Types.jl +++ b/src/Types.jl @@ -176,6 +176,15 @@ function Base.convert(::Type{VersionRange}, s::AbstractString) return VersionRange(lower, upper) end +Base.print(io::IO, r::VersionRange{0,0}) = print(io, '*') +function Base.print(io::IO, r::VersionRange{0,n}) where {n} + print(io, "0-") + join(io, r.upper.t, '.') +end +function Base.print(io::IO, r::VersionRange{m,0}) where {m} + join(io, r.lower.t, '.') + print(io, "-*") +end function Base.print(io::IO, r::VersionRange) join(io, r.lower.t, '.') if r.lower != r.upper @@ -183,7 +192,6 @@ function Base.print(io::IO, r::VersionRange) join(io, r.upper.t, '.') end end -Base.print(io::IO, ::VersionRange{0,0}) = print(io, "*") Base.show(io::IO, r::VersionRange) = print(io, "VersionRange(\"", r, "\")") Base.in(v::VersionNumber, r::VersionRange) = r.lower ≲ v ≲ r.upper diff --git a/test/resolve.jl b/test/resolve.jl index 9194cbca03a4c..c95ee23e800f4 100644 --- a/test/resolve.jl +++ b/test/resolve.jl @@ -197,6 +197,8 @@ function resolve_tst(deps_data, reqs_data, want_data = nothing) simplify_graph!(graph, verbose = VERBOSE) want = resolve(graph, verbose = VERBOSE) + # info("BACKTRACE:\n" * sprint(showbacktrace, graph)) + return want == wantuuids(want_data) end @@ -329,6 +331,7 @@ VERBOSE && info("SCHEME 5") deps_data = Any[ ["A", v"1", "B", "2-*"], ["A", v"1", "C", "2-*"], + # ["A", v"1", "julia", "10"], ["A", v"2", "B", "1"], ["A", v"2", "C", "1"], ["B", v"1", "C", "2-*"],