Get app versions at compile time?

I encountered this interesting thing while trying to add more info to Logger messages.

I can get the version of an app by running something like this:

:myapp
        |> Application.spec()
        |> Keyword.get(:vsn)
        |> List.to_string()

However, I thought this calculation maybe is expensive, so I tried to save this at compile time by putting it into a module attribute:

@app_ver :myapp
        |> Application.spec()
        |> Keyword.get(:vsn)
        |> List.to_string()

However I have noticed that Application.spec/1 returns nil at compile time. Is there a way to read and store an application version at compile time? Or is the version only known AFTER compilation?

1 Like

You cannot access the application spec because your application is being compiled. Fortunately, you can access information about your Mix project like this:

defmodule Foo do
  IO.inspect(Mix.Project.config()[:version])
end
3 Likes

Thank you! Is it possible to also get versions of dependencies?

You can grab information about your deps in that project config too.

I believe the dep apps are automatically loaded (if not, you can Application.load/1 them) so you can definitely call Application.spec/1 and it will work :slight_smile:

1 Like

thank you, that was the trick!

Oiy, is almost working – this does not work for mix compile
maybe I have to do something in application start?

your MixProject module is nothing special, it’s a module like any other (I mean aside from the fact that it’s only available at compile time). Make the deps/0 function public instead of private, and you’re good to go.

it just occurred to me that won’t tell you what the version is, just what version you wanted. You could also read the mix.lock file using Code.eval_file/1

Ah, of course I did not think of this!

"mix.lock"
  |> Code.eval_file()
  |> Tuple.to_list()
  |> Enum.at(0)
  |> Enum.reduce(%{}, fn
    {app, {:hex, _, version, _, _, _, _, _}}, acc ->
      Map.put(acc, app, version)

    {app, {:git, _, _, [{_, version}]}}, acc ->
      Map.put(acc, app, version)
  end)

However this has lots of warnings: warning: found quoted keyword "jason" but the quotes are not required.

Is there a way to avoidthose?

Is this on purpose that the mix.lock contains bad format? Shouldn’t have no quotes for keys or use =>?

I found solution to this: just add mix.lock to .formatter.exs:

[
  inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}", "mix.lock"]
]

and then the above works!

1 Like

you may want to use elem/2 instead of Tuple.to_list |> Tuple.at

for me, this works very well:

{:ok, version} = :application.get_key(:myapp, :vsn)
Logger.info("Application xxx #{version}: started")
1 Like

I like this – but it seems undefined at compile?

I’m not sure it is expensive (a benchmark would give us some clues here), I haven’t noticed any timing issue when running my app with :application.get_key(:myapp, :vsn)

I doubt any measurable performance drawbacks will occur with the following

<%= Application.get_env(:ex, :env) %>:<%= Application.spec(:ex, :vsn) %>

image