Skip to content

Commit b2ced4b

Browse files
authored
--outdated flag to status (#2284)
Allows filtering the output of `status` to only show packages that are "outdated" (not on their latest version). This also shows what packages (or project compat) that are holding packages back from updating to their latest version.
1 parent 1a1958f commit b2ced4b

File tree

7 files changed

+209
-32
lines changed

7 files changed

+209
-32
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
Pkg v1.8 Release Notes
2+
======================
3+
4+
- New `outdated::Bool` kwarg to `Pkg.status` (`--outdated` or `-o` in the REPL mode) to show
5+
information about packages not at the latest version.
6+
17
Pkg v1.7 Release Notes
28
======================
39

src/API.jl

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1522,9 +1522,8 @@ end
15221522

15231523
@deprecate status(mode::PackageMode) status(mode=mode)
15241524

1525-
function status(ctx::Context, pkgs::Vector{PackageSpec}; diff::Bool=false, mode=PKGMODE_PROJECT,
1526-
io::IO=stdout, kwargs...)
1527-
Operations.status(ctx.env, pkgs, mode=mode, git_diff=diff, io=io)
1525+
function status(ctx::Context, pkgs::Vector{PackageSpec}; diff::Bool=false, mode=PKGMODE_PROJECT, outdated::Bool=false, io::IO=stdout, kwargs...)
1526+
Operations.status(ctx.env, ctx.registries, pkgs; mode, git_diff=diff, io, outdated)
15281527
return nothing
15291528
end
15301529

@@ -1664,7 +1663,7 @@ function redo_undo(ctx, mode::Symbol, direction::Int)
16641663
snapshot = state.entries[state.idx]
16651664
ctx.env.manifest, ctx.env.project = snapshot.manifest, snapshot.project
16661665
write_env(ctx.env; update_undo=false)
1667-
Operations.show_update(ctx.env; io=ctx.io)
1666+
Operations.show_update(ctx.env, ctx.registries; io=ctx.io)
16681667
end
16691668

16701669

src/Operations.jl

Lines changed: 144 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,7 +1052,7 @@ function rm(ctx::Context, pkgs::Vector{PackageSpec}; mode::PackageMode)
10521052
prune_manifest(ctx.env)
10531053
# update project & manifest
10541054
write_env(ctx.env)
1055-
show_update(ctx.env; io=ctx.io)
1055+
show_update(ctx.env, ctx.registries; io=ctx.io)
10561056
end
10571057

10581058
update_package_add(ctx::Context, pkg::PackageSpec, ::Nothing, is_dep::Bool) = pkg
@@ -1196,7 +1196,7 @@ function add(ctx::Context, pkgs::Vector{PackageSpec}, new_git=Set{UUID}();
11961196
download_artifacts(ctx.env, platform=platform, julia_version=ctx.julia_version, io=ctx.io)
11971197

11981198
write_env(ctx.env) # write env before building
1199-
show_update(ctx.env; io=ctx.io)
1199+
show_update(ctx.env, ctx.registries; io=ctx.io)
12001200
build_versions(ctx, union(new_apply, new_git))
12011201
end
12021202

@@ -1214,7 +1214,7 @@ function develop(ctx::Context, pkgs::Vector{PackageSpec}, new_git::Set{UUID};
12141214
new_apply = download_source(ctx)
12151215
download_artifacts(ctx.env; platform=platform, julia_version=ctx.julia_version, io=ctx.io)
12161216
write_env(ctx.env) # write env before building
1217-
show_update(ctx.env; io=ctx.io)
1217+
show_update(ctx.env, ctx.registries; io=ctx.io)
12181218
build_versions(ctx, union(new_apply, new_git))
12191219
end
12201220

@@ -1280,7 +1280,7 @@ function up(ctx::Context, pkgs::Vector{PackageSpec}, level::UpgradeLevel;
12801280
new_apply = download_source(ctx)
12811281
download_artifacts(ctx.env, julia_version=ctx.julia_version, io=ctx.io)
12821282
write_env(ctx.env; skip_writing_project) # write env before building
1283-
show_update(ctx.env; io=ctx.io)
1283+
show_update(ctx.env, ctx.registries; io=ctx.io)
12841284
build_versions(ctx, union(new_apply, new_git))
12851285
end
12861286

@@ -1322,7 +1322,7 @@ function pin(ctx::Context, pkgs::Vector{PackageSpec})
13221322
new = download_source(ctx)
13231323
download_artifacts(ctx.env; julia_version=ctx.julia_version, io=ctx.io)
13241324
write_env(ctx.env) # write env before building
1325-
show_update(ctx.env; io=ctx.io)
1325+
show_update(ctx.env, ctx.registries; io=ctx.io)
13261326
build_versions(ctx, new)
13271327
end
13281328

@@ -1360,12 +1360,12 @@ function free(ctx::Context, pkgs::Vector{PackageSpec})
13601360
new = download_source(ctx)
13611361
download_artifacts(ctx.env, io=ctx.io)
13621362
write_env(ctx.env) # write env before building
1363-
show_update(ctx.env; io=ctx.io)
1363+
show_update(ctx.env, ctx.registries; io=ctx.io)
13641364
build_versions(ctx, new)
13651365
else
13661366
foreach(pkg -> manifest_info(ctx.env.manifest, pkg.uuid).pinned = false, pkgs)
13671367
write_env(ctx.env)
1368-
show_update(ctx.env; io=ctx.io)
1368+
show_update(ctx.env, ctx.registries; io=ctx.io)
13691369
end
13701370
end
13711371

@@ -1636,7 +1636,7 @@ function test(ctx::Context, pkgs::Vector{PackageSpec};
16361636
sandbox(ctx, pkg, source_path, testdir(source_path), test_project_override; force_latest_compatible_version, allow_earlier_backwards_compatible_versions, allow_reresolve) do
16371637
test_fn !== nothing && test_fn()
16381638
sandbox_ctx = Context(;io=ctx.io)
1639-
status(sandbox_ctx.env; mode=PKGMODE_COMBINED, io=sandbox_ctx.io)
1639+
status(sandbox_ctx.env, sandbox_ctx.registries; mode=PKGMODE_COMBINED, io=sandbox_ctx.io)
16401640
Pkg._auto_precompile(sandbox_ctx)
16411641
printpkgstyle(ctx.io, :Testing, "Running tests...")
16421642
flush(stdout)
@@ -1731,6 +1731,79 @@ function print_diff(io::IO, old::Union{Nothing,PackageSpec}, new::Union{Nothing,
17311731
end
17321732
end
17331733

1734+
function status_compat_info(pkg::PackageSpec, env::EnvCache, regs::Vector{Registry.RegistryInstance})
1735+
manifest, project = env.manifest, env.project
1736+
packages_holding_back = String[]
1737+
max_version, max_version_in_compat = v"0", v"0"
1738+
for reg in regs
1739+
reg_pkg = get(reg, pkg.uuid, nothing)
1740+
reg_pkg === nothing && continue
1741+
info = Registry.registry_info(reg_pkg)
1742+
reg_compat_info = Registry.compat_info(info)
1743+
max_version_reg = maximum(keys(reg_compat_info); init=v"0")
1744+
max_version = max(max_version, max_version_reg)
1745+
compat_spec = get_compat(env.project, pkg.name)
1746+
versions_in_compat = filter(in(compat_spec), keys(reg_compat_info))
1747+
max_version_in_compat = max(max_version_in_compat, maximum(versions_in_compat; init=v"0"))
1748+
end
1749+
max_version == v"0" && return nothing
1750+
pkg.version == max_version && return nothing
1751+
1752+
# Check compat of project
1753+
if pkg.version == max_version_in_compat && max_version_in_compat != max_version
1754+
return ["compat"], max_version, max_version_in_compat
1755+
end
1756+
1757+
manifest_info = get(manifest, pkg.uuid, nothing)
1758+
manifest_info === nothing && return nothing
1759+
1760+
# Check compat of dependees
1761+
for (uuid, dep_pkg) in manifest
1762+
is_stdlib(uuid) && continue
1763+
if !(pkg.uuid in values(dep_pkg.deps))
1764+
continue
1765+
end
1766+
dep_info = get(manifest, uuid, nothing)
1767+
dep_info === nothing && continue
1768+
for reg in regs
1769+
reg_pkg = get(reg, uuid, nothing)
1770+
reg_pkg === nothing && continue
1771+
info = Registry.registry_info(reg_pkg)
1772+
reg_compat_info = Registry.compat_info(info)
1773+
compat_info_v = get(reg_compat_info, dep_info.version, nothing)
1774+
compat_info_v === nothing && continue
1775+
compat_info_v_uuid = compat_info_v[pkg.uuid]
1776+
if !(max_version in compat_info_v_uuid)
1777+
push!(packages_holding_back, dep_pkg.name)
1778+
end
1779+
end
1780+
end
1781+
1782+
# Check compat with Julia itself
1783+
julia_compatible_versions = Set{VersionNumber}()
1784+
for reg in regs
1785+
reg_pkg = get(reg, pkg.uuid, nothing)
1786+
reg_pkg === nothing && continue
1787+
info = Registry.registry_info(reg_pkg)
1788+
reg_compat_info = Registry.compat_info(info)
1789+
compat_info_v = get(reg_compat_info, pkg.version, nothing)
1790+
versions = keys(reg_compat_info)
1791+
for v in versions
1792+
compat_info_v = get(reg_compat_info, v, nothing)
1793+
compat_info_v === nothing && continue
1794+
compat_info_v_uuid = compat_info_v[JULIA_UUID]
1795+
if VERSION in compat_info_v_uuid
1796+
push!(julia_compatible_versions, v)
1797+
end
1798+
end
1799+
end
1800+
if !(max_version in julia_compatible_versions)
1801+
push!(packages_holding_back, "julia")
1802+
end
1803+
1804+
return sort!(unique!(packages_holding_back)), max_version, max_version_in_compat
1805+
end
1806+
17341807
function diff_array(old_env::Union{EnvCache,Nothing}, new_env::EnvCache; manifest=true)
17351808
function index_pkgs(pkgs, uuid)
17361809
idx = findfirst(pkg -> pkg.uuid == uuid, pkgs)
@@ -1757,9 +1830,18 @@ function is_package_downloaded(project_file::String, pkg::PackageSpec)
17571830
return true
17581831
end
17591832

1760-
function print_status(env::EnvCache, old_env::Union{Nothing,EnvCache}, header::Symbol,
1761-
uuids::Vector, names::Vector; manifest=true, diff=false, ignore_indent=false, io)
1833+
struct PackageStatusData
1834+
uuid::UUID
1835+
old::Union{Nothing, PackageSpec}
1836+
new::Union{Nothing, PackageSpec}
1837+
downloaded::Bool
1838+
compat_data::Union{Nothing, Tuple{Vector{String}, VersionNumber, VersionNumber}}
1839+
end
1840+
1841+
function print_status(env::EnvCache, old_env::Union{Nothing,EnvCache}, registries::Vector{Registry.RegistryInstance}, header::Symbol,
1842+
uuids::Vector, names::Vector; manifest=true, diff=false, ignore_indent::Bool, outdated::Bool, io::IO)
17621843
not_installed_indicator = sprint((io, args) -> printstyled(io, args...; color=:red), "", context=io)
1844+
not_latest_version_indicator = sprint((io, args) -> printstyled(io, args...; color=:yellow), "", context=io)
17631845
filter = !isempty(uuids) || !isempty(names)
17641846
# setup
17651847
xs = diff_array(old_env, env; manifest=manifest)
@@ -1785,20 +1867,62 @@ function print_status(env::EnvCache, old_env::Union{Nothing,EnvCache}, header::S
17851867
# Sort stdlibs and _jlls towards the end in status output
17861868
xs = sort!(xs, by = (x -> (is_stdlib(x[1]), endswith(something(x[3], x[2]).name, "_jll"), something(x[3], x[2]).name, x[1])))
17871869
all_packages_downloaded = true
1870+
1871+
package_statuses = PackageStatusData[]
17881872
for (uuid, old, new) in xs
17891873
if Types.is_project_uuid(env, uuid)
17901874
continue
17911875
end
1876+
1877+
latest_version = true
1878+
# Compat info
1879+
cinfo = nothing
1880+
if outdated
1881+
if diff == false && !is_stdlib(new.uuid)
1882+
@assert old == nothing
1883+
cinfo = status_compat_info(new, env, registries)
1884+
if cinfo !== nothing
1885+
latest_version = false
1886+
end
1887+
end
1888+
end
1889+
# if we are running with compat, only show packages that are upper bounded
1890+
if outdated && latest_version
1891+
continue
1892+
end
1893+
17921894
pkg_downloaded = !is_instantiated(new) || is_package_downloaded(env.project_file, new)
1895+
17931896
all_packages_downloaded &= pkg_downloaded
1794-
print(io, pkg_downloaded ? " " : not_installed_indicator)
1795-
printstyled(io, " [", string(uuid)[1:8], "] "; color = :light_black)
1796-
diff ? print_diff(io, old, new) : print_single(io, new)
1897+
push!(package_statuses, PackageStatusData(uuid, old, new, pkg_downloaded, cinfo))
1898+
end
1899+
1900+
for pkg in package_statuses
1901+
latest_version = pkg.compat_data === nothing
1902+
print(io, pkg.downloaded ? (all_packages_downloaded ? "" : " ") : not_installed_indicator)
1903+
printstyled(io, " [", string(pkg.uuid)[1:8], "] "; color = :light_black)
1904+
diff ? print_diff(io, pkg.old, pkg.new) : print_single(io, pkg.new)
1905+
if outdated && !diff && pkg.compat_data !== nothing
1906+
packages_holding_back, max_version, max_version_compat = pkg.compat_data
1907+
if pkg.new.version !== max_version_compat && max_version_compat != max_version
1908+
printstyled(io, " [<v", max_version_compat, "]", color=:light_magenta)
1909+
printstyled(io, ",")
1910+
end
1911+
printstyled(io, " (<v", max_version, ")"; color=Base.warn_color())
1912+
if packages_holding_back == ["compat"]
1913+
printstyled(io, " [compat]"; color=:light_magenta)
1914+
else
1915+
pkg_str = isempty(packages_holding_back) ? "" : string(": ", join(packages_holding_back, ", "))
1916+
printstyled(io, pkg_str; color=Base.warn_color())
1917+
end
1918+
end
17971919
println(io)
17981920
end
1921+
17991922
if !all_packages_downloaded
18001923
printpkgstyle(io, :Info, "packages marked with $not_installed_indicator not downloaded, use `instantiate` to download", ignore_indent)
18011924
end
1925+
18021926
return nothing
18031927
end
18041928

@@ -1819,20 +1943,20 @@ function git_head_env(env, project_dir)
18191943
end
18201944
end
18211945

1822-
function show_update(env::EnvCache; ignore_indent=false, io::IO)
1946+
function show_update(env::EnvCache, registries::Vector{Registry.RegistryInstance}; io::IO)
18231947
old_env = EnvCache()
18241948
old_env.project = env.original_project
18251949
old_env.manifest = env.original_manifest
1826-
status(env; header=:Updating, mode=PKGMODE_COMBINED, env_diff=old_env, ignore_indent=ignore_indent, io=io)
1950+
status(env, registries; header=:Updating, mode=PKGMODE_COMBINED, env_diff=old_env, ignore_indent=false, io=io)
18271951
return nothing
18281952
end
18291953

1830-
function status(env::EnvCache, pkgs::Vector{PackageSpec}=PackageSpec[];
1831-
header=nothing, mode::PackageMode=PKGMODE_PROJECT, git_diff::Bool=false, env_diff=nothing, ignore_indent=false, io::IO)
1954+
function status(env::EnvCache, registries::Vector{Registry.RegistryInstance}, pkgs::Vector{PackageSpec}=PackageSpec[];
1955+
header=nothing, mode::PackageMode=PKGMODE_PROJECT, git_diff::Bool=false, env_diff=nothing, ignore_indent=true, io::IO, outdated::Bool=false)
18321956
io == Base.devnull && return
18331957
# if a package, print header
18341958
if header === nothing && env.pkg !== nothing
1835-
printpkgstyle(io, :Project, string(env.pkg.name, " v", env.pkg.version); color=Base.info_color())
1959+
printpkgstyle(io, :Project, string(env.pkg.name, " v", env.pkg.version), true; color=Base.info_color())
18361960
end
18371961
# load old env
18381962
old_env = nothing
@@ -1855,10 +1979,10 @@ function status(env::EnvCache, pkgs::Vector{PackageSpec}=PackageSpec[];
18551979
diff = old_env !== nothing
18561980
header = something(header, diff ? :Diff : :Status)
18571981
if mode == PKGMODE_PROJECT || mode == PKGMODE_COMBINED
1858-
print_status(env, old_env, header, filter_uuids, filter_names; manifest=false, diff=diff, ignore_indent=ignore_indent, io=io)
1982+
print_status(env, old_env, registries, header, filter_uuids, filter_names; manifest=false, diff, ignore_indent, io, outdated)
18591983
end
18601984
if mode == PKGMODE_MANIFEST || mode == PKGMODE_COMBINED
1861-
print_status(env, old_env, header, filter_uuids, filter_names; diff=diff, ignore_indent=ignore_indent, io=io)
1985+
print_status(env, old_env, registries, header, filter_uuids, filter_names; diff, ignore_indent, io, outdated)
18621986
end
18631987
end
18641988

src/Pkg.jl

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -384,25 +384,47 @@ from packages that are tracking a path.
384384
const resolve = API.resolve
385385

386386
"""
387-
Pkg.status([pkgs...]; mode::PackageMode=PKGMODE_PROJECT, diff::Bool=false, io::IO=stdout)
387+
Pkg.status([pkgs...]; mode::PackageMode=PKGMODE_PROJECT, diff::Bool=false, compat::Bool=false, io::IO=stdout)
388388
389389
Print out the status of the project/manifest.
390390
If `mode` is `PKGMODE_PROJECT`, print out status only about the packages
391391
that are in the project (explicitly added). If `mode` is `PKGMODE_MANIFEST`,
392392
print status also about those in the manifest (recursive dependencies). If there are
393393
any packages listed as arguments, the output will be limited to those packages.
394+
394395
Setting `diff=true` will, if the environment is in a git repository, limit
395396
the output to the difference as compared to the last git commit.
396397
398+
Setting `outdated=true` will only show packages that are not on the latest version,
399+
their maximum version and why they are not on the latest version (either due to other
400+
packages holding them back due to compatibility constraints, or due to compatibility in the project file).
401+
As an example, a status output like:
402+
```
403+
pkg> Pkg.status(; outdated=true)
404+
Status `Manifest.toml`
405+
[a8cc5b0e] Crayons v2.0.0 [<v3.0.0], (<v4.0.4)
406+
[b8a86587] NearestNeighbors v0.4.8 (<v0.4.9) [compat]
407+
[2ab3a3ac] LogExpFunctions v0.2.5 (<v0.3.0): SpecialFunctions
408+
```
409+
means that the latest version of Crayons is 4.0.4 but the latest version compatible
410+
with the `[compat]` section in the current project is 3.0.0.
411+
The latest version of NearestNeighbors is 0.4.9 but due to compat constrains in the project
412+
it is held back to 0.4.8.
413+
The latest version of LogExpFunctions is 0.3.0 but SpecialFunctions
414+
is holding it back to 0.2.5.
415+
397416
See [`Pkg.project`](@ref) and [`Pkg.dependencies`](@ref) to get the project/manifest
398417
status as a Julia object instead of printing it.
399418
400419
!!! compat "Julia 1.1"
401420
`Pkg.status` with package arguments requires at least Julia 1.1.
402421
403422
!!! compat "Julia 1.3"
404-
The `diff` keyword argument requires Julia 1.3. In earlier versions `diff=true`
423+
The `diff` keyword argument requires at least Julia 1.3. In earlier versions `diff=true`
405424
is the default for environments in git repositories.
425+
426+
!!! compat "Julia 1.8"
427+
The `outdated` keyword argument reguires at least Julia 1.8
406428
"""
407429
const status = API.status
408430

src/REPLMode/REPLMode.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ wrap_option(option::String) = length(option) == 1 ? "-$option" : "--$option"
147147
is_opt(word::AbstractString) = first(word) == '-' && word != "-"
148148

149149
function parse_option(word::AbstractString)::Option
150-
m = match(r"^(?: -([a-z]) | --([a-z]{2,})(?:\s*=\s*(\S*))? )$"ix, word)
150+
m = match(r"^(?: -([a-z]) | --((?:[a-z]{1,}-?)*)(?:\s*=\s*(\S*))? )$"ix, word)
151151
m === nothing && pkgerror("malformed option: ", repr(word))
152152
option_name = m.captures[1] !== nothing ? m.captures[1] : m.captures[2]
153153
option_arg = m.captures[3] === nothing ? nothing : String(m.captures[3])

0 commit comments

Comments
 (0)