Skip to content

Commit 8923da0

Browse files
committed
Ensure duplicate modules are recompiled, closes #13040
1 parent 0e1a5d3 commit 8923da0

File tree

2 files changed

+67
-26
lines changed

2 files changed

+67
-26
lines changed

lib/mix/lib/mix/compilers/elixir.ex

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ defmodule Mix.Compilers.Elixir do
173173
compiler_loop(stale, stale_modules, dest, timestamp, opts, state)
174174
else
175175
{:ok, info, state} ->
176-
{modules, _exports, sources, pending_modules, _pending_exports} = state
176+
{modules, _exports, sources, pending_modules, _stale_exports} = state
177177

178178
previous_warnings =
179179
if Keyword.get(opts, :all_warnings, true),
@@ -531,7 +531,7 @@ defmodule Mix.Compilers.Elixir do
531531

532532
defp remove_stale_entry(entry, acc, sources, stale_exports, compile_path) do
533533
module(module: module, sources: source_files, export: export) = entry
534-
{rest, exports, changed, stale} = acc
534+
{rest, exports, changed, stale_modules} = acc
535535

536536
{compile_references, export_references, runtime_references} =
537537
Enum.reduce(source_files, {[], [], []}, fn file, {compile_acc, export_acc, runtime_acc} ->
@@ -548,20 +548,20 @@ defmodule Mix.Compilers.Elixir do
548548
# If I changed in disk or have a compile time reference to
549549
# something stale or have a reference to an old export,
550550
# I need to be recompiled.
551-
has_any_key?(changed, source_files) or has_any_key?(stale, compile_references) or
551+
has_any_key?(changed, source_files) or has_any_key?(stale_modules, compile_references) or
552552
has_any_key?(stale_exports, export_references) ->
553553
remove_and_purge(beam_path(compile_path, module), module)
554554
changed = Enum.reduce(source_files, changed, &Map.put(&2, &1, true))
555-
{rest, Map.put(exports, module, export), changed, Map.put(stale, module, true)}
555+
{rest, Map.put(exports, module, export), changed, Map.put(stale_modules, module, true)}
556556

557557
# If I have a runtime references to something stale,
558558
# I am stale too.
559-
has_any_key?(stale, runtime_references) ->
560-
{[entry | rest], exports, changed, Map.put(stale, module, true)}
559+
has_any_key?(stale_modules, runtime_references) ->
560+
{[entry | rest], exports, changed, Map.put(stale_modules, module, true)}
561561

562562
# Otherwise, we don't store it anywhere
563563
true ->
564-
{[entry | rest], exports, changed, stale}
564+
{[entry | rest], exports, changed, stale_modules}
565565
end
566566
end
567567

@@ -1032,11 +1032,29 @@ defmodule Mix.Compilers.Elixir do
10321032
end
10331033
end
10341034

1035-
defp each_cycle(stale_modules, compile_path, timestamp, state) do
1036-
{modules, _exports, sources, pending_modules, pending_exports} = state
1035+
defp each_cycle(runtime_modules, compile_path, timestamp, state) do
1036+
{modules, _exports, sources, pending_modules, stale_exports} = state
1037+
1038+
stale_modules =
1039+
modules
1040+
|> Enum.map(&module(&1, :module))
1041+
|> Map.from_keys(true)
1042+
1043+
# Because a module may be accidentally overridden in another file,
1044+
# we need to mark any pending module that has been defined as changed.
1045+
{pending_modules, changed} =
1046+
Enum.reduce(pending_modules, {[], []}, fn module, {pending_acc, changed_acc} ->
1047+
if is_map_key(stale_modules, module(module, :module)) do
1048+
{pending_acc, module(module, :sources) ++ changed_acc}
1049+
else
1050+
{[module | pending_acc], changed_acc}
1051+
end
1052+
end)
10371053

1054+
# We don't need to pass stale_modules here because
1055+
# runtime dependencies have already been marked as stale.
10381056
{pending_modules, exports, changed} =
1039-
update_stale_entries(pending_modules, sources, [], %{}, pending_exports, compile_path)
1057+
update_stale_entries(pending_modules, sources, changed, %{}, stale_exports, compile_path)
10401058

10411059
# For each changed file, mark it as changed.
10421060
# If compilation fails mid-cycle, they will be picked next time around.
@@ -1045,18 +1063,13 @@ defmodule Mix.Compilers.Elixir do
10451063
end
10461064

10471065
if changed == [] do
1048-
stale_modules =
1049-
modules
1050-
|> Enum.map(&module(&1, :module))
1051-
|> Map.from_keys(true)
1052-
|> Map.merge(stale_modules)
1053-
1054-
{_, runtime_modules, sources} = fixpoint_runtime_modules(sources, stale_modules)
1066+
{_, runtime_modules, sources} =
1067+
fixpoint_runtime_modules(sources, Map.merge(stale_modules, runtime_modules))
10551068

10561069
runtime_paths =
10571070
Enum.map(runtime_modules, &{&1, Path.join(compile_path, Atom.to_string(&1) <> ".beam")})
10581071

1059-
state = {modules, exports, sources, pending_modules, pending_exports}
1072+
state = {modules, exports, sources, pending_modules, stale_exports}
10601073
{{:runtime, runtime_paths, []}, state}
10611074
else
10621075
Mix.Utils.compiling_n(length(changed), :ex)
@@ -1085,7 +1098,7 @@ defmodule Mix.Compilers.Elixir do
10851098

10861099
defp each_file(file, references, verbose, state, cwd) do
10871100
{compile_references, export_references, runtime_references, compile_env} = references
1088-
{modules, exports, sources, pending_modules, pending_exports} = state
1101+
{modules, exports, sources, pending_modules, stale_exports} = state
10891102

10901103
file = Path.relative_to(file, cwd)
10911104

@@ -1114,22 +1127,22 @@ defmodule Mix.Compilers.Elixir do
11141127
compile_env: compile_env
11151128
)
11161129

