Is there a way to make a file that always recompiles?

I have a file that returns some information about the current release for our application, such as the git hashref, etc.

Is there a way to tell mix that a particular file should always be recompiled so that this never becomes stale during development?

Maybe there is a better option but one choice would be to call a script file in your code. So something like

[{mod, _}] = Code.load_file("my_script.exs")
data = mod.my_function

Of course if this is something you call everywhere, you’d need to then cache the value in ETS or something.

Use @external_resource on something relevant like the application module. :slight_smile:

2 Likes

This is a ~95% solution, as it looks like it expects to be a file, and not something transient. If it were possible to specify @external_resource true, that would probably solve my particular use-case.

1 Like

Oh right! I had it backwards!

Well you could add a custom compiler to the compiler stage that runs before elixir and just delete that compiled file, or touch's it or whatever. :slight_smile:

2 Likes

Put it in mix.exs, that runs every time, read your git info and put it into project info

  def project do
    [
      app: :utilities,
      version: vers(),
      elixir: "~> 1.5",
      start_permanent: Mix.env == :prod,
      deps: deps()
    ]
  end
...
  defp vers do
    {desc, 0} = System.cmd("git", ["describe", "--first-parent"])
    parts = Regex.named_captures(~r/v?(?<xy>\d+\.\d+)(?<z>\.\d+)?(?<pre>-[a|b][[:digit:]]+)?((?<offset>-\d+-)g(?<hash>[[:xdigit:]]+))?\n?/, desc)
    vers = parts["xy"] <> parts["z"] <> parts["pre"] <> parts["offset"] <> parts["hash"]
    File.write!("./ci/VERSION.txt", vers)
    vers
  end

(Yes, it is completely insane that I go to that much regex trouble to strip out the “g” prefix on the short hash in git describe’s results. I freely admit this.)

The reason I write the result into a file is for reference in CI scripts: the version that the app self-reports as its version in reply headers, and the version used in the docker image tag, and the version in the k8s yaml are guaranteed to be the same because the build & deploy scripts all get them from git describe invoked during the compile. Of course note that it doesn’t work well if you try this on a local machine where you can be compiling work that is not yet committed–so yeah, don’t build & push uncommitted work :wink:

I have a structured version of what you’re during here (we call it the release-hashref, and it’s a JSON file), based on work I did with Cartage. Being a structured format, it can tell you the repo, the hash ref, the project name, the release package ID (the timestamp field, as our packages are all timestamped).

We’ve also built our release system such that it cannot be released to with uncommitted code. You can build a custom version from a branch, but you can’t make a package with uncommitted without going to more effort than is necessary.

Fwiw i just tried @external_resource "/dev/random" and sadly it didn’t work

4 Likes

Well yeah, my release system can’t release uncommitted code either–commits trigger builds. I was throwing that warning in for folks who might be building locally without the infrastructure of CI/CD, in that case you might even want to a call out to git to ensure nothing uncommitted when the env is prod.

I also write a couple more files than just VERSION.txt. But the whole thing has some specific dependencies on the environment that make it not a clean example to post it all…

I don’t remember what, if anything I did for this, but I’m wondering if @external_resource ".git/HEAD" might work… (or whatever recipe is necessary to make that happen).

(Actually, it might be necessary to read the .git/HEAD to see the ref: to which it refers, e.g., .git/refs/heads/master.)

2 Likes