I. What is Taskfile

Taskfile describes various execution tasks via yaml, and its core is written in go; it is more modern and easier to use than Makefile’s tab splitting and bash combination syntax (although it becomes a yaml engineer). Taskfile has advanced features such as built-in dynamic variables, operating system and other environment variable recognition that are more in line with the modern way of Coding.

In general, if you are not familiar with Makefile and expect to do some batch tasks with a Makefile-like tool, Taskfile is easier to get started with, has a lower learning curve and is fast enough compared to Makefile.

2. Installation and use

2.1. Install go-task

For mac users, there is an official brew installation method.

1
brew install go-task/tap/go-task

For Linux users, there are official installation packages for some Linux distributions, but since there is only one binary file, there is also an official quick install script.

1
2
3
4
5
6
# 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 local Go language development environment, you can install it directly via the go command.

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

2.2. Quick start

After installation, you just need to write a yaml file Taskfile.yml, and then you can run the corresponding task via task command.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
version: '3'

tasks:
  build:
    cmds:
      - echo "执行 build 任务"
      
  docker:
    cmds:
      - echo "打包 docker 镜像"

Taskfile

If you want to set a default task, just create a task with the name default.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
version: '3'

tasks:
  default:
    cmds:
      - echo "这是默认任务"

  build:
    cmds:
      - echo "执行 build 任务"

  docker:
    cmds:
      - echo "打包 docker 镜像"

Taskfile

3. Advanced use

3.1. Environment variables

Taskfile supports three kinds of environment variables:

  • Shell environment variables
  • Environment variables defined in Taskfile
  • Environment variables defined in the variable file

If you need to refer to the environment variables in the Shell, just use the $variable name method to refer to them directly.

1
2
3
4
5
6
version: '3'

tasks:
  default:
    cmds:
      - echo "$ABCD"

Taskfile

Environment variables can also be defined in the Taskfile.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
version: '3'

env:
  TENV2: "t2" # 全局环境变量

tasks:
  default:
    cmds:
      - echo "$TENV1"
      - echo "$TENV2"
    env:
      TENV1: "t1" # 单个 task 环境变量

Taskfile

In addition to this direct reference to variables, Taskfile also supports reading env files to load environment variables like docker-compose; Taskfile will load .env files in the same directory by default, or you can configure specific files within Taskfile with the dotenv command.

1
2
3
4
5
6
7
8
9
version: '3'

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

tasks:
  default:
    cmds:
      - echo "$ABCD"
      - echo "$TESTENV"

Taskfile

3.2. Enhanced variables

In addition to the standard environment variables, Taskfile has a built-in, more widely used enhanced variable vars; this variable mode can be read by go’s template engine (interpolated references), and has special features that environment variables do not have. The following is an example of a vars variable.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
version: '3'

# 全局 var 变量
vars:
  GLOBAL_VAR: "global var"

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

In addition to the above use of environment variables, vars also supports dynamic definitions; For example, if we want to get the current git commit id every time a task is executed, we can use the dynamic definition feature of vars.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
version: '3'

tasks:
  build:
    cmds:
      - go build -ldflags="-X main.Version={{.GIT_COMMIT}}" main.go
    vars:
      # 每次任务执行时, GIT_COMMIT 都会调用 shell 命令来生成这个变量
      GIT_COMMIT:
        sh: git log -n 1 --format=%h

The vars variable also has some special predefined variables built in, such as {{.TASK}} which always indicates the current task name, {{.CLI_ARGS}} which references command line input, etc..

1
2
3
4
5
6
version: '3'

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

If you run task yarn -- install, then the {{.CLI_ARGS}} value will become install and the yarn install command will be executed.

In addition, vars variables have other features, such as the ability to pass overrides when referencing across tasks, which will be described later.

3.3. Execution directory

The task defined in Taskfile is executed in the current directory by default, if you want to execute in another directory, you can set the execution directory directly by configuring the dir parameter instead of writing commands like cd manually.

1
2
3
4
5
6
7
version: '3'

tasks:
  test1:
    dir: /tmp # 在指定目录执行
    cmds:
      - "ls"

3.4. Task dependencies

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
version: '3'

tasks:
  build-jar:
    cmds:
      - echo "编译 jar 包..."
  build-static:
    cmds:
      - echo "编译前端 UI..."
  build-docker:
    deps: [build-jar, build-static]
    cmds:
      - echo "打包 docker 镜像..."

3.5. Task invocation