1117-
{modules, exports, [source | sources], pending_modules, pending_exports}
1130+
{modules, exports, [source | sources], pending_modules, stale_exports}
11181131
end
11191132

11201133
defp each_module(file, module, kind, external, new_export, state, timestamp, cwd) do
1121-
{modules, exports, sources, pending_modules, pending_exports} = state
1134+
{modules, exports, sources, pending_modules, stale_exports} = state
11221135

11231136
file = Path.relative_to(file, cwd)
11241137
external = process_external_resources(external, cwd)
11251138

11261139
old_export = Map.get(exports, module)
11271140

1128-
pending_exports =
1141+
stale_exports =
11291142
if old_export && old_export != new_export do
1130-
pending_exports
1143+
stale_exports
11311144
else
1132-
Map.delete(pending_exports, module)
1145+
Map.delete(stale_exports, module)
11331146
end
11341147

11351148
{module_sources, existing_module?} =
@@ -1163,7 +1176,7 @@ defmodule Mix.Compilers.Elixir do
11631176
)
11641177

11651178
modules = prepend_or_merge(modules, module, module(:module), module, existing_module?)
1166-
{modules, exports, [source | sources], pending_modules, pending_exports}
1179+
{modules, exports, [source | sources], pending_modules, stale_exports}
11671180
end
11681181

11691182
defp prepend_or_merge(collection, key, pos, value, true) do

lib/mix/test/mix/tasks/compile.elixir_test.exs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -931,10 +931,11 @@ defmodule Mix.Tasks.Compile.ElixirTest do
931931
assert File.regular?("_build/dev/lib/sample/ebin/Elixir.A.beam")
932932
assert File.regular?("_build/dev/lib/sample/ebin/Elixir.B.beam")
933933

934+
# Compile directly so it does not point to a .beam file
934935
Code.put_compiler_option(:ignore_module_conflict, true)
935936
Code.compile_file("lib/b.ex")
936-
force_recompilation("lib/a.ex")
937937

938+
force_recompilation("lib/a.ex")
938939
Mix.Tasks.Compile.Elixir.run(["--verbose"])
939940
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
940941
end)
@@ -963,6 +964,33 @@ defmodule Mix.Tasks.Compile.ElixirTest do
963964
end)
964965
end
965966

967+
test "compiles dependent changed on conflict" do
968+
in_fixture("no_mixfile", fn ->
969+
Mix.Project.push(MixTest.Case.Sample)
970+
971+
assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []}
972+
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
973+
assert_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
974+
purge([A, B])
975+
976+
capture_io(:stderr, fn ->
977+
File.write!("lib/a.ex", "defmodule B, do: :not_ok")
978+
assert {:ok, [_, _]} = Mix.Tasks.Compile.Elixir.run(["--verbose"])
979+
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
980+
assert_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
981+
purge([A, B])
982+
end)
983+
984+
capture_io(:stderr, fn ->
985+
File.write!("lib/a.ex", "defmodule A, do: :ok")
986+
assert {:ok, []} = Mix.Tasks.Compile.Elixir.run(["--verbose"])
987+
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
988+
assert_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
989+
purge([A, B])
990+
end)
991+
end)
992+
end
993+
966994
test "compiles dependent changed external resources" do
967995
in_fixture("no_mixfile", fn ->
968996
Mix.Project.push(MixTest.Case.Sample)

0 commit comments

Comments
 (0)