跳转至

Task: Go 编写的现代任务运行器

为什么要学 Task

每个项目都需要一套构建/测试/部署命令。传统做法是用 Makefile,但 Make 有很多历史包袱:Tab vs 空格敏感、平台兼容性差、语法晦涩。npm scripts 又依赖 Node.js 生态。

Task(又叫 Taskfile)是一个用 Go 编写的任务运行器,使用 YAML 定义任务。它的目标是做一个简单、跨平台、零依赖的 Make 替代品

维度Makenpm scriptsTask
语法Makefile (晦涩)JSON (受限)YAML (直观)
跨平台差 (需要 GNU Make)需要 Node.jsGo 单二进制
依赖管理文件级依赖任务级依赖
变量/环境复杂受限简洁的 .env 支持
增量运行文件时间戳文件 hash + 时间戳
Watch 模式需要额外工具需要 nodemon内置
并行执行有限需要 concurrently原生支持
命令补全内置

核心概念

白话解释

Task 就是一个"命令管家"。你在 Taskfile.yml 中定义一系列命名的任务,每个任务是一组要执行的命令。然后通过 task 任务名 来运行。

它比手动敲命令的优势在于: - 命令有名字,不用记长串参数 - 任务之间可以声明依赖关系 - 支持文件变化检测,避免重复执行 - YAML 格式,任何人都能读懂

核心概念表

概念说明示例
Task一个命名的命令集合build, test, deploy
Dep任务依赖,先执行依赖再执行自身deps: [lint, test]
Cmd要执行的 Shell 命令cmds: ["go build ."]
Var变量定义vars: { NAME: "app" }
Env环境变量env: { GO111MODULE: "on" }
Source/Generates增量运行的文件检测sources: ["**/*.go"]
Precondition前置条件检查preconditions: [test -f config.yml]
Include引入其他 Taskfileincludes: { docker: ./docker/Taskfile.yml }
Internal内部任务(不在列表中显示)internal: true

安装配置

安装方式

macOS

brew install go-task

Linux

# 通用安装脚本
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin

# Snap
sudo snap install task --classic

# Arch Linux
pacman -S go-task

Go 安装

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

Windows

# Chocolatey
choco install go-task

# Scoop
scoop install task

# WinGet
winget install Task.Task

Shell 补全

# Bash
echo 'eval "$(task --completion bash)"' >> ~/.bashrc

# Zsh
echo 'eval "$(task --completion zsh)"' >> ~/.zshrc

# Fish
task --completion fish | source
# 或持久化
task --completion fish > ~/.config/fish/completions/task.fish

第一个 Taskfile

在项目根目录创建 Taskfile.yml

# Taskfile.yml
version: '3'

tasks:
  hello:
    desc: "打个招呼"
    cmds:
      - echo "Hello, World!"

  build:
    desc: "构建项目"
    cmds:
      - go build -o bin/app .

  test:
    desc: "运行测试"
    cmds:
      - go test ./...

  default:
    desc: "默认任务"
    cmds:
      - task --list
# 运行任务
task hello
task build
task test

# 不带参数运行 default 任务
task

# 查看所有任务
task --list

快速上手

任务依赖

version: '3'

tasks:
  lint:
    desc: "代码检查"
    cmds:
      - golangci-lint run

  test:
    desc: "运行测试"
    deps: [lint]  # 先执行 lint
    cmds:
      - go test -v ./...

  build:
    desc: "构建"
    deps: [test]  # 先执行 test(test 又会先执行 lint)
    cmds:
      - go build -o bin/app .

  # 并行执行依赖
  ci:
    desc: "CI 流水线"
    deps:
      - lint
      - test
      - build:docs
    cmds:
      - echo "CI passed!"

变量和环境变量

version: '3'

vars:
  APP_NAME: myapp
  VERSION:
    sh: git describe --tags --always
  GO_FLAGS: -ldflags "-X main.version={{.VERSION}}"

env:
  CGO_ENABLED: '0'

