乐趣区

关于编译:含有CGO代码的项目如何实现跨平台编译

目前小菜刀的我的项目中须要用到 SQLite 数据库,https://github.com/mattn/go-s…,采纳 Go 的标准接口有利于我的项目后续扩大,因而抉择了该驱动。然而,它是基于 CGO 实现的,所以跨平台编译会比拟麻烦,小菜刀总结了一些教训,特分享给读者敌人们。

什么是跨平台编译?

简略地说,就是在一个平台上生成另一个平台上的可执行代码。这里须要留神的是,所谓平台,实际上蕴含两个概念:体系架构(Architecture)、操作系统 (Operating System)。同一个体系架构能够运行不同的操作系统;同样,同一个操作系统也能够在不同的体系架构上运行。

咱们晓得 Go 语言是反对跨平台编译的,在之前的文章《Go 穿插编译》中有具体介绍过怎么操作。Go 实现跨平台编译的思维其实很简略:通过保留能够生成最终机器码的多份翻译代码,在编译时依据 GOARCH=xxxGOOS=xxx 参数(对应体系架构和操作系统)进行初始化设置,最终调用对应平台编写的特定办法来生成机器码,从而实现跨平台编译。

CGO 编译存在的问题

有一点须要留神:Go 所谓的跨平台编译只是针对 Go 代码局部,它是 Go 的穿插编译器(cross-compiler toolchains)。当咱们应用了 CGO 时,要想实现跨平台编译,同时须要让 C /C++ 代码也反对跨平台。

package main

/*
#include <stdio.h>

void printint(int v) {printf("printint: %d\n", v);
}
*/
import "C"

func main() {
    v := 42
    C.printint(C.int(v))
}

小菜刀的开发机器:amd64 架构,darwin 零碎。指标编译平台:amd64 架构,linux 零碎。现想将上述含有 CGO 的代码编译为指标平台的可执行文件。

$ GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build -o main main.go

通过以上命令,失去编译谬误如下

/usr/local/go/pkg/tool/darwin_amd64/link: running clang failed: exit status 1
ld: warning: ignoring file /var/folders/xk/gn46n46d503dsztbc6_9qb2h0000gn/T/go-link-220081766/go.o, building for macOS-x86_64 but attempting to link with file built for unknown-unsupported file format (0x7F 0x45 0x4C 0x46 0x02 0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00)
Undefined symbols for architecture x86_64:
  "_main", referenced from:
     implicit entry/start for main executable
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

能够看到,因为 CGO 的存在,跨平台编译失败。那该如何解决呢?

其实思路能够很简略:和 Go 一样,当咱们领有指标平台的 C /C++ 代码翻译零碎后,天然就可能编译为指标平台的可执行文件。

Mac 下的可行计划

下载 linux 编译工具链

brew install FiloSottile/musl-cross/musl-cross

或者 windows 编译工具链

brew install mingw-w64

以 linux 编译工具链为例。在下载结束后,/usr/local/bin 下会存在以下对应平台 C /C++ 编译器

x86_64-linux-musl-addr2line   x86_64-linux-musl-elfedit     x86_64-linux-musl-gcov        x86_64-linux-musl-objcopy
x86_64-linux-musl-ar          x86_64-linux-musl-g++         x86_64-linux-musl-gcov-dump   x86_64-linux-musl-objdump
x86_64-linux-musl-as          x86_64-linux-musl-gcc         x86_64-linux-musl-gcov-tool   x86_64-linux-musl-ranlib
x86_64-linux-musl-c++         x86_64-linux-musl-gcc-9.2.0   x86_64-linux-musl-gprof       x86_64-linux-musl-readelf
x86_64-linux-musl-c++filt     x86_64-linux-musl-gcc-ar      x86_64-linux-musl-ld          x86_64-linux-musl-size
x86_64-linux-musl-cc          x86_64-linux-musl-gcc-nm      x86_64-linux-musl-ld.bfd      x86_64-linux-musl-strings
x86_64-linux-musl-cpp         x86_64-linux-musl-gcc-ranlib  x86_64-linux-musl-nm          x86_64-linux-musl-strip

上述指定下载命令只下载了 x86_64 体系下的编译器,但其实并不止这些。可通过 brew info musl-cross 命令进行查看。

$ brew info musl-cross
filosottile/musl-cross/musl-cross: stable 0.9.9 (bottled), HEAD
Linux cross compilers based on musl libc
https://github.com/richfelker/musl-cross-make
/usr/local/Cellar/musl-cross/0.9.9 (1,851 files, 245.8MB) *
  Poured from bottle on 2020-11-16 at 17:09:31
From: https://github.com/filosottile/homebrew-musl-cross/blob/master/musl-cross.rb
==> Dependencies
Build: gnu-sed ✔, make ✔
==> Options
--with-aarch64
    Build cross-compilers targeting arm-linux-muslaarch64
--with-arm
    Build cross-compilers targeting arm-linux-musleabi
--with-arm-hf
    Build cross-compilers targeting arm-linux-musleabihf
--with-i486
    Build cross-compilers targeting i486-linux-musl
--with-mips
    Build cross-compilers targeting mips-linux-musl
--with-mips64
    Build cross-compilers targeting mips64-linux-musl
--with-mips64el
    Build cross-compilers targeting mips64el-linux-musl
--with-mipsel
    Build cross-compilers targeting mipsel-linux-musl
--without-x86_64
    Do not build cross-compilers targeting x86_64-linux-musl
