实例讲解go build tag 标签在交叉编译时的应用

事情是这样的,在一个项目中,我需要读取系统中的某个文件的创建时间,根据时间做不同的处理,如超过一定时间后就删除掉。 查询了一下,获取文件创建时间可以使用标准库中的os 和 syscall 来完成,但是syscall库在不同的系统中的接口是不一样的,如在Mac下是这样使用的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package main

import (
	"fmt"
	"os"
	"syscall"
)

func main() {
	f, _ := os.Stat("1.txt")
	fattr, _ := f.Sys().(*syscall.Stat_t)
	fmt.Println(fattr.Ctimespec.Sec)
}

使用FileInfo.Sys() 返回的是一个接口类型,这个接口类型在不同的系统是不一样的,func (fs.FileInfo).Sys() interface{} 在Linux和Mac中是 *syscall.Stat_t,在windows中是*syscall.Win32FileAttributeData, 之后再获取属性的时候参数也不一样。。。 上面的代码是linux中是这样的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package main

import (
	"fmt"
	"os"
	"syscall"
)

func main() {
	f, _ := os.Stat("1.txt")
	fattr, _ := f.Sys().(*syscall.Stat_t)
	fmt.Println(fattr.Ctim.Sec )
}

在windows中又下下面的这样

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package main

import (
	"fmt"
	"os"
	"syscall"
)

func main() {
	f, _ := os.Stat("1.txt")
	fattr, _ := f.Sys().(*syscall.Win32FileAttributeData)
	fmt.Println(fattr.CreationTime.Nanoseconds()/1e9)
}

如果要画一个图表示

上面的其实还不是麻烦的,如果你在Mac上开发的,将来你的程序如果要在windows上运行的话,那么开发的时候写上适合windows的代码,就会在ide上直接报错了,也无法直接编译运行。

此时需要使用交叉编译,比如需要编译一个运行在x64位的windows上,则可以使用以下编译命令 GOOS=windows GOARCH=amd64 go build main.go 就可以正常编译了。

但是一个新的问题就来了,如果我想要在linux上使用,则我需要修改代码,改成可以在linux上运行的,然后使用linux上的交叉编译命令GOOS=linux GOARCH=amd64 go build main.go,Mac下还要修改代码,这样如果要生成不同的系统可执行文件,则需要不停的修改代码,有没有一种只编写一次的,不用反复修改的?

答案是有的,我们可以使用go:build 标签的方式

我们可以在文件头部添加 //go:build 标识来表示该文件在哪些系统和架构上进行编译 如我们可以单建一个util.go 文件,将获取文件创建时间函数单独抽出来写到这个util.go文件中,假设这里面的函数适用于Mac系统

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
//go:build darwin

package main

import (
	"errors"
	"os"
	"syscall"
)

func GetCtime(path string) (int64, error) {
	finfo, err := os.Stat(path)
	if err != nil {
		return 0, err
	}
	fattr, ok := finfo.Sys().(*syscall.Stat_t)
	if !ok {
		return 0, errors.New("获取文件属性失败")
	}
	return fattr.Ctimespec.Sec, nil
}

在最上面的添加一个tag, //go:build darwin表示这个文件是在darwin 系统上编译使用的 此时我们就可以再创建两个文件,分别为util_linux.go, util_windows.go 文件 然后给它们设置build 标签

1
2
3
//go:build linux

package main
1
2
3
//go:build windows

package main

go:build 的逻辑关系

go:build 标签可以有逻辑关系的,比如我想要设置某个文件在darwin和liunx系统同时编译,可以使用

1
//go:build darwin || linux 

也可以是且(AND)和或(OR)的关系混合

1
//go:build (darwin && amd64) || linux

自定义标签

除了GOOS和GOARCH,我们也可以自定义一些标签,如dev,release

1
//go:build dev

那么在编译文件的时候,就可以使用 go build -tags "dev" . , 则就可以使用带有dev 编译标签的文件进行编译了。