tasks:
  build:
    desc: "构建 {{.APP_NAME}}"
    cmds:
      - go build {{.GO_FLAGS}} -o bin/{{.APP_NAME}} .

  docker:
    desc: "构建 Docker 镜像"
    vars:
      IMAGE_TAG: '{{.APP_NAME}}:{{.VERSION}}'
    cmds:
      - docker build -t {{.IMAGE_TAG}} .
      - echo "Built {{.IMAGE_TAG}}"

.env 文件支持

version: '3'

dotenv: ['.env', '.env.local']

tasks:
  serve:
    desc: "启动开发服务器"
    cmds:
      - echo "Starting on port $PORT"
      - go run . -port $PORT
# .env
PORT=8080
DATABASE_URL=postgres://localhost:5432/mydb

带参数的任务

version: '3'

tasks:
  greet:
    desc: "问候某人"
    cmds:
      - echo "Hello, {{.CLI_ARGS}}!"

  migrate:
    desc: "运行数据库迁移"
    cmds:
      - goose {{.CLI_ARGS}}

  docker:run:
    desc: "运行指定的 Docker 容器"
    cmds:
      - docker run -it {{.IMAGE}}
    requires:
      vars: [IMAGE]
task greet -- World
# → Hello, World!

task migrate -- up
# → goose up

task docker:run IMAGE=nginx
# → docker run -it nginx

进阶用法

增量运行(Sources/Generates)

version: '3'

tasks:
  build:
    desc: "构建(仅源文件变化时)"
    sources:
      - "**/*.go"
      - go.mod
      - go.sum
    generates:
      - bin/app
    cmds:
      - go build -o bin/app .

  css:
    desc: "编译 CSS"
    sources:
      - "src/**/*.scss"
    generates:
      - "dist/style.css"
    cmds:
      - sass src/main.scss dist/style.css

  # 使用 checksum 方法(基于文件内容 hash)
  proto:
    desc: "编译 protobuf"
    method: checksum  # 默认是 timestamp
    sources:
      - "proto/**/*.proto"
    generates:
      - "gen/**/*.go"
    cmds:
      - protoc --go_out=gen/ proto/*.proto
task build  # 首次:执行构建
task build  # 再次:跳过(文件未变化)
# 修改 .go 文件后
task build  # 检测到变化:重新构建

Watch 模式

version: '3'

tasks:
  dev:
    desc: "开发模式(文件变化自动重启)"
    watch: true
    sources:
      - "**/*.go"
    cmds:
      - go run .

  test:watch:
    desc: "持续运行测试"
    watch: true
    sources:
      - "**/*.go"
      - "**/*_test.go"
    cmds:
      - go test ./...
task dev
# 修改 .go 文件后自动重新执行
# Ctrl+C 停止

前置条件

version: '3'

tasks:
  deploy:
    desc: "部署到生产"
    preconditions:
      - sh: test -f .env.production
        msg: "缺少 .env.production 文件"
      - sh: git diff --quiet
        msg: "有未提交的更改,请先提交"
      - sh: '[ "$(git branch --show-current)" = "main" ]'
        msg: "只能从 main 分支部署"
    cmds:
      - echo "Deploying..."
      - ./deploy.sh

任务分组(Include)

# Taskfile.yml(主文件)
version: '3'

includes:
  docker: ./taskfiles/Docker.yml
  db: ./taskfiles/Database.yml
  test:
    taskfile: ./taskfiles/Test.yml
    dir: ./tests  # 在指定目录下执行

tasks:
  setup:
    desc: "初始化项目"
    cmds:
      - task: docker:up
      - task: db:migrate
      - task: test:unit
# taskfiles/Docker.yml
version: '3'

tasks:
  up:
    desc: "启动 Docker 容器"
    cmds:
      - docker compose up -d

  down:
    desc: "停止 Docker 容器"
    cmds:
      - docker compose down
task docker:up     # 运行 Docker.yml 中的 up 任务
task db:migrate    # 运行 Database.yml 中的 migrate 任务

并行执行

version: '3'

tasks:
  # 依赖并行执行
  ci:
    deps:
      - task: lint
      - task: test:unit
      - task: test:integration

  # 命令并行执行
  dev:
    desc: "同时启动前后端"
    cmds:
      - task: backend
      - task: frontend
    parallel: true

  backend:
    internal: true
    cmds:
      - go run ./cmd/server

  frontend:
    internal: true
    dir: ./frontend
    cmds:
      - npm run dev

