Taskfile vs Makefile: Which Is the Superior Build Tool?

Follow us on WeChat: 「Wonderful Linux World
Set as a 「Star」 to explore Linux every day!
Taskfile vs Makefile: Which Is the Superior Build Tool?

1. What is Taskfile?

Taskfile describes various execution tasks using YAML, and its core is written in Go; compared to Makefile, which uses tab-separated and bash-combined syntax, Taskfile appears more modern and user-friendly (though it may turn you into a YAML engineer). Taskfile has built-in advanced features such as dynamic variables and recognition of operating system environment variables, which align more closely with modern coding practices.

Overall, if you are not very familiar with Makefile and wish to accomplish batch tasks using a tool similar to Makefile, Taskfile will be easier to get started with, offering a lower learning curve and sufficient speed.

2. Installation and Usage

Install go-task

For macOS users, the official installation method is provided via brew:

$ brew install go-task/tap/go-task

For Linux users, the official website provides installation packages for several Linux distributions, but since it only has a single binary file, there is also a quick installation script available:

# For Default Installation to ./bin with debug logging
$ sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d

# For Installation To /usr/local/bin for userwide access with debug logging
# May require sudo sh
$ sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin

If you already have a Go development environment locally, you can also install it directly using the go command:

$ go install github.com/go-task/task/v3/cmd/task@latest

Quick Start

After installation, you only need to create a Taskfile.yml YAML file, and then you can run the corresponding tasks using the task command:

version: '3'

tasks:
  build:
    cmds:
      - echo "Executing build task"
      
  docker:
    cmds:
      - echo "Packaging docker image"
Taskfile vs Makefile: Which Is the Superior Build Tool?

If you need to set a default task to execute, just create a task named default:

version: '3'

tasks:
  default:
    cmds:
      - echo "This is the default task"

  build:
    cmds:
      - echo "Executing build task"

  docker:
    cmds:
      - echo "Packaging docker image"
Taskfile vs Makefile: Which Is the Superior Build Tool?

3. Advanced Usage

Environment Variables

Taskfile supports referencing three types of environment variables:

  • Shell environment variables
  • Environment variables defined within Taskfile
  • Environment variables defined in variable files

If you need to reference a shell environment variable, simply use the $ variable_name syntax:

version: '3'

tasks:
  default:
    cmds:
      - echo "$ABCD"
Taskfile vs Makefile: Which Is the Superior Build Tool?

Similarly, you can also define environment variables within the Taskfile:

version: '3'

env:
  TENV2: "t2" # Global environment variable

tasks:
  default:
    cmds:
      - echo "$TENV1"
      - echo "$TENV2"
    env:
      TENV1: "t1" # Single task environment variable
Taskfile vs Makefile: Which Is the Superior Build Tool?

In addition to directly referencing variables, Taskfile also supports loading environment variables from .env files similar to docker-compose; Taskfile will automatically load the .env file in the same directory, and you can also configure specific files using the dotenv command in Taskfile:

version: '3'

dotenv: [".env", ".testenv"]

tasks:
  default:
    cmds:
      - echo "$ABCD"
      - echo "$TESTENV"
Taskfile vs Makefile: Which Is the Superior Build Tool?

Enhanced Variables

In addition to standard environment variables, Taskfile also includes a widely used type of enhanced variable called vars; this variable pattern can be read using Go’s template engine (interpolation reference) and has special features that environment variables do not possess. Here is an example of vars variable:

version: '3'

# Global var variable
vars:
  GLOBAL_VAR: "global var"

tasks:
  testvar:
    # task var variable
    vars:
      TASK_VAR: "task var"
    cmds:
      - "echo {{.GLOBAL_VAR}}"
      - "echo {{.TASK_VAR}}"

In addition to the similar use of environment variables, the vars enhanced variable also supports dynamic definitions; a common scenario is that we want to get the current git commit ID every time a task is executed, at which point we can use the dynamic definition feature of vars:

version: '3'

tasks:
  build:
    cmds:
      - go build -ldflags="-X main.Version={{.GIT_COMMIT}}" main.go
    vars:
      # Every time the task is executed, GIT_COMMIT will call a shell command to generate this variable
      GIT_COMMIT:
        sh: git log -n 1 --format=%h

