近期在某本書上看到Go跨平台交叉編譯的強大功能,於是想自己測試一下。以下記錄了測試過程以及一些結論,希望能給大家帶來幫助。
我的Linux環境如下:
uname -a
Linux Ubuntu-Server-14 3.13.0-32-generic #57-Ubuntu SMP Tue Jul 15 03:51:08 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
$ go version
go version go1.3.1 linux/amd64
跨平台交叉編譯涉及兩個重要的環境變量:GOOS和GOARCH,分別代表Target Host OS和Target Host ARCH,如果沒有顯式設置這些環境變量,我們通過go env可以看到go編譯器眼中這兩個環境變量的當前值:
$ go env
GOARCH="amd64"
GOOS="linux"
GOHOSTARCH="amd64"
GOHOSTOS="linux"
… …
這裡還有兩個變量GOHOSTOS和GOHOSTARCH,分別表示的是當前所在主機的的OS和CPU ARCH。我的Go是采用安裝包安裝的,因此默認情況下,這兩組環境變量的值都是來自當前主機的信息。
現在我們就來交叉編譯一下:在linux/amd64平台下利用Go編譯器編譯一個可以運行在linux/amd64下的程序,樣例程序如下:
//testport.go
package main
import (
"fmt"
"os/exec"
"bytes"
)
func main() {
cmd := exec.Command("uname", "-a")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
fmt.Println("Err when executing uname command")
return
}
fmt.Println("I am running on", out.String())
}
在Linux/amd64下編譯運行:
$ go build -o testport_linux testport.go
$ testport_linux
I am running on Linux ubuntu-Server-14 3.13.0-32-generic #57-Ubuntu SMP Tue Jul 15 03:51:08 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
接下來,我們來嘗試在Linux/amd64上編譯一個可以運行在darwin/amd64上的程序。我只需修改GOOS和GOARCH兩個標識目標主機OS和ARCH的環境變量:
$ GOOS=darwin GOARCH=amd64 go build -o testport_darwin testport.go
go build runtime: darwin/amd64 must be bootstrapped using make.bash
編譯器報錯了!提示darwin/amd64必須通過make.bash重新裝載。顯然,通過安裝包安裝到linux/amd64下的Go編譯器還無法直接交叉編譯出darwin/amd64下可以運行的程序,我們需要做一些准備工作。我們找找make.bash在哪裡!
我們到Go的$GOROOT路徑下去找make.bash,Go的安裝路徑下的組織很簡約,掃一眼便知make.sh大概在$GOROOT/src下,打開make.sh,我們在文件頭處看到如下一些內容:
# Environment variables that control make.bash:
#
# GOROOT_FINAL: The expected final Go root, baked into binaries.
# The default is the location of the Go tree during the build.
#
# GOHOSTARCH: The architecture for host tools (compilers and
# binaries). Binaries of this type must be executable on the current
# system, so the only common reason to set this is to set
# GOHOSTARCH=386 on an amd64 machine.
#
# GOARCH: The target architecture for installed packages and tools.
#
# GOOS: The target operating system for installed packages and tools.
… …
make.bash頭並未簡要說明文件的用途,但名為make.xx的文件想必是用來構建Go編譯工具的。這裡提到幾個環境變量可以控制 make.bash的行為,顯然GOARCH和GOOS更能引起我們的興趣。我們再回過頭來輸出testport.go編譯過程的詳細信息:
$ go build -x -o testport_linux testport.go
WORK=/tmp/go-build286732099
mkdir -p $WORK/command-line-arguments/_obj/
cd /home/linuxidc/Test/Go/porting
/usr/local/go/pkg/tool/linux_amd64/6g -o $WORK/command-line-arguments.a -trimpath $WORK -p command-line-arguments -complete -D _/home/linuxidc/Test/Go/porting -I $WORK -pack ./testport.go
cd .
/usr/local/go/pkg/tool/linux_amd64/6l -o testport_linux -L $WORK -extld=gcc $WORK/command-line-arguments.a
我們發現Go實際上用的是$GOROOT/pkg/tool/linux_amd64下的6g(編譯器)和6l(鏈接器)來完成整個編譯過程的,看到6g 和6l所在目錄名為linux_amd64,我們可以大膽猜測編譯darwin/amd64 go程序應該使用的是$GOROOT/pkg/tool/darwin_amd64下的工具。不過在我在$GOROOT/pkg/tool下沒有發現 darwin_amd64目錄,也就是說我們通過安裝包安裝的Go僅自帶了for linux_amd64的編譯工具,要想交叉編譯出for darwin_amd64的程序,我們需要通過make.bash來手工編譯出這些工具。
linuxidc@ubuntu-Server-14:/usr/local/go/pkg$ ls
linux_amd64 linux_amd64_race obj tool
linuxidc@ubuntu-Server-14:/usr/local/go/pkg/tool$ ls
linux_amd64
根據前面make.bash的用法說明,我們來嘗試構建一下:
cd $GOROOT/src
sudo GOOS=darwin GOARCH=amd64 ./make.bash
# Building C bootstrap tool.
cmd/dist
# Building compilers and Go bootstrap tool for host, linux/amd64.
… …
cmd/cc
cmd/gc
cmd/6l
cmd/6a
cmd/6c
cmd/6g
pkg/runtime
… …
cmd/go
pkg/runtime (darwin/amd64)
# Building packages and commands for host, linux/amd64.
runtime
… …
text/scanner
# Building packages and commands for darwin/amd64.
runtime
errors
… …
testing/quick
text/scanner
—
Installed Go for darwin/amd64 in /usr/local/go
Installed commands in /usr/local/go/bin
編譯後,我們再來試試編譯for darwin_amd64的程序:
$ GOOS=darwin GOARCH=amd64 go build -x -o testport_darwin testport.go
WORK=/tmp/go-build972764136
mkdir -p $WORK/command-line-arguments/_obj/
cd /home/linuxidc/Test/Go/porting
/usr/local/go/pkg/tool/linux_amd64/6g -o $WORK/command-line-arguments.a -trimpath $WORK -p command-line-arguments -complete -D _/home/linuxidc/Test/Go/porting -I $WORK -pack ./testport.go
cd .
/usr/local/go/pkg/tool/linux_amd64/6l -o testport_darwin -L $WORK -extld=gcc $WORK/command-line-arguments.a
將文件copy到我的Mac Air下執行:
$chmod +x testport_darwin
$testport_darwin
I am running on Darwin TonydeMacBook-Air.local 13.1.0 Darwin Kernel Version 13.1.0: Thu Jan 16 19:40:37 PST 2014; root:xnu-2422.90.20~2/RELEASE_X86_64 x86_64
編譯雖然成功了,但從-x輸出的詳細編譯過程來看,Go編譯連接使用的工具依舊是linux_amd64下的6g和6l,為什麼沒有使用darwin_amd64下的6g和6l呢?原來$GOROOT/pkg/tool/darwin_amd64下根本就沒有6g和6l:
/usr/local/go/pkg/tool/darwin_amd64$ ls
addr2line cgo fix nm objdump pack yacc
但查看一下pkg/tool/linux_amd64/下程序的更新時間:
/usr/local/go/pkg/tool/linux_amd64$ ls -l
… …
-rwxr-xr-x 1 root root 2482877 10月 20 15:12 6g
-rwxr-xr-x 1 root root 1186445 10月 20 15:12 6l
… …
我們發現6g和6l都是被剛才的make.bash新編譯出來的,我們可以得出結論:新6g和新6l目前既可以編譯本地程序(linux/amd64),也可以編譯darwin/amd64下的程序了,例如重新編譯testport_linux依舊ok:
$ go build -x -o testport_linux testport.go
WORK=/tmp/go-build636762567
mkdir -p $WORK/command-line-arguments/_obj/
cd /home/linuxidc/Test/Go/porting
/usr/local/go/pkg/tool/linux_amd64/6g -o $WORK/command-line-arguments.a -trimpath $WORK -p command-line-arguments -complete -D _/home/linuxidc/Test/Go/porting -I $WORK -pack ./testport.go
cd .
/usr/local/go/pkg/tool/linux_amd64/6l -o testport_linux -L $WORK -extld=gcc $WORK/command-line-arguments.a
如果我們還想給Go編譯器加上交叉編譯windows/amd64程序的功能,我們再執行一次make.bash:
sudo GOOS=windows GOARCH=amd64 ./make.bash
編譯成功後,我們來編譯一下Windows程序:
$ GOOS=windows GOARCH=amd64 go build -x -o testport_windows.exe testport.go
WORK=/tmp/go-build626615350
mkdir -p $WORK/command-line-arguments/_obj/
cd /home/linuxidc/Test/Go/porting
/usr/local/go/pkg/tool/linux_amd64/6g -o $WORK/command-line-arguments.a -trimpath $WORK -p command-line-arguments -complete -D _/home/linuxidc/Test/Go/porting -I $WORK -pack ./testport.go
cd .
/usr/local/go/pkg/tool/linux_amd64/6l -o testport_windows.exe -L $WORK -extld=gcc $WORK/command-line-arguments.a
把testport_windows.exe扔到Windows上執行,結果:
Err when executing uname command
顯然Windows下沒有uname命令,提示執行出錯。
至此,我的Go編譯器具備了在Linux下編譯windows/amd64和darwin/amd64的能力。如果你還想增加其他平台的能力,就像上面那樣操作執行make.bash即可。
如果在go源文件中有與C語言的交互代碼,那麼交叉編譯功能是否還能奏效呢?畢竟C在各個平台上的運行庫、鏈接庫等都是不同的。我們先來看看這個例子,我們使用之前在《探討docker容器對共享內存的支持情況》一文中的一個例子:
//testport_cgoenabled.go
package main
//#include <stdio.h>
//#include <sys/types.h>
//#include <sys/mman.h>
//#include <fcntl.h>
//
//#define SHMSZ 27
//
//int shm_rd()
//{
// char c;
// char *shm = NULL;
// char *s = NULL;
// int fd;
// if ((fd = open("./shm.txt", O_RDONLY)) == -1) {
// return -1;
// }
//
// shm = (char*)mmap(shm, SHMSZ, PROT_READ, MAP_SHARED, fd, 0);
// if (!shm) {
// return -2;
// }
//
// close(fd);
// s = shm;
// int i = 0;
// for (i = 0; i < SHMSZ – 1; i++) {
// printf("%c ", *(s + i));
// }
// printf("\n");
//
// return 0;
//}
import "C"
import "fmt"
func main() {
i := C.shm_rd()
if i != 0 {
fmt.Println("Mmap Share Memory Read Error:", i)
return
}
fmt.Println("Mmap Share Memory Read Ok")
}
我們先編譯出一個本地可運行的程序:
$ go build -x -o testport_cgoenabled_linux testport_cgoenabled.go
WORK=/tmp/go-build977176241
mkdir -p $WORK/command-line-arguments/_obj/
cd /home/linuxidc/Test/Go/porting
CGO_LDFLAGS="-g" "-O2" /usr/local/go/pkg/tool/linux_amd64/cgo -objdir $WORK/command-line-arguments/_obj/ — -I $WORK/command-line-arguments/_obj/ testport_cgoenabled.go
/usr/local/go/pkg/tool/linux_amd64/6c -F -V -w -trimpath $WORK -I $WORK/command-line-arguments/_obj/ -I /usr/local/go/pkg/linux_amd64 -o $WORK/command-line-arguments/_obj/_cgo_defun.6 -D GOOS_linux -D GOARCH_amd64 $WORK/command-line-arguments/_obj/_cgo_defun.c
gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -print-libgcc-file-name
gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -I $WORK/command-line-arguments/_obj/ -g -O2 -o $WORK/command-line-arguments/_obj/_cgo_main.o -c $WORK/command-line-arguments/_obj/_cgo_main.c
gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -I $WORK/command-line-arguments/_obj/ -g -O2 -o $WORK/command-line-arguments/_obj/_cgo_export.o -c $WORK/command-line-arguments/_obj/_cgo_export.c
gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -I $WORK/command-line-arguments/_obj/ -g -O2 -o $WORK/command-line-arguments/_obj/testport_cgoenabled.cgo2.o -c $WORK/command-line-arguments/_obj/testport_cgoenabled.cgo2.c
gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -o $WORK/command-line-arguments/_obj/_cgo_.o $WORK/command-line-arguments/_obj/_cgo_main.o $WORK/command-line-arguments/_obj/_cgo_export.o $WORK/command-line-arguments/_obj/testport_cgoenabled.cgo2.o -g -O2
/usr/local/go/pkg/tool/linux_amd64/cgo -objdir $WORK/command-line-arguments/_obj/ -dynimport $WORK/command-line-arguments/_obj/_cgo_.o -dynout $WORK/command-line-arguments/_obj/_cgo_import.c
/usr/local/go/pkg/tool/linux_amd64/6c -F -V -w -trimpath $WORK -I $WORK/command-line-arguments/_obj/ -I /usr/local/go/pkg/linux_amd64 -o $WORK/command-line-arguments/_obj/_cgo_import.6 -D GOOS_linux -D GOARCH_amd64 $WORK/command-line-arguments/_obj/_cgo_import.c
gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -o $WORK/command-line-arguments/_obj/_all.o $WORK/command-line-arguments/_obj/_cgo_export.o $WORK/command-line-arguments/_obj/testport_cgoenabled.cgo2.o -g -O2 -Wl,-r -nostdlib /usr/lib/gcc/x86_64-linux-gnu/4.8/libgcc.a
/usr/local/go/pkg/tool/linux_amd64/6g -o $WORK/command-line-arguments.a -trimpath $WORK -p command-line-arguments -D _/home/linuxidc/Test/Go/porting -I $WORK -pack $WORK/command-line-arguments/_obj/_cgo_gotypes.go $WORK/command-line-arguments/_obj/testport_cgoenabled.cgo1.go
pack r $WORK/command-line-arguments.a $WORK/command-line-arguments/_obj/_cgo_import.6 $WORK/command-line-arguments/_obj/_cgo_defun.6 $WORK/command-line-arguments/_obj/_all.o # internal
cd .
/usr/local/go/pkg/tool/linux_amd64/6l -o testport_cgoenabled_linux -L $WORK -extld=gcc $WORK/command-line-arguments.a
輸出了好多日志!不過可以看出Go編譯器先調用CGO對Go源碼中的C代碼進行了編譯,然後才是常規的Go編譯,最後通過6l鏈接在一起。Cgo似乎直接使用了Gcc。我們再來試試跨平台編譯:
$ GOOS=darwin GOARCH=amd64 go build -x -o testport_cgoenabled_darwin testport_cgoenabled.go
WORK=/tmp/go-build124869433
can't load package: no buildable Go source files in /home/linuxidc/Test/Go/porting
當我們編譯for Darwin/amd64平台的程序時,Go無法像之前那樣的順利完成編譯,而是提示錯誤。從網上給出的資料來看,如果Go源碼中包含C互操作代碼,那麼 目前依舊無法實現交叉編譯,因為cgo會直接使用各個平台的本地c編譯器去編譯Go文件中的C代碼。默認情況下,make.bash會置 CGO_ENABLED=0。
如果你非要將CGO_ENABLED設置為1去編譯go的話,至少我得到了如下錯誤,導致無法編譯通過:
$ sudo CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 ./make.bash –no-clean
… …
# Building packages and commands for darwin/amd64.
… …
37: error: 'AI_MASK' undeclared (first use in this function)