Inspecting, changing and debugging Elixir project dependencies

You have probably heard that Elixir is very explicit and I’d say the same!

One of the things I really like in Elixir projects is that its dependencies are all explicitly included in the deps/ directory. Every time we’re curious about how a dependency works, we can just look at deps/lib-name.

After working on a project for a while, I’ve noticed a strange behavior in one of our dependencies. I’ve opened the deps/lib-name directory in my editor, inserted a couple IO.inspect calls, and recompiled my dependencies with:

mix deps.compile

That seems logical, doesn’t it? For my surprise, I didn’t get the expected behavior. The reason is straight-forward. Elixir projects (and its dependencies) are by default compiled for dev, test and prod environments. These compiled files are located in _build/, in a directory that holds the environment name (_build/dev/, _build/test/ and _build/prod/). Accessing the applied changes in the source code depends on which environment we’re running it.

The kind of task we execute is deterministic. Running a test will make our _build/test/ compiled files to be used, just like iex -S mix would use build/dev/ files and so on.

What I did before would work if I tried to call the function I was inspecting through iex -S mix. That’s only because Elixir’s default environment is dev. In order to make those changes to be visible in my tests, I’d have to:

MIX_ENV=test mix deps.compile

There’s a simple test we can run to understand this better:

  1. Create a simple Elixir project with mix new project
  2. Run iex -S mix
  3. Take a look at the _build directory and notice that we now have a _build/dev directory with that project’s compiled file
  4. Run mix test and notice that we now have a _build/test

Using :path option

Instead of running mix deps.compile for every change, there is a more convenient alternative.

When we declare our dependencies in the mix.exs file we need to give it the library name and its version. We can also give it some extra options, among them there is a specific option that will help us when debugging dependencies: path.

  defp deps do
    [{:plug, path: "deps/plug"},
     ...]
  end

When we set :path, our dependency will be automatically recompiled by our project, as mentioned in Mix.Tasks.Deps docs (we can also access it through mix help deps).

Path and in umbrella dependencies are automatically recompiled by the parent project whenever they change.

This way, every time I make a change and run a Mix task like mix test or iex -S mix, the dependency will be recompiled without having to run the compile task over and over again. With the path option we can also omit the version because it’ll be retrieved from the project being addressed.

An important consideration here is that we can inform any path in this option, it doesn’t need to be a source code in the deps/ directory. It could, for example, point to a checkout of a dependency in our machine. This is an excellent option when we’re working on an open source project. We can test the changes we intend to submit (or to find an issue) inside a real project more easily.

A dependency of other dependencies

We know that Phoenix Framework uses Plug for managing its requests but it’s not listed in our project dependencies. That’s because Plug is a Phoenix dependency.

In order to inspect Plug we’d have to include it on our deps function and use the override: true option. Otherwise Mix will warn us that there is a dependency conflict.

  defp deps do
    [{:phoenix, "~> 1.1.4"},
     {:postgrex, ">= 0.0.0"},
     {:phoenix_ecto, "~> 2.0"},
     {:phoenix_html, "~> 2.4"},
     {:phoenix_live_reload, "~> 1.0", only: :dev},
     {:cowboy, "~> 1.0"},
     {:plug, path: "deps/plug", override: true}]
  end

Conclusion

I believe that Elixir has already proven that explicitness is a great asset. Keeping project’s dependencies in deps/ has already proved useful when searching for code and documentation.

In case you find a bug in any of your dependencies, we strongly recommend that you submit a pull request back to its repository. You’ll help other developers that are going through the same issue and you’ll he the community growth.

What about you? Are there any tips you’d like to share for debugging Elixir dependencies?


Subscribe to Elixir Radar

Comments are closed.