How to update Elixir dependencies?

How to keep Elixir deps up to date?

Kamil Lelonek
Kamil Lelonek  - Software Engineer

--

What are dependencies?

Just like it sounds — a dependency is some external code that your application depends on. You usually don’t want to implement it by yourself, and you probably need it to provide some additional functionality for your project. You save your time on coding something you really need.

Furthermore, you probably have more than one external library in your application. Most of them are under continuous development, which makes your project evolve. But it also means you will likely need to perform necessary updates from time to time.

New releases usually contain bug fixes, additional features, API changes, or performance improvements. It’s beneficial and safe to keep these packages up to date. Just have in mind some breaking changes in their interfaces, but I will address them later in this article.

How to manage Elixir dependencies?

Elixir dependencies are external packages you can pull into your project and start using without adding any extra code to your application. You need to declare a name with a version, and you can call them by their API.

While their names are pretty simple, e.g. yaml-elixir, ex_cors, or exnumerator, a version is not that obvious.

In Elixir, you use semantic versioning. A version is a string that must follow the format outlined in SemVer 2.0 schema. In a nutshell, it is combined with three numbers:

MAJOR.MINOR.PATCH

However, when it comes to defining requirements, it’s not always enough. Usually, you will also need to specify an operator. It will tell the package manager how flexible it can be to fetch a particular library.

Requirements support all standard operators such as >, >=, <, <=, ==, !=, and additionally the special one ~> that can also be used to set an upper boundary of the given version. You can also use and and or conjunctions for complex conditions.

In the next part of this article, you will learn where to put them in your project, but for now, have a look at a couple of examples:

"~> 1.1.0" # the same as ">= 1.1.0 and < 1.2.0"
"~> 1.1" # the same as ">= 1.1 and < 2.0"
">= 0.0.0" # basically the most recent version
"1.2.3" # only the exact version
etc.

If you wonder where the version parameter is declared in an external library, you can find it in its mix.exs file. It should be a part of a keyword list returned by the project/0 function.

def project do
[
version: "0.1.0",
# ...
]
end

How to require dependencies?

The main file to put your Elixir dependencies is mix.exs placed in your project root directory. It specifies many important attributes of an application but, in our context, the most important is the deps property in the keyword list returned by the project/0 function. It will look, more or less, like this:

def project do
[
# ...
deps: deps()
]
end

Notice you can put a list or a module attribute as a deps value, but usually, we create a separate deps/0 function returning a list of dependencies like:

def deps do
[
{:dep_from_hexpm, "~> 0.3.0"}
]
end

Dependencies must be specified in one of the following formats:

{app, requirement}
{app, opts}
{app, requirement, opts}

Where:

  • app is an atom representing the name of a library
  • requirement is a version requirement described above
  • opts is a keyword list of options

Now you can run a mix deps.tree task to print a visual representation of all packages and their requirements:

mix deps.tree
==> your_app
your_app
├── jason ~> 1.2 (Hex package)
└── tesla ~> 1.4 (Hex package)
├── jason >= 1.0.0 (Hex package)
└── mime ~> 1.0 (Hex package)

These dependencies can be immediately used within your entire application. You don’t have to explicitly import or require them, but just use their API directly.

Hex

If you come from another programming language, you are probably familiar with the package manager concept. For example, in Node, we have npm, in Ruby there is RubyGems, in Go, you can use dep and for Scala, you will leverage Maven.

In Elixir, we use hex package manager, which is “the package manager for the Erlang ecosystem”. Actually, any language that compiles to run on the BEAM VM can use Hex packages.

In the hex.pm package repository you can find a specific library you need in your project. It’s very useful to browse versions, docs, and the code, because you will find all the necessary information there.

To start using hex, you have to install it locally (unless already exists):

mix local.hex --force --if-missing

How to fetch Elixir dependencies?

You already know what Elixir dependencies are, how to find and where to require them in your project. Now is the time to download them using the Mix tool.

Mix is a build tool that provides tasks for managing your project dependencies.

All dependencies land into deps/ folder in your project root directory. With mix executable you can run the following commands:

# Gets all dependencies using the Hex package manager
# and stores them in the deps/ directory:
» mix deps.get
# Compiles all dependencies:
» mix deps.compile

You can download all the dependencies, compile them, and the entire project by the way by typing:

mix do deps.get, deps.compile, compile

Run just mix.deps to lists all dependencies and their statuses in the following format:

# APP VERSION (SCM) (MANAGER)
# [locked at REF]
# STATUS:

Where REF is either a package checksum or a commit object name with its branch/tag information.

How to verify Elixir dependencies?

Dependencies get outdated. They are (or at least should be 😉) evolving, so we have to keep them fresh. Fortunately, we are provided with great tooling to manage their versions and updates.

Just remember the limitations we have by using a specific version. If you declared “~> 1.1.0”, you are limited up to “~> 1.1.9” and similarly. Once you are aware of that, you can start upgrading your packages.

First of all, let’s use mix hex.outdated. It shows outdated Hex deps for the current project. The output looks as follows:

Dependency                 Current  Latest  Status
absinthe 1.5.5 1.6.2 Update not possible
cowboy 2.8.0 2.8.0 Up-to-date
credo 1.5.4 1.5.6 Update possible

Note 3 available statuses:

  • Up-to-date — the recent version of a package matches your declared requirement, no need to update.
  • Update possible — the declared requirement allows updating minor or patch version
  • Update not possible — due to the version lock in mix.exs it’s impossible to upgrade it

You can also view a specific package status:

mix hex.outdated credo
There is newer version of the dependency available 1.5.6 > 1.5.4!
Source Requirement Up-to-date
mix.exs ~> 1.5 Yes
Up-to-date indicates if the requirement matches the latest version.

The description is quite explanatory. It clearly says there is a newer version available. I have 1.5.4 currently installed, 1.5.6 is the latest one, and I declared ~> 1.5 in my mix.exs. I can update credo then.

The less you lock, the more you can update. Just beware of breaking changes and other limitations described in the SemVer Specification.

At the end of the output, you will find a link that provides a detailed summary of all packages. Have a look at an example of such a report here. Then, you can also browse a detailed diff of a particular package, e.g. https://diff.hex.pm/diff/timex/3.6.3..3.7.6, and see what changed across the given versions.

How to update Elixir dependencies?

Finally, it’s time to update available libraries. You can do it altogether by running the update command with a flag like that:

mix deps.update --all

Otherwise, you’ll see the following error:

** (Mix) "mix deps.update" expects dependencies as arguments or the --all option to update all dependencies

Let’s see also the first option now, to have more fine-grained control over our update.

I decided to update the credo library. You already know (from the previous paragraph) what version I have installed and what is the recent one. It’s time to run the above command with this specific package:

mix deps.update credo
Resolving Hex dependencies...
Dependency resolution completed:
Unchanged:
absinthe 1.5.5
absinthe_metrics 1.1.0
absinthe_phoenix 2.0.0
absinthe_plug 1.5.5
argon2_elixir 2.4.0
assertions 0.18.1
...
Upgraded:
credo 1.5.4 => 1.5.6
* Updating credo (Hex package)

The deps.update task tells you what dependencies are not changed and which of them has been upgraded. You can verify this by running git diff:

diff --git a/mix.lock b/mix.lock
index a52c0370..13175c0b 100644
--- a/mix.lock
+++ b/mix.lock
@@ -17,7 +17,7 @@

- "credo": {:hex, :credo, "1.5.4", "9914180105b438e378e94a844ec3a5088ae5875626fc945b7c1462b41afc3198", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cf51af45eadc0a3f39ba13b56fdac415c91b34f7b7533a13dc13550277141bc4"},
+ "credo": {:hex, :credo, "1.5.6", "e04cc0fdc236fefbb578e0c04bd01a471081616e741d386909e527ac146016c6", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "4b52a3e558bd64e30de62a648518a5ea2b6e3e5d2b164ef5296244753fc7eb17"},

And that’s basically it. No need to do anything else in the application codebase.

Bonus

In the end, I want to mention a useful tool that keeps external libraries up to date. In short — it creates pull requests to update Elixir dependencies in your project.

How does it work?

  1. Dependabot pulls and iterates on each dependency in your project to look for possible updates or security requirements.
  2. If any of them requires upgrading, Dependabot opens individual pull requests to update each one.
  3. Now it’s your turn to review and merge these PRs and make sure nothing breaks.

Finally, it’s also worth mentioning, Dependabot is free of charge.

Get the latest content immediately:
https://tinyletter.com/KamilLelonek

Summary

Let’s sum up what you’ve just learned. You already know what elixir dependencies are, how to require, fetch and manage them, and finally, what to do to keep them safely updated.

With this knowledge, you can make your product even more robust, error-proof and secure. You will always have the recent possible version of each package.

I hope you find this information useful, and you will start using them in your applications from now on. Please leave a comment if you have any insights.

--

--