View Source Mix (Mix v1.17.3)

Mix is a build tool that provides tasks for creating, compiling, and testing Elixir projects, managing its dependencies, and more.

Mix.Project

The foundation of Mix is a project. A project can be defined by using Mix.Project in a module, usually placed in a file named mix.exs:

defmodule MyApp.MixProject do
  use Mix.Project

  def project do
    [
      app: :my_app,
      version: "1.0.0"
    ]
  end
end

See the Mix.Project module for detailed documentation on Mix projects.

Once the project is defined, a number of default Mix tasks can be run directly from the command line:

  • mix compile - compiles the current project
  • mix test - runs tests for the given project
  • mix run - runs a particular command inside the project

Each task has its own options and sometimes specific configuration to be defined in the project/0 function. You can use mix help to list all available tasks and mix help NAME to show help for a particular task.

The best way to get started with your first project is by calling mix new my_project from the command line.

Mix.Task

Tasks are what make Mix extensible.

Projects can extend Mix behaviour by adding their own tasks. For example, adding the task below inside your project will make it available to everyone that uses your project:

defmodule Mix.Tasks.Hello do
  use Mix.Task

  def run(_) do
    Mix.shell().info("Hello world")
  end
end

The task can now be invoked with mix hello.

See the Mix.Task behaviour for detailed documentation on Mix tasks.

Dependencies

Mix also manages your dependencies and integrates nicely with the Hex package manager.

In order to use dependencies, you need to add a :deps key to your project configuration. We often extract the list of dependencies into its own function:

defmodule MyApp.MixProject do
  use Mix.Project

  def project do
    [
      app: :my_app,
      version: "1.0.0",
      deps: deps()
    ]
  end

  defp deps do
    [
      {:ecto, "~> 2.0"},
      {:plug, github: "elixir-lang/plug"}
    ]
  end
end

You can run mix help deps to learn more about dependencies in Mix.

Environments

Mix supports different environments. Environments allow developers to prepare and organize their project specifically for different scenarios. By default, Mix provides three environments:

  • :dev - the default environment
  • :test - the environment mix test runs on
  • :prod - the environment your dependencies run on

The environment can be changed via the command line by setting the MIX_ENV environment variable, for example:

$ MIX_ENV=prod mix run server.exs

You can also specify that certain dependencies are available only for certain environments:

{:some_test_dependency, "~> 1.0", only: :test}

When running Mix via the command line, you can configure the default environment or the preferred environment per task via the def cli function in your mix.exs. For example:

def cli do
  [
    default_env: :local,
    preferred_envs: [docs: :docs]
  ]
end

The environment can be read via Mix.env/0.

Targets

Besides environments, Mix supports targets. Targets are useful when a project needs to compile to different architectures and some of the dependencies are only available to some of them. By default, the target is :host but it can be set via the MIX_TARGET environment variable.

When running Mix via the command line, you can configure the default target or the preferred target per task via the def cli function in your mix.exs. For example:

def cli do
  [
    default_target: :local,
    preferred_targets: [docs: :docs]
  ]
end

The target can be read via Mix.target/0.

Configuration

Mix allows you to configure the application environment of your application and of your dependencies. See the Application module to learn more about the application environment. On this section, we will focus on how to configure it at two distinct moments: build-time and runtime.

Avoiding the application environment

The application environment is discouraged for libraries. See Elixir's Library Guidelines for more information.

Build-time configuration

Whenever you invoke a mix command, Mix loads the configuration in config/config.exs, if said file exists. It is common for the config/config.exs file itself to import other configuration based on the current MIX_ENV, such as config/dev.exs, config/test.exs, and config/prod.exs, by calling Config.import_config/1:

import Config
import_config "#{config_env()}.exs"

We say config/config.exs and all imported files are build-time configuration as they are evaluated whenever you compile your code. In other words, if your configuration does something like:

import Config
config :my_app, :secret_key, System.fetch_env!("MY_APP_SECRET_KEY")

