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.

原文地址:https://www.toutiao.com/article/7073345538501984781/

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340