diff options
author | Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com> | 2019-08-01 11:17:48 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-08-01 11:17:48 +0200 |
commit | d82616786567d497fa08f62d4f3dc2a7d2134306 (patch) | |
tree | 34d4d8ab43c2f4b54a8074762b9950b278db9895 | |
parent | b333510e61ec82283b50fc83cc3fcf0365d469ff (diff) | |
download | elixir-d82616786567d497fa08f62d4f3dc2a7d2134306.tar.gz |
Verify transient runtime module dependencies (#9255)
-rw-r--r-- | lib/mix/lib/mix/compilers/elixir.ex | 46 | ||||
-rw-r--r-- | lib/mix/test/mix/tasks/compile.elixir_test.exs | 44 |
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 |