前言: 前端时间把本地的 Golang 开发环境卸载了,如果编写代码的话就是启动一个 Golang 的 Docker 容器。这样做对于服务端开发本来也是没有问题的,但是有时候想要把程序放到 Windows 上面来执行,那就遇到麻烦了。因为 Docker 容器本质上是 Linux 环境,所以它直接编译的二进制可执行文件也是基于 Linux 的,直接是 Windows 上是无法运行的。不过幸好,Golang 在交叉编译这方面做得很好,我们只需要简单设置一下就可以了:CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go
CGO_ENABLE=0
关闭 CGO。
GOOS 目标操作系统,通常就是 windows
linux
drawin
。
GOARCH 目标架构,通常就是 amd64
,现在也有些是arm架构的,不过我也没有使用过。
注:我了解到有 Windows 容器这个概念,似乎是直接运行在 Windows 上的,不过我也没有仔细了解,毕竟技术栈还是基于 Linux 生态的。
这样做通常就能解决问题了,但是如果我们引入了第三方库,那么它就可能不行了。这里就和 CGO_ENABLE 这个选项有关了。前段时间在写那个 HTTP 代理的 demo,之后我引入了 sqlite3 这个库,用于记录访问数据。然后再次交叉编译之后就遇到问题了,我们来看一下这个问题。
编译
Windows 执行
执行的话,它直接就报错了,并且也说了原因是因为 CGO_ENABLED=0
,同时也指出了是 go-sqlite3 这个库导致的。
Linux执行
这里尝试在 Linux 执行一下,这样做也没有意义,不过这个报错很有趣的。它提示是没有文件或者目录,但是实际上是可执行文件无法解析导致的。所以以后遇到类似的问题,就不需要去想为什么文件不存在了,它就是报这个错误,并不是文件不存在。
那么该怎么解决呢?最方便快捷的方式就是重新在本地安装一个 Golang 的开发环境。 没开玩笑,因为这样确实是最快的,而且节省时间。如果你有时间和兴趣,那就来看接下来的部分吧。
什么是交叉编译?
所谓编译就是把代码变成二进制可执行文件的过程。代码就是一段 Unicode 字符,它是肯定可以跨平台的,但是最终运行的并不是它(最终运行的是代码的编译产物)。我们平时写完一个小程序,直接就点击运行了。但是这里的代码其实是不能直接运行的(编译型语言),它内部还是要进行一个编译的过程,把代码转成目标平台的二进制可执行文件。这个二进制可执行文件是和操作系统和目标架构绑定的,所以你换一个操作系统或者架构它就不能运行了。但是,有时候我们需要在某个架构的某个操作系统上,为特定架构和指定操作系统的另一个平台编译程序,这就是交叉编译。
那么我们这里遇到的问题就是我需要在 Linux 下交叉编译可以在 Windows 下可以运行的二进制可执行文件了。交叉编译一般需要交叉编译工具链,这个其实还是比较复杂的。所以我们就要采取一些简单的方式了——站在巨人的肩膀上。已经有人做了相关的工作了,我们直接采取拿来主义就好了。
交叉编译的 Docker 镜像
这是在 Github 上面找到的一个项目,因为上面说的这种需求还是挺常见的,所以已经有人做了相关的工作了,它是将交叉编译的相关工具链制作成了 Docker 镜像。
Docker image for building Go binaries for Windows with MinGW-w64 toolchain based on official Go Docker image.
The repository provides simple cross-compilation environment for windows 32 and 64bit builds.
Docker镜像,用于使用基于官方Go Docker镜像的 MinGW-w64 工具链为Windows构建Go二进制文件。
这个仓库提供了简单的 win32 和 win64 构建的跨平台编译环境。
注:虽然我也了解 Linux 上用 gcc/clang,windows 上用 mingw。不过我对于它们的了解也仅限于此了,以前也遇到过有些软件在 windows 上需要安装 mingw 的问题,不过也没有深入去了解过,因为现在使用的语言像是 Go、Python 开发 Web 服务通常也接触不到这些东西。
Docker image for building Go binaries with MinGW toolchain
拉取镜像
docker pull x1unix/go-mingw:latest # or "1.17" for specific Go version
在容器内编译
docker run --rm -it -v /YourPackageSrc:/go/work \
-w /go/work \
x1unix/go-mingw go build .
它的用法很简单,启动一个交互式容器,然后把本地项目挂载到容器内的目录中,直接编译就行了(默认是64位的,也可以编译32位的,不过我也没有这个需求,就没有尝试)。
解决问题
好了,那么我们用它来解决自己遇到的问题吧。
看这个输出,我感觉似乎引入了一些多余的库,而且最后生成的文件也是大了一圈:
程序启动正常:
PS:
这里用的这个 github 的项目简单易用,对于这种简单的需求,直接拉取镜像然后编译就行了。我在 Github 上面搜索的时候,还发现了一个更加强大的项目:vxbuild-cross,看描述它支持 Windows 和 MacOS,不过因为我没有 MacOS 的设备,所以我就没有尝试。不过还是把它记录在这里了,也许有人会需要这个东西呢。