From 07bbaf07a5a325b9400f1ac543b8255517fe52b8 Mon Sep 17 00:00:00 2001 From: Matthew Gilliam Date: Tue, 19 May 2026 14:14:08 -0400 Subject: [PATCH 1/7] update test matrix to include newer major and minor versions --- .github/workflows/main.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 78935855..e84547ca 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,8 +17,8 @@ jobs: - name: Install OTP and Elixir uses: erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # v1.24.0 with: - otp-version: 28.1 - elixir-version: 1.18.4 + otp-version: 28.5 + elixir-version: 1.19.5 version-type: strict - name: Install dependencies @@ -48,10 +48,11 @@ jobs: pair: # - erlang: maint # elixir: main - - - erlang: 28.1 - elixir: 1.18.4 - - erlang: 27.2 + - erlang: 29.0 + elixir: 1.20.0-rc.5 + - erlang: 28.5 + elixir: 1.19.5 + - erlang: 27.3 elixir: 1.17.3 - erlang: 26.2 elixir: 1.16.1 @@ -65,8 +66,8 @@ jobs: elixir: 1.12.3 env: - HEXPM_OTP: OTP-28.1 - HEXPM_ELIXIR: v1.18.4 + HEXPM_OTP: OTP-28.5 + HEXPM_ELIXIR: v1.19.5 HEXPM_BRANCH: main HEXPM_PATH: hexpm HEXPM_ELIXIR_PATH: hexpm_elixir From 22beed3f0a9e60fe5c7e54e6760d91dfb2e09935 Mon Sep 17 00:00:00 2001 From: Matthew Gilliam Date: Tue, 19 May 2026 14:33:41 -0400 Subject: [PATCH 2/7] test to see if yaml is truncating 29.0 to 29 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e84547ca..f5d24206 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -48,7 +48,7 @@ jobs: pair: # - erlang: maint # elixir: main - - erlang: 29.0 + - erlang: "29.0" elixir: 1.20.0-rc.5 - erlang: 28.5 elixir: 1.19.5 From fbcb2aa3162aaa658771ad63e99a7b2f95cab9f2 Mon Sep 17 00:00:00 2001 From: Matthew Gilliam Date: Tue, 19 May 2026 23:15:39 -0400 Subject: [PATCH 3/7] Fix test fixtures for ecto 3.3.2 and ecto_sql 3.3.3 ecto_3_3_2/mix.exs declared version "3.3.1" instead of "3.3.2", causing Mix 1.20 to raise a nomatchvsn error when updating ecto_sql (which pulls in ecto ~> 3.3.2). Also fix the copy-paste module name in ecto_sql_3_3_3. --- test/fixtures/ecto_3_3_2/mix.exs | 2 +- test/fixtures/ecto_sql_3_3_3/mix.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/fixtures/ecto_3_3_2/mix.exs b/test/fixtures/ecto_3_3_2/mix.exs index 2184222b..b0352e95 100644 --- a/test/fixtures/ecto_3_3_2/mix.exs +++ b/test/fixtures/ecto_3_3_2/mix.exs @@ -2,7 +2,7 @@ defmodule Ecto_3_3_2.Fixture.MixProject do use Mix.Project def project do - [app: :ecto, version: "3.3.1", deps: deps()] + [app: :ecto, version: "3.3.2", deps: deps()] end defp deps do diff --git a/test/fixtures/ecto_sql_3_3_3/mix.exs b/test/fixtures/ecto_sql_3_3_3/mix.exs index dd990000..244ac2ed 100644 --- a/test/fixtures/ecto_sql_3_3_3/mix.exs +++ b/test/fixtures/ecto_sql_3_3_3/mix.exs @@ -1,4 +1,4 @@ -defmodule Ecto.SQL_3_3_2.Fixture.MixProject do +defmodule Ecto.SQL_3_3_3.Fixture.MixProject do use Mix.Project def project do From 61c4f714460dc88168a13d8862776b4f5cb7c76a Mon Sep 17 00:00:00 2001 From: Matthew Gilliam Date: Wed, 20 May 2026 10:56:34 -0400 Subject: [PATCH 4/7] Fix false :divergedreq on Elixir >= 1.20 when updating deps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Elixir 1.20 removed the `recently_fetched?` guard from Mix.Dep.Loader.validate_app. Previously, when a dep was just fetched, validate_app would short-circuit to :compile status instead of reading the (potentially stale) .app file from the build directory. Without that guard, when a dep like ecto is updated from 3.3.1 → 3.3.2 during `deps.update ecto_sql`, Mix reads the old compiled ecto.app (version 3.3.1) and marks ecto as {:ok, "3.3.1"}. When ecto_sql 3.3.3 then requires ecto ~> 3.3.2, req_mismatch triggers :divergedreq — a false positive since ecto is already being updated to 3.3.2. Fix: delete the stale .app file from the build directory in Hex.SCM.update/1 after extracting the new tarball. This causes validate_app to see {:noappfile, ...} (not {:ok, "3.3.1"}), so req_mismatch is not triggered. --- lib/hex/scm.ex | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/hex/scm.ex b/lib/hex/scm.ex index 3fcf921e..9349c2a1 100644 --- a/lib/hex/scm.ex +++ b/lib/hex/scm.ex @@ -196,6 +196,15 @@ defmodule Hex.SCM do raise("Checksum mismatch against registry (outer)") end + # Delete stale build artifact so Mix's dep loader does not read an outdated + # .app version and incorrectly mark the dependency as :divergedreq. This + # situation arises on Elixir >= 1.20 which removed the `recently_fetched?` + # guard from Mix.Dep.Loader.validate_app (previously the guard prevented the + # loader from reading a stale .app file after a fetch). + if build = opts[:build] do + Path.join([build, "ebin", "#{name}.app"]) |> File.rm() + end + build_tools = guess_build_tools(meta) managers = From ebd72938c661a2d1e8c48c077975db0ef9f520e6 Mon Sep 17 00:00:00 2001 From: Matthew Gilliam Date: Wed, 20 May 2026 11:07:20 -0400 Subject: [PATCH 5/7] Add version-independent regression test for stale .app removal The existing ":divergedreq" integration test only fails on Elixir >= 1.20 (older versions mask the bug via Mix.Dep.Loader's recently_fetched? guard), so it cannot guard the fix when the suite runs on 1.19 and earlier. This test asserts the concrete behavior of the fix directly: after a transitive dependency is updated (ecto 3.3.1 -> 3.3.2), its stale compiled .app file must be gone. It fails without the SCM fix on every Elixir version and passes with it. Verified: - 1.19.5 without fix: existing test passes (masked), this test fails - 1.19.5 with fix: both pass - 1.20.0-rc.5 without fix: both fail - 1.20.0-rc.5 with fix: both pass --- test/hex/mix_task_test.exs | 41 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/test/hex/mix_task_test.exs b/test/hex/mix_task_test.exs index 451d54a7..69a3ad65 100644 --- a/test/hex/mix_task_test.exs +++ b/test/hex/mix_task_test.exs @@ -1053,6 +1053,47 @@ defmodule Hex.MixTaskTest do ]) end + test "updating a dependency removes its stale compiled .app file" do + Mix.Project.push(DependsOnEctoSQL) + + in_tmp(fn -> + Hex.State.put(:cache_home, File.cwd!()) + + Mix.Dep.Lock.write(%{ + ecto_sql: {:hex, :ecto_sql, "3.3.2"}, + ecto: {:hex, :ecto, "3.3.1"} + }) + + Mix.Task.run("deps.get") + compile() + + ecto_app = Path.join([Mix.Project.build_path(), "lib", "ecto", "ebin", "ecto.app"]) + + # Precondition: ecto 3.3.1 has been compiled and its .app reports 3.3.1. + assert {:ok, [{:application, :ecto, props}]} = + :file.consult(String.to_charlist(ecto_app)) + + assert props[:vsn] == ~c"3.3.1" + + Mix.Task.run("deps.update", ["ecto_sql"]) + + # When ecto is updated 3.3.1 -> 3.3.2 the SCM must remove the stale + # compiled .app. Otherwise Mix's dep loader (on Elixir >= 1.20, which + # dropped the recently_fetched? guard) reads the stale 3.3.1 version and + # incorrectly marks ecto as :divergedreq against ecto_sql 3.3.3's + # `~> 3.3.2` requirement. + refute File.exists?(ecto_app) + end) + after + purge([ + Ecto.SQL_3_3_2.Fixture.MixProject, + Ecto.SQL_3_3_3.Fixture.MixProject, + Ecto.Enum_1_4_0.Fixture.MixProject, + Ecto_3_3_1.Fixture.MixProject, + Ecto_3_3_2.Fixture.MixProject + ]) + end + test "prints a sponsors tip when updating or adding a package with sponsor link" do Mix.Project.push(DependsOnSponsored) From 00b078b7d655ea93c3513d7ef2fec201a8f357a1 Mon Sep 17 00:00:00 2001 From: Matthew Gilliam Date: Thu, 21 May 2026 09:17:50 -0400 Subject: [PATCH 6/7] revert scm change an bump to stable 1.20 --- .github/workflows/main.yml | 2 +- lib/hex/scm.ex | 9 --------- test/hex/mix_task_test.exs | 41 -------------------------------------- 3 files changed, 1 insertion(+), 51 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f5d24206..62bcf64e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -49,7 +49,7 @@ jobs: # - erlang: maint # elixir: main - erlang: "29.0" - elixir: 1.20.0-rc.5 + elixir: 1.20.0 - erlang: 28.5 elixir: 1.19.5 - erlang: 27.3 diff --git a/lib/hex/scm.ex b/lib/hex/scm.ex index 9349c2a1..3fcf921e 100644 --- a/lib/hex/scm.ex +++ b/lib/hex/scm.ex @@ -196,15 +196,6 @@ defmodule Hex.SCM do raise("Checksum mismatch against registry (outer)") end - # Delete stale build artifact so Mix's dep loader does not read an outdated - # .app version and incorrectly mark the dependency as :divergedreq. This - # situation arises on Elixir >= 1.20 which removed the `recently_fetched?` - # guard from Mix.Dep.Loader.validate_app (previously the guard prevented the - # loader from reading a stale .app file after a fetch). - if build = opts[:build] do - Path.join([build, "ebin", "#{name}.app"]) |> File.rm() - end - build_tools = guess_build_tools(meta) managers = diff --git a/test/hex/mix_task_test.exs b/test/hex/mix_task_test.exs index 69a3ad65..451d54a7 100644 --- a/test/hex/mix_task_test.exs +++ b/test/hex/mix_task_test.exs @@ -1053,47 +1053,6 @@ defmodule Hex.MixTaskTest do ]) end - test "updating a dependency removes its stale compiled .app file" do - Mix.Project.push(DependsOnEctoSQL) - - in_tmp(fn -> - Hex.State.put(:cache_home, File.cwd!()) - - Mix.Dep.Lock.write(%{ - ecto_sql: {:hex, :ecto_sql, "3.3.2"}, - ecto: {:hex, :ecto, "3.3.1"} - }) - - Mix.Task.run("deps.get") - compile() - - ecto_app = Path.join([Mix.Project.build_path(), "lib", "ecto", "ebin", "ecto.app"]) - - # Precondition: ecto 3.3.1 has been compiled and its .app reports 3.3.1. - assert {:ok, [{:application, :ecto, props}]} = - :file.consult(String.to_charlist(ecto_app)) - - assert props[:vsn] == ~c"3.3.1" - - Mix.Task.run("deps.update", ["ecto_sql"]) - - # When ecto is updated 3.3.1 -> 3.3.2 the SCM must remove the stale - # compiled .app. Otherwise Mix's dep loader (on Elixir >= 1.20, which - # dropped the recently_fetched? guard) reads the stale 3.3.1 version and - # incorrectly marks ecto as :divergedreq against ecto_sql 3.3.3's - # `~> 3.3.2` requirement. - refute File.exists?(ecto_app) - end) - after - purge([ - Ecto.SQL_3_3_2.Fixture.MixProject, - Ecto.SQL_3_3_3.Fixture.MixProject, - Ecto.Enum_1_4_0.Fixture.MixProject, - Ecto_3_3_1.Fixture.MixProject, - Ecto_3_3_2.Fixture.MixProject - ]) - end - test "prints a sponsors tip when updating or adding a package with sponsor link" do Mix.Project.push(DependsOnSponsored) From 9fdf6e5661aa3c9a501f02cefad172fb98b3aa67 Mon Sep 17 00:00:00 2001 From: Matthew Gilliam Date: Thu, 21 May 2026 22:11:53 -0400 Subject: [PATCH 7/7] rc6 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 62bcf64e..b6fc8efb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -49,7 +49,7 @@ jobs: # - erlang: maint # elixir: main - erlang: "29.0" - elixir: 1.20.0 + elixir: 1.20.0-rc.6 - erlang: 28.5 elixir: 1.19.5 - erlang: 27.3