CGO 跨平台静态编译

什么是跨平台编译?

  • 跨平台编译:即交叉编译,是在一个平台上生成另一个平台上的可执行文件。所谓平台,实际上包含两个概念:体系架构(Architecture)、操作系统 (Operating System)。同一个体系架构可以运行不同的操作系统;同样,同一个操作系统也可以在不同的体系架构上运行。
  • 静态编译:在编译可执行文件的时候,将可执行文件需要调用的对应库都集成到可执行文件内部,使得可执行文件不需要其他任何依赖就能运行。

Go 实现跨平台编译的思想其实很简单:通过保存可以生成最终机器码的多份翻译代码,在编译时根据 GOARCH=xxx 和 GOOS=xxx 参数(对应体系架构和操作系统)进行初始化设置,最终调用对应平台编写的特定方法来生成机器码,从而实现跨平台编译。

有一点需要注意:Go 所谓的跨平台编译只是针对 Go 代码部分,它是 Go 的跨平台编译器(cross-compiler toolchains)。当我们使用了 CGO 时,要想实现跨平台编译,同时需要让 C/C++代码也支持跨平台。

官方 Cgo 这块目前有一篇 博客命令行文档。比如 sqlite 的 golang 驱动 go-sqlite3 就是基于 Cgo 的实现。编译本地版本,Go 本身已经支持得非常好,基本不需要额外设置,直接通过 go build 编译即可,但是要想编译其他平台的二进制版本,就需要跨平台的 $(CC), $(CXX) 支持。

无 CGO 项目的交叉静态编译

在不启用 CGO 的情况下,跨平台编译是非常简单的,如下:

1
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags '-s -w --extldflags "-static -fpic"' main.go
  • CGO_ENABLED=0 这个值默认是 1,也就是开启的,需要手动指定为关闭,因为 CGO 是不支持跨平台编译的,使用 go env CGO_ENABLED 查看默认值
  • GOOS, GOARCH 构建的平台,GOOS=linux 是因为安卓底层就是 linux,aarch64 架构直接使用 arm64,如果 GOARCH=arm,则要使用 GOARM=7,指定 arm 版本可选 5,6,7,在 这里 看完整支持列表
  • -ldflags 编译选项,-s -w 去掉调试信息,可以减小构建后文件体积,
  • --extldflags "-static -fpic" 完全静态编译,要跨平台编译放到其他系统和架构中运行,建议静态编译,否则程序启动的时候会提示找不到依赖的 so 文件

这样编译生成的程序就可以任意放到指定平台下运行。由于 CGO 的存在,跨平台会编译失败。那该如何解决呢?

CGO 项目的交叉静态编译

和 Go 一样,当我们拥有目标平台的 C/C++代码编译器后,自然就能够编译为目标平台的可执行文件。

不同平台的编译器 下载地址

aarch64-linux 为例,下载 aarch64-linux 的编译器: https://musl.cc/aarch64-linux-musl-cross.tgz

解压,然后把解压好的目录下 bin 文件路径放到 PATH 环境变量中

此时,通过指定 C/C++编译器为/usr/local/bin/x86_64-linux-musl-gcc,替换默认的 C/C++编译器(本机编译,可通过 go env CC 查看),即可完成含有 CGO 的 Go 代码跨平台编译任务。

1
2
$ CGO_ENABLED=1 CC=aarch64-linux-musl-gcc CXX=aarch64-linux-musl-g++ GOOS=linux GOARCH=arm64 go build -o server -ldflags '-s -w --extldflags "-static -fpic"' main.go

  • CGO_ENABLED=1 开启 CGO,因为项目用到了 C 语言的代码
  • CC=aarch64-linux-musl-gcc 指定 gcc 的编译器为 aarch64-linux-musl-gcc,这个默认值是 gcc,也就是当前操作系统和架构使用的 gcc,使用命令 $(go env CC) --target-help 可以看看默认 gcc 支持什么平台
  • CXX=aarch64-linux-musl-g++ 指定 g++ 的编译器为 aarch64-linux-musl-g++,规则和 CC 一样,只是用来编 C++ 代码的,如果还用到了 C++ 代码,必须指定该项
  • -ldflags '-s -w' go 编译选项,-s -w 去掉调试信息,可以减小构建后文件体积

最终,在本机系统上就编译得到了 aarch64 linux 平台的可执行文件。