Recompiling mix.exs when external_resource changes

I would like to load my app version from an external VERSION file (so that other CI/CD tools have easy access to the value). I’ve updated my mix.exs to load the value at compile time (see below), which works great, however mix.exs doesn’t get recompiled when I modify my external VERSION file. I’ve added it as an @external_resource but maybe that module attribute has no effect in .exs files (I’m not entirely sure if a .exs script is considered a module) ?

@external_resource version_file = "VERSION"
@version File.read!(version_file) |> String.trim

def project do
  [
    version: @version,
    # ...
  ]
end

I can workaround it by also doing a touch mix.exs whenever I bump the version number, but was looking to see if there’s anyway to get the @external_resource attribute to work inside mix.exs ?

Thanks!

Hello and welcome,

exs files are for scripting, and are not compiled.

1 Like

Hmmm, I’m pretty sure that mix.exs is “compiled” every single time you run a mix command. But what doesn’t happen is that the rest of the project is not recompiled when you change your VERSION file. Judging from the docs on @external_resource:

Specifies an external resource for the current module.

Sometimes a module embeds information from an external file. This attribute allows the module to annotate which external resources have been used.

Tools like Mix may use this information to ensure the module is recompiled in case any of the external resources change.

I think that what is happening is that since mix.exs is an .exs file it always needs to be recompiled so adding @external_resource does not change which files need to be recompiled. And the behavior that you see after a touch mix.exs is (I think) special behavior specifically for mix.exs and configuration files. I could be wrong on some of these details, and if so I would be glad for someone to correct me.

Are there other specific files that you want to be recompiled when your VERSION file changes? One potential solution is to add the @extrernal_resource to those files directly. Or if you have some other goal then it would be helpful to know exactly what you’re trying to do. If it’s an issue in CI/CD environment maybe it’s best to not cache parts of the build. But it’s a little hard to say without more details.

It is considered a module if there is a defmodule contained within. But there are a few different behaviors of .exs file vs .ex files, for example the .beam files for .exs files are not persisted to disk (although they are loaded in memory when the beam is running).

2 Likes

Thanks, that’s a lot of useful info.

My test case was that I compiled and ran iex -S mix to verify the value for Application.spec(:my_app)[:vsn] and ensure it contained the value from my VERSION file - which it did. I then update the external VERSION file - without touching any other source code - and rerun iex -S mix - and the value for Application.spec(:my_app)[:vsn] still contained the previous version. I guess I was expecting the @external_resource attribute to cause mix to recompile whatever it is that generates the Application.spec(:my_app) keyword list.

I realize as I write this out this probably won’t be a real issue in the long run because - as you mention - once I setup a full CI pipeline I’ll probably be doing a full clean compile each time so this might just be a non-problem in the long run.

Update: I just noticed that if I look at the value of Mix.Project.config[:version] (in IEx) then that value does reflect the latest correct value from the VERSION file. So assuming that iex -S mix runs the mix.exs file each time that would make sense…

So I guess that just leaves me a bit confused about the difference between Mix.Project.config[:version] and Application.spec(:my_app, :vsn). The former is updated whenever I touch the VERSION file but the latter is only updated if (something) gets recompiled.

1 Like

Can you share the full code where you verified that the version did not change? Perhaps you used a place that was compile time evaluated?

The verification happened in IEx so it was a run time check calling Application.spec(:my_app, :vsn)

Dumb question, but have you restarted your iex after changing the VERSION file?

Yes, that’s what I’m doing - I quit IEx, modify the version file, then start fresh iex -S mix and inspect both Mix.Project.config[:version] and Application.spec(:my_app, :vsn) - the former shows the updated new VERSION while the latter shows the outdated previous VERSION.