1 Introduction to Mix

Elixir ships with a few applications to make building and deploying projects with Elixir easier and Mix is certainly their backbone.

Mix is a build tool that provides tasks for creating, compiling, testing (and soon releasing) Elixir projects. Mix is inspired by the Leiningen build tool for Clojure and was written by one of its contributors.

In this chapter, you will learn how to create projects using mix and install dependencies. In the following sections, we will also learn how to create OTP applications and create custom tasks with Mix.

1.1 Bootstrapping

In order to start your first project, simply use the mix new command passing the path to your project. For now, we will create an project called my_project in the current directory:

$ mix new my_project --bare

Mix will create a directory named my_project with few files in it:

.gitignore
README.md
mix.exs
lib/my_project.ex
test/test_helper.exs
test/my_project_test.exs

Let's take a brief look at some of these.

Note: Mix is an Elixir executable. This means that in order to run mix, you need to have elixir's executable in your PATH. If not, you can run it by passing the script as argument to elixir:

$ bin/elixir bin/mix new ./my_project

Note that you can also execute any script in your PATH from Elixir via the -S option:

$ bin/elixir -S mix new ./my_project

When using -S, elixir finds the script wherever it is in your PATH and executes it.

1.1.1 mix.exs

This is the file with your projects configuration. It looks like this:

defmodule MyProject.Mixfile do
  use Mix.Project

  def project do
    [ app: :my_project,
      version: "0.0.1",
      deps: deps ]
  end

  # Configuration for the OTP application
  def application do
    []
  end

  # Returns the list of dependencies in the format:
  # { :foobar, git: "https://github.com/elixir-lang/foobar.git", tag: "0.1" }
  #
  # To specify particular versions, regardless of the tag, do:
  # { :barbat, "~> 0.1", github: "elixir-lang/barbat" }
  defp deps do
    []
  end
end

Our mix.exs defines two functions: project, which returns project configuration like the project name and version, and application, which is used to generate an Erlang application that is managed by the Erlang Runtime. In this chapter, we will talk about the project function. We will go into detail about what goes in the application function in the next chapter.

1.1.2 lib/my_project.ex

This file contains a simple module definition to lay out our code:

defmodule MyProject do
end

1.1.3 test/my_project_test.exs

This file contains a stub test case for our project:

defmodule MyProjectTest do
  use ExUnit.Case

  test "the truth" do
    assert true
  end
end

It is important to note a couple things:

1) Notice the file is an Elixir script file (.exs). This is convenient because we don't need to compile test files before running them;

2) We define a test module named MyProjectTest, using ExUnit.Case to inject default behavior and define a simple test. You can learn more about the test framework in the ExUnit chapter;

1.1.4 test/test_helper.exs

The last file we are going to check is the test_helper.exs, which simply sets up the test framework:

ExUnit.start

This file will be automatically required by Mix every time before we run our tests. And that is it, our project is created. We are ready to move on!

1.2 Exploring

Now that we created our new project, what can we do with it? In order to check the commands available to us, just run the help task:

$ mix help

It will print all the available tasks. You can get further information by invoking mix help TASK.

Play around with the available tasks, like mix compile and mix test, and execute them in your project to check how they work.

1.3 Compilation

Mix can compile our project for us. The default configurations uses lib/ for source files and ebin/ for compiled beam files. You don't even have to provide any compilation-specific setup but if you must, some options are available. For instance, if you want to put your compiled files in another directory besides ebin, simply set in :compile_path in your mix.exs file:

def project do
  [compile_path: "ebin"]
end

In general, Mix tries to be smart and compiles only when necessary.

Note that after you compile for the first time, Mix generates a my_project.app file inside your ebin directory. This file defines an Erlang application based on the contents of the application function in your Mix project.

The .app file holds information about the application, what are its dependencies, which modules it defines and so forth. The application is automatically started by Mix every time you run some commands and we will learn how to configure it in the next chapter.

1.4 Dependencies

Mix is also able to manage dependencies. Dependencies should be listed in the project settings, as follows:

def project do
  [ app: :my_project,
    version: "0.0.1",
    deps: deps ]
end

defp deps do
  [ { :some_project, github: "some_project/other", tag: "0.3.0" },
    { :another_project, ">= 1.0.2", git: "https://example.com/another/repo.git" } ]
end

Note: Although not required, it is common to split dependencies into their own function.

The dependency is represented by an atom, followed by an optional the version requirement and some options detailed in the next section. Since most of the dependencies are git repositories, it is recommended to depend on a particular tag. However, if you want to depend on master or in a particular branch, you can define a version requirement to specify which versions of a given dependency you are willing to work against. It supports common operators like >=, <=, >, == as follows:

# Only version 2.0.0
"== 2.0.0"

# Anything later than 2.0.0
"> 2.0.0"

Requirements also support and and or for complex conditions:

# 2.0.0 and later until 2.1.0
">= 2.0.0 and < 2.1.0"

Since the example above is such a common requirement, it can be expressed as:

