20 April 2021
Taskfile or GNU make – the battle of automation tools. Let’s make the developer’s life easier!
GNU’s make and Go-based Task are two of the most commonly used build tools. Over the years, Make has become an industry-standard, even when it was not necessarily the right tool for the job. In this article, I’m going to go over some of the most important features, advantages, and disadvantages of both and help you make the right decision to improve your quality of life as a developer.
Automation is one of the most essential things in the work of a developer. As programmers, we constantly create all kinds of quality of life improvements for end-users. Unfortunately, during this process, we often forget to make our own life just a bit easier.
I worked with dozens of repositories full of copy-paste raw commands in the README file to set up the application, or even worse – with no (!) instruction on how to set up the application. It’s not possible to be efficient if we as developers have to spend 1-2 days setting up the project!
One automation utility to make life easier
One of the first things in daily work should be thinking about the others. It’s a good practice to document each step of the setup environment. It’s vital to ensure that the next person who joins the project won’t waste time thinking about how to run the application or do the deployment.
During application setup, one of the biggest pains is to copy-paste many complicated commands from documentation to run the project. There should be only one command to do the entire job!
What do you prefer? To run a simple make setup command or copy-paste line-by-line something like that:
The answer to this question is quite obvious…
In my daily work, I mainly use two tools to automate the setup processes. In this article, I want to explain why I prefer to use Taskfile over GNU Make most of the time.
The words “make” and “task” are both very common in the English language. In this article, I will use Makefile and Taskfile instead to avoid confusion and misunderstandings.
Task (Taskfile), i.e. – let’s keep things simple!
Task is a tool written in Golang. The syntax is based on YAML, which requires a specific structure. This structure might be annoying – the single-line script requires four lines of code to write. On the other hand, it’s a much simpler solution compared to GNU make. Getting started with Taskfile is very easy. The Taskfile help resources are satisfactory as well.
GNU make (Makefile), in other words…
GNU make is probably the most popular tool for automation setup. It’s fairly easy to run… and tremendously hard to implement. The Makefile documentation proves that this tool has many features for automating processes such as string editing, conditions, loops, recipes, functions, etc.
Sounds good? Of course it does! So why do I prefer Taskfile?
Because each time I have to write something in Makefile, I get a headache. At first glance, it looks like bash (many developers think the same), but in most cases, it doesn’t work like that.
When I need to create scripts, I often get errors. Stack Overflow rarely helps.
In the projects I worked on, most of Makefile features were useless. Complicated actions are always written in bash, Python or Go, which have more familiar syntax and are easier to maintain.
Makefile vs Taskfile – comparison
As I mentioned before, this article is more about why I (and my team members) prefer to use Taskfile rather than Makefile. I did my best to find counterparts in both technologies.
Unfortunately, the main reason we use Taskfile is because… it always works and is easier to use compared to GNU make.
Syntax
Let’s start with syntax. Both solutions are unique. Here is an example from Visual Studio Code.
Makefile
Taskfile
Amount of code
The “full usage” of Taskfile requires much more stuff to declare. Thankfully, version 3 reduced this amount. The interesting thing is that Taskfile provides the possibility of setting a description. Thanks to this feature, it’s easy to search for specific commands or try to understand implemented functions. Since the description is written in a human-friendly language, there is no need to read the code line by line.
Indention
Both Taskfile and Makefile implement an “off-side rule”. This means that the developer has to worry about indention.
Makefile forces to use <tab> to create the indention. If you want to use Makefile, then make sure to supply this information to your IDE! Many developers spend hours debugging Makefile scripts, because they used spaces instead of tabs. Taskfile doesn’t have this problem. It follows the YAML convention, which is much more flexible.
Habits
It’s more like a side note, but our minds do love habits. Many developers use the YAML format in their daily work. It’s much easier for them to read YAML than GNU Make. When you want to choose technology, please consider what is easier to read and if it’s worth dealing with the new Makefile format.
OSes
Makefile was created for Unix-based systems. It works great for them. As always, Windows users have to think about workarounds (not everyone uses WSL).
Taskfile was created to run cross-platform. Everybody can use the same commands. The only thing which is worth remembering is to set variables to identify OS instead of hardcoding values.
An example of a command to download Kubernetes binary that works for all OSes:
List of all commands
When developers have to deal with many projects (have to be onboarded somewhere), it’s important to get the knowledge of what is available without reinventing the wheel. In this case, it’s good to have the chance to see a list of available commands to run.
Taskfile has a built-in flag -l that delivers the list. It’s worth setting it as a “default” action. Remember – Taskfile only lists commands which have a description.
The same feature is also possible to implement using GNU Make, but it requires additional effort and code lines to achieve this goal.
Makefile vs Taskfile – functions
Name conflicts
By default, Makefile reads files, not commands. If there is a command called “test” and a directory with the same name, Makefile will try to “read” the directory, not the command! This behavior might confuse and take time to fix, especially when a new user doesn’t have knowledge of how Makefile works. The solution is to use PHONY, which requires additional effort (remember to use it + write additional code). Taskfile doesn’t have this problem.
Conditions
A major difference between Makefile and Taskfile is how conditions work. Makefile behaves like other popular languages. Nothing special (expect syntax) here.
Taskfile has three features related to conditions. The first is related to preconditions. The task command will not be executed when the result of preconditions is false. It helps to reduce the complexity of the code.
The second possibility is to use the status
function. When all conditions are met, the task will be skipped.
The third possibility is to use the “normal” if
command:
Variables
There are two types of variables: normal and environment. These types work with both Taskfile and Makefile, but the difference is in implementation.
Environment variables
Taskfile has the possibility to take environment variables from the .env file, declare them in the global file scope, and use them to forward for CLI scripts.
These functionalities are important during migrations from Makefile to Taskfile.
Below, you’ll find the same commands in Makefile.
Conclusions
- Makefile doesn’t provide the possibility of setting environment variables inside Makefile. The only way (except dirty hacks) is to put everything into the environment file.
- By default, it’s not possible to forward dynamic CLI arguments. To make it work, Makefile requires writing hacks (check ifeq part for an example).
- Both Makefile and Taskfile can read environment variables from .env by default. For custom filename, Makefile requires running the
include
andexport
commands. The problem is that the official documentation does not explain that solution. There’s a thread on StackExchange, however.
“Normal” variables
Taskfile has four different scopes/places to declare a variable. It’s useful during script preparation. The basic format uses brackets “{{ }}” to define them. Variables without dot “.” are built-in. To use self-declared variables, it’s required to add a dot at the beginning.
It looks similar in Makefile.
Concluding conclusions
- For the Makefile environment, variables and “normal” variables stored in files are the same.
- Makefile doesn’t have the local scope. Everything has to be global.
- Makefile doesn’t have the option of getting information about OS, architecture, etc. Taskfile has built-in variables to get this information. There are workarounds, but they all take too much space to copy-paste them to example scripts.
Parser
Taskfile uses a shell parser for Golang, which doesn’t work if it’s not in the library. I struggled with it once in the last 2 years of using Taskfile.
Documentation
One of the most important things is being able to find information. Here, Makefile has much more information since it has much more features. Personally, I prefer the Task documentation, because of the modern look and easy-to-read format of commands.
Split files
To set up a project for different environments, it’s required to run many commands. Sometimes, it’s good to split these commands into many directories. Local environment setup commands should go into the main directory, the ones for deployment into the DevOps directory, etc.
What’s frustrating is that developers have to find these files, go to the directory, and then they should be able to run commands. Quite annoying? A much better solution is to run all commands from the main directory.
For Makefile, there is no simple way. It’s required to have to cd do the directory to then execute a command. What about Taskfile? There is a built-in function called include
, which helps not only to execute commands from the main directory context, but also to execute them from destination directories. Quite confusing?
Main directory
Note that the second file has a path: devops/deployment/Taskfile.yml.
The result
One place to run the same commands from different directories
Sometimes, it’s required to run more fancy scripts. Imagine that a project has one repository for many Terraform modules. Most developers don’t want to have Terraform installed locally, as they want to use one terraform version without downloading terraform binary for each directory separately. With Taskfile, that is quite easy.
The usage here is task terraform:init PROJECT_NAME=<directory_name>
For Make, I wasn’t able to find a quick and easy solution to implement that.
Makefile vs Taskfile – summary
Without automation, our life would be really hard. Tools such as Makefile or Taskfile provide many features that make our daily work more efficient. We don’t need to copy/paste tons of commands anymore. Also, it’s much simpler to provide CI/CD later in a project.
Makefile has many features that are not in Taskfile. The reason why I decided to not write about them is that some are pretty similar between Taskfile and Makefile. Then, I have never used them in the past 6 years in IT.
Still, I can recommend Makefile for a project that needs to run 3-4 simple commands. It’s always better to have something than nothing 🙂 In any other case, because of its simplicity, easy-to-read documentation and a variety of modern features, Taskfile always wins over Makefile in my book.