我在暴躁同事小张的胁迫下学会了Go的交叉编译和条件编译

看不見的法師
发布: 2025-09-18 08:41:55
原创
302人浏览过

今天继续关于

go
登录后复制
开发经验的分享,这次的主题是关于
go
登录后复制
的交叉编译和条件编译,伴随着我对自己打不过、惹不起的壕同事小张还有运维们的碎碎念。

交叉编译

交叉编译是用来在一个平台上生成另一个平台的可执行程序。比如我工作开发时用的Mac,系统内核是

darwin
登录后复制
,小张用的是外星人,系统内核是
windows
登录后复制
(小张明显比我有钱,我的Mac是公司发的,人家的外星人是为打游戏自己买的)。

那么假如我编写的代码依赖了系统底层平台或处理器架构特性的

go
登录后复制
包时,比如说我上周在文章《Go服务迁到K8s后老抽风重启? 记一次完整的线上问题解决过程》里写的,为了把
go
登录后复制
运行时的
panic
登录后复制
错误重定向到日志文件,我用了
syscall.Dup2
登录后复制
这个函数把标准错误原来的文件描述符替换成了自己指定的日志文件的描述符。
syscall.Dup2
登录后复制
go
登录后复制
语言在类
Unix
登录后复制
系统,
X86_64
登录后复制
架构下才有的函数库,在Mac系统上、各种服务器环境上编译都没有问题,但是唯独在像小张这样不用办公电脑的土豪们用的
windows
登录后复制
系统上编译不过去。

所以在上篇文章说的那个为了追踪在

Kubernetes
登录后复制
上服务老重启的问题,用
syscall.Dup2
登录后复制
重定向标准输出的解决方案是有副作用的,我贴一下之前这个功能的代码。

代码语言:javascript代码运行次数:0运行复制
<pre class="brush:php;toolbar:false;">func RewriteStderrFile() error {   if runtime.GOOS == "windows" {      return nil   }   ......    file, err := os.OpenFile(stdErrFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)    if err != nil {      fmt.Println(err)        return err    }        if err = syscall.Dup2(int(file.Fd()), int(os.Stderr.Fd())); err != nil {        fmt.Println(err)        return err    }    ......    return nil}
登录后复制

天真的用了一个runtime.GOOS == "windows"的判断,我还想着能在代码里根据内核的不同执行不同的代码,但是

go
登录后复制
的软件包是先编译成可执行文件再执行的,这个判断根本没啥用。所以在
windows
登录后复制
系统下编译项目的时候,因为没有
syscall.Dup2
登录后复制
直接就失败了......。

我这不就是典型的动态语言的思维吗,之前还写文章跟别人讲《如何避免用动态语言的思维写Go代码》......这次打自己脸打的实在有点疼。

虽然项目这个更新已经上线了,但是土壕小张和运维我都惹不起,迫于无奈我就看了看

go
登录后复制
官方的标准库到底是怎么兼容多平台的。

条件编译

我发现在go的每个内置库里都有很多以不停系统名结尾的文件。下面是

go
登录后复制
的os[1]内置库源代码的部分截图:

我在暴躁同事小张的胁迫下学会了Go的交叉编译和条件编译

在有些文件里还有类似下面这样的注释:

代码语言:javascript代码运行次数:0运行复制
<pre class="brush:php;toolbar:false;">// +build aix darwin dragonfly freebsd js,wasm linux netbsd openbsd solarispackage os...
登录后复制

看了些资料后才知道,他们是用于

go
登录后复制
软件包的条件编译[2]的,条件编译的意思就是通过某种方式来指示编译器编译特定代码。

go
登录后复制
不支持宏,不可以像c语言那样使用
#define
登录后复制
来控制是否包含平台相关的特定代码。作为替代,
go
登录后复制
使用构建标签(
build tags
登录后复制
)和代码文件的命名约定来支持
go
登录后复制
软件包的条件编译。

构建标签

构建标签就是上面我说的源代码里的注释:

代码语言:javascript代码运行次数:0运行复制
<pre class="brush:php;toolbar:false;">// +build aix darwin dragonfly freebsd js,wasm linux netbsd openbsd solarispackage os...
登录后复制

需要注意的是,构建标签必须在代码文件里位于

package
登录后复制
声明的上方,并且后跟一个空行。

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

构建标签遵循以下三个原则:

空格隔开的选项是或(OR)的关系逗号隔开的选项是与(AND)的关系每个选项由字母和数字组成。如果前面加上
!
登录后复制
,则表示反义代码语言:javascript代码运行次数:0运行复制
<pre class="brush:php;toolbar:false;">// +build darwin freebsd netbsd openbsd
登录后复制

上面的例子,表示这个源码文件只会在支持

kqueue
登录后复制
BSD
登录后复制
系统中被编译。

一个源码文件可以包含多个构建标签。构建规则是每个独立规则的逻辑与关系。如下例子表示该文件将在

linux/386
登录后复制
darwin/386
登录后复制
平台才会被编译 。

