Creating custom Mix tasks for your Elixir projects.
Table of Contents
It’s not uncommon to want to extend your Elixir applications functionality by adding custom Mix tasks. Before we learn about how to create specific Mix tasks for our projects, let’s look at one that already exists:
$ mix phx.new my_phoenix_app * creating my_phoenix_app/config/config.exs * creating my_phoenix_app/config/dev.exs * creating my_phoenix_app/config/prod.exs * creating my_phoenix_app/config/prod.secret.exs * creating my_phoenix_app/config/test.exs * creating my_phoenix_app/lib/my_phoenix_app.ex * creating my_phoenix_app/lib/my_phoenix_app/endpoint.ex * creating my_phoenix_app/test/views/error_view_test.exs ...
As we can see from the shell command above, The Phoenix Framework has a custom Mix task to generate a new project. What if we could create something similar for our project? Well, the great news is we can, and Elixir makes this easy for us to do.
Let’s set up a basic Mix application.
$ mix new hello * creating README.md * creating .formatter.exs * creating .gitignore * creating mix.exs * creating lib * creating lib/hello.ex * creating test * creating test/test_helper.exs * creating test/hello_test.exs Your Mix project was created successfully. You can use "mix" to compile it, test it, and more: cd hello mix test Run "mix help" for more commands.
Now, in our lib/hello.ex file that Mix generated for us, let’s create a simple function that will output “Hello, World!”
defmodule Hello do @doc """ Outputs `Hello, World!` every time. """ def say do IO.puts("Hello, World!") end end
Custom Mix Task
Let’s create our custom Mix task. Create a new directory and file hello/lib/mix/tasks/hello.ex. Within this file, let’s insert these 7 lines of Elixir.
defmodule Mix.Tasks.Hello do use Mix.Task @shortdoc "Simply calls the Hello.say/0 function." def run(_) do # calling our Hello.say() function from earlier Hello.say() end end
Notice how we start the defmodule statement with
Mix.Tasks and the name we want to call from the command line.
On the second line, we introduce the
use Mix.Task which brings the
Mix.Task behaviour into the namespace.
We then declare a run function which ignores any arguments for now.
Within this function, we call our
Hello module and the
Loading your application
Mix does not automatically start our application or any of its dependencies which is fine for many Mix task use-cases but what if we need to use Ecto and interact with a database? In that case we need to make sure the app behind Ecto.Repo has started. There are 2 ways for us to handle this: explicitly starting an app or we can start our application which in turn will start the others.
Let’s look at how we can update our Mix task to start our application and dependencies:
defmodule Mix.Tasks.Hello do use Mix.Task @shortdoc "Simply calls the Hello.say/0 function." def run(_) do # This will start our application Mix.Task.run("app.start") Hello.say() end end
Mix Tasks in Action
Let’s checkout our mix task.
As long as we are in the directory it should work.
From the command line, run
mix hello, and we should see the following:
$ mix hello Hello, World!
Mix is quite friendly by default. It knows that everyone can make a spelling error now and then, so it uses a technique called fuzzy string matching to make recommendations:
$ mix hell ** (Mix) The task "hell" could not be found. Did you mean "hello"?
Did you also notice that we introduced a new module attribute,
@shortdoc? This comes in handy when shipping our application, such as when a user runs the
mix help command from the terminal.
$ mix help mix app.start # Starts all registered apps ... mix hello # Simply calls the Hello.say/0 function. ...
Note: Our code must be compiled before new tasks will appear in the
mix help output.
We can do this either by running
mix compile directly or by running our task as we did with
mix hello, which will trigger the compilation for us.
Caught a mistake or want to contribute to the lesson? Edit this page on GitHub!