The vars variable also includes some special predefined variables, such as {{.TASK}} which always represents the current task name, and {{.CLI_ARGS}} which can reference command line input, etc.

version: '3'

tasks:
  yarn:
    cmds:
      - yarn {{.CLI_ARGS}}

At this point, if you execute task yarn -- install, the value of {{.CLI_ARGS}} will become install, thus executing the yarn install command.

In addition, the vars variable has other features, such as allowing for overriding and passing when referencing across tasks, which will be introduced later.

Execution Directory

Tasks defined in Taskfile are executed in the current directory by default; if you wish to execute in another directory, you can directly set the execution directory using the dir parameter without manually writing commands like cd:

version: '3'

tasks:
  test1:
    dir: /tmp # Execute in the specified directory
    cmds:
      - "ls"

Task Dependencies

In CI environments, we often need to define the execution order and dependencies of tasks; Taskfile provides support for task dependencies through the deps configuration:

version: '3'

tasks:
  build-jar:
    cmds:
      - echo "Compiling jar package..."
  build-static:
    cmds:
      - echo "Compiling frontend UI..."
  build-docker:
    deps: [build-jar, build-static]
    cmds:
      - echo "Packaging docker image..."

Task Calls

When we define multiple tasks in Taskfile, it is likely that some tasks have certain similarities; at this point, we can define template tasks by calling tasks mutually and dynamically overriding vars variables:

version: '3'

tasks:
  docker:
    cmds:
      #- docker build -t {{.IMAGE_NAME}} {{.BUILD_CONTEXT}}
      - echo {{.IMAGE_NAME}} {{.BUILD_CONTEXT}}

  build-backend:
    cmds:
      - task: docker # Reference another task
        vars: { # Dynamically pass variables
          IMAGE_NAME: "backend",
          BUILD_CONTEXT: "maven/target"
        }

  build-frontend:
    cmds:
      - task: docker
        vars: {
          IMAGE_NAME: "frontend",
          BUILD_CONTEXT: "public"
        }
  default: # default is called when no task name is input in the command line
    cmds:
      - task: build-backend
      - task: build-frontend
Taskfile vs Makefile: Which Is the Superior Build Tool?

Including Other Files

Taskfile supports including other Taskfiles using the includes keyword, making it easier to structure Taskfiles.

It is important to note that since the included file may contain multiple tasks, you need to name the included files and reference the target tasks by name:

version: '3'

includes:
  file1: ./file1.yaml # Directly reference the yaml file
  dir2: ./dir2 # When referencing a directory, it defaults to referencing Taskfile.yaml in that directory
Taskfile vs Makefile: Which Is the Superior Build Tool?

When including other Taskfiles, by default, commands will be executed in the directory of the current main Taskfile; you can also control the execution directory of tasks in the included Taskfiles using the dir parameter:

version: '3'

includes:
  dir1: ./dirtest.yaml # Directly execute in the current directory
  dir2:
    taskfile: ./dirtest.yaml
    dir: /tmp # Execute in the specified directory
Taskfile vs Makefile: Which Is the Superior Build Tool?

Defer Handling

Those familiar with Go should know that there is a very convenient keyword defer in Go; this directive is used to define actions to be executed at the end of the code, such as resource cleanup. Taskfile also supports this directive, allowing us to easily perform cleanup operations during task execution:

version: '3'

tasks:
  default: # default is called when no task name is input in the command line
    cmds:
      - wget -q https://github.com/containerd/nerdctl/releases/download/v0.19.0/nerdctl-full-0.19.0-linux-amd64.tar.gz
      # Define cleanup actions
      - defer: rm -f nerdctl-full-0.19.0-linux-amd64.tar.gz
      - tar -zxf nerdctl-full-0.19.0-linux-amd64.tar.gz
Taskfile vs Makefile: Which Is the Superior Build Tool?

Of course, the defer directive can also reference other tasks for cleanup:

version: '3'

tasks:
  cleanup:
    cmds:
      - rm -f {{.FILE}}
  default: # default is called when no task name is input in the command line
    cmds:
      - wget -q https://github.com/containerd/nerdctl/releases/download/v0.19.0/nerdctl-full-0.19.0-linux-amd64.tar.gz
      # Reference other tasks for cleanup, and dynamic variables can also be passed
      - defer: {task: cleanup, vars: {FILE: nerdctl-full-0.19.0-linux-amd64.tar.gz}}
      - tar -zxf nerdctl-full-0.19.0-linux-amd64.tar.gz