--HEAD
    Install HEAD version

此时,通过指定 C /C++ 编译器为 /usr/local/bin/x86_64-linux-musl-gcc,替换默认的 C /C++ 编译器(本机编译,可通过go env CC 查看),即可实现含有 CGO 的 Go 代码穿插编译工作。

$ GOOS=linux CC="/usr/local/bin/x86_64-linux-musl-gcc" GOARCH=amd64 CGO_ENABLED=1 go build -ldflags "-linkmode external -extldflags -static" main.go

最终,在本机 mac 零碎上就编译失去了 amd64 linux 平台的可执行文件。

Docker 解决方案

在小菜刀通过上述形式实现 cgo 的跨平台编译之余,找到了另外一种可行计划:基于 Docker 容器的 xgo 打包工具。

它的实现也很简略:将多平台所须要的 Go 工具链,C/C++ 穿插编译器和头文件 / 库都组装到 Docker 容器中(因而,在镜像拉取时,会下载大量的依赖资源),再借助 xgo 打包工具实现跨平台编译。

  1. Docker 装置(省略)
  2. 拉取镜像
docker pull karalabe/xgo-latest
  1. 打包工具装置
go get github.com/karalabe/xgo

轻量级的命令包装器,它的作用就是简化简单的 Docker 命令。

  1. 跨平台编译

指定要编译的导入门路即可,其余工作由 xgo 实现。在本例中,代码地位位于$GOPATH/src/workspace/example/cgoDemo2/

xgo $GOPATH/src/workspace/example/cgoDemo2/

编译之后,本目录下会存在以下各平台可执行文件

$ ls -al
total 44960
drwxr-xr-x  23 slp  staff      736 Nov 17 11:43 .
drwxr-xr-x  39 slp  staff     1248 Nov 16 17:59 ..
-rwxr-xr-x   1 slp  staff  1761872 Nov 17 11:42 cgoDemo2-android-16-386
drwxr-xr-x   5 slp  staff      160 Nov 17 11:42 cgoDemo2-android-16-aar
-rwxr-xr-x   1 slp  staff  1778464 Nov 17 11:42 cgoDemo2-android-16-arm
-rwxr-xr-x   1 slp  staff   902436 Nov 17 11:43 cgoDemo2-darwin-10.6-386
-rwxr-xr-x   1 slp  staff  1053816 Nov 17 11:43 cgoDemo2-darwin-10.6-amd64
-rwxr-xr-x   1 slp  staff  1065232 Nov 17 11:43 cgoDemo2-ios-5.0-arm64
-rwxr-xr-x   1 slp  staff   978016 Nov 17 11:43 cgoDemo2-ios-5.0-armv7
drwxrwxrwx   3 slp  staff       96 Nov 17 11:43 cgoDemo2-ios-5.0-framework
-rwxr-xr-x   1 slp  staff  1084208 Nov 17 11:42 cgoDemo2-linux-386
-rwxr-xr-x   1 slp  staff  1226072 Nov 17 11:42 cgoDemo2-linux-amd64
-rwxr-xr-x   1 slp  staff  1093728 Nov 17 11:42 cgoDemo2-linux-arm-5
-rwxr-xr-x   1 slp  staff  1074348 Nov 17 11:43 cgoDemo2-linux-arm-6
-rwxr-xr-x   1 slp  staff  1073800 Nov 17 11:43 cgoDemo2-linux-arm-7
-rwxr-xr-x   1 slp  staff  1196520 Nov 17 11:43 cgoDemo2-linux-arm64
-rwxr-xr-x   1 slp  staff  1152088 Nov 17 11:43 cgoDemo2-linux-mips
-rwxr-xr-x   1 slp  staff  1274272 Nov 17 11:43 cgoDemo2-linux-mips64
-rwxr-xr-x   1 slp  staff  1271464 Nov 17 11:43 cgoDemo2-linux-mips64le
-rwxr-xr-x   1 slp  staff  1148892 Nov 17 11:43 cgoDemo2-linux-mipsle
-rwxr-xr-x   1 slp  staff  1712214 Nov 17 11:43 cgoDemo2-windows-4.0-386.exe
-rwxr-xr-x   1 slp  staff  2115121 Nov 17 11:43 cgoDemo2-windows-4.0-amd64.exe
-rw-r--r--   1 slp  staff      157 Nov 16 16:51 main.go

默认状况下,xgo 会尝试编译 Go 运行时所反对的所有平台。如果咱们只想构建特定的几个指标零碎,能够应用逗号分隔的 --targets 选项管制,例如 --targets=windows/amd64,linux/amd64 代表编译指标仅包含 amd64 架构的 windows 和 linux 平台。

$ xgo --targets=windows/amd64,linux/amd64 $GOPATH/src/workspace/example/cgoDemo2/

目前反对的平台列表如下

  • 操作系统: android, darwin, ios, linux, windows
  • 架构: 386, amd64, arm-5, arm-6, arm-7, arm64, mips, mipsle, mips64, mips64le

xgo 提供了比拟灵便的编译计划,通过 $ xgo -h 查看选项信息,更多详情可点击文末的 xgo 链接。

参考链接

[easy windows and linux cross-compilers for macOS] https://blog.filippo.io/easy-…

[musl-cross-make] https://github.com/richfelker…

[homebrew-musl-cross] https://github.com/FiloSottil…

[xgo] https://github.com/karalabe/xgo

退出移动版