summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>2019-08-01 11:17:48 +0200
committerGitHub <noreply@github.com>2019-08-01 11:17:48 +0200
commitd82616786567d497fa08f62d4f3dc2a7d2134306 (patch)
tree34d4d8ab43c2f4b54a8074762b9950b278db9895
parentb333510e61ec82283b50fc83cc3fcf0365d469ff (diff)
downloadelixir-d82616786567d497fa08f62d4f3dc2a7d2134306.tar.gz
Verify transient runtime module dependencies (#9255)
-rw-r--r--lib/mix/lib/mix/compilers/elixir.ex46
-rw-r--r--lib/mix/test/mix/tasks/compile.elixir_test.exs44
2 files changed, 60 insertions, 30 deletions
diff --git a/lib/mix/lib/mix/compilers/elixir.ex b/lib/mix/lib/mix/compilers/elixir.ex
index 2b21e542c..5c19f97c3 100644
--- a/lib/mix/lib/mix/compilers/elixir.ex
+++ b/lib/mix/lib/mix/compilers/elixir.ex
@@ -223,7 +223,7 @@ defmodule Mix.Compilers.Elixir do
update_stale_entries(pending_modules, sources, [], %{}, pending_structs, compile_path)
if changed == [] do
- {:runtime, dependent_modules(sources, modules, pending_modules)}
+ {:runtime, dependent_runtime_modules(sources, modules, pending_modules)}
else
modules =
for module(sources: source_files) = module <- modules do
@@ -236,29 +236,39 @@ defmodule Mix.Compilers.Elixir do
end
end
- defp dependent_modules(sources, all_modules, pending_modules) do
+ defp dependent_runtime_modules(sources, all_modules, pending_modules) do
changed_modules =
for module(module: module) = entry <- all_modules,
entry not in pending_modules,
into: %{},
do: {module, true}
- Enum.flat_map(pending_modules, fn module(sources: source_files, module: module) ->
- depending? =
- Enum.any?(source_files, fn file ->
- source(
- compile_references: compile_refs,
- struct_references: struct_refs,
- runtime_references: runtime_refs
- ) = List.keyfind(sources, file, source(:source))
-
- has_any_key?(changed_modules, compile_refs) or
- has_any_key?(changed_modules, struct_refs) or
- has_any_key?(changed_modules, runtime_refs)
- end)
-
- if depending?, do: [module], else: []
- end)
+ fixpoint_runtime_modules(sources, changed_modules, %{}, pending_modules)
+ end
+
+ defp fixpoint_runtime_modules(sources, changed, dependent, not_dependent) do
+ {new_dependent, not_dependent} =
+ Enum.reduce(not_dependent, {dependent, []}, fn module, {new_dependent, not_dependent} ->
+ depending? =
+ Enum.any?(module(module, :sources), fn file ->
+ source(runtime_references: runtime_refs) =
+ List.keyfind(sources, file, source(:source))
+
+ has_any_key?(changed, runtime_refs)
+ end)
+
+ if depending? do
+ {Map.put(new_dependent, module(module, :module), true), not_dependent}
+ else
+ {new_dependent, [module | not_dependent]}
+ end
+ end)
+
+ if map_size(dependent) != map_size(new_dependent) do
+ fixpoint_runtime_modules(sources, new_dependent, new_dependent, not_dependent)
+ else
+ Map.keys(new_dependent)
+ end
end
defp each_module(file, module, _binary, cwd) do
diff --git a/lib/mix/test/mix/tasks/compile.elixir_test.exs b/lib/mix/test/mix/tasks/compile.elixir_test.exs
index dc1e01820..bf38f7dc7 100644
--- a/lib/mix/test/mix/tasks/compile.elixir_test.exs
+++ b/lib/mix/test/mix/tasks/compile.elixir_test.exs
@@ -560,34 +560,54 @@ defmodule Mix.Tasks.Compile.ElixirTest do
in_fixture("no_mixfile", fn ->
File.write!("lib/a.ex", """
defmodule A do
- def foo(), do: B.bar()
+ def foo(), do: :ok
end
""")
File.write!("lib/b.ex", """
defmodule B do
- def bar(), do: :ok
+ def foo(), do: A.foo()
end
""")
- assert capture_io(:stderr, fn ->
- Mix.Tasks.Compile.Elixir.run(["--verbose"])
- end) == ""
+ File.write!("lib/c.ex", """
+ defmodule C do
+ def foo(), do: B.foo()
+ def bar(), do: B.bar()
+ end
+ """)
+
+ output =
+ capture_io(:stderr, fn ->
+ Mix.Tasks.Compile.Elixir.run(["--verbose"])
+ end)
+
+ refute output =~ "A.foo/0 is undefined or private"
+ assert output =~ "B.bar/0 is undefined or private"
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
assert_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
+ assert_received {:mix_shell, :info, ["Compiled lib/c.ex"]}
- File.write!("lib/b.ex", """
- defmodule B do
+ File.write!("lib/a.ex", """
+ defmodule A do
end
""")
- assert capture_io(:stderr, fn ->
- Mix.Tasks.Compile.Elixir.run(["--verbose"])
- end) =~ "B.bar/0 is undefined or private"
+ output =
+ capture_io(:stderr, fn ->
+ Mix.Tasks.Compile.Elixir.run(["--verbose"])
+ end)
- refute_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
- assert_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
+ # Check B due to direct dependency on A
+ # Check C due to transient dependency on A
+ assert output =~ "A.foo/0 is undefined or private"
+ assert output =~ "B.bar/0 is undefined or private"
+
+ # Ensure only A was recompiled
+ assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
+ refute_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
+ refute_received {:mix_shell, :info, ["Compiled lib/c.ex"]}
end)
end
end