The :secret_key key under :my_app will be computed on the host machine before your code compiles. This can be an issue if the machine compiling your code does not have access to all environment variables used to run your code, as loading the config above will fail due to the missing environment variable. Furthermore, even if the environment variable is set, changing the environment variable will require a full recompilation of your application by calling mix compile --force (otherwise your project won't start). Luckily, Mix also provides runtime configuration, which is preferred in such cases and we will see next.

Runtime configuration

To enable runtime configuration in your release, all you need to do is to create a file named config/runtime.exs:

import Config
config :my_app, :secret_key, System.fetch_env!("MY_APP_SECRET_KEY")

This file is executed whenever your project runs. If you assemble a release with mix release, it also executes every time your release starts.

Aliases

Aliases are shortcuts or tasks specific to the current project.

In the Mix.Task section, we have defined a task that would be available to everyone using our project as a dependency. What if we wanted the task to only be available for our project? Just define an alias:

defmodule MyApp.MixProject do
  use Mix.Project

  def project do
    [
      app: :my_app,
      version: "1.0.0",
      aliases: aliases()
    ]
  end

  defp aliases do
    [
      c: "compile",
      hello: &hello/1,
      paid_task: &paid_task/1
    ]
  end

  defp hello(_) do
    Mix.shell().info("Hello world")
  end

  defp paid_task(_) do
    Mix.Task.run("paid.task", [
      "first_arg",
      "second_arg",
      "--license-key",
      System.fetch_env!("SOME_LICENSE_KEY")
    ])
  end
end

In the example above, we have defined three aliases. One is mix c which is a shortcut for mix compile. Another is named mix hello and the third is named mix paid_task, which executes the code inside a custom function to invoke the paid.task task with several arguments, including one pulled from an environment variable.

Aliases may also be lists, specifying multiple tasks to be run consecutively:

[all: [&hello/1, "deps.get --only #{Mix.env()}", "compile"]]

In the example above, we have defined an alias named mix all, that prints "Hello world", then fetches dependencies specific to the current environment, and compiles the project.

Aliases can also be used to augment existing tasks. Let's suppose you want to augment mix clean to clean another directory Mix does not know about:

[clean: ["clean", &clean_extra/1]]

Where &clean_extra/1 would be a function in your mix.exs with extra cleanup logic.

If the alias is overriding an existing task, the arguments given to the alias will be forwarded to the original task in order to preserve semantics. Otherwise arguments given to the alias are appended to the arguments of the last task in the list.

Another use case of aliases is to run Elixir scripts and shell commands, for example:

# priv/hello1.exs
IO.puts("Hello One")

# priv/hello2.exs
IO.puts("Hello Two")

# priv/world.sh
#!/bin/sh
echo "world!"

# mix.exs
defp aliases do
  [
    some_alias: ["hex.info", "run priv/hello1.exs", "cmd priv/world.sh"]
  ]
end

In the example above we have created the alias some_alias that will run the task mix hex.info, then mix run to run an Elixir script, then mix cmd to execute a command line shell script. This shows how powerful aliases mixed with Mix tasks can be.

One common pitfall of aliases comes when trying to invoke the same task multiple times. Mix tasks are designed to run only once. This prevents the same task from being executed multiple times. For example, if there are several tasks depending on mix compile, the code will be compiled only once.

Similarly, mix format can only be invoked once. So if you have an alias that attempts to invoke mix format multiple times, it won't work unless it is explicitly reenabled using Mix.Task.reenable/1:

another_alias: [
  "format --check-formatted priv/hello1.exs",
  "cmd priv/world.sh",
  fn _ -> Mix.Task.reenable("format") end,
  "format --check-formatted priv/hello2.exs"
]

Some tasks are automatically reenabled though, as they are expected to be invoked multiple times, such as: mix cmd, mix do, mix xref, etc.

Finally, aliases defined in the current project do not affect its dependencies and aliases defined in dependencies are not accessible from the current project, with the exception of umbrella projects. Umbrella projects will run the aliases of its children when the umbrella project itself does not define said alias and there is no task with said name.

Environment variables

Several environment variables can be used to modify Mix's behavior.

Mix responds to the following variables:

  • MIX_ARCHIVES - specifies the directory into which the archives should be installed (default: ~/.mix/archives)

  • MIX_BUILD_PATH - sets the project Mix.Project.build_path/0 config. This option must always point to a subdirectory inside a temporary directory. For instance, never "/tmp" or "_build" but "_build/PROD" or "/tmp/PROD", as required by Mix. This environment variable is used mostly by external build tools. For your CI servers, you likely want to use MIX_BUILD_ROOT below.

  • MIX_BUILD_ROOT - sets the root directory where build artifacts should be written to. For example, "_build". If MIX_BUILD_PATH is set, this option is ignored.

  • MIX_DEBUG - outputs debug information about each task before running it

  • MIX_DEPS_PATH - sets the project Mix.Project.deps_path/0 config for the current project (default: deps)

  • MIX_ENV - specifies which environment should be used. See Environments

  • MIX_EXS - changes the full path to the mix.exs file

  • MIX_HOME - path to Mix's home directory, stores configuration files and scripts used by Mix (default: ~/.mix)

  • MIX_INSTALL_DIR (since v1.12.0) - specifies directory where Mix.install/2 keeps install cache

  • MIX_PATH - appends extra code paths

  • MIX_PROFILE - a list of comma-separated Mix tasks to profile the time spent on functions by the process running the task

  • MIX_QUIET - does not print information messages to the terminal

  • MIX_REBAR3 - path to rebar3 command that overrides the one Mix installs (default: ~/.mix/rebar3)

  • MIX_TARGET - specifies which target should be used. See Targets

  • MIX_XDG - asks Mix to follow the XDG Directory Specification for its home directory and configuration files. This behavior needs to be opt-in due to backwards compatibility. MIX_HOME has higher preference than MIX_XDG. If none of the variables are set, the default directory ~/.mix will be used

Environment variables that are not meant to hold a value (and act basically as flags) should be set to either 1 or true, for example:

$ MIX_DEBUG=1 mix compile

In addition, Mix also uses the following environment variables defined by other libraries

  • HEX_CACERTS_PATH - use specified CA certificate file instead of default system CA certificates. This configures how HTTPS calls are made via Erlang ssl module to fetch remote archives and packages. For more details, see mix hex.config.

Summary

Functions

Returns the default compilers used by Mix.

Sets Mix debug mode.

Returns true if Mix is in debug mode, false otherwise.

Ensures the given application from Erlang/OTP or Elixir and its dependencies are available in the path.

Returns the current Mix environment.

Changes the current Mix environment to env.

Installs and starts dependencies.

Returns the directory where the current Mix.install/2 project resides.

Returns whether Mix.install/2 was called in the current node.

The path for local archives or escripts.

Raises a Mix error that is nicely formatted, defaulting to exit status 1.

Raises a Mix error that is nicely formatted.

Returns the current shell.

Sets the current shell.

Returns the Mix target.

Changes the current Mix target to target.

Functions

@spec compilers() :: [atom()]

Returns the default compilers used by Mix.

It can be used in your mix.exs to prepend or append new compilers to Mix:

def project do
  [compilers: Mix.compilers() ++ [:foo, :bar]]
end
@spec debug(boolean()) :: :ok

Sets Mix debug mode.

@spec debug?() :: boolean()

Returns true if Mix is in debug mode, false otherwise.

Link to this function

ensure_application!(app)

View Source (since 1.15.0)

Ensures the given application from Erlang/OTP or Elixir and its dependencies are available in the path.

Generally speaking, you should list the Erlang application dependencies under the :extra_applications section of your mix.exs. This must only be used by Mix tasks which wish to avoid depending on Erlang/Elixir for certain reasons.

This function does not start the given applications.

@spec env() :: atom()

Returns the current Mix environment.

This function should not be used at runtime in application code (as opposed to infrastructure and build code like Mix tasks). Mix is a build tool and may not be available after the code is compiled (for example in a release).

To differentiate the program behavior depending on the environment, it is recommended to use application environment through Application.get_env/3. Proper configuration can be set in config files, often per-environment (see the Config module for more information).

@spec env(atom()) :: :ok

Changes the current Mix environment to env.

Be careful when invoking this function as any project configuration won't be reloaded.

This function should not be used at runtime in application code (see env/0 for more information).

Link to this function

install(deps, opts \\ [])

View Source (since 1.12.0)

Installs and starts dependencies.

The given deps should be in the same format as defined in a regular Mix project. See mix help deps for more information. As a shortcut, an atom can be given as dependency to mean the latest version. In other words, specifying :decimal is the same as {:decimal, ">= 0.0.0"}.

After each successful installation, a given set of dependencies is cached so starting another VM and calling Mix.install/2 with the same dependencies will avoid unnecessary downloads and compilations. The location of the cache directory can be controlled using the MIX_INSTALL_DIR environment variable.

This function can only be called outside of a Mix project and only with the same dependencies in the given VM.

Options

  • :force - if true, runs with empty install cache. This is useful when you want to update your dependencies or your install got into an inconsistent state. To use this option, you can also set the MIX_INSTALL_FORCE environment variable. (Default: false)

  • :verbose - if true, prints additional debugging information (Default: false)

  • :consolidate_protocols - if true, runs protocol consolidation via the mix compile.protocols task (Default: true)

  • :elixir - if set, ensures the current Elixir version matches the given version requirement (Default: nil)

  • :system_env (since v1.13.0) - a list or a map of system environment variable names with respective values as binaries. The system environment is made part of the Mix.install/2 cache, so different configurations will lead to different apps

  • :config (since v1.13.0) - a keyword list of keyword lists of compile-time configuration. The configuration is part of the Mix.install/2 cache, so different configurations will lead to different apps. For this reason, you want to minimize the amount of configuration set through this option. Use Application.put_all_env/2 for setting other runtime configuration.

  • :config_path (since v1.14.0) - path to a configuration file. If a runtime.exs file exists in the same directory as the given path, it is loaded too.

  • :lockfile (since v1.14.0) - path to a lockfile to be used as a basis of dependency resolution.

  • :start_applications (since v1.15.3) - if true, ensures that installed app and its dependencies are started after install (Default: true)

Examples

Installing :decimal and :jason:

Mix.install([
  :decimal,
  {:jason, "~> 1.0"}
])

Installing :nx and :exla, and configuring the underlying applications and environment variables:

Mix.install(
  [:nx, :exla],
  config: [
    nx: [default_backend: EXLA]
  ],
  system_env: [
    XLA_TARGET: "cuda111"
  ]
)

Installing a Mix project as a path dependency along with its configuration and deps:

# $ git clone https://github.com/hexpm/hexpm /tmp/hexpm
# $ cd /tmp/hexpm && mix setup

Mix.install(
  [
    {:hexpm, path: "/tmp/hexpm", env: :dev},
  ],
  config_path: "/tmp/hexpm/config/config.exs",
  lockfile: "/tmp/hexpm/mix.lock"
)

Hexpm.Repo.query!("SELECT COUNT(1) from packages")
#=> ...

The example above can be simplified by passing the application name as an atom for :config_path and :lockfile:

Mix.install(
  [
    {:hexpm, path: "/tmp/hexpm", env: :dev},
  ],
  config_path: :hexpm,
  lockfile: :hexpm
)

Limitations

There is one limitation to Mix.install/2, which is actually an Elixir behavior. If you are installing a dependency that defines a struct or macro, you cannot use the struct or macro immediately after the install call. For example, this won't work:

Mix.install([:decimal])
%Decimal{} = Decimal.new(42)

That's because Elixir first expands all structs and all macros, and then it executes the code. This means that, by the time Elixir tries to expand the %Decimal{} struct, the dependency has not been installed yet.

Luckily this has a straightforward solution, which is to move the code inside a module:

Mix.install([:decimal])

defmodule Script do
  def run do
    %Decimal{} = Decimal.new(42)
  end
end

Script.run()

The contents inside defmodule will only be expanded and executed after Mix.install/2 runs, which means that any struct, macros, and imports will be correctly handled.

Environment variables

The MIX_INSTALL_DIR environment variable configures the directory that caches all Mix.install/2. It defaults to the "mix/install" folder in the default user cache of your operating system. You can use install_project_dir/0 to access the directory of an existing install (alongside other installs):

iex> Mix.install([])
iex> Mix.install_project_dir()

The MIX_INSTALL_FORCE is available since Elixir v1.13.0 and forces Mix.install/2 to discard any previously cached entry of the current install.

The MIX_INSTALL_RESTORE_PROJECT_DIR environment variable may be specified since Elixir v1.16.2. It should point to a previous installation directory, which can be obtained with Mix.install_project_dir/0 (after calling Mix.install/2). Using a restore dir may speed up the installation, since matching dependencies do not need be refetched nor recompiled. This environment variable is ignored if :force is enabled.

Link to this function

install_project_dir()

View Source (since 1.16.2)
@spec install_project_dir() :: Path.t() | nil

Returns the directory where the current Mix.install/2 project resides.

Link to this function

installed?()

View Source (since 1.13.0)
@spec installed?() :: boolean()

Returns whether Mix.install/2 was called in the current node.

Link to this function

path_for(atom)

View Source (since 1.10.0)
@spec path_for(:archives | :escripts) :: String.t()

The path for local archives or escripts.

@spec raise(binary()) :: no_return()

Raises a Mix error that is nicely formatted, defaulting to exit status 1.

Link to this function

raise(message, opts)

View Source (since 1.12.3)
@spec raise(binary(), [{:exit_status, non_neg_integer()}]) :: no_return()

Raises a Mix error that is nicely formatted.

Options

  • :exit_status - defines exit status, defaults to 1
@spec shell() :: module()

Returns the current shell.

shell/0 can be used as a wrapper for the current shell. It contains conveniences for requesting information from the user, printing to the shell and so forth. The Mix shell is swappable (see shell/1), allowing developers to use a test shell that simply sends messages to the current process instead of performing IO (see Mix.Shell.Process).

By default, this returns Mix.Shell.IO.

Examples

Mix.shell().info("Preparing to do something dangerous...")

if Mix.shell().yes?("Are you sure?") do
  # do something dangerous
end
@spec shell(module()) :: :ok

Sets the current shell.

As an argument you may pass Mix.Shell.IO, Mix.Shell.Process, Mix.Shell.Quiet, or any module that implements the Mix.Shell behaviour.

After calling this function, shell becomes the shell that is returned by shell/0.

Examples

iex> Mix.shell(Mix.Shell.IO)
:ok

You can use shell/0 and shell/1 to temporarily switch shells, for example, if you want to run a Mix Task that normally produces a lot of output:

shell = Mix.shell()
Mix.shell(Mix.Shell.Quiet)

try do
  Mix.Task.run("noisy.task")
after
  Mix.shell(shell)
end
@spec target() :: atom()

Returns the Mix target.

@spec target(atom()) :: :ok

Changes the current Mix target to target.

Be careful when invoking this function as any project configuration won't be reloaded.