在软件开发中,重要的是要考虑您希望为其编译二进制文件的 操作系统 和底层处理器 架构 。 由于在不同的操作系统/架构平台上运行二进制文件通常很慢或不可能,因此为许多不同平台构建最终二进制文件以最大化程序的受众是一种常见的做法。 但是,当您用于开发的平台与您要将程序部署到的平台不同时,这可能会很困难。 例如,在过去,在 Windows 上开发程序并将其部署到 Linux 或 macOS 机器将涉及为您想要二进制文件的每个环境设置构建机器。 除了会增加成本并使协作测试和分发更加困难的其他考虑因素之外,您还需要保持工具同步。
Go 通过直接在 go build
工具以及 Go 工具链的其余部分中构建对多个平台的支持来解决这个问题。 通过使用 环境变量 和 构建标签 ,您可以控制构建最终二进制文件的操作系统和架构,此外还可以组合一个可以快速切换包含平台的工作流程 -依赖代码而不更改您的代码库。
在本教程中,您将组合一个示例应用程序,将 strings 连接到一个文件路径中,创建并选择性地包含平台相关的片段,并在您自己的系统上为多个操作系统和系统架构构建二进制文件,显示您将如何使用 Go 编程语言的这一强大功能。
先决条件
要遵循本文中的示例,您将需要:
- 按照 如何安装 Go 并设置本地编程环境 设置的 Go 工作区。
GOOS
和 GOARCH
的可能平台
在展示如何控制构建过程为不同平台构建二进制文件之前,让我们首先检查 Go 能够构建哪些类型的平台,以及 Go 如何使用环境变量 GOOS
和 引用这些平台[ X254X]。
Go 工具有一个命令可以打印 Go 可以构建的可能平台列表。 这个列表会随着每个新的 Go 版本而改变,所以这里讨论的组合在另一个版本的 Go 上可能不一样。 在编写本教程时,当前的 Go 版本是 1.13。
要查找此可能平台列表,请运行以下命令:
go tool dist list
您将收到类似于以下内容的输出:
Outputaix/ppc64 freebsd/amd64 linux/mipsle openbsd/386 android/386 freebsd/arm linux/ppc64 openbsd/amd64 android/amd64 illumos/amd64 linux/ppc64le openbsd/arm android/arm js/wasm linux/s390x openbsd/arm64 android/arm64 linux/386 nacl/386 plan9/386 darwin/386 linux/amd64 nacl/amd64p32 plan9/amd64 darwin/amd64 linux/arm nacl/arm plan9/arm darwin/arm linux/arm64 netbsd/386 solaris/amd64 darwin/arm64 linux/mips netbsd/amd64 windows/386 dragonfly/amd64 linux/mips64 netbsd/arm windows/amd64 freebsd/386 linux/mips64le netbsd/arm64 windows/arm
此输出是一组由 /
分隔的键值对。 组合的第一部分,在 /
之前,是操作系统。 在 Go 中,这些操作系统是环境变量 GOOS
的可能值,发音为“goose”,代表 Go Operating System。 在 /
之后的第二部分是架构。 和以前一样,这些都是环境变量的可能值:GOARCH
。 这发音为“gore-ch”,代表 Go Architecture。
让我们以 linux/386
为例,分解其中一种组合以了解其含义及其工作原理。 键值对以 GOOS
开头,在本例中为 linux
,指的是 Linux OS。 这里的 GOARCH
将是 386
,它代表 Intel 80386 微处理器 。
go build
命令有许多可用的平台,但大多数时候您最终会使用 linux
、 windows
或 darwin
作为GOOS
的值。 这些涵盖了三大操作系统平台:Linux、Windows 和 macOS,它基于 Darwin 操作系统,因此称为 darwin
。 但是,Go 也可以覆盖 nacl
等不太主流的平台,它代表了 Google 的 Native Client。
当你运行像 go build
这样的命令时,Go 使用当前平台的 GOOS
和 GOARCH
来确定如何构建二进制文件。 要找出您的平台是什么组合,您可以使用 go env
命令并将 GOOS
和 GOARCH
作为参数传递:
go env GOOS GOARCH
在测试这个例子时,我们在 macOS 上使用 AMD64 架构 的机器上运行此命令,因此我们将收到以下输出:
Outputdarwin amd64
这里命令的输出告诉我们,我们的系统有 GOOS=darwin
和 GOARCH=amd64
。
您现在知道 GOOS
和 GOARCH
在 Go 中是什么,以及它们的可能值。 接下来,您将编写一个程序以用作示例,说明如何使用这些环境变量和构建标记来构建其他平台的二进制文件。
使用 filepath.Join()
编写平台相关程序
在开始为其他平台构建二进制文件之前,让我们构建一个示例程序。 用于此目的的一个很好的示例是 Go 标准库中 path/filepath 包中的 Join
函数。 此函数接受多个字符串并返回一个字符串,该字符串与正确的文件路径分隔符连接在一起。
这是一个很好的示例程序,因为程序的操作取决于它运行的操作系统。 在 Windows 上,路径分隔符是反斜杠 \
,而基于 Unix 的系统使用正斜杠 /
。
让我们从构建一个使用 filepath.Join()
的应用程序开始,稍后,您将编写自己的 Join()
函数实现,该函数将代码自定义为特定于平台的二进制文件。
首先,在您的 src
目录中使用您的应用程序名称创建一个文件夹:
mkdir app
进入该目录:
cd app
接下来,在您选择的文本编辑器中创建一个名为 main.go
的新文件。 对于本教程,我们将使用 Nano:
nano main.go
打开文件后,添加以下代码:
src/app/main.go
package main import ( "fmt" "path/filepath" ) func main() { s := filepath.Join("a", "b", "c") fmt.Println(s) }
此文件中的 main()
函数使用 filepath.Join()
将三个 字符串 与正确的、平台相关的路径分隔符连接在一起。
保存并退出文件,然后运行程序:
go run main.go
运行此程序时,您将收到不同的输出,具体取决于您使用的平台。 在 Windows 上,您将看到由 \
分隔的字符串:
Outputa\b\c
在 macOS 和 Linux 等 Unix 系统上,您将收到以下信息:
Outputa/b/c
这表明,由于这些操作系统上使用不同的文件系统协议,程序将不得不为不同的平台构建不同的代码。 但是由于它已经根据操作系统使用了不同的文件分隔符,我们知道 filepath.Join()
已经解释了平台的差异。 这是因为 Go 工具链会自动检测您机器的 GOOS
和 GOARCH
并使用此信息来使用具有正确 构建标签 和文件分隔符的代码片段。
让我们考虑一下 filepath.Join()
函数从哪里获取分隔符。 运行以下命令以检查 Go 标准库中的相关代码段:
less /usr/local/go/src/os/path_unix.go
这将显示 path_unix.go
的内容。 查找文件的以下部分:
/usr/local/go/os/path_unix.go
. . . // +build aix darwin dragonfly freebsd js,wasm linux nacl netbsd openbsd solaris package os const ( PathSeparator = '/' // OS-specific path separator PathListSeparator = ':' // OS-specific path list separator ) . . .
本节为 Go 支持的所有类 Unix 系统定义了 PathSeparator
。 注意顶部的所有构建标签,它们都是与 Unix 相关的可能的 Unix GOOS
平台之一。 当 GOOS
匹配这些术语时,您的程序将产生 Unix 样式的文件路径分隔符。
按 q
返回命令行。
接下来,打开定义 filepath.Join()
在 Windows 上使用时的行为的文件:
less /usr/local/go/src/os/path_windows.go
您将看到以下内容:
/usr/local/go/src/os/path_windows.go
. . . package os const ( PathSeparator = '\\' // OS-specific path separator PathListSeparator = ';' // OS-specific path list separator ) . . .
尽管此处 PathSeparator
的值是 \\
,但代码将呈现 Windows 文件路径所需的单个反斜杠 (\
),因为第一个反斜杠仅需要作为转义字符.
请注意,与 Unix 文件不同,顶部没有构建标记。 这是因为 GOOS
和 GOARCH
也可以通过添加下划线 (_
) 和环境变量值作为文件名的后缀来传递给 go build
,我们将在 使用 GOOS 和 GOARCH 文件名后缀 一节中详细介绍。 在这里,path_windows.go
的 _windows
部分使文件的行为就像文件顶部具有构建标签 // +build windows
一样。 因此,当您的程序在 Windows 上运行时,它将使用 path_windows.go
代码片段中的 PathSeparator
和 PathListSeparator
常量。
要返回命令行,请按 q
退出 less
。
在这一步中,您构建了一个程序,展示了 Go 如何将 GOOS
和 GOARCH
自动转换为构建标签。 考虑到这一点,您现在可以更新程序并编写自己的 filepath.Join()
实现,使用构建标签为 Windows 和 Unix 平台手动设置正确的 PathSeparator
。
实现特定于平台的功能
现在您知道 Go 的标准库是如何实现特定于平台的代码的,您可以在自己的 app
程序中使用构建标签来执行此操作。 为此,您将编写自己的 filepath.Join()
实现。
打开你的 main.go
文件:
nano main.go
使用您自己的名为 Join()
的函数,将 main.go
的内容替换为以下内容:
src/app/main.go
package main import ( "fmt" "strings" ) func Join(parts ...string) string { return strings.Join(parts, PathSeparator) } func main() { s := Join("a", "b", "c") fmt.Println(s) }
Join
函数接受多个 parts
并使用 strings 包 中的 strings.Join() 方法将它们连接在一起以连接 [ X169X] 一起使用 PathSeparator
。
您还没有定义 PathSeparator
,所以现在在另一个文件中定义。 保存退出main.go
,打开你喜欢的编辑器,新建一个名为path.go
的文件:
nano path.go
定义 PathSeparator
并将其设置为等于 Unix 文件路径分隔符 /
:
src/app/path.go
package main const PathSeparator = "/"
编译并运行应用程序:
go build ./app
您将收到以下输出:
Outputa/b/c
这成功运行以获取 Unix 样式的文件路径。 但这还不是我们想要的:输出总是 a/b/c
,不管它运行在什么平台上。 要添加创建 Windows 样式文件路径的功能,您需要添加 PathSeparator
的 Windows 版本并告诉 go build
命令使用哪个版本。 在下一节中,您将使用 构建标签 来完成此操作。
使用 GOOS
或 GOARCH
构建标签
考虑到 Windows 平台,您现在将为 path.go
创建一个备用文件,并使用构建标签确保代码片段仅在 GOOS
和 GOARCH
是适当的平台时运行.
但首先,向 path.go
添加一个构建标签,告诉它为除 Windows 之外的所有内容构建。 打开文件:
nano path.go
将以下突出显示的构建标记添加到文件中:
src/app/path.go
// +build !windows package main const PathSeparator = "/"
Go 构建标签允许反转,这意味着您可以指示 Go 为除 Windows 之外的任何平台构建此文件。 要反转构建标签,请在标签前放置 !
。
保存并退出文件。
现在,如果你要在 Windows 上运行这个程序,你会得到以下错误:
Output./main.go:9:29: undefined: PathSeparator
在这种情况下,Go 将无法包含 path.go
来定义变量 PathSeparator
。
现在您已经确保 path.go
在 GOOS
是 Windows 时不会运行,添加一个新文件 windows.go
:
nano windows.go
在 windows.go
中,定义 Windows PathSeparator
,以及一个构建标签,让 go build
命令知道它是 Windows 实现:
src/app/windows.go
// +build windows package main const PathSeparator = "\\"
保存文件并退出文本编辑器。 该应用程序现在可以为 Windows 编译一种方式,为所有其他平台编译另一种方式。
虽然二进制文件现在将为其平台正确构建,但您必须进行进一步的更改才能为您无权访问的平台进行编译。 为此,您将在下一步中更改本地 GOOS
和 GOARCH
环境变量。
使用本地 GOOS
和 GOARCH
环境变量
早些时候,您运行了 go env GOOS GOARCH
命令来找出您正在处理的操作系统和架构。 当您运行 go env
命令时,它会查找两个环境变量 GOOS
和 GOARCH
; 如果找到,将使用它们的值,但如果没有找到,Go 将使用当前平台的信息设置它们。 这意味着您可以更改 GOOS
或 GOARCH
以便它们不会默认为您的本地操作系统和体系结构。
go build
命令的行为方式与 go env
命令类似。 您可以设置 GOOS
或 GOARCH
环境变量以使用 go build
为不同的平台构建。
如果您不使用 Windows 系统,请在运行 [ X157X] 命令:
GOOS=windows go build
现在列出当前目录中的文件:
ls
列出目录的输出显示项目目录中现在有一个 app.exe
Windows 可执行文件:
Outputapp app.exe main.go path.go windows.go
使用 file
命令,您可以获得有关此文件的更多信息,确认其构建:
file app.exe
您将收到:
Outputapp.exe: PE32+ executable (console) x86-64 (stripped to external PDB), for MS Windows
您还可以在构建时设置一个或两个环境变量。 运行以下命令:
GOOS=linux GOARCH=ppc64 go build
您的 app
可执行文件现在将被替换为不同架构的文件。 在此二进制文件上运行 file
命令:
file app
您将收到如下输出:
app: ELF 64-bit MSB executable, 64-bit PowerPC or cisco 7500, version 1 (SYSV), statically linked, not stripped
通过设置本地 GOOS
和 GOARCH
环境变量,您现在可以为任何 Go 兼容平台构建二进制文件,而无需复杂的配置或设置。 接下来,您将使用文件名约定来保持文件井井有条,并自动为特定平台构建,而无需构建标签。
使用 GOOS
和 GOARCH
文件名后缀
正如你之前看到的,Go 标准库大量使用构建标签来简化代码,将不同的平台实现分离到不同的文件中。 当您打开 os/path_unix.go
文件时,有一个构建标签列出了所有可能的组合,这些组合被认为是类 Unix 平台。 然而,os/path_windows.go
文件不包含构建标签,因为文件名上的后缀足以告诉 Go 该文件适用于哪个平台。
让我们看看这个特性的语法。 命名 .go
文件时,可以按顺序将 GOOS
和 GOARCH
作为后缀添加到文件名中,用下划线分隔值 (_
) . 如果你有一个名为 filename.go
的 Go 文件,你可以通过将文件名更改为 filename_GOOS_GOARCH.go
来指定操作系统和架构。 例如,如果您希望为具有 64 位 ARM 架构 的 Windows 编译它,您可以将文件名设为 filename_windows_arm64.go
。 这种命名约定有助于保持代码井井有条。
更新您的程序以使用文件名后缀而不是构建标签。 首先,重命名 path.go
和 windows.go
文件以使用 os
包中使用的约定:
mv path.go path_unix.go mv windows.go path_windows.go
更改两个文件名后,您可以删除添加到 path_windows.go
的构建标记:
nano path_windows.go
删除 // +build windows
使您的文件如下所示:
path_windows.go
package main const PathSeparator = "\\"
保存并退出文件。
因为 unix
不是有效的 GOOS
,所以 _unix.go
后缀对 Go 编译器没有意义。 但是,它确实传达了文件的预期目的。 与 os/path_unix.go
文件一样,您的 path_unix.go
文件仍然需要使用构建标签,因此请保持该文件不变。
通过使用文件名约定,您从源代码中删除了不需要的构建标记,并使文件系统更清晰。
结论
为不需要依赖项的多个平台生成二进制文件的能力是 Go 工具链的一个强大功能。 在本教程中,您通过添加构建标记和文件名后缀来标记某些代码片段以仅针对某些体系结构进行编译来使用此功能。 您创建了自己的平台相关程序,然后操纵 GOOS
和 GOARCH
环境变量为当前平台之外的平台生成二进制文件。 这是一项宝贵的技能,因为有一个持续集成过程是一种常见的做法,该过程会自动运行这些环境变量以构建适用于所有平台的二进制文件。
要进一步研究 go build
,请查看我们的 使用构建标签自定义 Go 二进制文件 教程。 如果您想全面了解 Go 编程语言的更多信息,请查看整个 Go 编程语言系列 。