Skip to content

Commit 9490940

Browse files
committed
add adown feature
1 parent b6e4785 commit 9490940

File tree

9 files changed

+138
-34
lines changed

9 files changed

+138
-34
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Pkg v1.13 Release Notes
1818
improving efficiency when performing several environment changes. ([#4262])
1919
- Added `Pkg.autoprecompilation_enabled(state::Bool)` to globally enable or disable automatic precompilation for Pkg
2020
operations. ([#4262])
21+
- Added `Pkg.downgrade`/`pkg> down` command to resolve packages to their lowest compatible versions ([#XXXX])
2122
- Implemented atomic TOML writes to prevent data corruption when Pkg operations are interrupted or multiple processes
2223
write simultaneously. All TOML files are now written atomically using temporary files and atomic moves. ([#4293])
2324
- Implemented lazy loading for RegistryInstance to significantly improve startup performance for operations that don't

src/API.jl

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ function check_readonly(ctx::Context)
154154
end
155155

156156
# Provide some convenience calls
157-
for f in (:develop, :add, :rm, :up, :pin, :free, :test, :build, :status, :why, :precompile)
157+
for f in (:develop, :add, :rm, :up, :down, :pin, :free, :test, :build, :status, :why, :precompile)
158158
@eval begin
159159
$f(pkg::Union{AbstractString, PackageSpec}; kwargs...) = $f([pkg]; kwargs...)
160160
$f(pkgs::Vector{<:AbstractString}; kwargs...) = $f([PackageSpec(pkg) for pkg in pkgs]; kwargs...)
@@ -170,8 +170,8 @@ for f in (:develop, :add, :rm, :up, :pin, :free, :test, :build, :status, :why, :
170170
pkgs = deepcopy(pkgs) # don't mutate input
171171
foreach(handle_package_input!, pkgs)
172172
ret = $f(ctx, pkgs; kwargs...)
173-
$(f in (:up, :pin, :free, :build)) && Pkg._auto_precompile(ctx)
174-
$(f in (:up, :pin, :free, :rm)) && Pkg._auto_gc(ctx)
173+
$(f in (:up, :down, :pin, :free, :build)) && Pkg._auto_precompile(ctx)
174+
$(f in (:up, :down, :pin, :free, :rm)) && Pkg._auto_gc(ctx)
175175
return ret
176176
end
177177
$f(ctx::Context; kwargs...) = $f(ctx, PackageSpec[]; kwargs...)
@@ -181,7 +181,7 @@ for f in (:develop, :add, :rm, :up, :pin, :free, :test, :build, :status, :why, :
181181
url = nothing, rev = nothing, path = nothing, mode = PKGMODE_PROJECT, subdir = nothing, kwargs...
182182
)
183183
pkg = PackageSpec(; name, uuid, version, url, rev, path, subdir)
184-
if $f === status || $f === rm || $f === up
184+
if $f === status || $f === rm || $f === up || $f === down
185185
kwargs = merge((; kwargs...), (:mode => mode,))
186186
end
187187
# Handle $f() case
@@ -436,13 +436,16 @@ function up(
436436
preserve::Union{Nothing, PreserveLevel} = isempty(pkgs) ? nothing : PRESERVE_ALL,
437437
update_registry::Bool = true,
438438
skip_writing_project::Bool = false,
439+
downgrade::Bool = false,
439440
kwargs...
440441
)
441442
Context!(ctx; kwargs...)
442443
Operations.ensure_manifest_registries!(ctx)
443444
check_readonly(ctx)
444445
if Operations.is_fully_pinned(ctx)
445-
printpkgstyle(ctx.io, :Update, "All dependencies are pinned - nothing to update.", color = Base.info_color())
446+
msg = downgrade ? "All dependencies are pinned - nothing to downgrade." : "All dependencies are pinned - nothing to update."
447+
action = downgrade ? :Downgrade : :Update
448+
printpkgstyle(ctx.io, action, msg, color = Base.info_color())
446449
return
447450
end
448451
if update_registry
@@ -462,10 +465,22 @@ function up(
462465
for pkg in pkgs
463466
update_source_if_set(ctx.env, pkg)
464467
end
465-
Operations.up(ctx, pkgs, level; skip_writing_project, preserve)
468+
Operations.up(ctx, pkgs, level; skip_writing_project, preserve, downgrade)
466469
return
467470
end
468471

472+
function down(
473+
ctx::Context, pkgs::Vector{PackageSpec};
474+
level::UpgradeLevel = UPLEVEL_MAJOR, mode::PackageMode = PKGMODE_PROJECT,
475+
preserve::Union{Nothing, PreserveLevel} = isempty(pkgs) ? nothing : PRESERVE_ALL,
476+
update_registry::Bool = true,
477+
skip_writing_project::Bool = false,
478+
kwargs...
479+
)
480+
# down is just up with downgrade=true
481+
return up(ctx, pkgs; level, mode, preserve, update_registry, skip_writing_project, downgrade=true, kwargs...)
482+
end
483+
469484
resolve(; io::IO = stderr_f(), kwargs...) = resolve(Context(; io); kwargs...)
470485
function resolve(ctx::Context; skip_writing_project::Bool = false, kwargs...)
471486
up(ctx; level = UPLEVEL_FIXED, mode = PKGMODE_MANIFEST, update_registry = false, skip_writing_project, kwargs...)

src/Operations.jl

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -695,7 +695,7 @@ end
695695
# all versioned packages should have a `tree_hash`
696696
function resolve_versions!(
697697
env::EnvCache, registries::Vector{Registry.RegistryInstance}, pkgs::Vector{PackageSpec}, julia_version,
698-
installed_only::Bool
698+
installed_only::Bool, downgrade::Bool = false
699699
)
700700
installed_only = installed_only || OFFLINE_MODE[]
701701

@@ -763,7 +763,7 @@ function resolve_versions!(
763763
# happened on a different julia version / commit and the stdlib version in the manifest is not the current stdlib version
764764
unbind_stdlibs = julia_version === VERSION
765765
reqs = Resolve.Requires(pkg.uuid => is_stdlib(pkg.uuid) && unbind_stdlibs ? VersionSpec("*") : VersionSpec(pkg.version) for pkg in pkgs)
766-
graph, compat_map = deps_graph(env, registries, names, reqs, fixed, julia_version, installed_only)
766+
graph, compat_map = deps_graph(env, registries, names, reqs, fixed, julia_version, installed_only, downgrade)
767767
Resolve.simplify_graph!(graph)
768768
vers = Resolve.resolve(graph)
769769

@@ -835,7 +835,7 @@ const PKGORIGIN_HAVE_VERSION = :version in fieldnames(Base.PkgOrigin)
835835
function deps_graph(
836836
env::EnvCache, registries::Vector{Registry.RegistryInstance}, uuid_to_name::Dict{UUID, String},
837837
reqs::Resolve.Requires, fixed::Dict{UUID, Resolve.Fixed}, julia_version,
838-
installed_only::Bool
838+
installed_only::Bool, downgrade::Bool = false
839839
)
840840
uuids = Set{UUID}()
841841
union!(uuids, keys(reqs))
@@ -981,7 +981,7 @@ function deps_graph(
981981
fixed = fixed_filtered
982982
end
983983

984-
return Resolve.Graph(all_compat, weak_compat, uuid_to_name, reqs, fixed, false, julia_version),
984+
return Resolve.Graph(all_compat, weak_compat, uuid_to_name, reqs, fixed, false, julia_version, downgrade),
985985
all_compat
986986
end
987987

@@ -2324,18 +2324,17 @@ function load_manifest_deps_up(
23242324
return pkgs
23252325
end
23262326

2327-
function targeted_resolve_up(env::EnvCache, registries::Vector{Registry.RegistryInstance}, pkgs::Vector{PackageSpec}, preserve::PreserveLevel, julia_version)
2327+
function targeted_resolve_up(env::EnvCache, registries::Vector{Registry.RegistryInstance}, pkgs::Vector{PackageSpec}, preserve::PreserveLevel, julia_version, downgrade::Bool = false)
23282328
pkgs = load_manifest_deps_up(env, pkgs; preserve = preserve)
23292329
check_registered(registries, pkgs)
2330-
deps_map = resolve_versions!(env, registries, pkgs, julia_version, preserve == PRESERVE_ALL_INSTALLED)
2330+
deps_map = resolve_versions!(env, registries, pkgs, julia_version, preserve == PRESERVE_ALL_INSTALLED, downgrade)
23312331
return pkgs, deps_map
23322332
end
23332333

23342334
function up(
23352335
ctx::Context, pkgs::Vector{PackageSpec}, level::UpgradeLevel;
2336-
skip_writing_project::Bool = false, preserve::Union{Nothing, PreserveLevel} = nothing
2336+
skip_writing_project::Bool = false, preserve::Union{Nothing, PreserveLevel} = nothing, downgrade::Bool = false
23372337
)
2338-
23392338
requested_pkgs = pkgs
23402339

23412340
new_git = Set{UUID}()
@@ -2353,11 +2352,11 @@ function up(
23532352
up_load_manifest_info!(pkg, entry)
23542353
end
23552354
if preserve !== nothing
2356-
pkgs, deps_map = targeted_resolve_up(ctx.env, ctx.registries, pkgs, preserve, ctx.julia_version)
2355+
pkgs, deps_map = targeted_resolve_up(ctx.env, ctx.registries, pkgs, preserve, ctx.julia_version, downgrade)
23572356
else
23582357
pkgs = load_direct_deps(ctx.env, pkgs; preserve = (level == UPLEVEL_FIXED ? PRESERVE_NONE : PRESERVE_DIRECT))
23592358
check_registered(ctx.registries, pkgs)
2360-
deps_map = resolve_versions!(ctx.env, ctx.registries, pkgs, ctx.julia_version, false)
2359+
deps_map = resolve_versions!(ctx.env, ctx.registries, pkgs, ctx.julia_version, false, downgrade)
23612360
end
23622361
update_manifest!(ctx.env, pkgs, deps_map, ctx.julia_version, ctx.registries)
23632362
new_apply = download_source(ctx)
@@ -2395,6 +2394,13 @@ function up(
23952394
return build_versions(ctx, union(new_apply, new_git))
23962395
end
23972396

2397+
function down(
2398+
ctx::Context, pkgs::Vector{PackageSpec}, level::UpgradeLevel;
2399+
skip_writing_project::Bool = false, preserve::Union{Nothing, PreserveLevel} = nothing
2400+
)
2401+
return up(ctx, pkgs, level; skip_writing_project, preserve, downgrade=true)
2402+
end
2403+
23982404
function update_package_pin!(registries::Vector{Registry.RegistryInstance}, pkg::PackageSpec, entry::Union{Nothing, PackageEntry})
23992405
if entry === nothing
24002406
cmd = Pkg.in_repl_mode() ? "pkg> resolve" : "Pkg.resolve()"

src/Pkg.jl

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export UpgradeLevel, UPLEVEL_MAJOR, UPLEVEL_MINOR, UPLEVEL_PATCH
2222
export PreserveLevel, PRESERVE_TIERED_INSTALLED, PRESERVE_TIERED, PRESERVE_ALL_INSTALLED, PRESERVE_ALL, PRESERVE_DIRECT, PRESERVE_SEMVER, PRESERVE_NONE
2323
export Registry, RegistrySpec
2424

25-
public activate, add, build, compat, develop, free, gc, generate, instantiate,
25+
public activate, add, build, compat, develop, downgrade, free, gc, generate, instantiate,
2626
pin, precompile, readonly, redo, rm, resolve, status, test, undo, update, why
2727

2828
depots() = Base.DEPOT_PATH
@@ -353,6 +353,15 @@ See also [`PackageSpec`](@ref), [`PackageMode`](@ref), [`UpgradeLevel`](@ref).
353353
"""
354354
const update = API.up
355355

356+
"""
357+
Pkg.downgrade(; kwargs...)
358+
Pkg.downgrade(pkg::Union{String, Vector{String}}; kwargs...)
359+
Pkg.downgrade(pkgs::Union{PackageSpec, Vector{PackageSpec}}; kwargs...)
360+
361+
Same as `Pkg.update` but prefer lower versions over higher.
362+
"""
363+
const downgrade = API.down
364+
356365
"""
357366
Pkg.test(; kwargs...)
358367
Pkg.test(pkg::Union{String, Vector{String}; kwargs...)

src/REPLMode/command_declarations.jl

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,34 @@ compound_declarations = [
390390
After any package updates the project will be precompiled. For more information see `pkg> ?precompile`.
391391
""",
392392
],
393+
PSA[
394+
:name => "downgrade",
395+
:short_name => "down",
396+
:api => API.down,
397+
:should_splat => false,
398+
:arg_count => 0 => Inf,
399+
:arg_parser => parse_package,
400+
:option_spec => [
401+
PSA[:name => "project", :short_name => "p", :api => :mode => PKGMODE_PROJECT],
402+
PSA[:name => "manifest", :short_name => "m", :api => :mode => PKGMODE_MANIFEST],
403+
PSA[:name => "major", :api => :level => UPLEVEL_MAJOR],
404+
PSA[:name => "minor", :api => :level => UPLEVEL_MINOR],
405+
PSA[:name => "patch", :api => :level => UPLEVEL_PATCH],
406+
PSA[:name => "fixed", :api => :level => UPLEVEL_FIXED],
407+
PSA[:name => "preserve", :takes_arg => true, :api => :preserve => do_preserve],
408+
],
409+
:completions => :complete_installed_packages,
410+
:description => "downgrade packages to minimum compatible versions",
411+
:help => md"""
412+
[down|downgrade] [-p|--project] [opts] pkg[=uuid] [@version] ...
413+
[down|downgrade] [-m|--manifest] [opts] pkg[=uuid] [@version] ...
414+
415+
opts: --major | --minor | --patch | --fixed
416+
--preserve=<all/direct/none>
417+
418+
Same as `update` except prefer lower versions instead of higher.
419+
""",
420+
],
393421
PSA[
394422
:name => "generate",
395423
:api => API.generate,

src/Resolve/Resolve.jl

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ function _resolve(graph::Graph, lower_bound::Union{Vector{Int}, Nothing}, previo
8080
np = graph.np
8181
spp = graph.spp
8282
gconstr = graph.gconstr
83+
downgrade = graph.downgrade
8384

8485
if lower_bound nothing
8586
for p0 in 1:np
@@ -134,6 +135,10 @@ function _resolve(graph::Graph, lower_bound::Union{Vector{Int}, Nothing}, previo
134135
log_event_global!(graph, "the solver found an optimal configuration")
135136
return sol
136137
else
138+
if downgrade
139+
log_event_global!(graph, "downgrade mode: using feasible configuration without further optimization")
140+
return sol
141+
end
137142
enforce_optimality!(sol, graph)
138143
if lower_bound nothing
139144
@assert all(sol .≥ lower_bound)
@@ -308,6 +313,7 @@ function greedysolver(graph::Graph)
308313
gadj = graph.gadj
309314
gmsk = graph.gmsk
310315
np = graph.np
316+
downgrade = graph.downgrade
311317

312318
push_snapshot!(graph)
313319
gconstr = graph.gconstr
@@ -320,10 +326,10 @@ function greedysolver(graph::Graph)
320326
# since it may include implicit requirements)
321327
req_inds = Set{Int}(p0 for p0 in 1:np if !gconstr[p0][end])
322328

323-
# set up required packages to their highest allowed versions
329+
# set up required packages to their highest (or lowest in downgrade mode) allowed versions
324330
for rp0 in req_inds
325-
# look for the highest version which satisfies the requirements
326-
rv0 = findlast(gconstr[rp0])
331+
# look for the highest/lowest version which satisfies the requirements
332+
rv0 = downgrade ? findfirst(gconstr[rp0]) : findlast(gconstr[rp0])
327333
@assert rv0 nothing && rv0 spp[rp0]
328334
sol[rp0] = rv0
329335
fill!(gconstr[rp0], false)
@@ -353,8 +359,8 @@ function greedysolver(graph::Graph)
353359
# scan dependencies
354360
for (j1, p1) in enumerate(gadj[p0])
355361
msk = gmsk[p0][j1]
356-
# look for the highest version which satisfies the requirements
357-
v1 = findlast(msk[:, s0] .& gconstr[p1])
362+
# look for the highest/lowest version which satisfies the requirements
363+
v1 = downgrade ? findfirst(msk[:, s0] .& gconstr[p1]) : findlast(msk[:, s0] .& gconstr[p1])
358364
v1 == spp[p1] && continue # p1 is not required by p0's current version
359365
# if we found a version, and the package was uninstalled
360366
# or the same version was already selected, we're ok;

src/Resolve/graphtype.jl

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,9 @@ mutable struct Graph
229229
# number of packages (all Vectors above have this length)
230230
np::Int
231231

232+
# downgrade mode: if true, prefer lower versions instead of higher
233+
downgrade::Bool
234+
232235
# some workspace vectors
233236
newmsg::Vector{FieldValue}
234237
diff::Vector{FieldValue}
@@ -241,7 +244,8 @@ mutable struct Graph
241244
reqs::Requires,
242245
fixed::Dict{UUID, Fixed},
243246
verbose::Bool = false,
244-
julia_version::Union{VersionNumber, Nothing} = VERSION
247+
julia_version::Union{VersionNumber, Nothing} = VERSION,
248+
downgrade::Bool = false
245249
)
246250

247251
# Tell the resolver about julia itself
@@ -342,7 +346,7 @@ mutable struct Graph
342346

343347
graph = new(
344348
data, gadj, gmsk, gconstr, adjdict, req_inds, fix_inds, ignored, solve_stack, spp, np,
345-
FieldValue[], FieldValue[], FieldValue[]
349+
downgrade, FieldValue[], FieldValue[], FieldValue[]
346350
)
347351

348352
_add_fixed!(graph, fixed)
@@ -366,8 +370,9 @@ mutable struct Graph
366370
fix_inds = copy(graph.fix_inds)
367371
ignored = copy(graph.ignored)
368372
solve_stack = [([copy(gc0) for gc0 in sav_gconstr], copy(sav_ignored)) for (sav_gconstr, sav_ignored) in graph.solve_stack]
373+
downgrade = graph.downgrade
369374

370-
return new(data, gadj, gmsk, gconstr, adjdict, req_inds, fix_inds, ignored, solve_stack, spp, np)
375+
return new(data, gadj, gmsk, gconstr, adjdict, req_inds, fix_inds, ignored, solve_stack, spp, np, downgrade)
371376
end
372377
end
373378

@@ -1206,6 +1211,7 @@ function validate_versions!(graph::Graph, sources::Set{Int} = Set{Int}(); skim::
12061211
id(p0::Int) = pkgID(p0, graph)
12071212

12081213
log_event_global!(graph, "validating versions [mode=$(skim ? "skim" : "deep")]")
1214+
downgrade = graph.downgrade
12091215

12101216
sumspp = sum(count(gconstr[p0]) for p0 in 1:np)
12111217

@@ -1220,7 +1226,9 @@ function validate_versions!(graph::Graph, sources::Set{Int} = Set{Int}(); skim::
12201226

12211227
gconstr0 = gconstr[p0]
12221228
old_gconstr0 = copy(gconstr0)
1223-
for v0 in reverse!(findall(gconstr0))
1229+
version_indices = findall(gconstr0)
1230+
downgrade || reverse!(version_indices) # prefer higher versions when upgrading
1231+
for v0 in version_indices
12241232
push_snapshot!(graph)
12251233
fill!(graph.gconstr[p0], false)
12261234
graph.gconstr[p0][v0] = true
@@ -1244,13 +1252,15 @@ function validate_versions!(graph::Graph, sources::Set{Int} = Set{Int}(); skim::
12441252
changed = true
12451253
unsat = !any(gconstr0)
12461254
if unsat
1247-
# we'll trigger a failure by pinning the highest version
1248-
v0 = findlast(old_gconstr0[1:(end - 1)])
1255+
# we'll trigger a failure by pinning the highest (or lowest) version
1256+
selector = downgrade ? findfirst : findlast
1257+
v0 = selector(old_gconstr0[1:(end - 1)])
12491258
@assert v0 nothing # this should be ensured by a previous pruning
12501259
# @info "pinning $(logstr(id(p0))) to version $(pvers[p0][v0])"
12511260
log_event_pin!(graph, pkgs[p0], pvers[p0][v0])
12521261
graph.gconstr[p0][v0] = true
1253-
err_msg_preamble = "Package $(logstr(id(p0))) has no possible versions; here is the log when trying to validate the highest version left until this point, $(logstr(id(p0), pvers[p0][v0]))):\n"
1262+
direction = downgrade ? "lowest" : "highest"
1263+
err_msg_preamble = "Package $(logstr(id(p0))) has no possible versions; here is the log when trying to validate the $direction version left until this point, $(logstr(id(p0), pvers[p0][v0]))):\n"
12541264
propagate_constraints!(graph, Set{Int}([p0]); err_msg_preamble)
12551265
@assert false # the above call must fail
12561266
end
@@ -1397,6 +1407,7 @@ function build_eq_classes_soft1!(graph::Graph, p0::Int)
13971407
gmsk = graph.gmsk
13981408
gconstr = graph.gconstr
13991409
ignored = graph.ignored
1410+
downgrade = graph.downgrade
14001411

14011412
# concatenate all the constraints; the columns of the
14021413
# result encode the behavior of each version
@@ -1417,10 +1428,15 @@ function build_eq_classes_soft1!(graph::Graph, p0::Int)
14171428
neq == eff_spp0 && return # nothing to do here
14181429

14191430
# group versions into sets that behave identically
1420-
# each set is represented by its highest-valued member
1421-
repr_vers = sort!(Int[findlast(isequal(repr_vecs[w0]), cvecs) for w0 in 1:neq])
1431+
# each set is represented by its extreme member (highest for upgrade, lowest for downgrade)
1432+
selector = downgrade ? findfirst : findlast
1433+
repr_vers = sort!(Int[selector(isequal(repr_vecs[w0]), cvecs) for w0 in 1:neq])
14221434
@assert all(>(0), repr_vers)
1423-
@assert repr_vers[end] == eff_spp0
1435+
if downgrade
1436+
@assert repr_vers[1] == 1
1437+
else
1438+
@assert repr_vers[end] == eff_spp0
1439+
end
14241440

14251441
# convert the version numbers into the original numbering
14261442
repr_vers = findall(gconstr0)[repr_vers]

0 commit comments

Comments
 (0)