diff --git a/src/API.jl b/src/API.jl index fcbc3bd99a..4f45f12212 100644 --- a/src/API.jl +++ b/src/API.jl @@ -311,7 +311,9 @@ end function up(ctx::Context, pkgs::Vector{PackageSpec}; level::UpgradeLevel=UPLEVEL_MAJOR, mode::PackageMode=PKGMODE_PROJECT, - update_registry::Bool=true, kwargs...) + update_registry::Bool=true, + skip_writing_project::Bool=false, + kwargs...) Context!(ctx; kwargs...) if update_registry Registry.download_default_registries(ctx.io) @@ -328,13 +330,13 @@ function up(ctx::Context, pkgs::Vector{PackageSpec}; manifest_resolve!(ctx.env.manifest, pkgs) ensure_resolved(ctx.env.manifest, pkgs) end - Operations.up(ctx, pkgs, level) + Operations.up(ctx, pkgs, level; skip_writing_project) return end 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...) +function resolve(ctx::Context; skip_writing_project::Bool=false, kwargs...) + up(ctx; level=UPLEVEL_FIXED, mode=PKGMODE_MANIFEST, update_registry=false, skip_writing_project, kwargs...) return nothing end diff --git a/src/Operations.jl b/src/Operations.jl index 661ab5de96..3a0f895077 100644 --- a/src/Operations.jl +++ b/src/Operations.jl @@ -177,6 +177,16 @@ function set_compat(proj::Project, name::String, compat::String) proj.compat[name] = Types.Compat(Types.semver_spec(compat), compat) end +function reset_all_compat!(proj::Project) + for name in keys(proj.compat) + compat = proj.compat[name] + if compat.val != Types.semver_spec(compat.str) + proj.compat[name] = Types.Compat(Types.semver_spec(compat.str), compat.str) + end + end + return nothing +end + function collect_project!(pkg::PackageSpec, path::String, deps_map::Dict{UUID,Vector{PackageSpec}}) deps_map[pkg.uuid] = PackageSpec[] @@ -1218,7 +1228,8 @@ function up_load_manifest_info!(pkg::PackageSpec, entry::PackageEntry) # `pkg.version` and `pkg.tree_hash` is set by `up_load_versions!` end -function up(ctx::Context, pkgs::Vector{PackageSpec}, level::UpgradeLevel) +function up(ctx::Context, pkgs::Vector{PackageSpec}, level::UpgradeLevel; + skip_writing_project::Bool=false) new_git = Set{UUID}() # TODO check all pkg.version == VersionSpec() # set version constraints according to `level` @@ -1236,7 +1247,7 @@ function up(ctx::Context, pkgs::Vector{PackageSpec}, level::UpgradeLevel) update_manifest!(ctx.env, pkgs, deps_map, ctx.julia_version) new_apply = download_source(ctx) download_artifacts(ctx.env, julia_version=ctx.julia_version, io=ctx.io) - write_env(ctx.env) # write env before building + write_env(ctx.env; skip_writing_project) # write env before building show_update(ctx.env; io=ctx.io) build_versions(ctx, union(new_apply, new_git)) end @@ -1435,8 +1446,17 @@ function sandbox(fn::Function, ctx::Context, target::PackageSpec, target_path::S with_temp_env(tmp) do temp_ctx = Context() temp_ctx.env.project.deps[target.name] = target.uuid + + if force_latest_compatible_version + apply_force_latest_compatible_version!( + temp_ctx; + target_name = target.name, + allow_earlier_backwards_compatible_versions, + ) + end + try - Pkg.resolve(temp_ctx; io=devnull) + Pkg.resolve(temp_ctx; io=devnull, skip_writing_project=true) @debug "Using _parent_ dep graph" catch err# TODO err isa Resolve.ResolverError || rethrow() @@ -1444,10 +1464,14 @@ function sandbox(fn::Function, ctx::Context, target::PackageSpec, target_path::S @debug err @warn "Could not use exact versions of packages in manifest, re-resolving" temp_ctx.env.manifest = Dict(uuid => entry for (uuid, entry) in temp_ctx.env.manifest if isfixed(entry)) - Pkg.resolve(temp_ctx; io=devnull) + Pkg.resolve(temp_ctx; io=devnull, skip_writing_project=true) @debug "Using _clean_ dep graph" end + if force_latest_compatible_version + reset_all_compat!(temp_ctx.env.project) + end + # Absolutify stdlibs paths for (uuid, entry) in temp_ctx.env.manifest if is_stdlib(uuid) @@ -1456,17 +1480,6 @@ function sandbox(fn::Function, ctx::Context, target::PackageSpec, target_path::S end write_env(temp_ctx.env, update_undo = false) - if force_latest_compatible_version - result = check_force_latest_compatible_version( - temp_ctx; - target_name = target.name, - allow_earlier_backwards_compatible_versions, - ) - if !result - pkgerror("One or more direct dependencies is not at the latest compatible version") - end - end - # Run sandboxed code path_sep = Sys.iswindows() ? ';' : ':' withenv(fn, "JULIA_LOAD_PATH" => "@$(path_sep)$(tmp)", "JULIA_PROJECT" => nothing) @@ -1813,26 +1826,27 @@ function status(env::EnvCache, pkgs::Vector{PackageSpec}=PackageSpec[]; end end -function check_force_latest_compatible_version(ctx::Types.Context; - target_name = nothing, - allow_earlier_backwards_compatible_versions::Bool = true) +function apply_force_latest_compatible_version!(ctx::Types.Context; + target_name = nothing, + allow_earlier_backwards_compatible_versions::Bool = true) direct_deps = load_direct_deps(ctx.env) direct_deps_uuids = [dep.uuid for dep in direct_deps] uuid_list = filter(!is_stdlib, direct_deps_uuids) - isempty(uuid_list) && return true - results = check_force_latest_compatible_version.( - Ref(ctx), - uuid_list; - target_name, - allow_earlier_backwards_compatible_versions, - ) - return all(results) + for uuid in uuid_list + apply_force_latest_compatible_version!( + ctx, + uuid; + target_name, + allow_earlier_backwards_compatible_versions, + ) + end + return nothing end -function check_force_latest_compatible_version(ctx::Types.Context, - uuid::Base.UUID; - target_name= nothing, - allow_earlier_backwards_compatible_versions::Bool = true) +function apply_force_latest_compatible_version!(ctx::Types.Context, + uuid::Base.UUID; + target_name= nothing, + allow_earlier_backwards_compatible_versions::Bool = true) dep = ctx.env.manifest[uuid] name = dep.name active_version = dep.version @@ -1840,37 +1854,32 @@ function check_force_latest_compatible_version(ctx::Types.Context, if !has_compat if name != target_name @warn( - "Package does not have a [compat] entry", + "Dependency does not have a [compat] entry", name, uuid, active_version, target_name, ) end - return true + return nothing end - compat_entry = ctx.env.project.compat[name].val + old_compat_spec = ctx.env.project.compat[name].val latest_compatible_version = get_latest_compatible_version( ctx, uuid, - compat_entry, + old_compat_spec, ) earliest_backwards_compatible_version = get_earliest_backwards_compatible_version(latest_compatible_version) if allow_earlier_backwards_compatible_versions - result = active_version >= earliest_backwards_compatible_version + version_for_intersect = only_major_minor_patch(earliest_backwards_compatible_version) else - result = active_version >= latest_compatible_version - end - if !result - @error( - "Package is not at the latest compatible version", - name, - uuid, - compat_entry, - active_version, - latest_compatible_version, - earliest_backwards_compatible_version, - allow_earlier_backwards_compatible_versions, - ) + version_for_intersect = only_major_minor_patch(latest_compatible_version) end - return result + compat_for_intersect = Pkg.Types.semver_spec("≥ $(version_for_intersect)") + new_compat_spec = Base.intersect(old_compat_spec, compat_for_intersect) + ctx.env.project.compat[name].val = new_compat_spec + return nothing +end + +function only_major_minor_patch(ver::Base.VersionNumber) + return Base.VersionNumber(ver.major, ver.minor, ver.patch) end function get_earliest_backwards_compatible_version(ver::Base.VersionNumber) diff --git a/src/Types.jl b/src/Types.jl index c75b58f31c..ae8875f3f1 100644 --- a/src/Types.jl +++ b/src/Types.jl @@ -932,8 +932,9 @@ manifest_info(::Manifest, uuid::Nothing) = nothing function manifest_info(manifest::Manifest, uuid::UUID)::Union{PackageEntry,Nothing} return get(manifest, uuid, nothing) end -function write_env(env::EnvCache; update_undo=true) - if env.project != env.original_project +function write_env(env::EnvCache; update_undo=true, + skip_writing_project::Bool=false) + if (env.project != env.original_project) && (!skip_writing_project) write_project(env) end if env.manifest != env.original_manifest diff --git a/test/force_latest_compatible_version.jl b/test/force_latest_compatible_version.jl index e570735c79..c140ced578 100644 --- a/test/force_latest_compatible_version.jl +++ b/test/force_latest_compatible_version.jl @@ -6,9 +6,21 @@ import ..Pkg # ensure we are using the correct Pkg import ..Utils using Test -const expected_message_1 = "One or more direct dependencies is not at the latest compatible version" -const expected_message_2 = "Package is not at the latest compatible version" -const expected_message_3 = "Package does not have a [compat] entry" +get_exception_message(ex::Pkg.Resolve.ResolverError) = ex.msg + +function get_exception_and_message(f::Function) + ex = try + f() + catch ex + ex + end + msg = get_exception_message(ex) + return ex, msg +end + +const exception_type_1 = Pkg.Resolve.ResolverError +const message_1 = "Unsatisfiable requirements detected for package" +const message_2 = "Dependency does not have a [compat] entry" const test_package_parent_dir = joinpath( @__DIR__, @@ -84,25 +96,23 @@ const test_package_parent_dir = joinpath( ) == nothing ) @test_throws( - Pkg.Types.PkgError(expected_message_1), + exception_type_1, Pkg.test(; force_latest_compatible_version = true, allow_earlier_backwards_compatible_versions = false, ), ) - @test_logs( - (:error, expected_message_2), - match_mode=:any, - begin - try - Pkg.test(; - force_latest_compatible_version = true, - allow_earlier_backwards_compatible_versions = false, - ) - catch - end - end, - ) + let + f = function () + Pkg.test(; + force_latest_compatible_version = true, + allow_earlier_backwards_compatible_versions = false, + ) + end + ex, msg = get_exception_and_message(f) + @test ex isa exception_type_1 + @test occursin(message_1, msg) + end end @testset "`allow_earlier_backwards_compatible_versions` = true" begin @@ -135,23 +145,21 @@ const test_package_parent_dir = joinpath( ) == nothing ) @test_throws( - Pkg.Types.PkgError(expected_message_1), + exception_type_1, Pkg.test(; force_latest_compatible_version = true, ), ) - @test_logs( - (:error, expected_message_2), - match_mode=:any, - begin - try - Pkg.test(; - force_latest_compatible_version = true, - ) - catch - end - end, - ) + let + f = function () + Pkg.test(; + force_latest_compatible_version = true, + ) + end + ex, msg = get_exception_and_message(f) + @test ex isa exception_type_1 + @test occursin(message_1, msg) + end end @testset "provide a value for `allow_earlier_backwards_compatible_versions`" begin @@ -163,25 +171,23 @@ const test_package_parent_dir = joinpath( ) == nothing ) @test_throws( - Pkg.Types.PkgError(expected_message_1), + exception_type_1, Pkg.test(; force_latest_compatible_version = true, allow_earlier_backwards_compatible_versions, ), ) - @test_logs( - (:error, expected_message_2), - match_mode=:any, - begin - try - Pkg.test(; - force_latest_compatible_version = true, - allow_earlier_backwards_compatible_versions, - ) - catch - end - end, - ) + let + f = function () + Pkg.test(; + force_latest_compatible_version = true, + allow_earlier_backwards_compatible_versions, + ) + end + ex, msg = get_exception_and_message(f) + @test ex isa exception_type_1 + @test occursin(message_1, msg) + end end end end @@ -200,22 +206,43 @@ const test_package_parent_dir = joinpath( for force_latest_compatible_version in [false, true] @testset "default value of `allow_earlier_backwards_compatible_versions`" begin @test_throws( - Pkg.Resolve.ResolverError, + exception_type_1, Pkg.test(; force_latest_compatible_version, ), ) + let + f = function () + Pkg.test(; + force_latest_compatible_version, + ) + end + ex, msg = get_exception_and_message(f) + @test ex isa exception_type_1 + @test occursin(message_1, msg) + end end @testset "provide a value for `allow_earlier_backwards_compatible_versions`" begin for allow_earlier_backwards_compatible_versions in [false, true] @test_throws( - Pkg.Resolve.ResolverError, + exception_type_1, Pkg.test(; force_latest_compatible_version, allow_earlier_backwards_compatible_versions, ), ) + let + f = function () + Pkg.test(; + force_latest_compatible_version, + allow_earlier_backwards_compatible_versions, + ) + end + ex, msg = get_exception_and_message(f) + @test ex isa exception_type_1 + @test occursin(message_1, msg) + end end end end @@ -269,7 +296,7 @@ const test_package_parent_dir = joinpath( ) == nothing ) @test_logs( - (:warn, expected_message_3), + (:warn, message_2), match_mode=:any, Pkg.test(; force_latest_compatible_version = true, @@ -286,7 +313,7 @@ const test_package_parent_dir = joinpath( ) == nothing ) @test_logs( - (:warn, expected_message_3), + (:warn, message_2), match_mode=:any, Pkg.test(; force_latest_compatible_version = true,