"~> 2.0.0"

Note that setting the version requirement does not affect the branch or tag that is checked out, so while a definition like the following is possible:

{ :some_project, "~> 0.5.0", github: "some_project/other", tag: "0.3.0" }

It will lead to a dependency that will never be satisfied, because the tag being checked out does not match the version requirement.

1.4.1 Source Code Management (SCM)

In the example above, we have used git to specify our dependencies. Mix is designed in a way it can support multiple SCM tools, shipping with :git and :path support by default. The most common options are:

  • :git - the dependency is a git repository that is retrieved and updated by Mix;
  • :path - the dependency is simply a path in the filesystem;
  • :compile - how to compile the dependency;
  • :app - the path of the application expected to be defined by the dependency;
  • :env - the environment to use from the dependency (more info below), defaults to :prod;

Each SCM may support custom options. :git, for example, supports the following:

  • :ref - an optional reference (a commit) to checkout the git repository;
  • :tag - an optional tag to checkout the git repository;
  • :branch - an optional branch to checkout the git repository;
  • :submodules - when true, initializes submodules recursively in the dependency;

1.4.2 Compiling dependencies

In order to compile a dependency, Mix looks into the repository for the best way to proceed. If the dependency contains one of the files below, it will proceed as follows:

  1. mix.exs - compiles the dependency directly with Mix by invoking the compile task;
  2. rebar.config or rebar.config.script - compiles using rebar compile deps_dir=DEPS, where DEPS is the directory where Mix will install the project dependencies by default;
  3. Makefile - simply invokes make;

If the dependency does not contain any of the above, you can specify a command directly with the :compile option:

    compile: "./configure && make"

If :compile is set to false, nothing is done.

1.4.3 Repeatability

An important feature in any dependency management tool is repeatability. For this reason when you first get your dependencies, Mix will create a file called mix.lock that contains checked out references for each dependency.

When another developer gets a copy of the same project, Mix will checkout exactly the same references, ensuring other developers can "repeat" the same setup.

Locks are automatically updated when deps.update is called and can be removed with deps.unlock.

1.4.4 Dependencies tasks

Elixir has many tasks to manage the project dependencies:

  • mix deps - List all dependencies and their status;
  • mix deps.get - Get all unavailable dependencies;
  • mix deps.compile - Compile dependencies;
  • mix deps.update - Update dependencies;
  • mix deps.clean - Remove dependencies files;
  • mix deps.unlock - Unlock the given dependencies;

Use mix help to get more information.

1.4.5 Dependencies of dependencies

If your dependency is another Mix or rebar project, Mix does the right thing: it will automatically fetch and handle all dependencies of your dependencies. However, if your project has two dependencies that share the same dependency and the SCM information for the shared dependency doesn't match between the parent dependencies, Mix will mark that dependency as diverged and emit a warning. To solve this issue you can declare the shared dependency in your project with the option override: true and Mix will use that SCM information to fetch the dependency.

1.5 Umbrella projects

It can be convenient to bundle multiple Mix projects together and run Mix tasks for them at the same time. They can be bundled and used together in what is called an umbrella project. An umbrella project can be created with the following command:

$ mix new project --umbrella

This will create a mix.exs file with the following contents:

defmodule Project.Mixfile do
  use Mix.Project

  def project do
    [ apps_path: "apps" ]
  end
end

The apps_path option specifies the directory where subprojects will reside. Mix tasks that run in the umbrella project will run for every project in the apps_path directory. For example mix compile or mix test will compile or test every project in the directory. It's important to note that an umbrella project is neither a regular Mix project, nor is it an OTP application nor can code source files be added.

If there are interdependencies between subprojects these have to be specified so that Mix can compile the projects in the correct order. If Project A depends on Project B, the dependency has to be specified in Project A's mix.exs file; modify the mix.exs file to specify the dependency:

defmodule A.Mixfile do
  use Mix.Project

  def project do
    [ app: :a,
      deps_path: "../../deps",
      lockfile: "../../mix.lock",
      deps: deps ]
  end

  defp deps do
    [ { :b, in_umbrella: true } ]
  end
end

Note the deps_path and lockfile options in the subproject above. If you have these options in all the subprojects in the umbrella they will share their dependencies. mix new inside the apps directory will automatically create a project with these options pre-set.

1.6 Environments

Mix has the concept of environments that allows a developer to customize compilation and other options based on an external setting. By default, Mix understands three environments:

  • dev - the one in which mix tasks are run by default;
  • test - used by mix test;
  • prod - the environment in which dependencies are loaded and compiled;

By default, these environments behave the same and all configuration we have seen so far will affect all three environments. Customization per environment can be done using the env: option:

def project do
  [ env: [
    prod: [compile_path: "prod_ebin"] ] ]
end

Mix will default to the dev environment (except for tests). The environment can be changed via the MIX_ENV environment variable:

$ MIX_ENV=prod mix compile

In the next chapters, we will learn more about building OTP applications with Mix and how to create your own tasks.