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:
- Create a simple Elixir project with
mix new project
- Run
iex -S mix
- Take a look at the
_build
directory and notice that we now have a_build/dev
directory with that project’s compiled file - 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?