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

目前小菜刀的项目中需要用到SQLite数据库,https://github.com/mattn/go-sqlite3支持database/sql接口,采用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-windows-and-linux-cross-compilers-for-macos/

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

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,377评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,390评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,967评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,344评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,441评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,492评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,497评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,274评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,732评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,008评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,184评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,837评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,520评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,156评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,407评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,056评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,074评论 2 352

推荐阅读更多精彩内容

  • 由于Go语言是跨平台的,有时我们在自己的开发环境中开发,测试完成后可能会部署到不同的操作系统环境下,不同的操作系统...
    itoocs阅读 269评论 0 1
  • > 跨平台的界面化golang便捷转换器,支持数据库转Struct、Sql转Struct、Json转Struct等...
    airplayx阅读 581评论 3 0
  • 最近研究了一下jna,发现可以用go干点事情。基本步骤: 安装go (这句就是废话) 安装docker(用来下载用...
    独自天涯_a9ba阅读 4,772评论 0 0
  • 基本命令 编译参数 参数名含义-x打印编译过程中的所有执行命令,执行生成的二进制文件-n打印编译过程中的所有执行命...
    xuanxiao阅读 1,727评论 0 0
  • 久违的晴天,家长会。 家长大会开好到教室时,离放学已经没多少时间了。班主任说已经安排了三个家长分享经验。 放学铃声...
    飘雪儿5阅读 7,518评论 16 22