Skip to content

Instantly share code, notes, and snippets.

@jeffweiss
Last active December 18, 2018 23:13
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save jeffweiss/9df547a4e472e3cf5bd3 to your computer and use it in GitHub Desktop.
Save jeffweiss/9df547a4e472e3cf5bd3 to your computer and use it in GitHub Desktop.
Retrieving application version number in mix.exs from git describe
defmodule MyApp.Mixfile do
use Mix.Project
def project do
[app: :my_app,
version: get_version,
elixir: "~> 1.0",
elixirc_paths: elixirc_paths(Mix.env),
compilers: Mix.compilers,
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
deps: deps]
end
# Configuration for the OTP application
#
# Type `mix help compile.app` for more information
def application do
[mod: {MyApp, []},
applications: [:logger]]
end
defp get_version do
version_from_file
|> handle_file_version
|> String.split("-")
|> case do
[tag] -> tag
[tag, _num_commits, commit] -> "#{tag}-#{commit}"
end
end
defp version_from_file(file \\ "VERSION") do
File.read(file)
end
defp handle_file_version({:ok, content}) do
content
end
defp handle_file_version({:error, _}) do
retrieve_version_from_git
end
defp retrieve_version_from_git do
require Logger
Logger.warn "Calling out to `git describe` for the version number. This is slow! You should think about a hook to set the VERSION file"
System.cmd("git", ["describe", "--always", "--tags"])
|> Tuple.to_list
|> List.first
|> String.strip
end
# Specifies which paths to compile per environment
defp elixirc_paths(:test), do: ["lib", "web", "test/support"]
defp elixirc_paths(_), do: ["lib", "web"]
# Specifies your project dependencies
#
# Type `mix help deps` for examples and options
defp deps do
[
]
end
end
#!/bin/bash
# installed to .git/hooks/post-checkout
`git describe --always --tags > VERSION`
#!/bin/bash
# installed to .git/hooks/post-commit
`git describe --always --tags > VERSION`
@jeffweiss
Copy link
Author

Thanks to @scrogson for reminding me about |> case

@mschae
Copy link

mschae commented Oct 5, 2015

Pretty sure the git_version on line 28 is obsolete.

@dch
Copy link

dch commented Oct 5, 2015

git describe --dirty --abbrev=7 --tags --always --first-parent is also useful BTW:

  • dirty if there are changes not committed/staged
  • first-parent finds the closest logical tag if possible

e.g. 1.6.1-4-g95cb436-dirty

@OnorioCatenacci
Copy link

Wow--that is a very handy idea.

@josevalim
Copy link

Please, please, please don't do it. PLEASE. You are now adding a system call via ports every time we need to load the dependency metadata. The Ruby/Rails community relied so heavily on it that it would add multiple seconds when booting Rails applications. Please don't do this.

@jeffweiss
Copy link
Author

@josevalim I updated with

  • VERSION file set by git hooks
  • warning about how slow it is if a VERSION file isn't there

Thoughts?

@jcelliott
Copy link

Thanks for this. I think @josevalim's point is valid, but your workaround here seems like a good approach. The one problem I see is with CI systems, which won't have the git hooks installed already. I wonder if anyone else has come up with a different solution to this? It seems like versioning based off git tags makes a lot of sense.

You could also change this:

System.cmd("git", ["describe", "--always", "--tags"]) 
|> Tuple.to_list 
|> List.first 
|> String.strip

to this:

System.cmd("git", ["describe", "--always", "--tags"]) 
|> elem(0)
|> String.strip

@ianheggie
Copy link

I merged the recommendations above, fixed the warnings about missing ()'s, removed 'v' and only changed one '-' to '.', so tags in the form vN.N work well with mix's SemVer check.

Given a tag like v2.3 then it will produce version "2.3", then after 5 extra commits produce "2.3.5-gitsha", optionally adding -dirty if there where uncommitted files (Officially this makes it a pre-release of 2.3.5, but since each commit increments the patch version, it isn't an issue).

  defp get_version do
    version_from_file()
    |> handle_file_version()
    |> String.replace_leading("v", "")
    |> String.replace("-", ".", global: false)
  end

  defp version_from_file(file \\ "VERSION") do
    File.read(file)
  end

  defp handle_file_version({:ok, content}) do
    content
  end

  defp handle_file_version({:error, _}) do
    retrieve_version_from_git()
  end

  defp retrieve_version_from_git do
    require Logger
    Logger.warn "Calling out to `git describe` for the version number. This is slow! You should think about a hook to set the VERSION file"
    System.cmd("git", ~w{describe --dirty --always --tags --first-parent}) 
    |> elem(0) 
    |> String.trim
  end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment