简介
Go 编程语言配备了丰富的工具链,使得获取包和构建可执行文件变得非常容易。Go 最强大的特性之一是能够为任何 Go 支持的外部平台交叉构建可执行文件。这使得测试和包分发变得更加容易,因为您不需要访问特定平台就能为其分发您的包。
在本教程中,您将使用 Go 的工具来从版本控制获取包并自动安装其可执行文件。然后,您将手动构建和安装可执行文件,以便熟悉该过程。接着,您将为不同架构构建一个可执行文件,并自动化构建过程以创建多个平台的可执行文件。完成后,您将知道如何为 Windows 和 macOS 构建可执行文件,以及其他您想要支持的平台。
先决条件
要完成本教程,您需要:
- 通过遵循 Ubuntu 16.04 初始服务器设置指南设置好的一个 Ubuntu 16.04 服务器,包括一个具有 sudo 非根用户和防火墙。
- 已安装 Go,如《如何在 Ubuntu 16.04 上安装 Go 1.6》中所述。
步骤 1 —— 从版本控制安装 Go 程序
在我们可以从 Go 包创建可执行文件之前,我们必须获取其源代码。go get
工具可以从 GitHub 等版本控制系统中获取包。在幕后,go get
将包克隆到 $GOPATH/src/
目录的子目录中。然后,如果适用,它会通过构建其可执行文件并将其放置在 $GOPATH/bin
目录中来安装包。如果您按照先决条件教程中的说明配置了 Go,那么 $GOPATH/bin
目录将包含在您的 $PATH
环境变量中,这可以确保您可以从系统的任何位置使用已安装的包。
go get
命令的语法是 go get package-import-path
。package-import-path
是一个唯一标识包的字符串。它通常是包在远程存储库(如 GitHub)中的位置,或者是您机器上 $GOPATH/src/
目录中的一个目录。
通常会使用 go get
与 -u
标志一起使用,该标志指示 go get
获取包及其依赖项,或者如果它们已经存在于机器上,则更新这些依赖项。
在本教程中,我们将安装一个用 Go 编写的 Web 服务器 Caddy。根据 Caddy 的说明,我们将使用 github.com/mholt/caddy/caddy
作为包导入路径。使用 go get
来获取并安装 Caddy:
go get -u github.com/mholt/caddy/caddy
该命令将花费一些时间来完成,但在获取包并安装它时,您不会看到任何进度。实际上,没有任何输出表明命令执行成功。
当命令完成时,您将在 $GOPATH/src/github.com/mholt/caddy
找到 Caddy 的源代码。此外,由于 Caddy 有一个可执行文件,它会自动创建并存储在 $GOPATH/bin
目录中。通过使用 which
命令来打印可执行文件的位置来验证这一点:
which caddy
您将看到以下输出:
/home/sammy/work/bin/caddy
让我们更详细地了解安装 Go 包的过程,从已经获取的包中创建可执行文件开始。
步骤 2 —— 构建可执行文件
go get
命令以单个命令下载了源代码并为我们安装了 Caddy 的可执行文件。但您可能希望重新构建可执行文件,或者从您自己的代码构建可执行文件。go build
命令用于构建可执行文件。
尽管我们已经安装了 Caddy,让我们再次手动构建它,以便熟悉该过程。执行 go build
并指定包导入路径:
go build github.com/mholt/caddy/caddy
与之前一样,没有输出表示成功操作。可执行文件将在当前目录中生成,与包含包的目录同名。在本例中,可执行文件将被命名为 caddy
。
如果您位于包目录中,可以省略包的路径,直接运行 go build
。
要为可执行文件指定不同的名称或位置,请使用 -o
标志。让我们构建一个名为 caddy-server
的可执行文件,并将其放置在当前工作目录中的 build
目录中:
go build -o build/caddy-server github.com/mholt/caddy/caddy
该命令将创建可执行文件,并且如果该目录不存在,则会创建 ./build
目录。
现在让我们来看一下安装可执行文件的过程。
步骤 3 — 安装可执行文件
构建可执行文件会在当前目录或指定目录中创建可执行文件。安装可执行文件是指创建可执行文件并将其存储在 $GOPATH/bin
中的过程。go install
命令的工作方式与 go build
相似,但 go install
会为您处理好将输出文件放在正确位置的工作。
要安装可执行文件,使用 go install
,后面跟上包导入路径。再次使用 Caddy 来尝试这一过程:
go install github.com/mholt/caddy/caddy
与 go build
一样,如果命令成功,您将看不到任何输出。与之前一样,可执行文件的名称与包含该包的目录名称相同。但是这次,可执行文件存储在 $GOPATH/bin
中。如果 $GOPATH/bin
是您的 $PATH
环境变量的一部分,那么该可执行文件将可以在操作系统的任何位置使用。您可以使用 which
命令验证其位置:
which caddy
您将看到以下输出:
[secondary_label Output of which]
/home/sammy/work/bin/caddy
现在您已经了解了 go get
、go build
和 go install
的工作原理以及它们之间的关系,让我们来探索 Go 中最受欢迎的功能之一:为其他目标平台创建可执行文件。
步骤 4 — 为不同架构构建可执行文件
go build
命令允许您为任何 Go 支持的目标平台在您的平台上构建可执行文件。这意味着您可以在不构建目标平台上的可执行文件的情况下测试、发布和分发您的应用程序。
交叉编译是通过设置指定目标操作系统和架构的必要环境变量来实现的。我们使用变量 GOOS
来指定目标操作系统,GOARCH
来指定目标架构。构建可执行文件的命令如下:
env GOOS=target-OS GOARCH=target-architecture go build package-import-path
env
命令在修改后的环境中运行程序。这使您可以仅对当前命令执行使用环境变量。这些变量在命令执行后被取消或重置。
下表显示了您可以使用的 GOOS
和 GOARCH
的可能组合:
GOOS - 目标操作系统 |
GOARCH - 目标平台 |
---|---|
android |
arm |
darwin |
386 |
darwin |
amd64 |
darwin |
arm |
darwin |
arm64 |
dragonfly |
amd64 |
freebsd |
386 |
freebsd |
amd64 |
freebsd |
arm |
linux |
386 |
linux |
amd64 |
linux |
arm |
linux |
arm64 |
linux |
ppc64 |
linux |
ppc64le |
linux |
mips |
linux |
mipsle |
linux |
mips64 |
linux |
mips64le |
netbsd |
386 |
netbsd |
amd64 |
netbsd |
arm |
openbsd |
386 |
openbsd |
amd64 |
openbsd |
arm |
plan9 |
386 |
plan9 |
amd64 |
solaris |
amd64 |
windows |
386 |
windows |
amd64 |
使用表中的值,我们可以这样为 Windows 64 位构建 Caddy:
env GOOS=windows GOARCH=amd64 go build github.com/mholt/caddy/caddy
再次,如果操作成功,将不会有任何输出。可执行文件将在当前目录中创建,使用包名称作为其名称。但是,由于我们为 Windows 构建了此可执行文件,因此名称以后缀 .exe
结尾。
您应该在当前目录中有 caddy.exe
文件,您可以使用 ls
命令进行验证。
ls caddy.exe
您将在输出中看到 caddy.exe
文件列出:
caddy.exe
让我们来看一下如何编写脚本来简化发布多个目标环境的软件。
步骤 5 —— 创建一个脚本来自动进行交叉编译
为多个平台创建可执行文件的过程可能有点繁琐,但我们可以创建一个脚本来简化这个过程。
该脚本将以包导入路径作为参数,遍历预定义的操作系统和平台对列表,并为每个对生成一个可执行文件,将输出放在当前目录中。每个可执行文件的命名方式为包名称,后跟目标平台和架构,形式为 package-OS-architecture
。这将是一个通用脚本,您可以在任何项目中使用。
切换到您的主目录并在文本编辑器中创建一个名为 go-executable-build.bash
的新文件:
cd ~
nano go-executable-build.bash
我们将以 shebang 行开始我们的脚本。该行定义了在以可执行文件形式运行时将解析此脚本的解释器。添加以下行以指定 bash
应执行此脚本:
#!/usr/bin/env bash
我们希望将包导入路径作为命令行参数。为此,我们将使用 $n
变量,其中 n
是非负数。变量 $0
包含您执行的脚本的名称,而 $1
及更大的值将包含用户提供的参数。添加以下行到脚本中,它将从命令行中获取第一个参数并将其存储在名为 package
的变量中:
...
package=$1
接下来,确保用户提供了这个值。如果未提供该值,则使用一条消息解释如何使用该脚本退出脚本:
...
if [[ -z "$package" ]]; then
echo "usage: $0 <package-name>"
exit 1
fi
此 if
语句检查 $package
变量的值。如果它未设置,我们使用 echo
打印正确的用法,然后使用 exit
终止脚本。exit
接受一个返回值作为参数,对于成功执行应为 0
,对于不成功执行应为任何非零值。这里我们使用 1
,因为脚本执行不成功。
接下来,我们要从路径中提取包名称。包导入路径由 /
字符分隔,包名称位于路径的末尾。首先,我们将使用 /
作为分隔符,将包导入路径拆分为数组:
package_split=(${package//\// })
包名称应该是这个新的 $package_split
数组的最后一个元素。在 Bash 中,您可以使用负数组索引来访问数组的末尾而不是开头。添加以下行以从数组中获取包名称并将其存储在名为 package_name
的变量中:
...
package_name=${package_split[-1]}
现在,您需要决定要为哪些平台和架构构建可执行文件。在本教程中,我们将为 Windows 64 位、Windows 32 位和 64 位 macOS 构建可执行文件。我们将这些目标放在一个格式为 OS/Platform
的数组中,因此我们可以使用相同的方法将每个对拆分为 GOOS
和 GOARCH
变量。将这些平台添加到脚本中:
...
platforms=("windows/amd64" "windows/386" "darwin/amd64")
接下来,我们将遍历平台数组,将每个平台条目拆分为 GOOS
和 GOARCH
环境变量的值,并使用这些值构建可执行文件。我们可以使用以下 for
循环来实现:
...
for platform in "${platforms[@]}"
do
...
done
在每次迭代中,platform
变量将包含来自 platforms
数组的一个条目。我们需要将 platform
拆分为两个变量 - GOOS
和 GOARCH
。在 for
循环中添加以下行:
for platform in "${platforms[@]}"
do
platform_split=(${platform//\// })
GOOS=${platform_split[0]}
GOARCH=${platform_split[1]}
done
接下来,我们将通过将包名称与操作系统和架构组合来生成可执行文件的名称。当我们为 Windows 构建时,还需要向文件名添加 .exe
后缀。在 for
循环中添加以下代码:
for platform in "${platforms[@]}"
do
platform_split=(${platform//\// })
GOOS=${platform_split[0]}
GOARCH=${platform_split[1]}
output_name=$package_name'-'$GOOS'-'$GOARCH
if [ $GOOS = "windows" ]; then
output_name+='.exe'
fi
done
设置好变量后,我们使用 go build
创建可执行文件。在 done
关键字上方将以下行添加到 for
循环的主体中:
...
if [ $GOOS = "windows" ]; then
output_name+='.exe'
fi
env GOOS=$GOOS GOARCH=$GOARCH go build -o $output_name $package
done
最后,我们应该检查是否有错误构建可执行文件。例如,如果我们尝试构建我们没有源代码的包,可能会遇到错误。我们可以检查 go build
命令的返回代码是否为非零值。变量 $?
包含上一个命令执行的返回代码。如果 go build
返回除 0
以外的任何值,就表示出现了问题,我们将希望退出脚本。在 for
循环中,将以下代码添加到 go build
命令之后,done
关键字之前。
...
env GOOS=$GOOS GOARCH=$GOARCH go build -o $output_name $package
if [ $? -ne 0 ]; then
echo 'An error has occurred! Aborting the script execution...'
exit 1
fi
现在,我们有一个可以从我们的 Go 包构建多个可执行文件的脚本。以下是完成的脚本:
#!/usr/bin/env bash
package=$1
if [[ -z "$package" ]]; then
echo "usage: $0 <package-name>"
exit 1
fi
package_split=(${package//\// })
package_name=${package_split[-1]}
platforms=("windows/amd64" "windows/386" "darwin/amd64")
for platform in "${platforms[@]}"
do
platform_split=(${platform//\// })
GOOS=${platform_split[0]}
GOARCH=${platform_split[1]}
output_name=$package_name'-'$GOOS'-'$GOARCH
if [ $GOOS = "windows" ]; then
output_name+='.exe'
fi
env GOOS=$GOOS GOARCH=$GOARCH go build -o $output_name $package
if [ $? -ne 0 ]; then
echo 'An error has occurred! Aborting the script execution...'
exit 1
fi
done
验证您的文件是否与上述代码匹配。然后保存文件并退出编辑器。
在使用脚本之前,我们必须使用 chmod
命令使其可执行:
chmod +x go-executable-build.bash
最后,通过为 Caddy 构建可执行文件来测试脚本:
./go-executable-build.bash github.com/mholt/caddy/caddy
如果一切顺利,您应该在当前目录中有可执行文件。没有输出表示脚本执行成功。您可以使用 ls
命令验证是否创建了可执行文件:
ls caddy*
您应该看到所有三个版本:
[secondary_label Example ls output]
caddy-darwin-amd64 caddy-windows-386.exe caddy-windows-amd64.exe
要更改目标平台,只需更改脚本中的 platforms
变量。
结论
在本教程中,您学习了如何使用 Go 的工具从版本控制系统获取软件包,以及为不同平台构建和交叉编译可执行文件。
您还创建了一个可以用于为多个平台交叉编译单个软件包的脚本。
为了确保您的应用程序正常工作,您可以查看测试和持续集成,如 Travis-CI 和 AppVeyor 用于在 Windows 上进行测试。
如果您对 Caddy 及其使用感兴趣,请查看《如何在 Ubuntu 16.04 上使用 Caddy 托管网站》。