How to force dependency between modules with dynamic data

Hi,

Say I have this code in config.exs:

config :myapp, mods: [A, B, C]

I pull this in some module:

  @mods Application.compile_env!(:myapp, :mods)

I would like this module to be recompiled when any of A, B or C changes.

I tried Enum.each(@mods, &require/1) but I now know that it is not supported.

The only solution I can came up with involves Code.eval_quoted at one point. Do you know a better way?

Thank you.

1 Like

require mod should setup a compile time dependency.

1 Like

But how do you call require mod given the list Application.compile_env!(:myapp, :mods) ?

As I said I tried Enum.each(@mods, &require/1) but it is not allowed, and you cannot unquote here either.

1 Like
defmodule A do
end

defmodule B do
end

defmodule D do
  defmacro req(mods) do
    for mod <- mods do
      quote do
        require unquote(mod)
      end
    end
  end
end

defmodule C do
  import D

  req([A, B])
end

This compiles.

1 Like

Sure because it is not dynamic.

defmodule A do
end

defmodule B do
end

defmodule D do
  defmacro req(mods) do
    quote do
      for mod <- unquote(mods) do
        require mod
      end
    end
  end
end

defmodule C do
  import D

  mods = Application.compile_env(:xd, :mods, [A, B])
  req(mods)
end

This does not compile.

1 Like

You moved the for mod <- mods into a quote do. It won’t work that way. But looking at Application.compile_env is doesn’t seem to return actual compile time values, which feels strange given the name. The macro just returns code for fetching a value.

2 Likes

Yes and also I cannot call it from my macro because it forbids it.

This, I think, does what is expected (basically @LostKobrakai’s solution with the addition of Code.eval_quoted/1 to get the runtime value at compile time) and then passing the dynamic configuration directly as a parameter. Its tricky to pass module level assigns as a parameter to a macro.

defmodule Mac do
  defmacro r(modules) do
    {modules, _} = Code.eval_quoted(modules)

    for module <- modules do
      quote do
        require unquote(module)
      end
    end
  end
end

defmodule C do
  require Mac

  Mac.r(Application.compile_env(:xd, :mods, [A, B]))
end
2 Likes

Thanks, I was trying to avoid eval_quoted because my team may not understand how it works.

Finally I got the easy way and added this in my module:

for handler <- @mods do
  @external_resource List.to_string(handler.module_info(:compile)[:source])
end

A bit ugly but it works as long as those modules are ours.

1 Like