Go 构建约束(build constraints)的用法

Go 编译器在构建时可以根据条件选择哪些文件参与编译,这个机制叫构建约束(build constraints),也叫构建标记(build tag)。常见的使用场景是跨平台代码:针对 Linux 和 Windows 分别写不同的实现,编译时只有当前平台对应的文件会被编译进去。

构建约束有两种方式:文件名后缀和 build tag 注释。

文件名后缀

最简单的方式,直接在文件名里带上平台信息,格式是:

filename_GOOS.go
filename_GOARCH.go
filename_GOOS_GOARCH.go

比如 net_linux.go 只在 Linux 下编译,net_windows.go 只在 Windows 下编译。Go 标准库里大量使用这种方式,看一眼 os 包的源码就能看到。

这种方式适合跨平台实现,简单直接,不需要写任何注释。

Build tag 注释

更灵活的方式是在文件顶部写注释标记,可以组合多个条件,也可以用自定义 tag。

新语法(Go 1.17+)

//go:build linux && amd64

注意 //go 之间没有空格,这和普通注释不一样。

用标准的布尔运算符组合条件:

//go:build linux || darwin
//go:build !windows
//go:build (linux || darwin) && amd64

旧语法(Go 1.16 及之前)

// +build linux,386

旧语法用逗号表示 AND,空格表示 OR,多个条件可以写多行(多行之间是 AND 关系)。这套规则很反直觉,容易写错:

// +build linux,386 darwin,!cgo

上面这行的意思是 (linux AND 386) OR (darwin AND NOT cgo),不看文档很难一眼看懂。

Go 1.17 之后,gofmt 会自动在旧语法下面补一行新语法,保持兼容:

//go:build !windows && !plan9
// +build !windows,!plan9

Go 1.18 开始,go fix 工具会直接移除旧的 // +build 注释。现在写新代码直接用 //go:build 就行。

写法要求

build tag 必须放在文件最顶部,在 package 声明之前,和下面的代码之间要有一个空行:

//go:build linux

package main

少了那个空行,Go 编译器不会把它识别为构建约束。

自定义 tag

除了平台和架构,还可以定义自己的 tag,在编译时通过 -tags 传入:

//go:build use_feature

编译时:

go build -tags use_feature

不带 -tags use_feature 时这个文件会被忽略,带了才参与编译。多个 tag 用空格分隔:

go build -tags "feature1 feature2"

这个用法常见于功能开关、调试模式、或者区分 CE/EE 版本之类的场景。

tag 可以用的值

  • 操作系统:linuxdarwinwindows 等(对应 GOOS
  • 架构:amd64arm64386 等(对应 GOARCH
  • 编译器:gcgccgo
  • CGO:cgo
  • Go 版本:go1.21go1.22
  • 自定义:任意字符串,通过 -tags 传入
  • ignore:约定俗成的忽略标记,让文件永远不参与编译