From 65101e941e2ffc38e521eb04c896751d5a2f916f Mon Sep 17 00:00:00 2001 From: Onni Hakala Date: Wed, 30 Sep 2026 00:46:16 +0300 Subject: [PATCH] Hash template paths relative to root __mix_recompile__?/0 compared an md5 of absolute template paths, so the same project in another directory (git worktree, copy-on-write clone, restored CI cache) recompiled every embed_templates module although no template was added or removed. Closes #15. --- lib/phoenix/template.ex | 11 +++++++++-- test/phoenix/template_test.exs | 10 ++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/phoenix/template.ex b/lib/phoenix/template.ex index b7ccceb..7870e66 100644 --- a/lib/phoenix/template.ex +++ b/lib/phoenix/template.ex @@ -385,7 +385,14 @@ defmodule Phoenix.Template do """ @spec hash(root, pattern :: String.t(), %{atom => module}) :: binary def hash(root, pattern \\ @default_pattern, engines \\ engines()) do - find_all(root, pattern, engines) + hash_paths(find_all(root, pattern, engines), root) + end + + # Paths are hashed relative to root, so the hash does not change + # when the project is moved or cloned to another directory. + defp hash_paths(paths, root) do + paths + |> Enum.map(&Path.relative_to(&1, root)) |> Enum.sort() |> :erlang.md5() end @@ -449,7 +456,7 @@ defmodule Phoenix.Template do __idempotent_setup__(module, engines) # Store the hashes so we define __mix_recompile__? - hash = paths |> Enum.sort() |> :erlang.md5() + hash = hash_paths(paths, root) args = if given_engines, do: [root, pattern, Macro.escape(given_engines)], else: [root, pattern] diff --git a/test/phoenix/template_test.exs b/test/phoenix/template_test.exs index c30eeb4..b02bcdc 100644 --- a/test/phoenix/template_test.exs +++ b/test/phoenix/template_test.exs @@ -30,6 +30,16 @@ defmodule Phoenix.TemplateTest do assert is_binary(Template.hash(@templates)) end + @tag :tmp_dir + test "hash/3 does not depend on where the root lives", %{tmp_dir: tmp_dir} do + copy = Path.join(tmp_dir, "templates") + File.cp_r!(@templates, copy) + assert Template.hash(copy) == Template.hash(@templates) + + File.write!(Path.join(copy, "added.html.eex"), "") + refute Template.hash(copy) == Template.hash(@templates) + end + test "format_encoder/1 returns the formatter for a given template" do assert Template.format_encoder("html") == Phoenix.HTML.Engine assert Template.format_encoder("js") == Phoenix.HTML.Engine