Go 构建约束

构建约束

在go中进行编译时,可能会带一些指示条件(如:不同平台、架构)让编译器选择满足条件的代码参与编译,将不满足条件的代码舍弃。这就是条件编译,也可称为构建(编译)约束。

目前,支持的构建约束有2种使用方式:

1.文件后缀

2.编译标签(build tag)

两者区别:

文件后缀方式多用于交叉编译 (跨平台)。编译标签方式多用于条件编译 (也可用于交叉编译)。构建约束官方文档

https://pkg.go.dev/cmd/go#hdr-Build_constraints

文件后缀的使用方式

编译器根据文件后缀来选择具体文件来参与编译操作,格式如下:

$filenamePrefix_$GOOS.go

$filenamePrefix_$GOARCH.go

$filenamePrefix_$GOOS_$GOARCH.go$filenamePrefix: 源码文件名称前缀(一般为包名称)。

$GOOS: 表示操作系统,从环境变量中获取。

$GOARCH: 表示系统架构,从环境变量中获取。

例如,Go源码中os包的Linux、windows实现

src/runtime/os_linux.go

src/runtime/os_linux_arm.go

src/runtime/os_linux_arm64.go

src/runtime/os_windows.go

src/runtime/os_windows_arm.go

src/runtime/os_windows_arm64.go

使用编译标识

使用编译标识指示编译器选择对应的文件进行编译(也称为: 交叉编译),可以得到非当前平台二进制文件。

// 非linux平台编译出linux平台运行的二进制文件

// $filenamePrefix_linux_arm64.go 文件参与编译过程

GOOS=linux GOARCH=arm64 go build

// 非Windows平台编译出Windows平台运行的二进制文件

// $filenamePrefix_windows_arm64.go 文件参与编译过程

GOOS=windows GOARCH=arm64 go build

不使用编译标识

go build不使用编译标识,编译器会根据当前环境编译出当前平台二进制文件。

编译标签(build tag)的使用方式

编译标签写法

目前,Go的构建约束支持两种写法:

①.// +build <tags>

②.//go:build <tags>

两种编译标签相同点

1.在源码文件顶部添加 (在所有代码之前),来决定文件是否参与编译

2.与其他注释之间需要存在一个空行

两种编译标签区别

1.起始位置是否包含空格

// +build 与双斜线之间包含空格

//go:build 与双斜线之间不存在空格

2.Go不同的版本支持

Go versions 1.16 and earlier used a different syntax for build constraints, with a "// +build" prefix.

The gofmt command will add an equivalent "//go:build" constraint when encountering the older syntax.在Go的1.16以及之前的版本使用 // +build 前缀来标识构建约束。

gofmt命令在遇到 // +build 前缀来标识构建约束时会添加一下等效的 //go:build 构建约束。

3.同一文件中编译标签的行数

// +build 在一个文件中可以存在多行

//go:build 在一个文件中只能存在一行,超过一行则会报错

例如,Go源码 src/math/big/arith_mipsx.s中

//go:build !math_big_pure_go && (mips || mipsle)

// +build !math_big_pure_go

// +build mips mipsle在该文件中,// +build 有两行,//go:build 仅有一行。

4.多个tag之间的连接符

// +build 多个tag之间,可用的连接符

空格表示:AND

逗号表示:OR

!表示:NOT

换行表示:AND//go:build 多个tag之间,可用的连接符

&& 表示:AND

|| 表示:OR

! 表示:NOT

() 表示:分组从这里可以看出 //go:build 多个tag之间的连接符更接近于代码规范,也更加容易理解(这也是替代// +build的一个原因)。

编译标签中多个tag的组合方式

tag 可指定为以下内容:

操作系统,环境变量中GOOS的值如:linux、darwin、windows等等

操作系统的架构,环境变量中GOARCH的值如:arch64、x86、i386等等

使用的编译器如:gc或者gccgo,是否开启CGO,cgo

golang版本号如:Go Version 1.1为go1.1, Go Version 1.12版本为go1.12,以此类推

其它自定义标签通过 go build -tags 自定义tag名称 指定tag值

示例

// +build linux,386 darwin,!cgo

表示 (linux && 386) || (darwin && !cgo)

// +build linux darwin

// +build amd64

表示 (linux || darwin) && amd64

// +build ignore

表示 该文件不参与编译过程

自定义tag的使用方式

新建 buildtag 项目,包含文件如下:

