---
title: Running Project Mix Commands from Any Directory
teaser: 'How I discovered how to run project-specific Mix commands from any directory.

  '
tags: elixir,web
author: Jake Craige
published_on: 2017-02-03
---

While adding [Credo] as supported linter on [Hound], I ran into a problem that
I hadn't seen before. I needed to run `mix credo` in a temporary directory, but
soon discovered **Mix only searches the current directory for a `.mix.exs`.**
What this means is that you can only run `mix` from the root of your project if
you depend on any configuration or dependencies in your `mix.exs` file. In most
cases this isn't a problem, but for how Hound works, it introduced a challenge.

To use Credo, you add it to your `mix.exs` file and execute `mix credo` to run
it. When Hound lints a file, it downloads one file at a time into a temporary
directory and runs the linter with a shell command, in that directory, on that
file.

The challenge here is that to run Credo we need to run `mix credo`, which
depends on a `mix.exs`, but we don't have one. We only have the one file.

# Problem Solving

One approach to solve this would be to add a `mix.exs` in the temporary
directory with Credo as a dependency before running `mix credo`. But there's
a huge problem with this, since Hound lints each file individually, we would be
compiling Credo for each file of a project. This would be seriously time
consuming and expensive.

What we'd like is to install the linter once, and reuse it for every file. This
way we only need to compile it once and each file can use the pre-compiled
version. This is how all of Hound's existing linters work.

Following that approach, let's add a `mix.exs` to the root of the linter app
that looks like so:

```elixir
defmodule Linters.Mixfile do
  use Mix.Project

  def project do
    [
      app: :linters,
      version: "0.1.0",
      elixir: "~> 1.4",
      build_embedded: Mix.env == :prod,
      start_permanent: Mix.env == :prod,
      deps: deps(),
   ]
  end

  defp deps do
    [{:credo, "~> 0.6"}]
  end
end
```

After installing our dependencies with `mix deps.get`, we can execute
`mix credo` to run the linter. Sweet!

Unfortunately, we're now back to the initial problem, we can run it from the
root of the project, but nowhere else. If you `cd` into any directory that
doesn't contain this `mix.exs` and try to run `mix credo`, it will fail like
this:

```sh
$ mix credo
** (Mix) The task "credo" could not be found
```

Next, we need to figure out how to make that work. After a bit of "sleuthing",
a.k.a googling, I came across an environment variable that looked like it would
solve this problem, `MIX_EXS`. From [the docs][mix-exs]:

> `MIX_EXS` - changes the full path to the `mix.exs` file

Since we know we need the `mix.exs` to load our dependencies, let's try using
this. Assuming we're in a directory called `linters` and we navigate to its
parent via `cd ..`. We'll run it like so:

```sh
$ MIX_EXS=linters/mix.exs mix credo
Unchecked dependencies for environment dev:
* credo (Hex package)
  the dependency is not available, run "mix deps.get"
** (Mix) Can't continue due to errors on dependencies
```

Seems like progress! This says the credo dependency isn't available, which is
unexpected since we've already compiled it when we installed it above. It tells
us to run `mix deps.get`, so let's try it.

```sh
$ MIX_EXS=linters/mix.exs mix deps.get
Running dependency resolution...
Dependency resolution completed:
  bunt 0.1.6
  credo 0.5.3
* Getting credo (Hex package)
  Checking package (https://repo.hex.pm/tarballs/credo-0.5.3.tar)
  Using locally cached package
* Getting bunt (Hex package)
  Checking package (https://repo.hex.pm/tarballs/bunt-0.1.6.tar)
  Using locally cached package

$ MIX_EXS=linters/mix.exs mix credo
==> bunt
Compiling 2 files (.ex)
Generated bunt app
==> credo
Compiling 122 files (.ex)
Generated credo app
No files found!

Please report incorrect results: https://github.com/rrrene/credo/issues

Analysis took 0.00 seconds (0.00s to load, 0.00s running checks)
0 mods/funs, found no issues.

Use `--strict` to show all issues, `--help` for options.
```

Successful Credo run (Yay!), but an unexpected compilation since we've already
compiled it in the `linters` directory. This is exactly what we were trying to
avoid in the first place. Turns out this is compiling them into our current
directory. If we run `ls` we'll see that it has created a few files and
directories, `_build`, `deps` and `mix.lock`.

I have to admit, this one took quite a while to figure out what was going on and
how to get past it. Lots of "sleuthing" occurred. Eventually I discovered that
these were all configurable in the `mix.exs` file from
[this documentation][config-docs].

That docs tell us that we can configure the `:deps_path` and `:lockfile` but
don't mention how to change the build path. This is one of those places where
open source is really awesome. I was able to discover it _is_ configurable
through an aptly named `:build_path` key [in the source][build-path-config].

Now that we can configure it, let's do so. The problem with the defaults is that
they assume a relative path from where we're running `mix`, and that's not true
for our use. So let's update the project config to use absolute ones:

```elixir
  def project do
    [
      app: :linters,
      version: "0.1.0",
      elixir: "~> 1.4",
      build_embedded: Mix.env == :prod,
      start_permanent: Mix.env == :prod,
      deps: deps(),
      lockfile: Path.expand("mix.lock", __DIR__),
      deps_path: Path.expand("deps", __DIR__),
      build_path: Path.expand("_build", __DIR__),
   ]
  end
```

Now, after removing the build artifacts from our previous attempt,`_build`,
`deps` and `mix.lock`, we can try running the Credo again:

```sh
$ MIX_EXS=linters/mix.exs mix credo
No files found!

Please report incorrect results: https://github.com/rrrene/credo/issues

Analysis took 0.03 seconds (0.00s to load, 0.03s running checks)
0 mods/funs, found no issues.

Use `--strict` to show all issues, `--help` for options.
```

There we go! We can now run `mix credo` from anywhere on our system as long as
we specify the `MIX_EXS` environment variable with the path to the project that
has it compiled.

[Credo]: https://github.com/rrrene/credo
[Hound]: https://houndci.com
[mix-exs]: https://hexdocs.pm/mix/master/Mix.html#module-environment-variables
[config-docs]: https://hexdocs.pm/mix/Mix.Project.html#module-configuration
[build-path-config]: https://github.com/elixir-lang/elixir/blob/690c2c40c948564f7db5fccfd66246ed78c6fe8d/lib/mix/lib/mix/project.ex#L361
