Taskfile - 比 Makefile 更好用的構建工具

  1. Taskfile 是什麼

Taskfile 通過 yaml 來描述各種執行任務, 其核心採用 go 編寫; 相較於 Makefile 的 tab 分割和 bash 結合語法  Taskfile 顯得更加現代化和易於使用 (雖然會變成 yaml 工程師). Taskfile  內置了動態變量、操作系統等環境變量識別等高級功能都更貼合現代化的 Coding 方式。

總體來說如果你是一個對 Makefile 不太熟悉的人, 又期望通過類似 Makefile 的工具完成一些批量任務, 那麼相對於 Makefile 來說 Taskfile 會更加便於入門, 學習曲線更低且速度也足夠快。

  1. 安裝及使用

安裝 go-task

對於 mac 用戶來說官方提供了 brew 安裝方式:

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

對於 Linux 用戶, 官方提供了部分 Linux 發行版的安裝包, 但由於其只有一個二進制文件, 所以官方也提供了快速安裝腳本:

# 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

如果本地已經有了 Go 語言開發環境也可以直接通過 go 命令安裝:

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

快速開始

安裝完成後, 只需要編寫一個 Taskfile.yml 的 yaml 文件, 然後就可以通過 task 命令運行相應的任務:

version: '3'

tasks:
  build:
    cmds:
      - echo "執行 build 任務"
      
  docker:
    cmds:
      - echo "打包 docker 鏡像"

如果需要設置默認執行任務, 只需要創建一個名字爲 default 的任務即可:

version: '3'

tasks:
  default:
    cmds:
      - echo "這是默認任務"

  build:
    cmds:
      - echo "執行 build 任務"

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

  1. 進階使用

環境變量

Taskfile 支持引用三種環境變量:

如果需要引用 Shell 內的環境變量只需要使用 $ 變量名 方式直接引用即可:

version: '3'

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

同樣在 Taskfile 內也可以定義環境變量:

version: '3'

env:
  TENV2: "t2" # 全局環境變量

tasks:
  default:
    cmds:
      - echo "$TENV1"
      - echo "$TENV2"
    env:
      TENV1: "t1" # 單個 task 環境變量

除了這種直接引用變量的方式, Taskfile 也支持類似 docker-compose 一樣讀取 env 文件來加載環境變量; Taskfile 會默認加載同級目錄下的 .env 文件, 也可以在 Taskfile 內通過 dotenv 命令來配置特定文件:

version: '3'

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

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

增強變量

除了標準的環境變量以外, 在 Taskfile 中還內置了一種使用更加廣泛的增強變量 vars; 該變量模式可以通過 go 的模版引擎進行讀取 (插值引用), 且具有環境變量不具備的特殊特性. 以下爲 vars 變量的示例:

version: '3'

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

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

除了上面與環境變量類似的使用以外, vars 增強變量還支持動態定義; 常見的場景, 比如我們想每次 task 執行時都獲取當前的 git commit id, 此時可以使用 vars 的動態定義特性:

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

vars 變量還內置了一些特殊的預定義變量, 例如 {{.TASK}} 變量永遠表示當前的任務名稱、{{.CLI_ARGS}} 可以引用命令行輸入等.

version: '3'

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

此時如果執行 task yarn -- install, 那麼 {{.CLI_ARGS}} 值將會變成 install 從而執行 yarn install 命令.

除此之外, vars 變量還具備一些其他特性, 比如跨任務引用時可進行覆蓋傳遞等, 這些特性將會在後面介紹.

執行目錄

Taskfile 內定義的 task 默認在當前目錄下執行, 如果期望在其他目錄執行, 無需手動編寫 cd 等命令, 可以直接通過配置 dir 參數來設置執行目錄:

version: '3'

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

任務依賴

在 CI 等環境的使用中, 我們常常需要定義任務的執行順序和依賴關係; Taskfile 中通過 deps 配置來提供任務依賴關係的支持:

version: '3'

