diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ea1bdbf342..01c9fbd12d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,8 +14,8 @@ jobs: fail-fast: false matrix: julia-version: - # - '1.6' - - 'nightly' + - '1.6' + # - 'nightly' julia-arch: - 'x64' - 'x86' @@ -53,6 +53,8 @@ jobs: env: JULIA_PKG_SERVER: ${{ matrix.pkg-server }} - uses: julia-actions/julia-processcoverage@v1 + env: + JULIA_PKG_SERVER: ${{ matrix.pkg-server }} - uses: codecov/codecov-action@v1 with: file: lcov.info diff --git a/src/API.jl b/src/API.jl index b3b8c90513..896d7a6cf7 100644 --- a/src/API.jl +++ b/src/API.jl @@ -12,7 +12,7 @@ using Serialization import ..depots, ..depots1, ..logdir, ..devdir import ..Operations, ..GitTools, ..Pkg, ..UPDATED_REGISTRY_THIS_SESSION -import ..can_fancyprint +import ..can_fancyprint, ..DEFAULT_IO using ..Types, ..TOML using ..Types: VersionTypes using Base.BinaryPlatforms @@ -46,12 +46,17 @@ function project(ctx::Context)::ProjectInfo ) end -function check_package_name(x::AbstractString, mode=nothing) +function check_package_name(x::AbstractString, mode::Union{Nothing,String,Symbol}=nothing) if !Base.isidentifier(x) - message = "`$x` is not a valid package name" - if mode !== nothing && any(occursin.(['\\','/'], x)) # maybe a url or a path - message *= "\nThe argument appears to be a URL or path, perhaps you meant " * - "`Pkg.$mode(url=\"...\")` or `Pkg.$mode(path=\"...\")`." + message = sprint() do iostr + print(iostr, "`$x` is not a valid package name") + if endswith(lowercase(x), ".jl") + print(iostr, ". Perhaps you meant `$(chop(x; tail=3))`") + end + if mode !== nothing && any(occursin.(['\\','/'], x)) # maybe a url or a path + print(iostr, "\nThe argument appears to be a URL or path, perhaps you meant ", + "`Pkg.$mode(url=\"...\")` or `Pkg.$mode(path=\"...\")`.") + end end pkgerror(message) end @@ -68,8 +73,9 @@ for f in (:develop, :add, :rm, :up, :pin, :free, :test, :build, :status) @eval begin $f(pkg::Union{AbstractString, PackageSpec}; kwargs...) = $f([pkg]; kwargs...) $f(pkgs::Vector{<:AbstractString}; kwargs...) = $f([PackageSpec(pkg) for pkg in pkgs]; kwargs...) - function $f(pkgs::Vector{PackageSpec}; kwargs...) + function $f(pkgs::Vector{PackageSpec}; io::IO=DEFAULT_IO[], kwargs...) ctx = Context() + kwargs = merge((;kwargs...), (:io => io,)) ret = $f(ctx, pkgs; kwargs...) $(f in (:add, :up, :pin, :free, :build)) && Pkg._auto_precompile(ctx) return ret @@ -255,7 +261,7 @@ function up(ctx::Context, pkgs::Vector{PackageSpec}; return end -resolve(; kwargs...) = resolve(Context(); kwargs...) +resolve(; io::IO=DEFAULT_IO[], kwargs...) = resolve(Context(;io); kwargs...) function resolve(ctx::Context; kwargs...) up(ctx; level=UPLEVEL_FIXED, mode=PKGMODE_MANIFEST, update_registry=false, kwargs...) return nothing @@ -924,8 +930,8 @@ function precompile(ctx::Context; internal_call::Bool=false, strict::Bool=false, io = ctx.io fancyprint = can_fancyprint(io) - # when manually called, unsuspend all packages that were suspended due to precomp errors - internal_call ? recall_suspended_packages() : precomp_unsuspend!() + recall_precompile_state() # recall suspended and force-queued packages + !internal_call && precomp_unsuspend!() # when manually called, unsuspend all packages that were suspended due to precomp errors direct_deps = [ Base.PkgId(uuid, name) @@ -941,12 +947,13 @@ function precompile(ctx::Context; internal_call::Bool=false, strict::Bool=false, end depsmap = Dict{Base.PkgId, Vector{Base.PkgId}}(Iterators.filter(!isnothing, deps_pair_or_nothing)) #flat map of each dep and its deps - if ctx.env.pkg !== nothing && isfile( joinpath( dirname(ctx.env.project_file), "src", ctx.env.pkg.name * ".jl") ) - depsmap[Base.PkgId(ctx.env.pkg.uuid, ctx.env.pkg.name)] = [ + ctx_env_pkg = ctx.env.pkg + if ctx_env_pkg !== nothing && isfile( joinpath( dirname(ctx.env.project_file), "src", "$(ctx_env_pkg.name).jl") ) + depsmap[Base.PkgId(ctx_env_pkg.uuid, ctx_env_pkg.name)] = [ Base.PkgId(last(x), first(x)) for x in ctx.env.project.deps if !Base.in_sysimage(Base.PkgId(last(x), first(x))) ] - push!(direct_deps, Base.PkgId(ctx.env.pkg.uuid, ctx.env.pkg.name)) + push!(direct_deps, Base.PkgId(ctx_env_pkg.uuid, ctx_env_pkg.name)) end started = Dict{Base.PkgId,Bool}() @@ -1029,6 +1036,7 @@ function precompile(ctx::Context; internal_call::Bool=false, strict::Bool=false, n_total = length(depsmap) bar.max = n_total - n_already_precomp final_loop = false + n_print_rows = 0 while !printloop_should_exit lock(print_lock) do term_size = Base.displaysize(stdout)::Tuple{Int,Int} @@ -1038,37 +1046,39 @@ function precompile(ctx::Context; internal_call::Bool=false, strict::Bool=false, else pkg_queue end - str = "" - if i > 1 - str *= string(ansi_moveup(last_length+1), ansi_movecol1, ansi_cleartoend) - end - bar.current = n_done - n_already_precomp - bar.max = n_total - n_already_precomp - str *= sprint(io -> show_progress(io, bar); context=io) * '\n' - for dep in pkg_queue_show - name = dep in direct_deps ? dep.name : string(color_string(dep.name, :light_black)) - if dep in precomperr_deps - str *= string(color_string(" ? ", Base.warn_color()), name, "\n") - elseif haskey(failed_deps, dep) - str *= string(color_string(" ✗ ", Base.error_color()), name, "\n") - elseif was_recompiled[dep] - interrupted_or_done.set && continue - str *= string(color_string(" ✓ ", :green), name, "\n") - @async begin # keep successful deps visible for short period - sleep(1); - filter!(!isequal(dep), pkg_queue) + str = sprint() do iostr + if i > 1 + print(iostr, ansi_moveup(n_print_rows), ansi_movecol1, ansi_cleartoend) + end + bar.current = n_done - n_already_precomp + bar.max = n_total - n_already_precomp + final_loop || print(iostr, sprint(io -> show_progress(io, bar); context=io), "\n") + for dep in pkg_queue_show + name = dep in direct_deps ? dep.name : string(color_string(dep.name, :light_black)) + if dep in precomperr_deps + print(iostr, color_string(" ? ", Base.warn_color()), name, "\n") + elseif haskey(failed_deps, dep) + print(iostr, color_string(" ✗ ", Base.error_color()), name, "\n") + elseif was_recompiled[dep] + interrupted_or_done.set && continue + print(iostr, color_string(" ✓ ", :green), name, "\n") + @async begin # keep successful deps visible for short period + sleep(1); + filter!(!isequal(dep), pkg_queue) + end + elseif started[dep] + # Offset each spinner animation using the first character in the package name as the seed. + # If not offset, on larger terminal fonts it looks odd that they all sync-up + anim_char = anim_chars[(i + Int(dep.name[1])) % length(anim_chars) + 1] + anim_char_colored = dep in direct_deps ? anim_char : color_string(anim_char, :light_black) + print(iostr, " $anim_char_colored $name\n") + else + print(iostr, " $name\n") end - elseif started[dep] - # Offset each spinner animation using the first character in the package name as the seed. - # If not offset, on larger terminal fonts it looks odd that they all sync-up - anim_char = anim_chars[(i + Int(dep.name[1])) % length(anim_chars) + 1] - anim_char_colored = dep in direct_deps ? anim_char : color_string(anim_char, :light_black) - str *= string(" $anim_char_colored ", name, "\n") - else - str *= " " * name * "\n" end end last_length = length(pkg_queue_show) + n_print_rows = count("\n", str) print(io, str) end printloop_should_exit = interrupted_or_done.set && final_loop @@ -1103,15 +1113,17 @@ function precompile(ctx::Context; internal_call::Bool=false, strict::Bool=false, wait(was_processed[dep]) end - suspended = if haskey(man, pkg.uuid) # to handle the working environment uuid - precomp_suspended(make_pkgspec(man, pkg.uuid)) + if haskey(man, pkg.uuid) # to handle the working environment uuid + suspended = precomp_suspended(make_pkgspec(man, pkg.uuid)) + queued = precomp_queued(make_pkgspec(man, pkg.uuid)) else - false + suspended = false + queued = false end # skip stale checking and force compilation if any dep was recompiled in this session any_dep_recompiled = any(map(dep->was_recompiled[dep], deps)) is_stale = true - if (any_dep_recompiled || (!suspended && (is_stale = _is_stale(paths, sourcepath)))) + if (queued || any_dep_recompiled || (!suspended && (is_stale = _is_stale(paths, sourcepath)))) Base.acquire(parallel_limiter) is_direct_dep = pkg in direct_deps iob = IOBuffer() @@ -1133,10 +1145,12 @@ function precompile(ctx::Context; internal_call::Bool=false, strict::Bool=false, end if ret isa Base.PrecompilableError push!(precomperr_deps, pkg) + haskey(man, pkg.uuid) && precomp_queue!(make_pkgspec(man, pkg.uuid)) !fancyprint && lock(print_lock) do println(io, string(color_string(" ? ", Base.warn_color()), name)) end else + queued && (haskey(man, pkg.uuid) && precomp_dequeue!(make_pkgspec(man, pkg.uuid))) !fancyprint && lock(print_lock) do println(io, string(color_string(" ✓ ", :green), name)) end @@ -1149,6 +1163,7 @@ function precompile(ctx::Context; internal_call::Bool=false, strict::Bool=false, println(io, string(color_string(" ✗ ", Base.error_color()), name)) end if haskey(man, pkg.uuid) + queued && precomp_dequeue!(make_pkgspec(man, pkg.uuid)) precomp_suspend!(make_pkgspec(man, pkg.uuid)) end else @@ -1180,31 +1195,35 @@ function precompile(ctx::Context; internal_call::Bool=false, strict::Bool=false, handle_interrupt(err) end notify(first_started) # in cases of no-op or !fancyprint - save_suspended_packages() # save list to scratch space + save_precompile_state() # save lists to scratch space wait(t_print) !all(istaskdone, tasks) && return # if some not finished, must have errored or been interrupted seconds_elapsed = round(Int, (time_ns() - time_start) / 1e9) ndeps = count(values(was_recompiled)) if ndeps > 0 || !isempty(failed_deps) plural = ndeps == 1 ? "y" : "ies" - str = "$(ndeps) dependenc$(plural) successfully precompiled in $(seconds_elapsed) seconds" - if n_already_precomp > 0 || !isempty(skipped_deps) - str *= " (" - n_already_precomp > 0 && (str *= "$n_already_precomp already precompiled") - !isempty(circular_deps) && (str *= ", $(length(circular_deps)) skipped due to circular dependency") - !isempty(skipped_deps) && (str *= ", $(length(skipped_deps)) skipped during auto due to previous errors") - str *= ")" - end - if !isempty(precomperr_deps) - plural = length(precomperr_deps) == 1 ? "y" : "ies" - str *= string("\n", - color_string(string(length(precomperr_deps)), Base.warn_color()), - " dependenc$(plural) failed but may be precompilable after restarting julia" - ) - end - if internal_call && !isempty(failed_deps) - plural = length(failed_deps) == 1 ? "y" : "ies" - str *= "\n" * color_string("$(length(failed_deps))", Base.error_color()) * " dependenc$(plural) errored" + str = sprint() do iostr + print(iostr, " $(ndeps) dependenc$(plural) successfully precompiled in $(seconds_elapsed) seconds") + if n_already_precomp > 0 || !isempty(skipped_deps) + print(iostr, " (") + n_already_precomp > 0 && (print(iostr, "$n_already_precomp already precompiled")) + !isempty(circular_deps) && (print(iostr, ", $(length(circular_deps)) skipped due to circular dependency")) + !isempty(skipped_deps) && (print(iostr, ", $(length(skipped_deps)) skipped during auto due to previous errors")) + print(iostr, ")") + end + if !isempty(precomperr_deps) + plural = length(precomperr_deps) == 1 ? "y" : "ies" + print(iostr, "\n ", + color_string(string(length(precomperr_deps)), Base.warn_color()), + " dependenc$(plural) failed but may be precompilable after restarting julia" + ) + end + if internal_call && !isempty(failed_deps) + plural1 = length(failed_deps) == 1 ? "y" : "ies" + plural2 = length(failed_deps) == 1 ? "" : "s" + print(iostr, "\n ", color_string("$(length(failed_deps))", Base.error_color()), " dependenc$(plural1) errored. ") + print(iostr, "To see a full report either run `import Pkg; Pkg.precompile()` or load the package$(plural2)") + end end lock(print_lock) do println(io, str) @@ -1214,7 +1233,7 @@ function precompile(ctx::Context; internal_call::Bool=false, strict::Bool=false, n_direct_errs = 0 for (dep, err) in failed_deps if strict || (dep in direct_deps) - err_str *= "\n" * "$dep" * "\n\n" * err * (n_direct_errs > 0 ? "\n" : "") + err_str = string(err_str, "\n$dep\n\n$err", (n_direct_errs > 0 ? "\n" : "")) n_direct_errs += 1 end end @@ -1229,29 +1248,34 @@ function precompile(ctx::Context; internal_call::Bool=false, strict::Bool=false, nothing end -const pkgs_precompile_suspended = PackageSpec[] -function save_suspended_packages() +const pkgs_precompile_suspended = PackageSpec[] # packages that shouldn't be retried during autoprecomp +const pkgs_precompile_pending = PackageSpec[] # packages that need to be retried after restart +function save_precompile_state() path = Operations.pkg_scratchpath() - fpath = joinpath(path, string("suspend_cache_", hash(Base.active_project() * string(Base.VERSION)))) - mkpath(path); Base.Filesystem.rm(fpath, force=true) - open(fpath, "w") do io - serialize(io, pkgs_precompile_suspended) + for (prefix, store) in (("suspend_cache_", pkgs_precompile_suspended), ("pending_cache_", pkgs_precompile_pending)) + fpath = joinpath(path, string(prefix, hash(string(Base.active_project(), Base.VERSION)))) + mkpath(path); Base.Filesystem.rm(fpath, force=true) + open(fpath, "w") do io + serialize(io, store) + end end return nothing end -function recall_suspended_packages() - fpath = joinpath(Operations.pkg_scratchpath(), string("suspend_cache_", hash(Base.active_project() * string(Base.VERSION)))) - if isfile(fpath) - v = open(fpath) do io - try - deserialize(io) - catch - PackageSpec[] +function recall_precompile_state() + for (prefix, store) in (("suspend_cache_", pkgs_precompile_suspended), ("pending_cache_", pkgs_precompile_pending)) + fpath = joinpath(Operations.pkg_scratchpath(), string(prefix, hash(string(Base.active_project(), Base.VERSION)))) + if isfile(fpath) + open(fpath) do io + try + pkgspecs = deserialize(io) + append!(empty!(store), pkgspecs) + catch + empty!(store) + end end + else + empty!(store) end - append!(empty!(pkgs_precompile_suspended), v) - else - empty!(pkgs_precompile_suspended) end return nothing end @@ -1260,6 +1284,10 @@ precomp_unsuspend!() = empty!(pkgs_precompile_suspended) precomp_suspended(pkg::PackageSpec) = pkg in pkgs_precompile_suspended precomp_prune_suspended!(pkgs::Vector{PackageSpec}) = filter!(in(pkgs), pkgs_precompile_suspended) +precomp_queue!(pkg::PackageSpec) = push!(pkgs_precompile_pending, pkg) +precomp_dequeue!(pkg::PackageSpec) = filter!(!isequal(pkg), pkgs_precompile_pending) +precomp_queued(pkg::PackageSpec) = pkg in pkgs_precompile_pending + function tree_hash(repo::LibGit2.GitRepo, tree_hash::String) try return LibGit2.GitObject(repo, tree_hash) @@ -1360,11 +1388,11 @@ function instantiate(ctx::Context; manifest::Union{Bool, Nothing}=nothing, if ctx.env.pkg !== nothing push!(art_pkgs, ctx.env.pkg) end - Operations.download_artifacts(ctx, art_pkgs; platform, verbose) + Operations.download_artifacts(ctx, art_pkgs; platform, verbose, io=ctx.io) # Run build scripts Operations.build_versions(ctx, union(UUID[pkg.uuid for pkg in new_apply], new_git); verbose) - allow_autoprecomp && Pkg._auto_precompile(ctx; kwargs...) + allow_autoprecomp && Pkg._auto_precompile(ctx) end @@ -1378,12 +1406,12 @@ function status(ctx::Context, pkgs::Vector{PackageSpec}; diff::Bool=false, mode= end -function activate(;temp=false,shared=false) +function activate(;temp=false, shared=false, io::IO=DEFAULT_IO[]) shared && pkgerror("Must give a name for a shared environment") temp && return activate(mktempdir()) Base.ACTIVE_PROJECT[] = nothing p = Base.active_project() - p === nothing || printpkgstyle(Context(), :Activating, "environment at $(pathrepr(p))") + p === nothing || printpkgstyle(io, :Activating, "environment at $(pathrepr(p))") add_snapshot_to_undo() return nothing end @@ -1404,7 +1432,7 @@ function _activate_dep(dep_name::AbstractString) end end end -function activate(path::AbstractString; shared::Bool=false, temp::Bool=false) +function activate(path::AbstractString; shared::Bool=false, temp::Bool=false, io::IO=DEFAULT_IO[]) temp && pkgerror("Can not give `path` argument when creating a temporary environment") if !shared # `pkg> activate path`/`Pkg.activate(path)` does the following @@ -1440,7 +1468,7 @@ function activate(path::AbstractString; shared::Bool=false, temp::Bool=false) p = Base.active_project() if p !== nothing n = ispath(p) ? "" : "new " - printpkgstyle(Context(), :Activating, "$(n)environment at $(pathrepr(p))") + printpkgstyle(io, :Activating, "$(n)environment at $(pathrepr(p))") end add_snapshot_to_undo() return nothing diff --git a/src/Artifacts.jl b/src/Artifacts.jl index 357bc5a2a8..b802dbdf57 100644 --- a/src/Artifacts.jl +++ b/src/Artifacts.jl @@ -286,7 +286,7 @@ end """ download_artifact(tree_hash::SHA1, tarball_url::String, tarball_hash::String; - verbose::Bool = false) + verbose::Bool = false, io::IO=DEFAULT_IO[]) Download/install an artifact into the artifact store. Returns `true` on success. @@ -299,6 +299,7 @@ function download_artifact( tarball_hash::Union{String, Nothing} = nothing; verbose::Bool = false, quiet_download::Bool = false, + io::IO=DEFAULT_IO[], ) if artifact_exists(tree_hash) return true @@ -319,7 +320,7 @@ function download_artifact( # the filesystem ACLs for executable permissions, which git tree hashes care about. try download_verify_unpack(tarball_url, tarball_hash, dest_dir, ignore_existence=true, - verbose=verbose, quiet_download=quiet_download) + verbose=verbose, quiet_download=quiet_download, io=io) catch e # Clean that destination directory out if something went wrong rm(dest_dir; force=true, recursive=true) @@ -339,7 +340,8 @@ function download_artifact( # `create_artifact()` wrapper does, so we use that here. calc_hash = try create_artifact() do dir - download_verify_unpack(tarball_url, tarball_hash, dir, ignore_existence=true, verbose=verbose, quiet_download=quiet_download) + download_verify_unpack(tarball_url, tarball_hash, dir, ignore_existence=true, verbose=verbose, + quiet_download=quiet_download, io=io) end catch e if isa(e, InterruptException) @@ -379,7 +381,10 @@ end """ ensure_artifact_installed(name::String, artifacts_toml::String; platform::AbstractPlatform = HostPlatform(), - pkg_uuid::Union{Base.UUID,Nothing}=nothing) + pkg_uuid::Union{Base.UUID,Nothing}=nothing, + verbose::Bool = false, + quiet_download::Bool = false, + io::IO=DEFAULT_IO[]) Ensures an artifact is installed, downloading it via the download information stored in `artifacts_toml` if necessary. Throws an error if unable to install. @@ -391,20 +396,22 @@ function ensure_artifact_installed(name::String, artifacts_toml::String; platform::AbstractPlatform = HostPlatform(), pkg_uuid::Union{Base.UUID,Nothing}=nothing, verbose::Bool = false, - quiet_download::Bool = false) + quiet_download::Bool = false, + io::IO=DEFAULT_IO[]) meta = artifact_meta(name, artifacts_toml; pkg_uuid=pkg_uuid, platform=platform) if meta === nothing error("Cannot locate artifact '$(name)' in '$(artifacts_toml)'") end return ensure_artifact_installed(name, meta, artifacts_toml; platform=platform, - verbose=verbose, quiet_download=quiet_download) + verbose=verbose, quiet_download=quiet_download, io=io) end function ensure_artifact_installed(name::String, meta::Dict, artifacts_toml::String; platform::AbstractPlatform = HostPlatform(), verbose::Bool = false, - quiet_download::Bool = false) + quiet_download::Bool = false, + io::IO=DEFAULT_IO[]) hash = SHA1(meta["git-tree-sha1"]) if !artifact_exists(hash) @@ -412,8 +419,8 @@ function ensure_artifact_installed(name::String, meta::Dict, artifacts_toml::Str # TODO: only do this if Pkg server knows about this package if (server = pkg_server()) !== nothing url = "$server/artifact/$hash" - download_success = with_show_download_info(name, quiet_download) do - download_artifact(hash, url; verbose=verbose, quiet_download=quiet_download) + download_success = with_show_download_info(io, name, quiet_download) do + download_artifact(hash, url; verbose=verbose, quiet_download=quiet_download, io=io) end download_success && return artifact_path(hash) end @@ -428,8 +435,8 @@ function ensure_artifact_installed(name::String, meta::Dict, artifacts_toml::Str for entry in meta["download"] url = entry["url"] tarball_hash = entry["sha256"] - download_success = with_show_download_info(name, quiet_download) do - download_artifact(hash, url, tarball_hash; verbose=verbose, quiet_download=quiet_download) + download_success = with_show_download_info(io, name, quiet_download) do + download_artifact(hash, url, tarball_hash; verbose=verbose, quiet_download=quiet_download, io=io) end download_success && return artifact_path(hash) end @@ -439,9 +446,7 @@ function ensure_artifact_installed(name::String, meta::Dict, artifacts_toml::Str end end -function with_show_download_info(f, name, quiet_download) - # TODO: Use DEFAULT_IO? - io = stderr +function with_show_download_info(f, io, name, quiet_download) fancyprint = can_fancyprint(io) if !quiet_download fancyprint && print_progress_bottom(io) @@ -464,7 +469,8 @@ end pkg_uuid = nothing, include_lazy = false, verbose = false, - quiet_download = false) + quiet_download = false, + io::IO=DEFAULT_IO[]) Installs all non-lazy artifacts from a given `(Julia)Artifacts.toml` file. `package_uuid` must be provided to properly support overrides from `Overrides.toml` entries in depots. @@ -479,7 +485,8 @@ function ensure_all_artifacts_installed(artifacts_toml::String; pkg_uuid::Union{Nothing,Base.UUID} = nothing, include_lazy::Bool = false, verbose::Bool = false, - quiet_download::Bool = false) + quiet_download::Bool = false, + io::IO=DEFAULT_IO[]) if !isfile(artifacts_toml) return end @@ -499,7 +506,7 @@ function ensure_all_artifacts_installed(artifacts_toml::String; # Otherwise, let's try and install it! ensure_artifact_installed(name, meta, artifacts_toml; platform=platform, - verbose=verbose, quiet_download=quiet_download) + verbose=verbose, quiet_download=quiet_download, io=io) end end diff --git a/src/MiniProgressBars.jl b/src/MiniProgressBars.jl index 5d300b7fb0..f4c4cd61fb 100644 --- a/src/MiniProgressBars.jl +++ b/src/MiniProgressBars.jl @@ -21,25 +21,6 @@ end const NONINTERACTIVE_TIME_GRANULARITY = Ref(2.0) const PROGRESS_BAR_PERCENTAGE_GRANULARITY = Ref(0.1) -function pretend_cursor() - io = stderr - bar = MiniProgressBar(; indent=2, header = "Progress", color = Base.info_color(), - percentage=false, always_reprint=true) - bar.max = 40 - start_progress(io, bar) - sleep(0.5) - for i in 1:40 - bar.current = i - print_progress_bottom(io) - println("Downloading ... $i") - show_progress(io, bar) - x = randstring(7) - sleep(0.01) - end - end_progress(io, bar) - print("Hello!") -end - function start_progress(io::IO, _::MiniProgressBar) ansi_disablecursor = "\e[?25l" print(io, ansi_disablecursor) @@ -67,18 +48,22 @@ function show_progress(io::IO, p::MiniProgressBar) end p.prev = p.current p.has_shown = true - n_filled = ceil(Int, p.width * perc / 100) - n_left = p.width - n_filled + + progress_text = if p.percentage + @sprintf "%2.1f %%" perc + else + string(p.current, "/", p.max) + end + + max_progress_width = max(0, min(displaysize(io)[2] - textwidth(p.header) - textwidth(progress_text) - 10 , p.width)) + n_filled = ceil(Int, max_progress_width * perc / 100) + n_left = max_progress_width - n_filled print(io, " "^p.indent) printstyled(io, p.header, color=p.color, bold=true) print(io, " [") print(io, "="^n_filled, ">") print(io, " "^n_left, "] ", ) - if p.percentage - @printf io "%2.1f %%" perc - else - print(io, p.current, "/", p.max) - end + print(io, progress_text) print(io, "\r") end diff --git a/src/Operations.jl b/src/Operations.jl index df258eb5ae..7f38bcb0f6 100644 --- a/src/Operations.jl +++ b/src/Operations.jl @@ -14,7 +14,8 @@ import ..Artifacts: ensure_all_artifacts_installed, artifact_names, extract_all_ using Base.BinaryPlatforms import ...Pkg import ...Pkg: pkg_server -import ..Pkg: can_fancyprint +import ...Pkg: can_fancyprint, DEFAULT_IO +import ..Types: printpkgstyle ######### # Utils # @@ -565,7 +566,8 @@ end function install_archive( urls::Vector{Pair{String,Bool}}, hash::SHA1, - version_path::String + version_path::String; + io::IO=DEFAULT_IO[] )::Bool tmp_objects = String[] url_success = false @@ -574,7 +576,7 @@ function install_archive( push!(tmp_objects, path) # for cleanup url_success = true try - PlatformEngines.download(url, path; verbose=false) + PlatformEngines.download(url, path; verbose=false, io=io) catch e e isa InterruptException && rethrow() url_success = false @@ -669,7 +671,8 @@ end function download_artifacts(ctx::Context, pkgs::Vector{PackageSpec}; platform::AbstractPlatform=HostPlatform(), julia_version = VERSION, - verbose::Bool=false) + verbose::Bool=false, + io::IO=DEFAULT_IO[]) # Filter out packages that have no source_path() # pkg_roots = String[p for p in source_path.((ctx,), pkgs) if p !== nothing] # this runs up against inference limits? pkg_roots = String[] @@ -677,12 +680,13 @@ function download_artifacts(ctx::Context, pkgs::Vector{PackageSpec}; p = source_path(ctx, pkg) p !== nothing && push!(pkg_roots, p) end - return download_artifacts(ctx, pkg_roots; platform=platform, verbose=verbose) + return download_artifacts(ctx, pkg_roots; platform=platform, verbose=verbose, io=io) end function download_artifacts(ctx::Context, pkg_roots::Vector{String}; platform::AbstractPlatform=HostPlatform(), - verbose::Bool=false) + verbose::Bool=false, + io::IO=DEFAULT_IO[]) # List of Artifacts.toml files that we're going to download from artifacts_tomls = String[] @@ -776,7 +780,7 @@ function download_source(ctx::Context, pkgs::Vector{PackageSpec}, url = get_archive_url_for_version(repo_url, pkg.tree_hash) url !== nothing && push!(archive_urls, url => false) end - success = install_archive(archive_urls, pkg.tree_hash, path) + success = install_archive(archive_urls, pkg.tree_hash, path, io=ctx.io) if success && readonly set_readonly(path) # In add mode, files should be read-only end @@ -1101,17 +1105,15 @@ function rm(ctx::Context, pkgs::Vector{PackageSpec}) println(ctx.io, "No changes") return end - # only declare `compat` for direct dependencies + # only declare `compat` for remaining direct or `extra` dependencies # `julia` is always an implicit direct dependency filter!(ctx.env.project.compat) do (name, _) - name == "julia" || name in keys(ctx.env.project.deps) + name == "julia" || name in keys(ctx.env.project.deps) || name in keys(ctx.env.project.extras) end - deps_names = append!(collect(keys(ctx.env.project.deps)), - collect(keys(ctx.env.project.extras))) + deps_names = union(keys(ctx.env.project.deps), keys(ctx.env.project.extras)) filter!(ctx.env.project.targets) do (target, deps) !isempty(filter!(in(deps_names), deps)) end - # only keep reachable manifest entires prune_manifest(ctx) # update project & manifest @@ -1232,7 +1234,7 @@ function add(ctx::Context, pkgs::Vector{PackageSpec}, new_git=UUID[]; # After downloading resolutionary packages, search for (Julia)Artifacts.toml files # and ensure they are all downloaded and unpacked as well: - download_artifacts(ctx, pkgs; platform=platform) + download_artifacts(ctx, pkgs; platform=platform, io=ctx.io) write_env(ctx.env) # write env before building show_update(ctx) @@ -1251,7 +1253,7 @@ function develop(ctx::Context, pkgs::Vector{PackageSpec}, new_git::Vector{UUID}; pkgs, deps_map = _resolve(ctx, pkgs, preserve) update_manifest!(ctx, pkgs, deps_map) new_apply = download_source(ctx, pkgs; readonly=true) - download_artifacts(ctx, pkgs; platform=platform) + download_artifacts(ctx, pkgs; platform=platform, io=ctx.io) write_env(ctx.env) # write env before building show_update(ctx) build_versions(ctx, union(UUID[pkg.uuid for pkg in new_apply], new_git)) @@ -1316,7 +1318,7 @@ function up(ctx::Context, pkgs::Vector{PackageSpec}, level::UpgradeLevel) deps_map = resolve_versions!(ctx, pkgs) update_manifest!(ctx, pkgs, deps_map) new_apply = download_source(ctx, pkgs) - download_artifacts(ctx, pkgs) + download_artifacts(ctx, pkgs; io=ctx.io) write_env(ctx.env) # write env before building show_update(ctx) build_versions(ctx, union(UUID[pkg.uuid for pkg in new_apply], new_git)) @@ -1359,7 +1361,7 @@ function pin(ctx::Context, pkgs::Vector{PackageSpec}) update_manifest!(ctx, pkgs, deps_map) new = download_source(ctx, pkgs) - download_artifacts(ctx, pkgs) + download_artifacts(ctx, pkgs; io=ctx.io) write_env(ctx.env) # write env before building show_update(ctx) build_versions(ctx, UUID[pkg.uuid for pkg in new]) @@ -1396,7 +1398,7 @@ function free(ctx::Context, pkgs::Vector{PackageSpec}) pkgs, deps_map = _resolve(ctx, pkgs, PRESERVE_TIERED) update_manifest!(ctx, pkgs, deps_map) new = download_source(ctx, pkgs) - download_artifacts(ctx, new) + download_artifacts(ctx, new; io=ctx.io) write_env(ctx.env) # write env before building show_update(ctx) build_versions(ctx, UUID[pkg.uuid for pkg in new]) @@ -1742,12 +1744,14 @@ print_single(ctx::Context, pkg::PackageSpec) = printstyled(ctx.io, stat_rep(pkg) is_instantiated(::Nothing) = false is_instantiated(x::PackageSpec) = x.version != VersionSpec() || is_stdlib(x.uuid) +# Compare an old and new node of the dependency graph and print a single line to summarize the change function print_diff(ctx::Context, old::Union{Nothing,PackageSpec}, new::Union{Nothing,PackageSpec}) if !is_instantiated(old) && is_instantiated(new) printstyled(ctx.io, "+ $(stat_rep(new))"; color=:light_green) elseif !is_instantiated(new) printstyled(ctx.io, "- $(stat_rep(old))"; color=:light_red) - elseif is_tracking_registry(old) && is_tracking_registry(new) && new.version isa VersionNumber && old.version isa VersionNumber + elseif is_tracking_registry(old) && is_tracking_registry(new) && + new.version isa VersionNumber && old.version isa VersionNumber && new.version != old.version if new.version > old.version printstyled(ctx.io, "↑ $(stat_rep(old)) ⇒ $(stat_rep(new; name=false))"; color=:light_yellow) else diff --git a/src/Pkg.jl b/src/Pkg.jl index 902bfd0811..fa60d9eacb 100644 --- a/src/Pkg.jl +++ b/src/Pkg.jl @@ -32,7 +32,7 @@ devdir(depot = depots1()) = get(ENV, "JULIA_PKG_DEVDIR", joinpath(depots1(), "de envdir(depot = depots1()) = joinpath(depot, "environments") const UPDATED_REGISTRY_THIS_SESSION = Ref(false) const OFFLINE_MODE = Ref(false) -const DEFAULT_IO = Ref{Union{Nothing,IO}}(nothing) +const DEFAULT_IO = Ref{IO}() can_fancyprint(io::IO) = (io isa Base.TTY) && (get(ENV, "CI", nothing) != "true") @@ -215,7 +215,7 @@ by starting julia with `--inline=no`. const test = API.test """ - Pkg.gc() + Pkg.gc(; io::IO=DEFAULT_IO[]) Garbage collect packages that are no longer reachable from any project. Only packages that are tracked by version are deleted, so no packages @@ -225,9 +225,9 @@ const gc = API.gc """ - Pkg.build(; verbose = false) - Pkg.build(pkg::Union{String, Vector{String}}; verbose = false) - Pkg.build(pkgs::Union{PackageSpec, Vector{PackageSpec}}; verbose = false) + Pkg.build(; verbose = false, io::IO=DEFAULT_IO[]) + Pkg.build(pkg::Union{String, Vector{String}}; verbose = false, io::IO=DEFAULT_IO[]) + Pkg.build(pkgs::Union{PackageSpec, Vector{PackageSpec}}; verbose = false, io::IO=DEFAULT_IO[]) Run the build script in `deps/build.jl` for `pkg` and all of its dependencies in depth-first recursive order. @@ -241,8 +241,8 @@ redirecting to the `build.log` file. const build = API.build """ - Pkg.pin(pkg::Union{String, Vector{String}}) - Pkg.pin(pkgs::Union{PackageSpec, Vector{PackageSpec}}) + Pkg.pin(pkg::Union{String, Vector{String}}; io::IO=DEFAULT_IO[]) + Pkg.pin(pkgs::Union{PackageSpec, Vector{PackageSpec}}; io::IO=DEFAULT_IO[]) Pin a package to the current version (or the one given in the `PackageSpec`) or to a certain git revision. A pinned package is never updated. @@ -256,8 +256,8 @@ Pkg.pin(name="Example", version="0.3.1") const pin = API.pin """ - Pkg.free(pkg::Union{String, Vector{String}}) - Pkg.free(pkgs::Union{PackageSpec, Vector{PackageSpec}}) + Pkg.free(pkg::Union{String, Vector{String}}; io::IO=DEFAULT_IO[]) + Pkg.free(pkgs::Union{PackageSpec, Vector{PackageSpec}}; io::IO=DEFAULT_IO[]) If `pkg` is pinned, remove the pin. If `pkg` is tracking a path, @@ -272,8 +272,8 @@ const free = API.free """ - Pkg.develop(pkg::Union{String, Vector{String}}) - Pkg.develop(pkgs::Union{Packagespec, Vector{Packagespec}}) + Pkg.develop(pkg::Union{String, Vector{String}}; io::IO=DEFAULT_IO[]) + Pkg.develop(pkgs::Union{Packagespec, Vector{Packagespec}}; io::IO=DEFAULT_IO[]) Make a package available for development by tracking it by path. If `pkg` is given with only a name or by a URL, the package will be downloaded @@ -346,7 +346,7 @@ Request a `ProjectInfo` struct which contains information about the active proje const project = API.project """ - Pkg.instantiate(; verbose = false) + Pkg.instantiate(; verbose = false, io::IO=DEFAULT_IO[]) If a `Manifest.toml` file exists in the active project, download all the packages declared in that manifest. @@ -360,7 +360,7 @@ dependencies in the manifest and instantiate the resulting project. const instantiate = API.instantiate """ - Pkg.resolve() + Pkg.resolve(; io::IO=DEFAULT_IO[]) Update the current manifest with potential changes to the dependency graph from packages that are tracking a path. @@ -368,7 +368,7 @@ from packages that are tracking a path. const resolve = API.resolve """ - Pkg.status([pkgs...]; mode::PackageMode=PKGMODE_PROJECT, diff::Bool=false) + Pkg.status([pkgs...]; mode::PackageMode=PKGMODE_PROJECT, diff::Bool=false, io::IO=stdout) Print out the status of the project/manifest. If `mode` is `PKGMODE_PROJECT`, print out status only about the packages @@ -389,7 +389,7 @@ const status = API.status """ - Pkg.activate([s::String]; shared::Bool=false) + Pkg.activate([s::String]; shared::Bool=false, io::IO=DEFAULT_IO[]) Activate the environment at `s`. The active environment is the environment that is modified by executing package commands. @@ -543,6 +543,7 @@ const RegistrySpec = Types.RegistrySpec function __init__() + DEFAULT_IO[] = stderr if isdefined(Base, :active_repl) REPLMode.repl_init(Base.active_repl) else @@ -636,7 +637,7 @@ function _run_precompilation_script_setup() write("registries/Registry/T/TestPkg/Package.toml", """ name = "TestPkg" uuid = "$uuid" - repo = "$tmp/TestPkg.jl" + repo = "$(escape_string(tmp))/TestPkg.jl" """) return tmp end diff --git a/src/PlatformEngines.jl b/src/PlatformEngines.jl index ba16c71d53..d32289f37e 100644 --- a/src/PlatformEngines.jl +++ b/src/PlatformEngines.jl @@ -5,7 +5,7 @@ module PlatformEngines using SHA, Downloads, Tar -import ...Pkg: Pkg, TOML, pkg_server, depots1, can_fancyprint +import ...Pkg: Pkg, TOML, pkg_server, depots1, can_fancyprint, DEFAULT_IO using ..MiniProgressBars using Base.BinaryPlatforms, p7zip_jll @@ -242,6 +242,7 @@ function download( verbose::Bool = false, headers::Vector{Pair{String,String}} = Pair{String,String}[], auth_header::Union{Pair{String,String}, Nothing} = nothing, + io::IO=DEFAULT_IO[] ) if auth_header === nothing auth_header = get_auth_header(url, verbose=verbose) @@ -253,7 +254,6 @@ function download( push!(headers, header) end - io = stderr do_fancy = verbose && can_fancyprint(io) progress = if do_fancy bar = MiniProgressBar(header="Downloading", color=Base.info_color()) @@ -269,7 +269,7 @@ function download( try Downloads.download(url, dest; headers, progress) finally - do_fancy && end_progress(stderr, bar) + do_fancy && end_progress(io, bar) end end @@ -405,6 +405,7 @@ end force::Bool = false, verbose::Bool = false, quiet_download::Bool = false, + io::IO=DEFAULT_IO[], ) Helper method to download tarball located at `url`, verify it matches the @@ -439,6 +440,7 @@ function download_verify_unpack( force::Bool = false, verbose::Bool = false, quiet_download::Bool = false, + io::IO=DEFAULT_IO[], ) # First, determine whether we should keep this tarball around remove_tarball = false diff --git a/src/REPLMode/REPLMode.jl b/src/REPLMode/REPLMode.jl index fed597b25f..8bed8d6598 100644 --- a/src/REPLMode/REPLMode.jl +++ b/src/REPLMode/REPLMode.jl @@ -514,7 +514,7 @@ function promptf() else project_name = projname(project_file) if project_name !== nothing - prefix = string("(", project_name, ") ") + prefix = "($(project_name)) " prev_prefix = prefix prev_project_timestamp = mtime(project_file) prev_project_file = project_file @@ -522,9 +522,9 @@ function promptf() end end if Pkg.OFFLINE_MODE[] - prefix = prefix * "[offline] " + prefix = "$(prefix)[offline] " end - return prefix * "pkg> " + return "$(prefix)pkg> " end # Set up the repl Pkg REPLMode diff --git a/src/Types.jl b/src/Types.jl index 4f0dbe7673..886ae2fe54 100644 --- a/src/Types.jl +++ b/src/Types.jl @@ -363,6 +363,19 @@ function stdlibs() end is_stdlib(uuid::UUID) = uuid in keys(stdlibs()) +# Find the entry in `STDLIBS_BY_VERSION` +# that corresponds to the requested version, and use that. +function get_last_stdlibs(julia_version::VersionNumber) + last_stdlibs = Dict{UUID,String}() + for (version, stdlibs) in STDLIBS_BY_VERSION + if VersionNumber(julia_version.major, julia_version.minor, julia_version.patch) < version + break + end + last_stdlibs = stdlibs + end + return last_stdlibs +end + # Allow asking if something is an stdlib for a particular version of Julia function is_stdlib(uuid::UUID, julia_version::Union{VersionNumber, Nothing}) # Only use the cache if we are asking for stdlibs in a custom Julia version @@ -381,16 +394,7 @@ function is_stdlib(uuid::UUID, julia_version::Union{VersionNumber, Nothing}) return false end - # If we are given an actual version, find the entry in `STDLIBS_BY_VERSION` - # that corresponds to the requested version, and use that. - last_stdlibs = Dict{UUID,String}() - for (version, stdlibs) in STDLIBS_BY_VERSION - if VersionNumber(julia_version.major, julia_version.minor, julia_version.patch) < version - break - end - last_stdlibs = stdlibs - end - + last_stdlibs = get_last_stdlibs(julia_version) # Note that if the user asks for something like `julia_version = 0.7.0`, we'll # fall through with an empty `last_stdlibs`, which will always return `false`. return uuid in keys(last_stdlibs) @@ -1009,7 +1013,7 @@ function clone_or_cp_registries(ctx::Context, regs::Vector{RegistrySpec}, depot: if url !== nothing && registry_use_pkg_server() # download from Pkg server try - download_verify_unpack(url, nothing, tmp, ignore_existence = true) + download_verify_unpack(url, nothing, tmp, ignore_existence = true, io = ctx.io) catch err pkgerror("could not download $url") end @@ -1147,7 +1151,7 @@ function update_registries(ctx::Context, regs::Vector{RegistrySpec} = collect_re # TODO: update faster by using a diff, if available mktempdir() do tmp try - download_verify_unpack(url, nothing, tmp, ignore_existence = true) + download_verify_unpack(url, nothing, tmp, ignore_existence = true, io = ctx.io) catch err @warn "could not download $url" end diff --git a/test/api.jl b/test/api.jl index 2907be2f41..42f06233d8 100644 --- a/test/api.jl +++ b/test/api.jl @@ -211,4 +211,8 @@ end end end end +@testset "Pkg.API.check_package_name: Error message if package name ends in .jl" begin + @test_throws Pkg.Types.PkgError("`Example.jl` is not a valid package name. Perhaps you meant `Example`") Pkg.API.check_package_name("Example.jl") +end + end # module APITests diff --git a/test/new.jl b/test/new.jl index ea00e38130..5b51791e9c 100644 --- a/test/new.jl +++ b/test/new.jl @@ -1719,6 +1719,16 @@ end @test !haskey(Pkg.Types.Context().env.project.compat, "Example") @test haskey(Pkg.Types.Context().env.project.compat, "julia") end end + # rm should not unnecessarily remove compat entries + isolate(loaded_depot=true) do; mktempdir() do tempdir + path = copy_test_package(tempdir, "CompatExtras") + Pkg.activate(path) + @test haskey(Pkg.Types.Context().env.project.compat, "Aqua") + @test haskey(Pkg.Types.Context().env.project.compat, "DataFrames") + Pkg.rm("DataFrames") + @test !haskey(Pkg.Types.Context().env.project.compat, "DataFrames") + @test haskey(Pkg.Types.Context().env.project.compat, "Aqua") + end end # rm removes unused recursive depdencies isolate(loaded_depot=true) do; mktempdir() do tempdir path = copy_test_package(tempdir, "SimplePackage") @@ -1904,14 +1914,32 @@ end @test occursin(r"Updating `.+Manifest\.toml`", readline(io)) @test occursin(r"\[7876af07\] ~ Example v\d\.\d\.\d `https://github.com/JuliaLang/Example.jl.git#master` ⇒ v\d\.\d\.\d", readline(io)) # Removing registered version + Pkg.rm("Example"; status_io=io) - @test occursin(r"Updating `.+Project.toml`", readline(io)) - @test occursin(r"\[7876af07\] - Example v\d\.\d\.\d", readline(io)) - @test occursin(r"Updating `.+Manifest.toml`", readline(io)) - @test occursin(r"\[7876af07\] - Example v\d\.\d\.\d", readline(io)) + output = String(take!(io)) + @test occursin(r"Updating `.+Project.toml`", output) + @test occursin(r"\[7876af07\] - Example v\d\.\d\.\d", output) + @test occursin(r"Updating `.+Manifest.toml`", output) + @test occursin(r"\[7876af07\] - Example v\d\.\d\.\d", output) + + # Pinning a registered package + Pkg.add("Example") + Pkg.pin("Example"; status_io=io) + output = String(take!(io)) + @test occursin(r"Updating `.+Project.toml`", output) + @test occursin(r"\[7876af07\] ~ Example v\d\.\d\.\d ⇒ v\d\.\d\.\d ⚲", output) + @test occursin(r"Updating `.+Manifest.toml`", output) + + # Free a pinned package + Pkg.free("Example"; status_io=io) + output = String(take!(io)) + @test occursin(r"Updating `.+Project.toml`", output) + @test occursin(r"\[7876af07\] ~ Example v\d\.\d\.\d ⚲ ⇒ v\d\.\d\.\d", output) + @test occursin(r"Updating `.+Manifest.toml`", output) end # Project Status API isolate(loaded_depot=true) do + Pkg.Registry.add(Pkg.RegistrySpec[], io=devnull) # load reg before io capturing io = PipeBuffer() ## empty project Pkg.status(;io=io) @@ -1930,6 +1958,7 @@ end end ## status warns when package not installed isolate() do + Pkg.Registry.add(Pkg.RegistrySpec[], io=devnull) # load reg before io capturing Pkg.activate(joinpath(@__DIR__, "test_packages", "Status")) io = PipeBuffer() Pkg.status(; io=io) @@ -1946,6 +1975,7 @@ end end # Manifest Status API isolate(loaded_depot=true) do + Pkg.Registry.add(Pkg.RegistrySpec[], io=devnull) # load reg before io capturing io = PipeBuffer() ## empty manifest Pkg.status(;io=io, mode=Pkg.PKGMODE_MANIFEST) @@ -1961,6 +1991,7 @@ end end # Diff API isolate(loaded_depot=true) do + Pkg.Registry.add(Pkg.RegistrySpec[], io=devnull) # load reg before io capturing io = PipeBuffer() projdir = dirname(Pkg.project().path) mkpath(projdir) @@ -2488,9 +2519,13 @@ using Pkg.Types: is_stdlib end @testset "STDLIBS_BY_VERSION up-to-date" begin - test_result = Pkg.Types.STDLIBS_BY_VERSION[end][2] == Pkg.Types.load_stdlib() + last_stdlibs = Pkg.Types.get_last_stdlibs(VERSION) + test_result = last_stdlibs == Pkg.Types.load_stdlib() if !test_result - @error("STDLIBS_BY_VERSION out of date! Re-run generate_historical_stdlibs.jl!") + @error("STDLIBS_BY_VERSION out of date! Manually fix given the info below, or re-run generate_historical_stdlibs.jl!") + @show length(last_stdlibs) length(Pkg.Types.load_stdlib()) + @show setdiff(last_stdlibs, Pkg.Types.load_stdlib()) + @show setdiff(Pkg.Types.load_stdlib(), last_stdlibs) end @test test_result end diff --git a/test/test_packages/CompatExtras/Project.toml b/test/test_packages/CompatExtras/Project.toml new file mode 100644 index 0000000000..fc36433e02 --- /dev/null +++ b/test/test_packages/CompatExtras/Project.toml @@ -0,0 +1,16 @@ +name = "CompatExtras" +uuid = "37f6bc05-9e90-43d5-90c6-4d69d7097606" +version = "0.1.0" + +[deps] +DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" + +[compat] +Aqua = "0.5" +DataFrames = "0.22" + +[extras] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" + +[targets] +test = ["Aqua"]