Featured image of post Go 项目里怎么组织 Makefile、Taskfile 和 CI 命令

Go 项目里怎么组织 Makefile、Taskfile 和 CI 命令

记录 Go 项目里如何把 fmt、lint、test、build、run 等命令收口到 Makefile 或 Taskfile,并让本地和 CI 复用同一套入口。

Go 项目里命令一多,就会出现一个很常见的问题:每个人本地跑的命令不一样,CI 里跑的又是另一套。

最后就会变成“我本地是好的”。

所以我更倾向于把常用命令收口到一个地方:要么 Makefile,要么 Taskfile。工具选哪个不是重点,重点是本地和 CI 复用同一批入口。

这里我吃过的亏是:README 写了一套命令,CI 写了一套命令,开发同学本地又有自己的脚本。最后问题不是工具不够,而是没有一个“项目认可的入口”。命令入口不统一时,任何排查都会先浪费在“你到底跑的是哪条命令”上。

我一般会保留这些命令

一个普通 Go 服务,至少可以有这些命令:

1
2
3
4
5
make fmt
make lint
make test
make build
make run

或者 Taskfile:

1
2
3
4
5
task fmt
task lint
task test
task build
task run

命令不需要多,但名字要稳定。新人进项目后,不应该先去 README 里翻十几条不同命令。

Makefile 的写法

Makefile 最大的优点是常见,很多 CI 环境也天然支持。一个简单版本可以这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
APP := myapp

.PHONY: fmt lint test build run

fmt:
	go fmt ./...

lint:
	golangci-lint run ./...

test:
	go test ./...

build:
	go build -o bin/$(APP) ./cmd/server

run:
	go run ./cmd/server

这里我会显式写 .PHONY,避免目标名和文件名冲突。构建产物统一放到 bin,临时文件不要散在项目根目录。

如果项目要兼容 Windows,本地 Make 的体验可能会有差异,这时可以考虑 Task。

Taskfile 的写法

Task 的配置更像 YAML,对不熟悉 Makefile 的人更友好:

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

tasks:
  fmt:
    cmds:
      - go fmt ./...

  lint:
    cmds:
      - golangci-lint run ./...

  test:
    cmds:
      - go test ./...

  build:
    cmds:
      - go build -o bin/myapp ./cmd/server

  run:
    cmds:
      - go run ./cmd/server

Taskfile 的另一个好处是可以比较自然地组织依赖关系,比如 build 前先跑 test:

1
2
3
4
5
tasks:
  build:
    deps: [test]
    cmds:
      - go build -o bin/myapp ./cmd/server

但我不会为了用 Task 而删掉已有 Makefile。如果团队已经熟悉 Makefile,继续用就行。

CI 不要另写一套逻辑

CI 里最容易犯的错,是把命令重新写一遍:

1
2
- run: go test ./...
- run: golangci-lint run ./...

短期没问题,长期容易和本地命令分叉。更好的方式是让 CI 调本地同一个入口:

1
2
- run: make lint
- run: make test

或者:

1
2
- run: task lint
- run: task test

这样本地修问题和 CI 失败看到的是同一套命令,排查成本会低很多。

我一般会把命令分成几类:本地开发、代码质量、测试、构建发布、清理。不要把一个命令做得太重。比如 make test 就只跑测试,不要顺手构建镜像、推送镜像。

我会单独留一个重一点的 ci 入口,但它只是组合已有命令:

1
2
3
.PHONY: ci

ci: fmt lint test build

这样本地想完整模拟 CI 可以跑 make ci,但日常调试不会被迫跑一大串流程。

Makefile 和 Taskfile 本质上都是命令入口。项目里真正需要统一的是:本地和 CI 跑同样的命令,常用命令名字稳定,构建产物位置固定,复杂流程拆开。

参考资料

本文采用 CC BY-NC-SA 4.0 协议,转载请注明出处。
记录工程实践和排障过程。
使用 Hugo 构建
主题 StackJimmy 设计