4. Advanced Applications

Dynamic Detection

Output Detection

Sometimes, for certain tasks, we may want to implement caching, for example, if a file has already been downloaded, we do not want to repeat the download; for this requirement, Taskfile allows us to define source files and generated files, and determine whether to execute the task based on the hash values of this set of files:

version: '3'

tasks:
  default:
    cmds:
      - wget -q https://github.com/containerd/nerdctl/releases/download/v0.19.0/nerdctl-full-0.19.0-linux-amd64.tar.gz
    sources:
      - testfile
    generates:
      - nerdctl-full-0.19.0-linux-amd64.tar.gz
Taskfile vs Makefile: Which Is the Superior Build Tool?

From the above figure, we can see that when the task is executed for the first time, a .task directory will be generated, which contains the hash values of the files; when the task is executed again, if the hash value does not change, the actual task will not really execute. Taskfile has two default file detection methods: checksum and timestamp; checksum performs hash detection of files (default), and this mode only requires defining the sources configuration; timestamp performs timestamp detection of files, and this mode requires defining both sources and generates configurations.

version: '3'

tasks:
  build:
    cmds:
      - go build .
    sources:
      - ./*.go
    generates:
      - app{{exeExt}}
    method: checksum # Specify detection method

In addition to the two built-in detection modes, we can also define our own detection commands through the status configuration; if the command execution result is 0, it is considered that the file is up to date and the task does not need to be executed:

version: '3'

tasks:
  generate-files:
    cmds:
      - mkdir directory
      - touch directory/file1.txt
      - touch directory/file2.txt
    # test existence of files
    status:
      - test -d directory
      - test -f directory/file1.txt
      - test -f directory/file2.txt

Input Detection

The above output detection is used to detect the results of the files generated by the task, and in some cases we may want to determine a condition before running the task, identifying whether the task needs to run without executing at all; at this point, we can use the preconditions configuration directive:

version: '3'

tasks:
  generate-files:
    cmds:
      - mkdir directory
      - touch directory/file1.txt
      - touch directory/file2.txt
    # test existence of files
    preconditions:
      - test -f .env
      - sh: "[ 1 = 0 ]"
        msg: "One doesn't equal Zero, Halting"

Go Template Engine

In the variable section above, we have already demonstrated part of the template engine’s usage; in fact, Taskfile integrates the slim-sprig[1] library, which provides some convenient methods that can be used within the template engine:

version: '3'

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

For more details about these methods and the use of the template engine, please refer to the Go Template documentation as well as the slim-sprig[2] documentation.

Interactive Terminal

Some task commands may require an interactive terminal to execute; at this point, you can set the interactive option for the task; when interactive is set to true, the task can open an interactive terminal during execution:

version: '3'

tasks:
  cmds:
    - vim my-file.txt
  interactive: true

For more details on using Taskfile, please read its official documentation[3]; this article will not elaborate further due to space limitations.

Reference Links

[1]

slim-sprig: https://go-task.github.io/slim-sprig/

[2]

slim-sprig: https://go-task.github.io/slim-sprig/

[3]

Official Documentation: https://taskfile.dev/

Original: https://mritd.com/2022/04/25/taskfile-a-better-build-tool-than-makefile/

This article is reprinted from: 「Cloud Native Laboratory」; original: https://url.hi-linux.com/Wfp51, copyright belongs to the original author. Welcome contributions, submission email: [email protected].

Taskfile vs Makefile: Which Is the Superior Build Tool?

Recently, we have established a technical exchange WeChat group. Many industry experts have already joined the group; interested students can join us for technical discussions by replying 「Join Group」 directly in the WeChat public account 「Wonderful Linux World」.

Taskfile vs Makefile: Which Is the Superior Build Tool?

You might also like

Click the image below to read

Taskfile vs Makefile: Which Is the Superior Build Tool?

Analysis of Kafka Principles and Partition Allocation StrategyTaskfile vs Makefile: Which Is the Superior Build Tool?Click the image above, ‘Meituan|Ele.me’ delivery red envelopes are free every day

Taskfile vs Makefile: Which Is the Superior Build Tool?

For more interesting internet news, follow the ‘Wonderful Internet’ video account to stay updated!

Leave a Comment