When we define multiple tasks in Taskfile, it is likely that some tasks have some similarity, so we can define Template Task by calling each other and dynamically overriding vars variables.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
version: '3'

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

  build-backend:
    cmds:
      - task: docker # 引用其他 task
        vars: { # 动态传入变量
          IMAGE_NAME: "backend",
          BUILD_CONTEXT: "maven/target"
        }

  build-frontend:
    cmds:
      - task: docker
        vars: {
          IMAGE_NAME: "frontend",
          BUILD_CONTEXT: "public"
        }
  default: # default 用于在命令行不显示输入任何 task 名称时调用
    cmds:
      - task: build-backend
      - task: build-frontend

taskfile

3.6. Importing other files

Taskfile supports importing other Taskfiles via the includes keyword to facilitate the structured processing of Taskfile.

It should be noted that since the introduced file may contain multiple tasks, you need to name the introduced file and refer to the target task by naming it.

1
2
3
4
5
version: '3'

includes:
  file1: ./file1.yaml # 直接引用 yaml 文件
  dir2: ./dir2 # 引用目录时默认引用该目录下的 Taskfile.yaml

Taskfile

When introducing other Taskfiles, by default the commands will be executed in the current main Taskfile directory, but we can also use the dir parameter to control the execution of tasks in the introduced Taskfile in a specific directory.

1
2
3
4
5
6
7
version: '3'

includes:
  dir1: ./dirtest.yaml # 直接在当前目录执行
  dir2:
    taskfile: ./dirtest.yaml
    dir: /tmp # 在指定目录执行

taskfile

3.7. defer handling

Anyone familiar with the go language should know that go has a handy keyword defer; this directive is used to define actions to be performed at the end of the final code, such as resource cleanup. Taskfile also supports this directive, which allows us to complete some cleanup operations during task execution.

1
2
3
4
5
6
7
8
9
version: '3'

tasks:
  default: # default 用于在命令行不显示输入任何 task 名称时调用
    cmds:
      - wget -q https://github.com/containerd/nerdctl/releases/download/v0.19.0/nerdctl-full-0.19.0-linux-amd64.tar.gz
      # 定义清理动作
      - defer: rm -f nerdctl-full-0.19.0-linux-amd64.tar.gz
      - tar -zxf nerdctl-full-0.19.0-linux-amd64.tar.gz

taskfile defer

Of course, in addition to writing commands directly, the defer command can also reference other tasks to complete cleanup.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
version: '3'

tasks:
  cleanup:
    cmds:
      - rm -f {{.FILE}}
  default: # default 用于在命令行不显示输入任何 task 名称时调用
    cmds:
      - wget -q https://github.com/containerd/nerdctl/releases/download/v0.19.0/nerdctl-full-0.19.0-linux-amd64.tar.gz
      # 引用其他 task 进行清理, 同时也可以传递动态变量
      - 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

4.1, dynamic detection

4.1.1, Output detection

At some point, we may expect to cache some tasks, for example, not to run the download repeatedly if the file has already been downloaded; for this need, Taskfile allows us to define the source file and the generated file, and determine whether the task needs to be executed by the hash value of this set of files.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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

As you can see from the above diagram, when a task is executed for the first time, a .task directory is generated, which contains the hash value of the file; when the task is executed repeatedly, the real task is not really executed if the hash value does not change. Taskfile has two types of file detection by default: checksum and timestamp. checksum performs hash detection of files (default), which only requires defining sources configuration; timestamp performs timestamp detection of files, which requires defining both sources and generates configuration .

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
version: '3'

tasks:
  build:
    cmds:
      - go build .
    sources:
      - ./*.go
    generates:
      - app{{exeExt}}
    method: checksum # 指定检测方式

In addition to the two built-in detection modes, we can also define our own detection commands via the status configuration. If the command execution result is 0, the file is considered up-to-date and no task needs to be executed.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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

4.1.2. Input detection

The above output detection is used to detect the results of the files generated by the task, etc. In some cases we may want to determine a condition before running the task, to determine if the task needs to be run without executing it at all; in this case we can use the preconditions configuration directive.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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"

4.2. The Go Template Engine

The use of the stencil engine has been shown in the variables section above. Taskfile actually integrates with the slim-sprig library. This library provides a number of convenient methods that can be used within the template engine.

1
2
3
4
5
6
version: '3'

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

Please refer to the Go Template documentation and the slim-sprig documentation for details on these methods and the use of the template engine.

4.3. Interactive terminal

Some task commands may require interactive terminal to execute, so you can set interactive option for task; when interactive is set to true, task can open interactive terminal when running.

1
2
3
4
5
6
version: '3'

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

For more details on the use of Taskfile, please read its official documentation, and I will not go into too much detail in this article.