tasks:
  build-jar:
    cmds:
      - echo "編譯 jar 包..."
  build-static:
    cmds:
      - echo "編譯前端 UI..."
  build-docker:
    deps: [build-jar, build-static]
    cmds:
      - echo "打包 docker 鏡像..."

任務調用

當我們在 Taskfile 中定義了多個任務時, 很可能一些任務具有一定的相似性, 此時我們可以通過任務互相調用和 vars 變量動態覆蓋的方式來定義模版 Task:

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 支持通過 includes 關鍵字來引入其他 Taskfile, 從而方便 Taskfile 的結構化處理.

需要注意的是, 由於引入的文件中可能會包含多特 task, 所以在使用時需要對引入的文件進行命名, 且通過命名引用目標 task:

version: '3'

includes:
  file1: ./file1.yaml # 直接引用 yaml 文件
  dir2: ./dir2 # 引用目錄時默認引用該目錄下的 Taskfile.yaml

在引入其他 Taskfile 時, 默認情況下會在當前主 Taskfile 目錄下執行命令, 我們同樣可以通過 dir 參數來控制引入的 Taskfile 內的 task 在特定目錄下執行:

version: '3'

includes:
  dir1: ./dirtest.yaml # 直接在當前目錄執行
  dir2:
    taskfile: ./dirtest.yaml
    dir: /tmp # 在指定目錄執行

defer 處理

熟悉 go 語言的同學應該知道, go 裏面有個很方便的關鍵字 defer; 該指令用於定義在最終代碼收尾時要執行的動作, 常見的比如資源清理等. Taskfile 中同樣支持了該指令, 可以方便我們在任務執行期間完成一些清理操作:

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

當然, defer 指令除了直接寫命令以外, 還可以引用其他 task 完成清理:

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
  1. 高級應用

動態檢測

輸出檢測

在某些時候, 一些任務我們可能期望進行緩存處理, 比如說已經下載好了文件就不要重複運行下載; 針對於這種需求, Taskfile 允許我們定義源文件和生成的文件, 通過這組文件的 hash 值來確定是否需要執行該任務:

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

從上圖中可以看到, 當首次執行任務時會生成 .task 目錄, 該目錄包含文件的 hash 值; 當重複執行任務時, 如果 hash 值不改變則真實任務不會真正執行. Taskfile 默認有兩種文件檢測的方式: checksumtimestamp, checksum 執行文件的 hash 檢測 (默認), 該模式只需要定義 sources 配置; timestamp 執行文件的時間戳檢測, 該模式需要同時定義 sourcesgenerates 配置.

version: '3'

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

除了內置的兩種檢測模式外, 我們還可以通過 status 配置來定義自己的檢測命令, 如果命令執行結果爲 0, 則認爲文件是最新的, 不需要執行任務:

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

輸入檢測

上面的輸出檢測用於檢測任務生成的文件結果等, 在某些情況下我們可能期望在運行任務之前來判斷某個條件, 在完全不執行的情況下確定任務是否需要運行; 此時我們可以使用 preconditions 配置指令:

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 模版引擎

在上面變量環節中已經展示了一部分模版引擎的使用, 實際上 Taskfile 內集成了 slim-sprig[1] 庫, 該庫中提供了一些比較便利的方法, 這些方法都可以在模版引擎內使用:

version: '3'

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

關於這些方法和模版引擎的使用具體請參考 Go Template 相關文檔以及 slim-sprig[2] 文檔.

交互式終端

有些任務命令可能需要交互式終端來執行, 此時可以爲 task 設置 interactive 選項; 當 interactive 設置爲 true 時, task 在運行時可以打開交互式終端:

version: '3'

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

更多關於 Taskfile 的細節使用請閱讀其官方文檔 [3], 本文限於篇幅不在過多闡述。

引用鏈接

[1]

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

[2]

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

[3]

官方文檔: https://taskfile.dev/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/ahwxtGtzIgefTYHx0A-mtA