diff options
author | José Valim <jose.valim@dashbit.co> | 2021-12-10 09:49:47 +0100 |
---|---|---|
committer | José Valim <jose.valim@dashbit.co> | 2021-12-10 09:49:47 +0100 |
commit | 382c6d2e0e083b15cd02c0eaddd6c187082a600f (patch) | |
tree | 0a38e4e8ec176cf0e75b45aa7c5733f537f9a8ff | |
parent | 99006eb081cc1cce2426c80732a20a9168d24473 (diff) | |
download | elixir-382c6d2e0e083b15cd02c0eaddd6c187082a600f.tar.gz |
Track environment changes from compile_env
-rw-r--r-- | lib/elixir/lib/application.ex | 12 | ||||
-rw-r--r-- | lib/mix/lib/mix/compilers/elixir.ex | 22 | ||||
-rw-r--r-- | lib/mix/test/mix/tasks/compile.elixir_test.exs | 86 |
3 files changed, 109 insertions, 11 deletions
diff --git a/lib/elixir/lib/application.ex b/lib/elixir/lib/application.ex index be2e87414..b1b1a0c89 100644 --- a/lib/elixir/lib/application.ex +++ b/lib/elixir/lib/application.ex @@ -53,9 +53,15 @@ defmodule Application do See the "Configuration" section in the `Mix` module for more information. You can also change the application environment dynamically by using functions - such as `put_env/3` and `delete_env/2`. However, as a rule of thumb, each application - is responsible for its own environment. Please do not use the functions in this - module for directly accessing or modifying the environment of other applications. + such as `put_env/3` and `delete_env/2`. + + > Note: Each application is responsible for its own environment. Do not + > use the functions in this module for directly accessing or modifying + > the environment of other applications. Whenever you change the application + > environment, Elixir's build tool will only recompile the files that + > belong to that application. So if you read the application environment + > of another application, there is a chance you will be depending on + > outdated configuration, as your file won't be recompiled as it changes. ### Compile-time environment diff --git a/lib/mix/lib/mix/compilers/elixir.ex b/lib/mix/lib/mix/compilers/elixir.ex index 15ad7a75b..fe3ca6e3b 100644 --- a/lib/mix/lib/mix/compilers/elixir.ex +++ b/lib/mix/lib/mix/compilers/elixir.ex @@ -75,9 +75,8 @@ defmodule Mix.Compilers.Elixir do new_lock = Enum.sort(Mix.Dep.Lock.read()) new_config = Enum.sort(Mix.Tasks.Loadconfig.read_compile()) - apps = [] - apps = merge_appset(old_lock, new_lock, apps) - apps = merge_appset(old_config, new_config, apps) + config_apps = merge_appset(old_config, new_config, []) + apps = merge_appset(old_lock, new_lock, config_apps) if Mix.Project.config()[:app] in apps do {true, stale, new_lock, new_config} @@ -95,7 +94,14 @@ defmodule Mix.Compilers.Elixir do end end) - {false, stale ++ apps_stale, new_lock, new_config} + compile_env_stale = + for source(compile_env: compile_env, modules: modules) <- all_sources, + Enum.any?(config_apps, &List.keymember?(compile_env, &1, 0)), + module <- modules, + do: module + + stale = (stale ++ compile_env_stale) ++ apps_stale + {false, stale, new_lock, new_config} end true -> @@ -288,8 +294,8 @@ defmodule Mix.Compilers.Elixir do dest ) do modules_to_recompile = - for module(module: module, recompile?: true) <- all_modules, - recompile_module?(module), + for module(module: module, recompile?: recompile?) <- all_modules, + recompile_module?(module, recompile?) or Map.has_key?(stale_local_mods, module), do: module {checkpoint_stale, checkpoint_modules} = parse_checkpoint(manifest) @@ -495,8 +501,8 @@ defmodule Mix.Compilers.Elixir do :ok end - defp recompile_module?(module) do - Code.ensure_loaded?(module) and + defp recompile_module?(module, recompile?) do + recompile? and Code.ensure_loaded?(module) and function_exported?(module, :__mix_recompile__?, 0) and module.__mix_recompile__?() end diff --git a/lib/mix/test/mix/tasks/compile.elixir_test.exs b/lib/mix/test/mix/tasks/compile.elixir_test.exs index 8566fbab9..bf9fcc86e 100644 --- a/lib/mix/test/mix/tasks/compile.elixir_test.exs +++ b/lib/mix/test/mix/tasks/compile.elixir_test.exs @@ -324,6 +324,92 @@ defmodule Mix.Tasks.Compile.ElixirTest do Application.put_env(:logger, :compile_time_purge_matching, []) end + test "recompiles files when config changes via compile_env" do + in_fixture("no_mixfile", fn -> + Mix.Project.push(MixTest.Case.Sample) + File.mkdir_p!("config") + + File.write!("lib/a.ex", """ + defmodule A do + _ = Application.compile_env(:logger, :level) + end + """) + + assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []} + assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]} + assert_received {:mix_shell, :info, ["Compiled lib/b.ex"]} + + recompile = fn -> + Mix.ProjectStack.pop() + Mix.Project.push(MixTest.Case.Sample) + Mix.Tasks.Loadconfig.load_compile("config/config.exs") + Mix.Tasks.Compile.Elixir.run(["--verbose"]) + end + + # Adding config recompiles + File.write!("config/config.exs", """ + import Config + config :logger, :level, :debug + """) + + File.touch!("_build/dev/lib/sample/.mix/compile.elixir", @old_time) + assert recompile.() == {:ok, []} + assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]} + refute_received {:mix_shell, :info, ["Compiled lib/b.ex"]} + assert File.stat!("_build/dev/lib/sample/.mix/compile.elixir").mtime > @old_time + + # Changing config recompiles + File.write!("config/config.exs", """ + import Config + config :logger, :level, :info + """) + + File.touch!("_build/dev/lib/sample/.mix/compile.elixir", @old_time) + assert recompile.() == {:ok, []} + assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]} + refute_received {:mix_shell, :info, ["Compiled lib/b.ex"]} + assert File.stat!("_build/dev/lib/sample/.mix/compile.elixir").mtime > @old_time + + # Removing config recompiles + File.write!("config/config.exs", """ + import Config + """) + + File.touch!("_build/dev/lib/sample/.mix/compile.elixir", @old_time) + assert recompile.() == {:ok, []} + assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]} + refute_received {:mix_shell, :info, ["Compiled lib/b.ex"]} + assert File.stat!("_build/dev/lib/sample/.mix/compile.elixir").mtime > @old_time + + # Changing self fully recompiles + File.write!("config/config.exs", """ + import Config + config :sample, :foo, :bar + """) + + File.touch!("_build/dev/lib/sample/.mix/compile.elixir", @old_time) + assert recompile.() == {:ok, []} + assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]} + assert_received {:mix_shell, :info, ["Compiled lib/b.ex"]} + assert File.stat!("_build/dev/lib/sample/.mix/compile.elixir").mtime > @old_time + + # Changing an unknown dependency returns :ok but does not recompile + File.write!("config/config.exs", """ + import Config + config :sample, :foo, :bar + config :unknown, :unknown, :unknown + """) + + # We use ensure_touched because an outdated manifest would recompile anyway. + ensure_touched("config/config.exs", "_build/dev/lib/sample/.mix/compile.elixir") + assert recompile.() == {:ok, []} + refute_received {:mix_shell, :info, ["Compiled lib/a.ex"]} + refute_received {:mix_shell, :info, ["Compiled lib/b.ex"]} + end) + after + Application.delete_env(:sample, :foo, persistent: true) + end + test "recompiles files when lock changes" do in_fixture("no_mixfile", fn -> Mix.Project.push(MixTest.Case.Sample) |