`mix run` always compiles but `mix compile` does not?

A surprising ex_cldr issue was opened this morning and I hope someone can help clarify the behaviour of what triggers (re)compilation in mix run that is different to mix compile and iex -S mix. Its easily reproducible and a test app repo is here.

:white_check_mark: mix compile compiles once as expected

mix compile proceeds as expected - the module is compiled (along with several other modules generated at compile time). The next time compilation is requested, nothing is compiled (normal expectation).

kip@Kips-MBP my_app % rm -rf _build
kip@Kips-MBP my_app % mix compile
==> decimal
Compiling 4 files (.ex)
Generated decimal app
...
==> my_app
Compiling 1 file (.ex)
Generating MyApp for 3 locales named [:en, :fr, :und] with a default locale named :fr
Generated my_app app
kip@Kips-MBP my_app % mix compile
kip@Kips-MBP my_app % mix compile
kip@Kips-MBP my_app % iex -S mix
Erlang/OTP 25 [erts-13.2] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace]

Interactive Elixir (1.14.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>

:x: mix run compiles every time

It appears mix run uses a different recompilation model to mix compile or iex -S mix but I can’t work out what is triggering (re)compilation. The generated modules are also always recompiled (but without the warnings) if I rename my_app.ex to my_app.exs. The warnings are for modules that are generated at compile time. I typically understand these warnings to mean that the module was created but one already existed - which is true, they have already been compiled.

kip@Kips-MBP my_app % rm -rf _build
kip@Kips-MBP my_app % mix run lib/my_app.ex
==> decimal
Compiling 4 files (.ex)
Generated decimal app
==> jason
...
==> my_app
Compiling 1 file (.ex)
Generating MyApp for 3 locales named [:en, :fr, :und] with a default locale named :fr
Generated my_app app
warning: redefining module MyApp (current version loaded from _build/dev/lib/my_app/ebin/Elixir.MyApp.beam)
  lib/my_app.ex:1

Generating MyApp for 3 locales named [:en, :fr, :und] with a default locale named :fr
warning: redefining module MyApp.Locale (current version loaded from _build/dev/lib/my_app/ebin/Elixir.MyApp.Locale.beam)
  lib/my_app.ex:1

warning: redefining module MyApp.AcceptLanguage (current version loaded from _build/dev/lib/my_app/ebin/Elixir.MyApp.AcceptLanguage.beam)
  lib/my_app.ex:1
...
# Each invocation always rebuilds
kip@Kips-MBP my_app % mix run lib/my_app.ex
warning: redefining module MyApp (current version loaded from _build/dev/lib/my_app/ebin/Elixir.MyApp.beam)
  lib/my_app.ex:1

Generating MyApp for 3 locales named [:en, :fr, :und] with a default locale named :fr
warning: redefining module MyApp.Locale (current version loaded from _build/dev/lib/my_app/ebin/Elixir.MyApp.Locale.beam)
  lib/my_app.ex:1

warning: redefining module MyApp.AcceptLanguage (current version loaded from _build/dev/lib/my_app/ebin/Elixir.MyApp.AcceptLanguage.beam)
  lib/my_app.ex:1

Sample app

The code for this very small example is here. Its a simple module (same principle in all ex_cldr applications).

defmodule MyApp do
  use Cldr,
    locales: ["en", "fr"],
    default_locale: "fr",
    providers: []
end

And the deps are equally simple:

  defp deps do
    [
      {:jason, "~> 1.0"},
      {:ex_cldr_numbers, "~> 2.30"}
    ]
  end

If you got this far - thank you! Any and all suggestions welcome. Tested on Elixir 1.12 and 1.14.4.

mix run is the same as iex -S mix and they call mix compile so the compilation of your project code they do will be the same.

But when you pass a file to mix run it will evaluate that file, essentially compiling it without storing any compilation artifacts. You shouldn’t pass files from lib/ to mix run since that will compile the file twice, which is why you get the “redefining module” warnings.

You should only pass script files (.exs) to mix run.

2 Likes

@ericmj thanks very much for the clarification. Greatly appreciated. My heart rate is down to normal again now.

4 Likes