So, after spending time moving from Make to Task, I decided. I’m going with Task in my future projects. Make will not be removed from my projects immediately, but I will not use it in the future. Let me tell you why.

All this is my personal opinion. I’m not saying that Task is better than Make. I think that Task is better for me.

Global Taskfile

I have a global Task in my home directory for standard stuff. It contains tasks to update my tools, run some diagnostics, etc. Because it is global, it is accessible from any directory. For example:

version: '3'

tasks:
  build:
    dir: '{{.USER_WORKING_DIR}}'
    cmds:
      - go build . -o app

In the above example, task -g build will build the Golang app in my current working directory. I do not need to add Task for every project. I must add Taskfile to the project if I share it. But, as long it is experimental, there is no need. Anyway, storing everyday tasks as global is a nice feature.

The {{.USER_WORKING_DIR}} is a variable set by Task. It is the current working directory. And the dir slug defines the folder where the task will be executed. In this case, it is the current working directory. But you can set it to any folder you want. So you do not need to switch directories to run a task. Just set the dir slug to the folder where you want to run the job. Neat!

Including/Grouping Task Files

The explicit inclusion of other Taskfiles with namespace is really neat. This way, you get a context of tasks. For example:

version: '3'

includes:
  tools: ./Taskfile.tools.yml
  docs: ./docs/

Assuming I have the task install_tools in the file Taskfile.tools.yml, I can run it with task tools:install_tools. Also, suppose I have Taskfile.yml file in the docs directory. I can run it with task docs:build_docs from the root directory. This way, I’m groping tasks by context.

In case you have a CI pipeline and need to do builds for different platforms. You can split your tasks into different files with the platform postfix. For example, you have different tasks for linux and windows platforms. You can split them into separate files. I name them Taskfile_linux.yml and Taskfile_windows.yml. Then you can include them in the main Taskfile.yml file with the following:

version: '3'

includes:
  build: ./Taskfile_{{OS}}.yml

If you run task build:build on the linux platform, it will include Taskfile_linux.yml and run the build task from it. If you run the same task on windows platform, it will consist of Taskfile_windows.yml and run the build job from it. This way, you can have different tasks for different platforms.

Task Dependencies and Skipping Done Tasks

Task dependencies are really nice. You can define a task that depends on other tasks. For example:

version: '3'

tasks:
 manifests:
    desc: Generate manifests e.g. CRD, RBAC etc.
    deps:
      - task: tools:controller-gen
    cmds:
      - controller-gen rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
    sources:
      - main.go
      - apis/**/*.go
      - pkg/**/*.go
    generates:
      - config/crd/bases/**/*.yaml

The manifests task in the above example depends on the tools:controller-gen task. Dependency tasks are run in parallel. This way, you can define tasks that rely on other tasks.

Also, the above example shows how to define sources and generated files. The sources slug defines files used to generate other files. The generates slug defines files that are generated by the task. This way, you can tell Task which files are used to generate other files. It is to determine whether the task needs to be run. Task will generate a fingerprint for sources and compare it next time the task needs to be run. The task is considered done if the fingerprint is not changed, assuming the generated files are present.

Also, there is an alternative with a status slug. You can define a command that will return 0 if the task is done. For example:

  install-cert-manager:
    desc: Install cert-manager    
    deps:
      - init-cluster
    cmds:
      - kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/{{.CERT_MANAGER_VERSION}}/cert-manager.yaml
      - echo "Waiting for cert-manager to be ready" && sleep 25
    status:
      - kubectl -n cert-manager get pods | grep Running | wc -l | grep -q 3

Golang Template Engine

Task uses the Golang template engine. As a Golang user, this is a big plus. I can use all the features of the Golang template engine plus one that Task provides, like including functions from https://go-task.github.io/slim-sprig/. For example:

version: '3'

tasks:
 print-date:
    cmds:
      - echo {{now | date "2006-01-02"}}
      - echo '{{OS}} {{ARCH}}'

Format Is YAML

I like it when a file has a structure and text editors can understand it. I can understand it as well. It is enough said.

Conclusion

There are other things that I like about Task but I will not go into details. I have covered the most important ones, at least for me.

While evaluating Makefile and Task, I discovered another tool called Just. It is more similar to Makefile than Task. For that reason, I did not want to spend a lot of time on it. But I see it as a good alternative to Makefile. Especially if you do not like YAML.

Let me know what you think. Thanks for reading. Enjoy!

References