Golang条件编译介绍

相信熟悉 Golang 的小伙伴不少都知道 条件编译 这个事,最近项目中也可能会用到这个东西。所以特意重新学习下,记录下学习的过程。这样用的时候记不住了,还可以直接过来看自己的笔记。

文章很多内容来源于参考资料,感谢。

1、条件编译简介

1.1、为什么需要条件编译

在实际的项目开发中,我们可能需要根据条件的不同,来编译release和debug版本代码的需求,也可能需要根据运行环境的不同来编译不同的文件,诸如此类的需求时,我们应该怎么做呢?

这个时候就可以使用 Golang 中的条件编译了,Golang 支持两种条件编译方式:

  • 构建标签( Build tags)
  • 文件后缀(File suffixes)

1.2、条件编译原理简单介绍

Go使用 go/build包 中定义的标签系统(system of tags)和命名约定(naming convention)以及go tool中的相应支持来允许Go包编译特定代码。

说简单的,就是通过约定好的规定,在执行go build 选择性地包含或排除代码的机制。

注意,go1.17 过后,条件编译的写法和之前有些不同,需要注意一下。

具体的tag匹配规则可以看go/build/build.go ,不过对于日常使用来说,知道使用的规则即可,无需深入源码。

接下来,我们就一起看看 golang 条件编译的 tag 规则是怎么样的吧。

2、条件编译实现方式

2.1、构建标签( Build tags)

第一种实现条件编译的方法是在源码中插入注释,被称之为构建标签。

构建标签的注释应该尽可能的接近源码文件的顶部位置。

当Go编译一个包时,它会分析包内的每个源码文件并查找构建标签。标签决定了这个源码文件是否被编译。

2.1.1、使用方法
  1. 构建约束以一行+build开始的注释。在+build之后列出了一些条件,在这些条件成立时,该文件应包含在编译的包中;
  2. 约束可以出现在任何源文件中,不限于go文件;
  3. +build必须出现在package语句之前,+build注释之后应要有一个空行,否则不会生效。
// +build debug

package main

import "fmt"

func main() {
	fmt.Println("Hello World!")
}

可以用以下命令构建:

go build -tags "debug"

条件编译的最小单元是以 文件 来进行的。

2.1.2、语法规则
  • 构建约束以一行+build开始的注释。在+build之后列出了一些条件,在这些条件成立时,该文件应包含在编译的包中;

  • 约束可以出现在任何源文件中,不限于go文件;

  • +build必须出现在package语句之前,+build注释之后应要有一个空行。

  • 多个条件之间,空格表示OR;逗号表示AND;叹号(!)表示NOT

  • 一个文件可以有多个+build,它们之间的关系是AND

  • tag的名称只允许是字母数字或_

  • 当不想编译某个文件时,可以加上// +build ignore。这里的ignore可以是其他单词,只是ignore更能让人知道什么意思

2.2、文件后缀(File suffixes)

第二种条件编译的方法是通过源码文件的文件名实现的。这种方案比构造标签方案更简单。

go/build包的文档有关于命名约定的描述。简单来说,如果文件名包含_$GOOS.go后缀,那么这个源码文件只会在对应的平台被编译。其他平台会忽略这个文件。另一种约定是_$GOARCH.go。这两种后缀可以组合起来,但要保证顺序,正确的格式是_$GOOS_$GOARCH.go,错误的格式是_$GOARCH_$GOOS.go

以下是文件名后缀的一些例子:

mypkg_freebsd_arm.go // 只在 freebsd/arm 系统编译
mypkg_plan9.go       // 只在 plan9 编译

源码文件光有后缀是不够的,比如如下文件名:

_linux.go
_freebsd_386.go

即使是在linux或freebsd系统,这两个文件也会被忽略,原因是go/build包会忽略所有文件名以._开始的文件。

2.3、额外说明

构建标签和文件后缀如何选择?

我的理解是按照需求颗粒度,如果说,编译条件需要按照系统来进行划分,可以使用文件后缀,否则就选项构建标签,这样颗粒度更小也更加灵活。

构建标签和文件名后缀在功能上是重叠的。比如,一个名为mypkg_linux.go的文件,再包含构建标签// +build linux会显得多余。

通常来说,当只有一个特定平台或体系需要指定时,我们选择文件名后缀的方式。比如:

mypkg_linux.go         // 只在 linux 系统编译
mypkg_windows_amd64.go // 只在 windows amd 64位 平台编译

相反,如果你的文件需要指定给多个平台或体系使用,或者你需要排除某个特定平台时,我们选择构建标签的方式。比如:

// 在所有类unix平台编译
% grep '+build' $HOME/go/src/pkg/os/exec/lp_unix.go
// +build darwin dragonfly freebsd linux netbsd openbsd

// 在非Windows平台编译
% grep '+build' $HOME/go/src/pkg/os/types_notwin.go
// +build !windows

3、条件编译实战

以下介绍都是基于go 1.18、windows 介绍的

3.1、构建标签

一共三个文件,分别是:

-- main.go
-- tag_ignore.go
-- tag_include.go

main.go

package main

import "fmt"

func main() {
	fmt.Println("Hello World!")
}

tag_ignore.go

//go:build ignore
// +build ignore

package main

import "fmt"

func init() {
	fmt.Println("perform tag_ignore")
}

tag_include.go

//go:build include
// +build include

package main

import "fmt"

func init() {
	fmt.Println("perform tag_include")
}

# 未使用 tags 标签
go build .\main.go .\tag_ignore.go .\tag_include.go

生成 main.exe

----------------------------------------------------

# 使用 tags 标签
go build  -tags "include"

生成 build_tag.exe

从上面可知未将 tag_ignore.go 编译进二进制文件。

3.2、文件后缀

这里就比较好理解,如果使用的是 goland 当在 windows 上面新建文件名为 tag_linux.go时,goland 会自动提示 tag_linux.go is ignored by the build tool because of the OS mismatch

参考资料:

[译] Go语言如何使用条件编译

Go语言 通过go bulid -tags 实现编译控制

go build -tags 试验

go 编译标签( build tag)-注释里的编译语法

posted @ 2023-07-12 23:38  画个一样的我  阅读(1392)  评论(0编辑  收藏  举报