Skip to content

Commit 9f30b81

Browse files
authored
Merge pull request #2720 from JuliaLang/backports-release-1.7
Backports for 1.7
2 parents df2476a + c749ca9 commit 9f30b81

File tree

8 files changed

+1250
-58
lines changed

8 files changed

+1250
-58
lines changed

src/API.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1435,7 +1435,9 @@ function instantiate(ctx::Context; manifest::Union{Bool, Nothing}=nothing,
14351435
return instantiate(Context(); manifest=manifest, update_registry=update_registry, verbose=verbose, kwargs...)
14361436
end
14371437
if (!isfile(ctx.env.manifest_file) && manifest === nothing) || manifest == false
1438-
up(ctx; update_registry=update_registry)
1438+
# given no manifest exists, only allow invoking a registry update if there are project deps
1439+
allow_registry_update = isfile(ctx.env.project_file) && !isempty(ctx.env.project.deps)
1440+
up(ctx; update_registry = update_registry && allow_registry_update)
14391441
return
14401442
end
14411443
if !isfile(ctx.env.manifest_file) && manifest == true

src/REPLMode/REPLMode.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,7 @@ function try_prompt_pkg_add(pkgs::Vector{Symbol})
692692
println(ctx.io)
693693
return false
694694
end
695+
resp = strip(resp)
695696
if lowercase(resp) in ["y", "yes"]
696697
API.add(string.(available_pkgs))
697698
if length(available_pkgs) < length(pkgs)

src/Resolve/Resolve.jl

Lines changed: 172 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -341,57 +341,18 @@ function verify_solution(sol::Vector{Int}, graph::Graph)
341341
return true
342342
end
343343

344+
344345
"""
345-
Push the given solution to a local optimium if needed: keeps increasing
346-
the states of the given solution as long as no constraints are violated.
347-
It also removes unnecessary parts of the solution which are unconnected
348-
to the required packages.
346+
Uninstall unreachable packages:
347+
start from the required ones and keep only the packages reachable from them along the graph.
349348
"""
350-
function enforce_optimality!(sol::Vector{Int}, graph::Graph)
349+
function _uninstall_unreachable!(sol::Vector{Int}, why::Vector{Union{Symbol,Int}}, graph::Graph)
351350
np = graph.np
352351
spp = graph.spp
353352
gadj = graph.gadj
354353
gmsk = graph.gmsk
355354
gconstr = graph.gconstr
356-
pkgs = graph.data.pkgs
357355

358-
# keep a track for the log
359-
why = Union{Symbol,Int}[0 for p0 = 1:np]
360-
361-
restart = true
362-
while restart
363-
restart = false
364-
for p0 = 1:np
365-
s0 = sol[p0]
366-
s0 == spp[p0] && (why[p0] = :uninst; continue) # the package is not installed
367-
368-
# check if bumping to the higher version would violate a constraint
369-
gconstr[p0][s0+1] || (why[p0] = :constr; continue)
370-
371-
# check if bumping to the higher version would violate a constraint
372-
viol = false
373-
for (j1,p1) in enumerate(gadj[p0])
374-
s1 = sol[p1]
375-
msk = gmsk[p0][j1]
376-
if !msk[s1, s0+1]
377-
viol = true
378-
why[p0] = p1
379-
break
380-
end
381-
end
382-
viol && continue
383-
384-
# So the solution is non-optimal: we bump it manually
385-
sol[p0] += 1
386-
restart = true
387-
end
388-
end
389-
390-
# Finally uninstall unneeded packages:
391-
# start from the required ones and keep only
392-
# the packages reachable from them along the graph.
393-
# (These should have been removed in the previous step, but in principle
394-
# an unconnected yet self-sustaining cycle may have survived.)
395356
uninst = trues(np)
396357
staged = Set{Int}(p0 for p0 = 1:np if !gconstr[p0][end])
397358
seen = copy(staged)
@@ -400,7 +361,7 @@ function enforce_optimality!(sol::Vector{Int}, graph::Graph)
400361
staged_next = Set{Int}()
401362
for p0 in staged
402363
s0 = sol[p0]
403-
@assert s0 < spp[p0]
364+
s0 == spp[p0] && continue
404365
uninst[p0] = false
405366
for (j1,p1) in enumerate(gadj[p0])
406367
gmsk[p0][j1][end,s0] && continue # the package is not required by p0 at version s0
@@ -415,6 +376,173 @@ function enforce_optimality!(sol::Vector{Int}, graph::Graph)
415376
sol[p0] = spp[p0]
416377
why[p0] = :uninst
417378
end
379+
end
380+
381+
"""
382+
Push the given solution to a local optimum if needed: keeps increasing
383+
the states of the given solution as long as no constraints are violated.
384+
It might also install additional packages, if needed to bump the ones already
385+
installed.
386+
It also removes unnecessary parts of the solution which are unconnected
387+
to the required packages.
388+
"""
389+
function enforce_optimality!(sol::Vector{Int}, graph::Graph)
390+
np = graph.np
391+
spp = graph.spp
392+
gadj = graph.gadj
393+
gmsk = graph.gmsk
394+
gconstr = graph.gconstr
395+
pkgs = graph.data.pkgs
396+
397+
# keep a track for the log
398+
why = Union{Symbol,Int}[0 for p0 = 1:np]
399+
400+
# Strategy:
401+
# There's a cycle in which first the unnecessary (unconnected) packages are removed,
402+
# then we make a pass over the whole packages trying to bump each of them.
403+
# We repeat the above two steps until no further action is allowed.
404+
# When attempting to bump a package, we may attempt to bump or install other packages
405+
# if needed. Except if the bump would uninstall a package, in which cases we don't
406+
# touch anything else: we do it only if it has no consequence at all. This strategy
407+
# favors installing packages as needed.
408+
# During the bumping pass, we keep an upper and lower bound for each package, which
409+
# progressively shrink. These are used when adjusting for the effect of an attempted bump.
410+
# The way it's written should ensure that no package is ever downgraded (unless it was
411+
# originally unneeded, and then got removed, and later reinstalled to a lower version as
412+
# a consequence of a bump of some other package).
413+
414+
# move_up is used to keep track of which packages can move up
415+
# (they start installed and can be bumped) and which down (they start uninstalled and
416+
# can be installed)
417+
move_up = BitVector(undef, length(sol))
418+
# lower and upper bounds on the valid range of each package
419+
upperbound = similar(spp)
420+
lowerbound = similar(spp)
421+
# backup space for restoring the state if an attempted bump fails
422+
bk_sol = similar(sol)
423+
bk_lowerbound = similar(lowerbound)
424+
bk_upperbound = similar(upperbound)
425+
426+
# auxiliary sets to perform breadth-first search on the graph
427+
seen = Set{Int}()
428+
staged = Set{Int}()
429+
staged_next = Set{Int}()
430+
431+
old_sol = similar(sol) # to detect if we made any changes
432+
allsols = Set{Vector{Int}}() # used to make 100% sure we avoid infinite loops
433+
434+
while true
435+
copy!(old_sol, sol)
436+
push!(allsols, copy(sol))
437+
438+
# step 1: uninstall unneded packages
439+
_uninstall_unreachable!(sol, why, graph)
440+
441+
# setp 2: try to bump each installed package in turn
442+
443+
move_up .= sol .≠ spp
444+
copy!(upperbound, spp)
445+
let move_up = move_up
446+
lowerbound .= [move_up[p0] ? sol[p0] : 1 for p0 = 1:np]
447+
end
448+
449+
for p0 = 1:np
450+
s0 = sol[p0]
451+
s0 == spp[p0] && (why[p0] = :uninst; continue) # the package is not installed
452+
move_up[p0] || continue # the package is only installed as a result of a previous bump, skip it
453+
454+
@assert upperbound[p0] == spp[p0]
455+
456+
# pick the next version that doesn't violate a constraint (if any)
457+
bump_range = collect(s0+1:spp[p0])
458+
bump = let gconstr = gconstr
459+
findfirst(v0->gconstr[p0][v0], bump_range)
460+
end
461+
462+
# no such version was found, skip this package
463+
bump nothing && (why[p0] = :constr; continue)
464+
465+
# assume that we will succeed in bumping the version (otherwise we
466+
# roll-back at the end)
467+
468+
new_s0 = bump_range[bump]
469+
try_uninstall = new_s0 == spp[p0] # are we trying to uninstall a package?
470+
471+
copy!(bk_sol, sol)
472+
copy!(bk_lowerbound, lowerbound)
473+
copy!(bk_upperbound, upperbound)
474+
sol[p0] = new_s0
475+
476+
# if we're trying to uninstall, the bump is "soft": we don't update the
477+
# lower bound so that the package can be reinstalled later in the pass
478+
# if needed by another package
479+
try_uninstall || (lowerbound[p0] = new_s0) # note that we're in the move_up case
480+
481+
empty!(seen)
482+
empty!(staged)
483+
empty!(staged_next)
484+
push!(staged, p0)
485+
486+
while !isempty(staged)
487+
for f0 in staged
488+
for (j1,f1) in enumerate(gadj[f0])
489+
f1 == p0 && continue
490+
f1 seen && continue
491+
s1 = sol[f1]
492+
msk = gmsk[f0][j1]
493+
if try_uninstall
494+
bump_range = [s1] # when uninstalling, no further changes are allowed
495+
else
496+
lb1 = lowerbound[f1]
497+
ub1 = upperbound[f1]
498+
@assert lb1 s1 ub1
499+
if move_up[f1]
500+
s1 > lb1 && @assert s1 == spp[f1]
501+
# the arrangement of the range gives precedence to improving the
502+
# current situation, but allows reinstalling a package if needed
503+
bump_range = vcat(s1:ub1, s1-1:-1:lb1)
504+
else
505+
bump_range = collect(ub1:-1:lb1)
506+
end
507+
end
508+
bump = let gconstr = gconstr
509+
findfirst(v1->(gconstr[f1][v1] && msk[v1, sol[f0]]), bump_range)
510+
end
511+
if bump nothing
512+
why[p0] = f1 # TODO: improve this? (ideally we might want the path from p0 to f1)
513+
@goto abort
514+
end
515+
new_s1 = bump_range[bump]
516+
sol[f1] = new_s1
517+
new_s1 == s1 && continue
518+
push!(staged_next, f1)
519+
if move_up[f1]
520+
lowerbound[f1] = new_s1
521+
else
522+
upperbound[f1] = new_s1
523+
end
524+
end
525+
end
526+
union!(seen, staged)
527+
staged, staged_next = staged_next, staged
528+
empty!(staged_next)
529+
end
530+
531+
# if we're here the bump was successful, there's nothing more to do
532+
continue
533+
534+
## abort the bumping: restore the solution
535+
@label abort
536+
537+
copy!(sol, bk_sol)
538+
copy!(lowerbound, bk_lowerbound)
539+
copy!(upperbound, bk_upperbound)
540+
end
541+
sol old_sol || break
542+
# It might be possible in principle to contrive a situation in which
543+
# the solutions oscillate
544+
sol allsols && break
545+
end
418546

419547
@assert verify_solution(sol, graph)
420548

src/Resolve/graphtype.jl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ mutable struct ResolveLog
4444
pool::Dict{UUID,ResolveLogEntry}
4545

4646
# journal: record all messages in order (shared between all entries)
47-
journal::Vector{Tuple{UUID,String}}
47+
journal::ResolveJournal
4848

4949
# exact: keeps track of whether the resolve process is still exact, or
5050
# heuristics have been employed
@@ -1019,6 +1019,8 @@ function propagate_constraints!(graph::Graph, sources::Set{Int} = Set{Int}(); lo
10191019
Set{Int}(p0 for p0 = 1:np if !gconstr[p0][end]) :
10201020
sources
10211021

1022+
seen = copy(staged)
1023+
10221024
while !isempty(staged)
10231025
staged_next = Set{Int}()
10241026
for p0 in staged
@@ -1048,6 +1050,8 @@ function propagate_constraints!(graph::Graph, sources::Set{Int} = Set{Int}(); lo
10481050
if gconstr1 old_gconstr1
10491051
push!(staged_next, p1)
10501052
log_events && log_event_implicit_req!(graph, p1, added_constr1, p0)
1053+
elseif p1 seen
1054+
push!(staged_next, p1)
10511055
end
10521056
if !any(gconstr1)
10531057
if exact
@@ -1060,6 +1064,7 @@ function propagate_constraints!(graph::Graph, sources::Set{Int} = Set{Int}(); lo
10601064
end
10611065
end
10621066
end
1067+
union!(seen, staged_next)
10631068
staged = staged_next
10641069
end
10651070
return graph

src/Types.jl

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -464,15 +464,50 @@ function write_env_usage(source_file::AbstractString, usage_filepath::AbstractSt
464464
# Ensure that log dir exists
465465
!ispath(logdir()) && mkpath(logdir())
466466

467-
# Generate entire entry as a string first
468-
entry = sprint() do io
469-
TOML.print(io, Dict(source_file => [Dict("time" => now())]))
470-
end
471-
472-
# Append entry to log file in one chunk
473467
usage_file = joinpath(logdir(), usage_filepath)
474-
open(usage_file, append=true) do io
475-
write(io, entry)
468+
timestamp = now()
469+
470+
## Atomically write usage file
471+
while true
472+
# read existing usage file
473+
usage = if isfile(usage_file)
474+
TOML.parsefile(usage_file)
475+
else
476+
Dict{String, Any}()
477+
end
478+
479+
# record new usage
480+
usage[source_file] = [Dict("time" => timestamp)]
481+
482+
# keep only latest usage info
483+
for k in keys(usage)
484+
times = map(d -> Dates.DateTime(d["time"]), usage[k])
485+
usage[k] = [Dict("time" => maximum(times))]
486+
end
487+
488+
# Write to a temp file in the same directory as the destination
489+
temp_usage_file = tempname(logdir())
490+
open(temp_usage_file, "w") do io
491+
TOML.print(io, usage, sorted=true)
492+
end
493+
494+
# Move the temp file into place, replacing the original
495+
mv(temp_usage_file, usage_file, force = true)
496+
497+
# Check that the new file has what we want in it
498+
new_usage = if isfile(usage_file)
499+
TOML.parsefile(usage_file)
500+
else
501+
Dict{String, Any}()
502+
end
503+
if haskey(new_usage, source_file)
504+
for e in new_usage[source_file]
505+
if Dates.DateTime(e["time"]) >= timestamp
506+
return
507+
end
508+
end
509+
end
510+
# If not, try again
476511
end
477512
end
478513

test/resolve.jl

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,11 +407,19 @@ end
407407

408408
@test sanity_tst(ResolveData.deps_data, ResolveData.problematic_data)
409409
@test resolve_tst(ResolveData.deps_data, ResolveData.reqs_data, ResolveData.want_data)
410+
411+
## DEPENDENCY SCHEME 12: A LARGER, MORE DIFFICULT REALISTIC EXAMPLE
412+
## ref Pkg.jl issue #1949
413+
414+
include("resolvedata2.jl")
415+
416+
@test sanity_tst(ResolveData2.deps_data, ResolveData2.problematic_data)
417+
@test resolve_tst(ResolveData2.deps_data, ResolveData2.reqs_data, ResolveData2.want_data)
410418
end
411419

412420
@testset "nasty" begin
413421
VERBOSE && @info("SCHEME NASTY")
414-
## DEPENDENCY SCHEME 12: A NASTY CASE
422+
## DEPENDENCY SCHEME 13: A NASTY CASE
415423

416424
include("NastyGenerator.jl")
417425
deps_data, reqs_data, want_data, problematic_data = NastyGenerator.generate_nasty(5, 20, q=20, d=4, sat = true)

0 commit comments

Comments
 (0)