Created
June 7, 2019 20:27
-
-
Save mithereal/464c3d4ed880b522d660ce471274d0b0 to your computer and use it in GitHub Desktop.
semantic versioning for elixir apps
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- | |
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