From Makefiles to Taskfiles

The good old make
command has been around for ages. It helps us compile code, build projects, run tests and automate all sorts of tasks. As projects grow, Makefiles can become hard to use. The syntax, while simple, can get cryptic with complex rules and conditionals. Debugging Makefiles is no fun either. Error messages from make
can be hard to decipher, making it tough to figure out what went wrong. A single space in the wrong place can result in Makefile:42: *** commands commence before first target. Stop.
😬
Meet Taskfile - a task runner tool that aims to be simpler and easier to use than Makefile
What's Taskfile?
A Taskfile is essentially a configuration file for the Task tool. It uses a YAML-based syntax, which is both human-readable and easy to write. Instead of the somewhat cryptic syntax of Makefiles, Taskfiles are structured and clear. This makes them easier to maintain and understand, especially for new team members.
Let’s take a look at some of the key features:
Cross-Platform Compatibility - one of the coolest things about Taskfiles is their cross-platform support. You write a Taskfile once, and it works on any system that has the Task tool installed, even Windows!
Built-in Dependency Management - similar to Makefile, Taskfiles offer built-in support for dependencies. You can define which tasks depend on others, ensuring that tasks run in the correct order.
Parallel Execution - you can easily specify tasks to run in parallel, which can significantly speed up your build and deployment processes
Templating and Variable Substitution - you can define variables and templates directly in your Taskfile, making your task definitions more dynamic and reusable.
Simplicity and Readability - YAML readability makes it easy for anyone on your team to understand and modify the Taskfile.
Let's try it
Grab the project you are currently working on and let's try Taskfile in action
First we install it and create a simple Taskfile.yml
$ brew install go-task
$ vim Taskfile.yml # or task --init
As mentioned above it uses simple Yaml schema. Let's start simple by defining a few basic tasks. Here’s an example:
version: '3'
tasks:
build:
cmds:
- go build ./...
test:
cmds:
- go test ./...
Here we basically define build and test tasks similar to how you will do in a Makefile. Next you likely would need to define some dependencies, like so
version: '3'
tasks:
build:
desc: Build the project
cmds:
- go build ./...
test:
desc: Test the project
deps: [build]
cmds:
- go test ./...
Note: dependencies run in parallel, so dependencies of a task should not depend on one another.
Let's run it! You can view all available tasks by executing task --list-all
$ task --list-all
task: Available tasks for this project:
* build: Build the project
* test: Test the project
$ task build
task: [build] go build ./...
$ task test
task: [build] go build ./...
task: [test] go test ./...
Your first taskfile is ready to go!
Let's extend it more
Now that we have hello world Taskfile, let's extend it further. In the example below we add:
vars
- which can be interpolated and reused across all the tasksenv
- which are env variables passed to all the taskssources
- when given, task will compare the checksum of the source files to determine if it's necessary to run the task (to avoid unnecessary build execution for example)
version: '3'
env:
GOFLAGS: -mod=mod
vars:
goTestArgs: -v
tasks:
build:
desc: Build the project
cmds:
- go build ./...
test:
desc: Test the project
deps: [build]
cmds:
- go test {{.goTestArgs}} ./...
env
and vars
here are set on the global level, but you can also do the same on the task level for better flexibility.
What else can you do with Taskfiles:
Including other Taskfiles (e.g. OS specific)
Loop over variables/tasks and even use Go's template engine
Do task cleanups using
defer
functionalityUse a dry-run mode
and more!
Conclusion
Go ahead, give Taskfiles a try. It's powerful, flexible and can simplify your build and automation processes. Want to see how others in open source use it? Here is sourcegraph link to find good examples!