Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mithereal/464c3d4ed880b522d660ce471274d0b0 to your computer and use it in GitHub Desktop.
Save mithereal/464c3d4ed880b522d660ce471274d0b0 to your computer and use it in GitHub Desktop.
semantic versioning for elixir apps
---
id: 7
title: "A simple way to automatically set the semantic version of your Elixir app"
date: 2019-06-07T07:47:32Z
layout: default
tags:
- elixir
- Phoenix
- Semver
- Version
- Phoenix
- Git
---
This works if you use Git for your version control. The basic idea is to use git tags,
and the number of commits since the git tag to generate your version number. Elixir
allows you to use a version string like below (You can read more about this at https://hexdocs.pm/elixir/Version.html)
[MAJOR].[MINOR].[PATCH]-[pre_release_info]+[build_info]
When I want to bump the major or the minor version, I create a tag with the version information e.g. `v1.4` using the command
git tag v1.4 --annotate --message 'Version 1.4'
git push --tags --all
I use the git describe command to get the major, minor and the patch info. A part of the describe output also goes into the build information
git describe
# => v1.4-270-fa78ab71e
# => major.minor-patch-git_commit_id
Putting all of this together, I have the following in my mix config, it also uses the BUILD_NUMBER passed by Jenkins (the build server that we use)
```
defmodule Core.Mixfile do
use Mix.Project
@default_version "1.0.0-default"
@version_file_location "../../VERSION"
def project do
[app: :core,
version:version(), # call out to another function which generates the version
# ...
]
end
# ...
defp version do
# Build the version number from Git.
# It will be something like 1.0.0-beta1 when built against a tag, and
# 1.0.0-beta1+18.ga9f2f1ee when built against something after a tag.
case get_version() do
{:ok, string} ->
case Regex.run(~r/(v[\d\.]+(?:\-[a-zA-Z]+\d*)?)(.*)/, String.trim(string)) do
[_, version, commit] -> String.replace(version, ~r/^v/, "") <> (commit |> String.replace(~r/^-/, "+") |> String.replace("-", "."))
_-> @default_version
end
{"version_from_file", vsn} -> vsn
_-> @default_version
end
end
defp get_version do
case File.read(@version_file_location) do
{:error, _} ->
case System.cmd("git", ["describe"]) do
{string, 0} -> {:ok, string}
{error, errno} -> {:error, "Could not get version. errno: #{inspect errno}, error: #{inspect error}"}
end
{:ok, vsn} -> {"version_from_file", vsn}
end
end
```
Creating such a beautiful version number without showing it anywhere wouldn't be very useful :)
I usually put the version information of the app in a footer and the head inside a meta tag (if it is a phoenix app)
```
defmodule Core do
# cache the app_version during build time
@app_version Mix.Project.config[:version]
def app_version, do: @app_version
end
```
Inside the `app.html`
```
<!doctype html>
...
<meta name="version" content="<%= Core.app_version %>">
...
```
So, now when something goes wrong I can take a look at the current version of the app by visiting a page, and know which precise git commit reproduces the problem.
Our QA team too uses this information when filing bug reports.
I also send this version info to my error monitoring and metric services like Rollbar and AppSignal
Hope you find this technique useful :)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment