# This file is a part of Julia. License is MIT: https://julialang.org/license module Query import ...Pkg.PkgError using ..Types function requirements(reqs::Dict, fix::Dict, avail::Dict) bktrc = ResolveBacktrace() for (p,f) in fix bktrc[p] = ResolveBacktraceItem(:fixed, f.version) end for (p,vs) in reqs bktrcp = get!(bktrc, p) do; ResolveBacktraceItem() end push!(bktrcp, :required, vs) end for (p1,f1) in fix for p2 in keys(f1.requires) haskey(avail, p2) || haskey(fix, p2) || throw(PkgError("unknown package $p2 required by $p1")) end satisfies(p1, f1.version, reqs) || warn("$p1 is fixed at $(f1.version) conflicting with top-level requirement: $(reqs[p1])") for (p2,f2) in fix satisfies(p1, f1.version, f2.requires) || warn("$p1 is fixed at $(f1.version) conflicting with requirement for $p2: $(f2.requires[p1])") end end reqs = deepcopy(reqs) for (p,f) in fix merge_requires!(reqs, f.requires) for (rp,rvs) in f.requires bktrcp = get!(bktrc, rp) do; ResolveBacktraceItem() end push!(bktrcp, p=>bktrc[p], rvs) end end for (p,f) in fix delete!(reqs, p) end reqs, bktrc end # Specialized copy for the avail argument below because the deepcopy is slow function availcopy(avail) new_avail = similar(avail) for (pkg, vers_avail) in avail new_vers_avail = similar(vers_avail) for (version, pkg_avail) in vers_avail new_vers_avail[version] = copy(pkg_avail) end new_avail[pkg] = new_vers_avail end return new_avail end function dependencies(avail::Dict, fix::Dict = Dict{String,Fixed}("julia"=>Fixed(VERSION))) avail = availcopy(avail) conflicts = Dict{String,Set{String}}() for (fp,fx) in fix delete!(avail, fp) for (ap,av) in avail, (v,a) in copy(av) if satisfies(fp, fx.version, a.requires) delete!(a.requires, fp) else haskey(conflicts, ap) || (conflicts[ap] = Set{String}()) push!(conflicts[ap], fp) delete!(av, v) end end end again = true while again again = false deleted_pkgs = String[] for (ap,av) in avail if isempty(av) delete!(avail, ap) push!(deleted_pkgs, ap) again = true end end for dp in deleted_pkgs for (ap,av) in avail, (v,a) in copy(av) if haskey(a.requires, dp) haskey(conflicts, ap) || (conflicts[ap] = Set{String}()) union!(conflicts[ap], conflicts[dp]) delete!(av, v) end end end end avail, conflicts end function partial_update_mask(instd::Dict{String,Tuple{VersionNumber,Bool}}, avail::Dict{String,Dict{VersionNumber,Available}}, upkgs::Set{String}) dont_update = Set{String}() isempty(upkgs) && return dont_update avail_new = deepcopy(avail) for p in upkgs haskey(instd, p) || throw(PkgError("Package $p is not installed")) v = instd[p][1] if haskey(avail, p) for vn in keys(avail[p]) vn < v && delete!(avail_new[p], vn) end end end avail_new = dependencies_subset(avail_new, upkgs) for p in keys(avail) !haskey(avail_new, p) && push!(dont_update, p) end for p in keys(instd) !haskey(avail_new, p) && p ∉ upkgs && push!(dont_update, p) end return dont_update end # Try to produce some helpful message in case of a partial update which does not go all the way # (Does not do a full analysis, it only checks requirements and direct dependents.) function check_partial_updates(reqs::Requires, deps::Dict{String,Dict{VersionNumber,Available}}, want::Dict{String,VersionNumber}, fixed::Dict{String,Fixed}, upkgs::Set{String}) for p in upkgs if !haskey(want, p) if !haskey(fixed, p) warn("Something went wrong with the update of package $p, please submit a bug report") continue end v = fixed[p].version else v = want[p] if haskey(fixed, p) && v != fixed[p].version warn("Something went wrong with the update of package $p, please submit a bug report") continue end end haskey(deps, p) || continue vers = sort!(collect(keys(deps[p]))) higher_vers = vers[vers .> v] isempty(higher_vers) && continue # package p has been set to the highest available version # Determine if there are packages which depend on `p` and somehow prevent its update to # the latest version blocking_parents = Set{String}() for (p1,d1) in deps p1 in upkgs && continue # package `p1` is among the ones to be updated, skip the check haskey(fixed, p1) || continue # if package `p1` is not fixed, it can't be blocking r1 = fixed[p1].requires # get `p1` requirements haskey(r1, p) || continue # check if package `p1` requires `p` vs1 = r1[p] # get the versions of `p` allowed by `p1` requirements any(hv in vs1 for hv in higher_vers) && continue # package `p1` would allow some of the higher versions, # therefore it's not responsible for blocking `p` push!(blocking_parents, p1) # package `p1` is blocking the update of `p` end # Determine if the update of `p` is prevented by explicit user-provided requirements blocking_reqs = (haskey(reqs, p) && all(hv ∉ reqs[p] for hv in higher_vers)) # Determine if the update of `p` is prevented by it being fixed (e.g. it's dirty, or pinned...) isfixed = haskey(fixed, p) msg = "Package $p was set to version $v, but a higher version $(vers[end]) exists.\n" if isfixed msg *= " The package is fixed. You can try using `Pkg.free(\"$p\")` to update it." elseif blocking_reqs msg *= " The update is prevented by explicit requirements constraints. Edit your REQUIRE file to change this." elseif !isempty(blocking_parents) msg *= string(" To install the latest version, you could try updating these packages as well: ", join(blocking_parents, ", ", " and "), ".") else msg *= " To install the latest version, you could try doing a full update with `Pkg.update()`." end info(msg) end end const PackageState = Union{Void,VersionNumber} function diff(have::Dict, want::Dict, avail::Dict, fixed::Dict) change = Vector{Tuple{String,Tuple{PackageState,PackageState}}}(0) remove = Vector{Tuple{String,Tuple{PackageState,PackageState}}}(0) for pkg in collect(union(keys(have),keys(want))) h, w = haskey(have,pkg), haskey(want,pkg) if h && w if have[pkg] != want[pkg] push!(change, (pkg,(have[pkg], want[pkg]))) end elseif h push!(remove, (pkg,(have[pkg],nothing))) elseif w push!(change, (pkg,(nothing,want[pkg]))) end end append!(sort!(change), sort!(remove)) end function check_requirements(reqs::Requires, deps::Dict{String,Dict{VersionNumber,Available}}, fix::Dict) for (p,vs) in reqs if !any(vn->(vn in vs), keys(deps[p])) remaining_vs = VersionSet() err_msg = "fixed packages introduce conflicting requirements for $p: \n" available_list = sort!(collect(keys(deps[p]))) for (p1,f1) in fix f1r = f1.requires haskey(f1r, p) || continue err_msg *= " $p1 requires versions $(f1r[p])" if !any([vn in f1r[p] for vn in available_list]) err_msg *= " [none of the available versions can satisfy this requirement]" end err_msg *= "\n" remaining_vs = intersect(remaining_vs, f1r[p]) end if isempty(remaining_vs) err_msg *= " the requirements are unsatisfiable because their intersection is empty" else err_msg *= " available versions are $(join(available_list, ", ", " and "))" end throw(PkgError(err_msg)) end end end # If there are explicitly required packages, dicards all versions outside # the allowed range. # It also propagates requirements: when all allowed versions of a required package # require some other package, this creates a new implicit requirement. # The propagation is tracked so that in case a contradiction is detected the error # message allows to determine the cause. # This is a pre-pruning step, so it also creates some structures which are later used by pruning function filter_versions(reqs::Requires, deps::Dict{String,Dict{VersionNumber,Available}}, bktrc::ResolveBacktrace) allowed = Dict{String,Dict{VersionNumber,Bool}}() staged = copy(reqs) while !isempty(staged) staged_next = Requires() for (p,vs) in staged # Parse requirements and store allowed versions. depsp = deps[p] if !haskey(allowed, p) allowedp = Dict{VersionNumber,Bool}(vn=>true for vn in keys(depsp)) allowed[p] = allowedp seen = false else allowedp = allowed[p] oldallowedp = copy(allowedp) seen = true end for vn in keys(depsp) allowedp[vn] &= vn ∈ vs end @assert !isempty(allowedp) if !any(values(allowedp)) err_msg = "Unsatisfiable requirements detected for package $p:\n" err_msg *= string(bktrc[p]) err_msg *= """The intersection of the requirements is $(bktrc[p].versionreq). None of the available versions can satisfy this requirement.""" throw(PkgError(err_msg)) end # If we've seen this package already and nothing has changed since # the last time, we stop here. seen && allowedp == oldallowedp && continue # Propagate requirements: # if all allowed versions of a required package require some other package, # then compute the union of the allowed versions for that other package, and # treat that as a new requirement. # Start by filtering out the non-allowed versions fdepsp = Dict{VersionNumber,Available}(vn=>depsp[vn] for vn in keys(depsp) if allowedp[vn]) # Collect all required packages isreq = Dict{String,Bool}(rp=>true for a in values(fdepsp) for rp in keys(a.requires)) # Compute whether a required package appears in all requirements for a in values(fdepsp), rp in keys(isreq) haskey(a.requires, rp) || (isreq[rp] = false) end staged_new = Set{String}() for a in values(fdepsp) for (rp,rvs) in a.requires # Skip packages that may not be required isreq[rp] || continue # Compute the union of the version sets snvs = get!(staged_next, rp, copy(rvs)) union!(snvs, rvs) push!(staged_new, rp) end end for rp in staged_new srvs = staged_next[rp] isreq[rp] || continue bktrcp = get!(bktrc, rp) do; ResolveBacktraceItem(); end push!(bktrcp, p=>bktrc[p], srvs) if isa(bktrcp.versionreq, VersionSet) && isempty(bktrcp.versionreq) err_msg = "Unsatisfiable requirements detected for package $rp:\n" err_msg *= string(bktrcp) err_msg *= "The intersection of the requirements is empty." throw(PkgError(err_msg)) end end end staged = staged_next end filtered_deps = Dict{String,Dict{VersionNumber,Available}}() for (p,depsp) in deps filtered_deps[p] = Dict{VersionNumber,Available}() allowedp = get(allowed, p, Dict{VersionNumber,Bool}()) fdepsp = filtered_deps[p] for (vn,a) in depsp get(allowedp, vn, true) || continue fdepsp[vn] = a end end return filtered_deps, allowed end # Reduce the number of versions by creating equivalence classes, and retaining # only the highest version for each equivalence class. # Two versions are equivalent if: # 1) They appear together as dependecies of another package (i.e. for each # dependency relation, they are both required or both not required) # 2) They have the same dependencies # Preliminarily calls filter_versions. function prune_versions(reqs::Requires, deps::Dict{String,Dict{VersionNumber,Available}}, bktrc::ResolveBacktrace) filtered_deps, allowed = filter_versions(reqs, deps, bktrc) # To each version in each package, we associate a BitVector. # It is going to hold a pattern such that all versions with # the same pattern are equivalent. vmask = Dict{String,Dict{VersionNumber, BitVector}}() # For each package, we examine the dependencies of its versions # and put together those which are equal. # While we're at it, we also collect all dependencies into alldeps alldeps = Dict{String,Set{VersionSet}}() for (p,fdepsp) in filtered_deps # Extract unique dependencies lists (aka classes), thereby # assigning an index to each class. uniqdepssets = unique(a.requires for a in values(fdepsp)) # Store all dependencies seen so far for later use for r in uniqdepssets, (rp,rvs) in r get!(alldeps, rp) do; Set{VersionSet}() end push!(alldeps[rp], rvs) end # If the package has just one version, it's uninteresting length(deps[p]) == 1 && continue # Grow the pattern by the number of classes luds = length(uniqdepssets) @assert !haskey(vmask, p) vmask[p] = Dict{VersionNumber,BitVector}() vmaskp = vmask[p] for vn in keys(fdepsp) vmaskp[vn] = falses(luds) end for (vn,a) in fdepsp vmind = findfirst(uniqdepssets, a.requires) @assert vmind > 0 vm = vmaskp[vn] vm[vmind] = true end end # Produce dependency patterns. for (p,vss) in alldeps, vs in vss # packages with just one version, or dependencies # which do not distiguish between versions, are not # interesting (length(deps[p]) == 1 || vs == VersionSet()) && continue # Store the dependency info in the patterns @assert haskey(vmask, p) for (vn,vm) in vmask[p] push!(vm, vn in vs) end end # At this point, the vmask patterns are computed. We divide them into # classes so that we can keep just one version for each class. pruned_vers = Dict{String,Vector{VersionNumber}}() eq_classes = Dict{String,Dict{VersionNumber,Vector{VersionNumber}}}() for (p, vmaskp) in vmask vmask0_uniq = unique(values(vmaskp)) nc = length(vmask0_uniq) classes = [VersionNumber[] for c0 = 1:nc] for (vn,vm) in vmaskp c0 = findfirst(vmask0_uniq, vm) push!(classes[c0], vn) end map(sort!, classes) # For each nonempty class, we store only the highest version) pruned_vers[p] = VersionNumber[] prunedp = pruned_vers[p] eq_classes[p] = Dict{VersionNumber,Vector{VersionNumber}}() eqclassp = eq_classes[p] for cl in classes if !isempty(cl) vtop = maximum(cl) push!(prunedp, vtop) @assert !haskey(eqclassp, vtop) eqclassp[vtop] = cl end end sort!(prunedp) end # Put non-allowed versions into eq_classes for (p, allowedp) in allowed haskey(eq_classes, p) || continue eqclassp = eq_classes[p] for (vn, a) in allowedp a && continue eqclassp[vn] = [vn] end end # Put all remaining packages into eq_classes for (p, depsp) in deps haskey(eq_classes, p) && continue eq_classes[p] = Dict{VersionNumber,Vector{VersionNumber}}() eqclassp = eq_classes[p] for vn in keys(depsp) eqclassp[vn] = [vn] end end # Recompute deps. We could simplify them, but it's not worth it new_deps = Dict{String,Dict{VersionNumber,Available}}() for (p,depsp) in deps @assert !haskey(new_deps, p) if !haskey(pruned_vers, p) new_deps[p] = depsp continue end new_deps[p] = Dict{VersionNumber,Available}() pruned_versp = pruned_vers[p] for (vn,a) in depsp vn ∈ pruned_versp || continue new_deps[p][vn] = a end end #println("pruning stats:") #numvers = 0 #numdeps = 0 #for (p,d) in deps, (vn,a) in d # numvers += 1 # for r in a.requires # numdeps += 1 # end #end #numnewvers = 0 #numnewdeps = 0 #for (p,d) in new_deps, (vn,a) in d # numnewvers += 1 # for r in a.requires # numnewdeps += 1 # end #end #println(" before: vers=$numvers deps=$numdeps") #println(" after: vers=$numnewvers deps=$numnewdeps") #println() return new_deps, eq_classes end prune_versions(deps::Dict{String,Dict{VersionNumber,Available}}) = prune_versions(Dict{String,VersionSet}(), deps, ResolveBacktrace()) prune_versions(deps::Dict{String,Dict{VersionNumber,Available}}, bktrc::ResolveBacktrace) = prune_versions(Dict{String,VersionSet}(), deps, bktrc) # Build a graph restricted to a subset of the packages function subdeps(deps::Dict{String,Dict{VersionNumber,Available}}, pkgs::Set{String}) sub_deps = Dict{String,Dict{VersionNumber,Available}}() for p in pkgs haskey(sub_deps, p) || (sub_deps[p] = Dict{VersionNumber,Available}()) sub_depsp = sub_deps[p] for (vn,a) in deps[p] sub_depsp[vn] = a end end return sub_deps end # Build a subgraph incuding only the (direct and indirect) dependencies # of a given package set function dependencies_subset(deps::Dict{String,Dict{VersionNumber,Available}}, pkgs::Set{String}) staged::Set{String} = filter(p->p in keys(deps), pkgs) allpkgs = copy(staged) while !isempty(staged) staged_next = Set{String}() for p in staged, a in values(get(deps, p, Dict{VersionNumber,Available}())), rp in keys(a.requires) rp ∉ allpkgs && rp ≠ "julia" && push!(staged_next, rp) end union!(allpkgs, staged_next) staged = staged_next end return subdeps(deps, allpkgs) end # Build a subgraph incuding only the (direct and indirect) dependencies and dependants # of a given package set function undirected_dependencies_subset(deps::Dict{String,Dict{VersionNumber,Available}}, pkgs::Set{String}) graph = Dict{String, Set{String}}() for (p,d) in deps haskey(graph, p) || (graph[p] = Set{String}()) for a in values(d), rp in keys(a.requires) push!(graph[p], rp) haskey(graph, rp) || (graph[rp] = Set{String}()) push!(graph[rp], p) end end staged = pkgs allpkgs = copy(pkgs) while !isempty(staged) staged_next = Set{String}() for p in staged, rp in graph[p] rp ∉ allpkgs && push!(staged_next, rp) end union!(allpkgs, staged_next) staged = staged_next end return subdeps(deps, allpkgs) end function prune_dependencies(reqs::Requires, deps::Dict{String,Dict{VersionNumber,Available}}) bktrc = ResolveBacktrace() for (p,vs) in reqs bktrc[p] = ResolveBacktraceItem(:required, vs) end return prune_dependencies(reqs, deps, bktrc) end function prune_dependencies(reqs::Requires, deps::Dict{String,Dict{VersionNumber,Available}}, bktrc::ResolveBacktrace) deps = dependencies_subset(deps, Set{String}(keys(reqs))) deps, _ = prune_versions(reqs, deps, bktrc) return deps end end # module