Golang 的交叉编译工具

前言

之前对交叉编译的印象还是停留在 C/C++ 上,那时候并没有嵌入式设备需要进行编程。主要是 Android NDK,环境的配置让我觉得麻烦,所以对需要交叉编译时候都尽量避免,不会有多大热情。
当我想将程序放进 ARM 设备中(路由器)运行的时候,我想,我得把以前的一部分 Go 程序重写一个 Python 版本,才能放弃服务器上的一部分业务。现在发现我简直多此一举啊!

原来接触了才知道 Go 的交叉编译如此方便,Go 语言的编译器真的非常赞!刷新了我的认知。

交叉编译 ARM 平台

在 Go 官方的 Wiki 上有对交叉编译到 ARM 平台的说明,简单的来讲,只需要设置几个 go build 运行时的变量即可:

GOOS=linux GOARCH=arm go build ${args}
  • GOOS: 系统平台(还支持 windows、darwin)
  • GOARCH: CPU 架构(还支持 amd64、386 等)

如果在 Windows 上,你可能需要这样:

set GOOS=linux
set GOARCH=arm
go build ${args}

当然,在 Linux 上,可以非常方便的添加一个命令别名:

alias go-build-arm='GOOS=linux GOARCH=arm go build'

然后就可以直接使用这个别名编译代码:

go-build-arm file.go

的确可以说,不能更方便。

写一个基于 go 编译器的快捷编译程序

如果是 Windows 不能这么方便,但是写一个 cmd 文件来调用,加入 PATH 也可以达到一样的效果。当然,为了跨平台最好的方式还是写一个调用 go build 命令的程序。
既然是 go build 为什么不能用 go 写?所以说时迟那时快,我立马就写了一个!在这里,主要使用了 exec 库和 os 库。

首先,创建一个 Cmd 对象:

os.Args[0] = "build"
cmd := exec.Command("go", os.Args...)

os.Args[0] 的默认值是执行文件自身,不需要给 go build,所以这里恰好把 go 的 ‘build’ 参数替换到 os.Args[0] 的位置。再恰好的接收后续参数。

我们给 Cmd 对象添加两个环境变量,交叉编译必须的:

env := os.Environ()
env = append(env, fmt.Sprintf("GOOS=%s", "linux"))
env = append(env, fmt.Sprintf("GOARCH=%s", "arm"))
cmd.Env = env

然后就可以执行了:

out, err := cmd.CombinedOutput()

处理下输出和错误,完整的就是:

package main

import (
	"os/exec"
	"os"
	"fmt"
)

func main() {
	Build()
}

func GoCmdArg(arg string) {
	os.Args[0] = arg
	cmd := exec.Command("go", os.Args...)
	env := os.Environ()
	env = append(env, fmt.Sprintf("GOOS=%s", "linux"))
	env = append(env, fmt.Sprintf("GOARCH=%s", "arm"))
	cmd.Env = env
	out, err := cmd.CombinedOutput()
	if err != nil {
		fmt.Print(string(out))
		os.Exit(1)
	}
	fmt.Print(string(out))
}

func Build() {
	GoCmdArg("build")
}

最后

当然,这个程序可以继续优化。例如我们没必要等待命令执行完毕再输出,所以我们需要建立子进程管道得到流?不,我们不用处理输出内容,所以连管道都不需要。非常简单的重定向一下 Stderr 和 Stdout 到系统默认设备(终端)即可:


package main

import (
	"os/exec"
	"os"
	"fmt"
)

func main() {
	Build()
}

func GoCmdArg(arg string) {
	os.Args[0] = arg
	cmd := exec.Command("go", os.Args...)
	env := os.Environ()
	env = append(env, fmt.Sprintf("GOOS=%s", "linux"))
	env = append(env, fmt.Sprintf("GOARCH=%s", "arm"))
	cmd.Env = env
	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout
	cmd.Run()
}

func Build() {
	GoCmdArg("build")
}

如果添加几个子命令和配置项,那么作为一个支持多平台的快捷交叉编译程序也没啥不好的。说不定就是之后升级的方向:

所以我有在 Github 上创建项目,可以执行下面命令直接安装:

go get -u github.com/hentioe/go2arm && go install github.com/hentioe/go2arm

全局命令 go2arm:

go2arm file.go