From c8b8f6c2a03e0ad7f57fbdf4c5af45c2454ec33e Mon Sep 17 00:00:00 2001 From: Dilum Aluthge Date: Tue, 2 Sep 2025 15:57:57 -0400 Subject: [PATCH 1/2] Better stdlib support --- src/CompatHelper.jl | 1 + src/init.jl | 28 +++++++ src/utilities/new_versions.jl | 134 +++++++++++++++++++++++++++++---- src/utilities/types.jl | 9 ++- test/utilities/new_versions.jl | 16 +++- 5 files changed, 170 insertions(+), 18 deletions(-) create mode 100644 src/init.jl diff --git a/src/CompatHelper.jl b/src/CompatHelper.jl index a11fd063..0bf2d4e7 100644 --- a/src/CompatHelper.jl +++ b/src/CompatHelper.jl @@ -36,5 +36,6 @@ include("main.jl") include("exceptions.jl") include("dependencies.jl") include("pull_requests.jl") +include("init.jl") end # module diff --git a/src/init.jl b/src/init.jl new file mode 100644 index 00000000..fe117daf --- /dev/null +++ b/src/init.jl @@ -0,0 +1,28 @@ +const STDLIB_SET = Ref{Set{Package}}() + +function initialize_stdlib_set() + set = Set{Package}() + + stdlibs_from_pkg = Pkg.Types.stdlibs() + for (uuid, info) in pairs(stdlibs_from_pkg) + name = info[1] + push!(set, Package(name, uuid)) + end + + STDLIB_SET[] = set +end + +# Like `in`, but enforcing correct types +function typed_in(element::T, set::Set{T}) where {T} + return element in set +end + +function is_stdlib(p::Package) + # Not thread-safe, but we don't use multiple threads in this package, so not really a problem. + return typed_in(p, STDLIB_SET[]) +end + +function __init__() + initialize_stdlib_set() + return nothing +end diff --git a/src/utilities/new_versions.jl b/src/utilities/new_versions.jl index c6b7be39..c7ac350d 100644 --- a/src/utilities/new_versions.jl +++ b/src/utilities/new_versions.jl @@ -12,22 +12,131 @@ function body_info(::NewEntry, name::AbstractString) return "This is a brand new compat entry. Previously, you did not have a compat entry for the `$(name)` package.\n\n" end -function new_compat_entry( - ::KeepEntry, old_compat::AbstractString, new_compat::AbstractString -) - return "$(strip(old_compat)), $(strip(new_compat))" +function new_compat_entry(entry_type::EntryType, dep::DepInfo) + if is_stdlib(dep.package) + return new_compat_entry(entry_type, StdlibPackage(), dep, dep.latest_version) + else + return new_compat_entry(entry_type, RegularPackage(), dep) + end end -function new_compat_entry( - ::Union{DropEntry,NewEntry}, old_compat::AbstractString, new_compat::AbstractString -) +# RegularPackage: +# RegularPackage = not a stdlib +function new_compat_entry(entry_type::EntryType, ::RegularPackage, dep::DepInfo) + old_compat = dep.version_verbatim + new_compat = compat_version_number(dep.latest_version) + result = new_compat_entry( + entry_type, + RegularPackage(), + old_compat, + new_compat, + ) + return result +end +function new_compat_entry(::KeepEntry, ::RegularPackage, old_compat::String, new_compat::String) + return "$(strip(old_compat)), $(strip(new_compat))" +end +function new_compat_entry(::Union{DropEntry,NewEntry}, ::RegularPackage, old_compat::String, new_compat::String) return "$(strip(new_compat))" end - -function new_compat_entry(::EntryType, old_compat::Nothing, new_compat::AbstractString) +function new_compat_entry(::EntryType, ::RegularPackage, old_compat::Nothing, new_compat::String) return "$(strip(new_compat))" end +# StdlibPackage: +function new_compat_entry(entry_type::EntryType, ::StdlibPackage, dep::DepInfo) + old_compat = dep.version_verbatim + new_compat = compat_version_number(dep.latest_version) + result = new_compat_entry( + entry_type, + RegularPackage(), + dep, + old_compat, + new_compat, + ) + return result +end +function new_compat_entry(::KeepEntry, ::StdlibPackage, dep::DepInfo, old_compat::String) + compat_entry = strip(old_compat) + if !isnothing(dep.latest_version) + compat_entry = append_latest_version(old_compat, ver) + end + compat_entry = ensure_stdlib_entry_supports_older_julia(compat_entry) + return compat_entry +end +function new_compat_entry(::Union{DropEntry,NewEntry}, ::StdlibPackage, dep::DepInfo, old_compat::String) + # compat_entry = strip(old_compat) + # compat_entry = append_latest_version(old_compat, ver) # TODO + compat_entry = strip(compat_version_number(dep.latest_version)) + compat_entry = ensure_stdlib_entry_supports_older_julia(compat_entry) + return compat_entry +end +function new_compat_entry(::EntryType, ::StdlibPackage, dep::DepInfo, old_compat::Nothing) + compat_entry = stdlib_initial_compat_entry(dep) + if isnothing(dep.latest_version) + compat_entry = append_latest_version(old_compat, ver) + end + compat_entry = ensure_stdlib_entry_supports_older_julia(compat_entry::String) + return compat_entry +end + +function append_latest_version(old_compat::String, ver::VersionNumber) + potential_addition = compat_version_number(ver) + if ver in Pkg.Types.semver_spec(old_compat) + return old_compat + else + new_compat = "$(strip(old_compat)), $(strip(potential_addition))" + return new_compat + end +end + +# Like `in`, but enforcing correct types +function typed_equality(x::T, y::T) where {T} + return x == y +end + +function stdlib_initial_compat_entry(dep::DepInfo) + SHA_package = Package("SHA", UUIDs.UUID("ea8e919c-243c-51af-8825-aaa63cd721ce")) + if typed_equality(dep.package, SHA_package) + # The SHA stdlib is a weird case, because it has had both 1.x.y versions and 0.7.x versions. + # So we need to support both. + # (plus "< 0.0.1", of course). + compat_entry = "<0.0.1, 0.7, 1" + else + # For all other stdlibs, just 1.x.y is sufficient. + # (plus "< 0.0.1", of course). + compat_entry = "< 0.0.1, 1" + end + return compat_entry +end + +function ensure_stdlib_entry_supports_older_julia(compat_entry::String) + # On older versions of Julia, when you run `Pkg.test()`, Pkg will set the versions + # of all stdlibs to v0.0.0 + # + # Therefore, if a package Foo.jl depends on stdlib Bar, and if Foo.jl's [compat] entry + # for Bar does not cover v0.0.0, then when you run `Pkg.test("MyPackage)`, you will get + # an error. This occurs even if Foo.jl is an indirect (recursive) dependency of MyPackage. + # + # Therefore, if Foo.jl's [compat] entry for Bar does not cover v0.0.0, then we can see + # widespread breakage (throughout the ecosystem) whenever people try to run their package + # tests (either locally or in CI) on older versions of Julia. We don't want that kind of + # breakage (even if the Julia versions are a little old), + # + # Note: To reduce noise, we won't open a PR if the only change would be e.g. + # "1, 2" ---> "< 0.0.1, 1, 2". + # We'll only open a PR if there is some other change (a non-"< 0.0.1" change) that we are + # already going to open a PR for. And in that case, we'll just include the "< 0.0.1" in + # that same PR. + # Hopefully this will reduce noise. + + compat_spec = Pkg.Types.semver_spec(compat_entry) + if v"0.0.0" ∉ compat_spec + compat_entry = "< 0.0.1, " * compat_entry + end + return compat_entry +end + function compat_version_number(ver::VersionNumber) (ver.major > 0) && return "$(ver.major)" (ver.minor > 0) && return "0.$(ver.minor)" @@ -239,15 +348,12 @@ function make_pr_for_new_version( end # Get new compat entry version, pr title, and pr body text - compat_entry_for_latest_version = compat_version_number(dep.latest_version) - brand_new_compat = new_compat_entry( - entry_type, dep.version_verbatim, compat_entry_for_latest_version - ) + brand_new_compat = new_compat_entry(entry_type, dep) new_pr_title, new_pr_body = pr_info( dep.version_verbatim, dep.package.name, section_string(dep_section), - compat_entry_for_latest_version, + compat_version_number(dep.latest_version), brand_new_compat, subdir_string(subdir), body_info(entry_type, dep.package.name), diff --git a/src/utilities/types.jl b/src/utilities/types.jl index 61aa4818..af07a00b 100644 --- a/src/utilities/types.jl +++ b/src/utilities/types.jl @@ -1,7 +1,10 @@ abstract type EntryType end -struct KeepEntry <: EntryType end -struct DropEntry <: EntryType end -struct NewEntry <: EntryType end +struct KeepEntry <: EntryType end # "1, 2" ---> "1, 2, 3" +struct DropEntry <: EntryType end # "1, 2" ---> "3" +struct NewEntry <: EntryType end # no [compat] entry ---> "3" + +struct StdlibPackage end +struct RegularPackage end # RegularPackage = not a stdlib struct DefaultBranch end diff --git a/test/utilities/new_versions.jl b/test/utilities/new_versions.jl index e6596487..62fa05b0 100644 --- a/test/utilities/new_versions.jl +++ b/test/utilities/new_versions.jl @@ -2,6 +2,8 @@ keep_entry = CompatHelper.KeepEntry() drop_entry = CompatHelper.DropEntry() new_entry = CompatHelper.NewEntry() +not_stdlib = CompatHelper.RegularPackage() + hostname = "hostname" github = GitHubActions(; clone_hostname=hostname) gitlab = GitLabCI(; clone_hostname=hostname) @@ -60,7 +62,7 @@ end @testset "$(entry)" for entry in entries for case in cases[entry] old_compat, new_compat, expected = case - result = CompatHelper.new_compat_entry(entry, old_compat, new_compat) + result = CompatHelper.new_compat_entry(entry, not_stdlib, old_compat, new_compat) @test result == expected end @@ -79,6 +81,18 @@ end @test CompatHelper.compat_version_number(vn) == expected end +@testset "append_latest_version -- $(inputs)" for (inputs, expected) in [ + (("1", VersionNumber("1.0.0")), "1"), + (("1", VersionNumber("2.0.0")), "1, 2"), + (("1", VersionNumber("1.0.0")), "1"), + (("1, 2", VersionNumber("2.0.0")), "1, 2"), + (("1, 2", VersionNumber("3.0.0")), "1, 2, 3"), +] + (old_compat, ver) = inputs + observed_output = CompatHelper.append_latest_version(old_compat, ver) + @test observed_output == expected +end + @testset "subdir_string -- $(subdir)" for (subdir, expected) in [ ("foobar", "foobar"), ("foo/bar", "bar"), ("1", "1"), ("", "") ] From 074d182b424c3472f5e452a60d44913ea9430c86 Mon Sep 17 00:00:00 2001 From: Dilum Aluthge Date: Tue, 2 Sep 2025 16:01:09 -0400 Subject: [PATCH 2/2] fix format --- src/utilities/new_versions.jl | 31 ++++++++++++++----------------- test/utilities/new_versions.jl | 4 +++- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/utilities/new_versions.jl b/src/utilities/new_versions.jl index c7ac350d..a0977fd9 100644 --- a/src/utilities/new_versions.jl +++ b/src/utilities/new_versions.jl @@ -25,21 +25,22 @@ end function new_compat_entry(entry_type::EntryType, ::RegularPackage, dep::DepInfo) old_compat = dep.version_verbatim new_compat = compat_version_number(dep.latest_version) - result = new_compat_entry( - entry_type, - RegularPackage(), - old_compat, - new_compat, - ) + result = new_compat_entry(entry_type, RegularPackage(), old_compat, new_compat) return result end -function new_compat_entry(::KeepEntry, ::RegularPackage, old_compat::String, new_compat::String) +function new_compat_entry( + ::KeepEntry, ::RegularPackage, old_compat::String, new_compat::String +) return "$(strip(old_compat)), $(strip(new_compat))" end -function new_compat_entry(::Union{DropEntry,NewEntry}, ::RegularPackage, old_compat::String, new_compat::String) +function new_compat_entry( + ::Union{DropEntry,NewEntry}, ::RegularPackage, old_compat::String, new_compat::String +) return "$(strip(new_compat))" end -function new_compat_entry(::EntryType, ::RegularPackage, old_compat::Nothing, new_compat::String) +function new_compat_entry( + ::EntryType, ::RegularPackage, old_compat::Nothing, new_compat::String +) return "$(strip(new_compat))" end @@ -47,13 +48,7 @@ end function new_compat_entry(entry_type::EntryType, ::StdlibPackage, dep::DepInfo) old_compat = dep.version_verbatim new_compat = compat_version_number(dep.latest_version) - result = new_compat_entry( - entry_type, - RegularPackage(), - dep, - old_compat, - new_compat, - ) + result = new_compat_entry(entry_type, RegularPackage(), dep, old_compat, new_compat) return result end function new_compat_entry(::KeepEntry, ::StdlibPackage, dep::DepInfo, old_compat::String) @@ -64,7 +59,9 @@ function new_compat_entry(::KeepEntry, ::StdlibPackage, dep::DepInfo, old_compat compat_entry = ensure_stdlib_entry_supports_older_julia(compat_entry) return compat_entry end -function new_compat_entry(::Union{DropEntry,NewEntry}, ::StdlibPackage, dep::DepInfo, old_compat::String) +function new_compat_entry( + ::Union{DropEntry,NewEntry}, ::StdlibPackage, dep::DepInfo, old_compat::String +) # compat_entry = strip(old_compat) # compat_entry = append_latest_version(old_compat, ver) # TODO compat_entry = strip(compat_version_number(dep.latest_version)) diff --git a/test/utilities/new_versions.jl b/test/utilities/new_versions.jl index 62fa05b0..238ff93b 100644 --- a/test/utilities/new_versions.jl +++ b/test/utilities/new_versions.jl @@ -62,7 +62,9 @@ end @testset "$(entry)" for entry in entries for case in cases[entry] old_compat, new_compat, expected = case - result = CompatHelper.new_compat_entry(entry, not_stdlib, old_compat, new_compat) + result = CompatHelper.new_compat_entry( + entry, not_stdlib, old_compat, new_compat + ) @test result == expected end