Introducing the taskfile

Stop using a Makefile for executing recurring tasks

Andrés Cecilia Luque
4 min readNov 24, 2020
The good old Makefile

TL;DR

A Makefile has important limitations when using it to execute recurring shell tasks.

A better alternative is to use a shell script with functions, which I called taskfile. Try it out by running the following command in your terminal, which will create a basic taskfile in the working directory:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/acecilia/taskfile/master/start.sh)"

How did we end up here?

When a software project grows, most of the times you end up having a list of recurring tasks you need to perform from the top of the repository. For example, some simple ones: build, test, release...

There are multiple alternatives for organizing the code that each of these tasks implements:

But let’s focus on one of the most popular and widespread solutions: to define each of the recurring tasks as a target inside a Makefile

The good and the bad of a Makefile

Using a Makefile to store and execute these recurring tasks seems like a good solution at first:

  • Mostly everybody knows how to run a Makefile when they see it
  • The make command line tool is multiplatform, and in many cases comes pre-installed in the OS, so no additional install steps are required
  • The syntax looks clear: each repetitive task is define as a target
  • Targets can depend on each other: running one task can trigger any other task in a specific order
build:
bazel build ...

test: build
bazel test ...

But as soon as the project grows and the recurring tasks become more complex than a one-line-command, some problems appear. What are supposed to be simple things become complex, verbose or not intuitive:

my_target1:
for i in $$(ls); do \
echo "This is a file: $$i"; \
done
my_target2:
$(eval MY_VAR := "this is a local variable")
echo $(MY_VAR)
my_target3:
echo "$$TMPDIR"
  • Whe executing the task, all commands run inside it are printed by default. To avoid it, you need to prefix the command with @ or to fully avoid printing the commands by adding .SILENT to the top of the Makefile:
my_target4:
@echo "hello world"

You can understand how all this small issues become a main problem as this recurring tasks grow and increase in complexity. The problem is that we are missusing the Makefile: make is a build automation tool, and was never intended to be use as a shortland for recuring tasks.

The alternative: taskfile

Most of the times, these recurring tasks are simple shell commands: the most ideal solution would be to write them inside a shell script file. We would also need a way of grouping the code based on the recurring tasks to perform. The solution is very simple: to use a shell script with functions inside.

#!/bin/zshset -euo pipefail # 1name="Andres" # 2say_hello() {
surname="$1" # 3
echo "Hello, I am $name $surname" # 4
}
# What this task does: Says hello and bye # 5
say_hello_and_bye() {
say_hello $@ # 6
echo "Bye!"
}
"$@" # 7

Let’s go trough the details:

  1. set -euo pipefail enables a sort of strict mode for running the script
  2. It is possible to define global variables
  3. It is possible to define local variables
  4. It is possible to reuse global and local variables easily
  5. It is possible to add comments and document your code
  6. It is possible to have some tasks calling other tasks
  7. This is what makes everything work: executes all the parameters passed to the script as if they were a function

So the above script can be called as follows:

  • Running it as ./taskfile_example.sh say_hello Cecilia will print:
Hello, I am Andres Cecilia
  • Running it as ./taskfile_example.sh say_hello_and_bye Cecilia will print:
Hello, I am Andres Cecilia 
Bye!
  • If you want to go the extra mile, you can rename the script as taskfile and add the alias alias task="./taskfile" to your shell, so calling the tasks inside the taskfile is even shorter. Some examples:
task say_hello Cecilia
task say_hello_and_bye Cecilia
task build
task test

Try it out now

Running the following command from your terminal will create a basic taskfile in the working directory:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/acecilia/taskfile/master/start.sh)"

--

--