"module is not available"-error when calling a module inside mix.exs

In my mix.exs I used to be able to call other Modules in order to fetch some configurations for my application-env configuration. Somehow this is not possible anymore since a few weeks (or the update to Elixir 1.11 maybe)

Here is an example of calling a module MyApp.Config.application_env() from inside my mix.exs file:

./mix.exs

defmodule MyApp.MixProject do
   use Mix.Project

     def project do
    [
      app: :my_app
      version: "0.2.0",
      elixir: "~> 1.5",
      elixirc_paths: elixirc_paths(Mix.env()),
      compilers: [:phoenix, :gettext] ++ Mix.compilers(),
      start_permanent: Mix.env() == :prod,
      aliases: aliases(),
      deps: deps(),
      test_paths: ["test", "lib"]
    ]
  end

  def application do
    [
      mod: {MyApp.Application, []},
      extra_applications: [:logger, :runtime_tools],
      env: MyApp.Config.application_env()
    ]
  end

  # Specifies which paths to compile per environment.
  defp elixirc_paths(:test), do: ["lib", "test/support"]
  defp elixirc_paths(_), do: ["lib"]

  # deps and aliases ....

./lib/config.ex

defmodule MyApp.Config do
  def application_env do
    [my_config: "foo", another_config: "bar"]
  end
end

Since the update to Elixir 1.11 I cannot call this module anymore since the compiler complains like this:

** (UndefinedFunctionError) function MyApp.Config.application_env/0 is undefined (module MyApp.Config is not available)
    MyApp.Config.application_env()
    mix.exs:28: MyApp.MixProject.application/0
    (mix 1.11.0) lib/mix/tasks/compile.app.ex:382: Mix.Tasks.Compile.App.project_apps/1
    (mix 1.11.0) lib/mix/tasks/compile.all.ex:97: Mix.Tasks.Compile.All.load_apps/2
    (mix 1.11.0) lib/mix/tasks/compile.all.ex:24: Mix.Tasks.Compile.All.run/1
    (mix 1.11.0) lib/mix/task.ex:394: Mix.Task.run_task/3
    (mix 1.11.0) lib/mix/tasks/compile.ex:119: Mix.Tasks.Compile.run/1
    (mix 1.11.0) lib/mix/task.ex:394: Mix.Task.run_task/3

I tried require-ing and import-ing the MyApp.Config module, but nothing helped. The config.ex file is compiled and put into the _build/dev/ebin folder, but somehow it is not available during compile-time. I also tried to set it to an module attribute like this: @config MyApp.Config, but even this didn’t help.

I have the feeling that it is related to the latest Compilation time improvements in 1.11 which states:

This change allows us to mark import s and require s as “exports dependencies” instead of “compile time” dependencies

Does somebody know how to make my MyApp.Config module available at compile-time again?

When mix.exs is evaluated your application is not available, as your application is built using the evaluation result of the mix.exs.

So by requiring your applications code within mix.exs you are creating a circular dependency that your application needs to be compiled in order to get compiled.

1 Like

Hmm, that makes sense, but now I’m wondering why it worked before :thinking:

On another node: How can I then extract logic to set the application env option to another module/script/config file? I need to set default values for many application environment variables and don’t want to do this in every application which uses this library. So, I cannot use the config/... files since these are overwritten by the implementing applications, I believe. The advice in this thread was to use the application env option in mix.exs, which is why I use above setup. But I also don’t want to have 30 defp functions in my mix.exs and therefore extracting them to another module. Do you know a better/different way of setting a multitude of default values in a library maybe?

Try to refactor your library to not need to rely on those defaults…

If you insist on providing them, you could have a an extra file in the project root defining them and load it via Code.eval_file/2.

Thanks for the hint!

I used Code.compile_file/2 now in order to compile the MyApp.Config-module before setting the application env option like this:

def application do
  [{my_config, _binary}] = Code.compile_file("lib/config.ex")

  [
      mod: {MyApp.Application, []},
      extra_applications: [:logger, :httpoison, :runtime_tools, :cachex],
      env: my_config.application_env()
  ]
end
1 Like

Out of interest - why? Why not other way around?

At work, we have ~5 Phoenix micro-services which all share some common libraries like a Neuron API-Client or a Cachex request handler. These libraries (Neuron, Cachex) need to be configured, both locally and on our servers, for the test, dev, and prod environments. We don’t want to copy&paste the same configuration to all our micro-services, which is why we bundled the libraries and their configuration in a library of our own. Since the config.exs of a library is ignored when the library is used in a project like one of our micro-services, we needed to put the shared configurations in the application env option of our own library. We have ~30 private functions which return a configuration for a “sub-library” so we didn’t want to put all these functions into our mix.exs, but in a separate Config-Module.

If you know a way to refactor this nicely, I would much appreciate to hear about it :slight_smile:

config.exs is ignored, but the :env in application is not. So if you have application that is using that, then you can configure it there. In general, it depends, as there is few possible solutions, depending on use case.