条件执行

version: '3'

tasks:
  install:
    desc: "安装依赖"
    status:
      - test -d node_modules
    cmds:
      - npm install

  build:linux:
    desc: "Linux 构建"
    platforms: [linux]
    cmds:
      - make build

  build:mac:
    desc: "macOS 构建"
    platforms: [darwin]
    cmds:
      - make build

实际项目 Taskfile 示例

# 一个 Go Web 项目的完整 Taskfile
version: '3'

dotenv: ['.env']

vars:
  APP_NAME: myapi
  VERSION:
    sh: git describe --tags --always 2>/dev/null || echo "dev"
  LDFLAGS: -ldflags "-X main.version={{.VERSION}}"

tasks:
  default:
    cmds:
      - task --list-all

  setup:
    desc: "项目初始化"
    cmds:
      - go mod download
      - cp -n .env.example .env || true
      - task: db:migrate

  dev:
    desc: "开发模式"
    watch: true
    sources:
      - "**/*.go"
      - ".env"
    cmds:
      - go run {{.LDFLAGS}} ./cmd/server

  build:
    desc: "构建生产版本"
    deps: [lint, test]
    sources:
      - "**/*.go"
      - go.mod
    generates:
      - bin/{{.APP_NAME}}
    env:
      CGO_ENABLED: '0'
    cmds:
      - go build {{.LDFLAGS}} -o bin/{{.APP_NAME}} ./cmd/server

  test:
    desc: "运行测试"
    cmds:
      - go test -race -coverprofile=coverage.out ./...
      - go tool cover -func=coverage.out

  lint:
    desc: "代码检查"
    cmds:
      - golangci-lint run ./...

  db:migrate:
    desc: "数据库迁移"
    cmds:
      - goose -dir migrations postgres "$DATABASE_URL" up

  db:rollback:
    desc: "回滚迁移"
    cmds:
      - goose -dir migrations postgres "$DATABASE_URL" down

  docker:build:
    desc: "构建 Docker 镜像"
    cmds:
      - docker build -t {{.APP_NAME}}:{{.VERSION}} .

  docker:run:
    desc: "运行 Docker 容器"
    deps: [docker:build]
    cmds:
      - docker run -p 8080:8080 --env-file .env {{.APP_NAME}}:{{.VERSION}}

  clean:
    desc: "清理构建产物"
    cmds:
      - rm -rf bin/ coverage.out

常见问题

Q1: Task 和 Make 该选哪个?

  • 新项目:用 Task(YAML 更易读,跨平台更好)
  • 已有 Makefile 的项目:不必迁移(Make 完全够用)
  • C/C++ 项目:Make 的文件级依赖更适合编译链
  • 跨平台项目:Task(避免 GNU Make 在 Windows 上的问题)

Q2: 命令中的 Shell 语法在 Windows 上不兼容?

Task 默认使用系统 Shell(Linux/Mac 是 sh,Windows 是 cmd)。建议: - 用 Go 模板语法代替 Shell 特性 - 或在 Taskfile 中指定 Shell:set: [pipefail]

Q3: 如何传递动态参数?

# 使用 -- 传递参数
task test -- -v -run TestSpecific

# 使用变量
task build VERSION=1.2.3

Q4: Taskfile 支持中文任务名吗?

支持,但不推荐。任务名是命令行参数,中文输入不方便:

tasks:
  构建:
    cmds:
      - go build .

Q5: 如何在多个项目之间共享 Taskfile?

使用 includes 从公共位置引入:

includes:
  common: ~/shared-taskfiles/Common.yml

参考资源

资源链接
官方网站https://taskfile.dev
GitHub 仓库https://github.com/go-task/task
配置参考https://taskfile.dev/reference/schema
使用指南https://taskfile.dev/usage
API 参考https://taskfile.dev/api
示例集合https://taskfile.dev/community

总结:Task 是 Make 的现代化替代品。YAML 语法让任何人都能读懂构建配置,内置的 watch 模式和增量运行减少了对额外工具的依赖。如果你正在开始一个新项目,或者厌倦了 Makefile 的语法怪癖,Task 是一个值得采用的工具。