代码语言:javascript代码运行次数:0运行复制
<pre class="brush:php;toolbar:false;">// +build linux darwin// +build 386
登录后复制

用逻辑表达式表示就是:(linux OR darwin) AND 386。

文件名后缀

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

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

会译·对照式翻译
会译·对照式翻译

会译是一款AI智能翻译浏览器插件,支持多语种对照式翻译

会译·对照式翻译0
查看详情 会译·对照式翻译

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

代码语言:javascript代码运行次数:0运行复制
<pre class="brush:php;toolbar:false;">mypkg_freebsd_arm.go // 只在 freebsd/arm 系统编译mypkg_plan9.go       // 只在 plan9 编译mypkg_darwin.go      // 只在macos 系统编译
登录后复制

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

代码语言:javascript代码运行次数:0运行复制
<pre class="brush:php;toolbar:false;">_linux.go_freebsd_386.go
登录后复制

即使是在Linux或FreeBSD系统,这两个文件也会被忽略,原因是

go/build
登录后复制
包会忽略所有文件名以
.
登录后复制
_
登录后复制
开始的文件。

使用构建标签还是文件名后缀

构建标签和文件名后缀在功能上是重叠的。比如,一个名为

mypkg_linux.go
登录后复制
的文件,再包含构建标签
// +build linux
登录后复制
会显得多余。

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

代码语言:javascript代码运行次数:0运行复制
<pre class="brush:php;toolbar:false;">mypkg_linux.go         // 只在 linux 系统编译mypkg_windows_amd64.go // 只在 windows amd 64位 平台编译
登录后复制

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

代码语言:javascript代码运行次数:0运行复制
<pre class="brush:php;toolbar:false;">// 在所有类unix平台编译// +build darwin dragonfly freebsd linux netbsd openbsd// 在非Windows平台编译// +build !windows
登录后复制
实践应用

应用环境,我就说下是怎么解决文章开头说的问题让小张大佬平复心情的吧......。

设置条件编译

首先我像下面这样,在包里建了两个源码文件,用来分别存放在

windows
登录后复制
系统和非
windows
登录后复制
系统下使用的
RewriteStderrFile
登录后复制
函数:

代码语言:javascript代码运行次数:0运行复制
<pre class="brush:php;toolbar:false;">project|└───pkg1│   │----rewrite_err_unix.go│   │----rewrite_err_windows.go
登录后复制

因为我们的项目在那几个大佬电脑的

windows
登录后复制
系统上编译和运行的时候都是开发阶段,其他测试上线之类的环境都是
Linux
登录后复制
系统,所以我懒癌发作,直接写了个空函数,毕竟只要能编译运行小张就不会太难为我了。

代码语言:javascript代码运行次数:0运行复制
<pre class="brush:php;toolbar:false;">//rewrite_err_windows.gopackage pkg1func RewriteStdErrLog(topic string) error { return nil}
登录后复制

对于要在服务器上和Mac电脑上编译的源码,跟之前的差不多,只是增加了构建标签:

代码语言:javascript代码运行次数:0运行复制
<pre class="brush:php;toolbar:false;">//+build darwin linuxpackage pkg1......func RewritePanicsToFile(topic string) error {    ......    file, err := os.OpenFile(stdErrFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)    if err != nil {      fmt.Println(err)        return err    }   if err = syscall.Dup2(int(errFileHandler.Fd()), int(os.Stderr.Fd())); err != nil {      return err   }    ......     return nil}
登录后复制
执行交叉编译

交叉编译的执行就非常简单了,在编译时给go build命令设置

OS
登录后复制
ARCH
登录后复制
参数即可:

比如在Mac 下编译

windows
登录后复制
64位可执行程序,用:

代码语言:javascript代码运行次数:0运行复制
<pre class="brush:php;toolbar:false;">CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go
登录后复制

在Mac系统执行完上面的命令就会编译生成软件包在Windows系统上的可执行文件(.exe文件)

如果是

windows
登录后复制
下编译 Mac 64位可执行程序,用:

代码语言:javascript代码运行次数:0运行复制
<pre class="brush:php;toolbar:false;">SET CGO_ENABLED=0SET GOOS=darwinSET GOARCH=amd64go build main.go
登录后复制
总结

事实上,除了用于

.go
登录后复制
的Go源码文件,构建标签和文件名后缀这些条件编译规则可以作用于任何go tool可以编译的源码文件,包括
.c
登录后复制
.s
登录后复制
文件。
go
登录后复制
标准库中,尤其是
runtime
登录后复制
syscall
登录后复制
OS
登录后复制
net
登录后复制
包中包含了大量这种例子。咱们一定要去看看,多学习,尤其是身边有像小张这样又壕又凶的队友的同学们,一定把今天我说的这些都学会......。

参考资料

[1]

os: https://github.com/golang/go/tree/master/src/os

以上就是我在暴躁同事小张的胁迫下学会了Go的交叉编译和条件编译的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号