Elixir: Allow @requirements to be listed on Mix tasks

Created on 5 Feb 2020  ·  4Comments  ·  Source: elixir-lang/elixir

Today, if you want to define and use a Mix task in your project itself, you need to make sure to call "compile" or "app.start" before. The issue with this approach is that, if you update the task itself, calling "compile" will re-compile the task but not update the version currently running.

In other words, this is not enough:

defmodule Mix.Tasks.MyTask do
  use Mix.Task

  def run(argv) do
    Mix.Task.run("compile")
    # task code
  end
end

To fix it, you need to do:

defmodule Mix.Tasks.MyTask do
  use Mix.Task

  def run(argv) do
    Mix.Task.run("compile")
    __MODULE__.compiled_run(args)
  end

  def compiled_run(args) do
    # task code
  end
end

To make this easier, my suggestion is to add a @requirements annotation of tasks that should be run before the current one. So the outcome is this:

defmodule Mix.Tasks.MyTask do
  use Mix.Task

  @requirements "compile"
  # or: @requirements ["compile", "app.start"]

  def run(argv) do
    # task code
  end
end

It is a small amount of indirection but it will yield actual improvements to those writing their own tasks.

Mix Bug Feature Intermediate Discussion

Most helpful comment

"compile --warnings-as-errors" — need to split this string to separate the task name from the arguments, probably need to deal with shell quoting/escaping while splitting too.

this syntax is the same as in aliases:

defp aliases() do
  [
    test: ["compile --warnings-as-errors", "test"]
  ]
end

so that would be my first choice but option 4. sounds good to me too.

All 4 comments

The issue with this approach is that, if you update the task itself, calling "compile" will re-compile the task but not update the version currently running

Oh, I shot myself in the foot because of this several times before, thank you for the workaround 👍

So the outcome is this

What if I wanted to run a mix task with arguments as a requirement, how would I be supposed to specify it? I see several options:

  • "compile --warnings-as-errors" — need to split this string to separate the task name from the arguments, probably need to deal with shell quoting/escaping while splitting too.
  • ["compile", "--warnings-as-errors"] — this would conflict with the suggested way of specifying multiple requirements.
  • Register @requirements as accumulative, and specify a single task requirement (with optional arguments) at a time:

    @requirements ["compile", "--warnings-as-errors"]
    @requirements "app.start"
    

    This looks kinda verbose and error-prone.

  • {"compile", ["--warnings-as-errors"]} — my favorite so far.

"compile --warnings-as-errors" — need to split this string to separate the task name from the arguments, probably need to deal with shell quoting/escaping while splitting too.

this syntax is the same as in aliases:

defp aliases() do
  [
    test: ["compile --warnings-as-errors", "test"]
  ]
end

so that would be my first choice but option 4. sounds good to me too.

this syntax is the same as in aliases

Good point, I've looked up how Mix handles aliases and it uses OptionParser.split/1 under the hood, so splitting is already covered. Given that, I now think it makes sense to use the first option too.

I vote for option 1 too, I think it's the most intuitive since it's already present in the Elixir landscape. Thanks for bringing up those points @smaximov! 💟

Was this page helpful?
0 / 5 - 0 ratings

Related issues

josevalim picture josevalim  ·  42Comments

michalmuskala picture michalmuskala  ·  35Comments

josevalim picture josevalim  ·  44Comments

josevalim picture josevalim  ·  31Comments

josevalim picture josevalim  ·  33Comments