➜ tree

.

├── demo_not_tag.go

├── demo_tag.go

├── go.mod

└── main.gomain.go 文件

package main

import "fmt"

func main() {

fmt.Println(demo(1, 2))

}demo_tag.go 文件

//go:build use

package main

func demo(a, b int) int {

return a + b + 1

}demo_not_tag.go 文件

//go:build !use

package main

func demo(a, b int) int {

return a + b

}从上面代码可以看到 demo_tag.go 文件中 //go:build use 与 demo_not_tag.go 文件中 //go:build !use。

分别使用 go build 与 go build -tags use 执行,结果如下所示:

➜ go build

➜ ./buildtag

3

➜ go build -tags use

➜ ./buildtag

4可以看出:

使用 go build 调用的demo方法为 demo_not_tag.go 文件中demo方法使用 go build -tags use 调用的demo方法为 demo_tag.go 文件中demo方法如果有多个tag可以使用空格分隔

例如:go build -tags "use use1 use2"

总结一句话就是:编译器根据 tag标识 有选择性的加载对应文件进行编译

Go源码中关于构建约束的部分追溯

src/cmd/asm/internal/lex/tokenizer.go

func (t *Tokenizer) Next() ScanToken {

s := t.s

for {

t.tok = ScanToken(s.Scan())

if t.tok != scanner.Comment {

break

}

text := s.TokenText()

t.line += strings.Count(text, "n")

// TODO: Use constraint.IsGoBuild once it exists.

if strings.HasPrefix(text, "//go:build") {

t.tok = BuildComment

break

}

}

......

return t.tok

}在词法分析时, 存在 "//go:build" 开头的文本,则标识为 token 为 BuildComment

src/cmd/asm/internal/lex/lex.go

// A ScanToken represents an input item. It is a simple wrapping of rune, as

// returned by text/scanner.Scanner, plus a couple of extra values.

type ScanToken rune

const (

// Asm defines some two-character lexemes. We make up

// a rune/ScanToken value for them - ugly but simple.

LSH ScanToken = -1000 - iota // << Left shift.

RSH // >> Logical right shift.

ARR // -> Used on ARM for shift type 3, arithmetic right shift.

ROT // @> Used on ARM for shift type 4, rotate right.

Include // included file started here

BuildComment // //go:build or +build comment

macroName // name of macro that should not be expanded

)BuildComment 代表 //go:build or +build 注释

src/cmd/fix/buildtag.go

package main

import (

"go/ast"

"strings"

)

func init() {

register(buildtagFix)

}

const buildtagGoVersionCutoff = 1_18

var buildtagFix = fix{

name: "buildtag",

date: "2021-08-25",

f: buildtag,

desc: `Remove +build comments from modules using Go 1.18 or later`,

}

func buildtag(f *ast.File) bool {

if goVersion < buildtagGoVersionCutoff {

return false

}

// File is already gofmt-ed, so we know that if there are +build lines,

// they are in a comment group that starts with a //go:build line followed

// by a blank line. While we cannot delete comments from an AST and

// expect consistent output in general, this specific case - deleting only

// some lines from a comment block - does format correctly.

fixed := false

for _, g := range f.Comments {

sawGoBuild := false

for i, c := range g.List {

if strings.HasPrefix(c.Text, "//go:build ") {

sawGoBuild = true

}

if sawGoBuild && strings.HasPrefix(c.Text, "// +build ") {

g.List = g.List[:i]

fixed = true

break

}

}

}

return fixed

}Remove +build comments from modules using Go 1.18 or later 明确说明在 1.18或者更新的版本中会移除 +build。

src/cmd/fix/buildtag_test.go

package main

func init() {

addTestCases(buildtagTests, buildtag)

}

var buildtagTests = []testCase{

{

Name: "buildtag.oldGo",

Version: 1_10,

In: `//go:build yes

// +build yes

package main

`,

},

{

Name: "buildtag.new",

Version: 1_99,

In: `//go:build yes

// +build yes

package main

`,

Out: `//go:build yes

package main

`,

},

}buildtagTests中 In、Out可以看出不同版本对于 // +build 的处理方式。

fix的作用

Fix finds Go programs that use old APIs and rewrites them to use

newer ones. After you update to a new Go release, fix helps make

the necessary changes to your programs.

相关文章

学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习...
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面...
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生...
Can’t connect to local MySQL server through socket \'/v...
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 ...
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服...