含有CGO代碼的項目如何實現跨平臺編譯

目前小菜刀的項目中須要用到SQLite數據庫,https://github.com/mattn/go-s...,採用Go的標準接口有利於項目後續擴展,所以選擇了該驅動。可是,它是基於CGO實現的,因此跨平臺編譯會比較麻煩,小菜刀總結了一些經驗,特分享給讀者朋友們。linux

什麼是跨平臺編譯?

簡單地說, 就是在一個平臺上生成另外一個平臺上的可執行代碼。這裏須要注意的是,所謂平臺,實際上包含兩個概念:體系架構(Architecture)、操做系統 (Operating System)。同一個體系架構能夠運行不一樣的操做系統;一樣,同一個操做系統也能夠在不一樣的體系架構上運行。android

咱們知道Go語言是支持跨平臺編譯的,在以前的文章《Go交叉編譯》中有詳細介紹過怎麼操做。Go實現跨平臺編譯的思想其實很簡單:經過保存能夠生成最終機器碼的多份翻譯代碼,在編譯時根據 GOARCH=xxxGOOS=xxx參數(對應體系架構和操做系統)進行初始化設置,最終調用對應平臺編寫的特定方法來生成機器碼,從而實現跨平臺編譯。ios

CGO編譯存在的問題

有一點須要注意:Go所謂的跨平臺編譯只是針對Go代碼部分,它是Go的交叉編譯器(cross-compiler toolchains)。當咱們使用了CGO時,要想實現跨平臺編譯,同時須要讓C/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的代碼編譯爲目標平臺的可執行文件。git

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

經過以上命令,獲得編譯錯誤以下github

/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的存在,跨平臺編譯失敗。那該如何解決呢?sql

其實思路能夠很簡單:和Go同樣,當咱們擁有目標平臺的C/C++代碼翻譯系統後,天然就可以編譯爲目標平臺的可執行文件。docker

Mac下的可行方案

下載linux編譯工具鏈數據庫

brew install FiloSottile/musl-cross/musl-cross

或者windows編譯工具鏈macos

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

相關文章
相關標籤/搜索