在软件开发中,重要的是要考虑要为其编译二进制文件的操作system和底层处理器architecture。由于在不同的操作系统/体系结构平台上运行二进制文件通常很慢或不可能,因此为许多不同的平台构建最终的二进制文件是一种常见的做法,以最大限度地扩大程序的受众。但是,当您用于开发的平台与您要将程序部署到的平台不同时,这可能会很困难。例如,在过去,在Windows上开发程序并将其部署到Linux或MacOS计算机上需要为您想要的二进制文件所在的每个环境设置构建机器。除了会增加成本并使协作测试和分发更加困难的其他考虑因素外,您还需要保持您的工具同步。
Go通过将对多平台的支持直接构建到Go Build
工具以及Go工具链的其余部分来解决这个问题。通过使用环境variables和Build tags,],您可以控制您的最终二进制文件是针对哪个操作系统和体系结构构建的,此外,您还可以组合一个工作流程,在不更改代码库的情况下快速切换平台相关代码的包含。
在本教程中,您将组合一个示例应用程序,该应用程序将strings连接到一个文件路径中,创建并选择性地包含依赖于平台的片段,并在您自己的系统上为多个操作系统和系统架构构建二进制文件,向您展示如何使用Go编程语言的这种强大功能。
前提条件
要遵循本文中的示例,您将需要:
- 按照如何安装Go并设置本地编程环境设置的Go工作区。
GOOS
和GOARCH
可能的平台
在展示如何控制构建过程来为不同的平台构建二进制文件之前,我们先来看看Go可以为哪种平台构建,以及Go如何使用环境变量GOOS
和GOARCH
引用这些平台。
Go工具有一个命令,可以打印Go可以在其上构建的可能平台的列表。这个列表可能会随着每个新的围棋版本而改变,所以这里讨论的组合在另一个版本的围棋上可能不同。在撰写本教程时,当前的Go版本是1.13
.
要查找此可能的平台列表,请运行以下命令:
1go tool dist list
您将收到类似以下内容的输出:
1[secondary_label Output]
2aix/ppc64 freebsd/amd64 linux/mipsle openbsd/386
3android/386 freebsd/arm linux/ppc64 openbsd/amd64
4android/amd64 illumos/amd64 linux/ppc64le openbsd/arm
5android/arm js/wasm linux/s390x openbsd/arm64
6android/arm64 linux/386 nacl/386 plan9/386
7darwin/386 linux/amd64 nacl/amd64p32 plan9/amd64
8darwin/amd64 linux/arm nacl/arm plan9/arm
9darwin/arm linux/arm64 netbsd/386 solaris/amd64
10darwin/arm64 linux/mips netbsd/amd64 windows/386
11dragonfly/amd64 linux/mips64 netbsd/arm windows/amd64
12freebsd/386 linux/mips64le netbsd/arm64 windows/arm
此输出是一组由/
分隔的键值对。组合的第一部分,在/
之前,是操作系统。在Go中,这些操作系统是环境变量GOOS
的可能值,发音为goose
,代表 Go Operating System 。第二部分,在/
之后,是架构。和前面一样,这些都是环境变量的可能值:GOOGLE
。它的发音是gore-ch
,代表** Go Architecture** 。
让我们以linux/386
为例,分解其中一种组合,以了解它的含义和工作原理。键-值对以GOOS
开头,在本例中为linux
,指的是linux OS.这里的GOARCH
应该是`386‘,它代表英特尔80386 microprocessor.
有许多平台可以使用go Build
命令,但大多数情况下,您最终会使用linux
、windows
或darwin
作为GOOS
的值。它们涵盖了三大操作系统平台:LINUX,Windows,)和MacOS,),它基于达尔文操作system,因此被称为Darwin‘。然而,Go也可以覆盖不太主流的平台,比如代表谷歌原生Client](https://developer.chrome.com/native-client).的
nacl`
当您运行go Build
这样的命令时,Go会使用当前平台的GOOS
和GOARCH
来确定如何构建二进制文件。要了解您的平台是什么组合,可以使用go env
命令,并传入GOOS
和GOARCH
作为参数:
1go env GOOS GOARCH
在测试此示例时,我们在安装了AMD64 architecture,]的计算机上的MacOS上运行了此命令,因此我们将收到以下输出:
1[secondary_label Output]
2darwin
3amd64
这里,命令的输出告诉我们,我们的系统有GOOS=darwin
和GOARCH=amd64
。
您现在知道了围棋中的GOOS‘和
GOARCH`是什么,以及它们可能的值。接下来,您将编写一个程序,作为如何使用这些环境变量和构建标记为其他平台构建二进制文件的示例。
用filepath.Join()
编写平台相关程序
在开始为其他平台构建二进制文件之前,让我们先构建一个示例程序。Go标准库的path/filepath
包中的`Join‘函数就是一个很好的例子。此函数接受多个字符串,并返回一个用正确的文件路径分隔符连接在一起的字符串。
这是一个很好的示例程序,因为程序的操作取决于它运行在哪个操作系统上。在Windows上,路径分隔符是反斜杠\
,而基于Unix的系统使用正斜杠/
。
让我们首先构建一个使用filepath.Join()
的应用程序,然后编写您自己的Join()
函数实现,该实现将代码定制为特定于平台的二进制文件。
首先,在您的src
目录中创建一个与您的应用程序同名的文件夹:
1mkdir app
移至该目录:
1cd app
接下来,在您选择的文本编辑器中创建一个名为main.go
的新文件。在本教程中,我们将使用Nano:
1nano main.go
打开文件后,添加以下代码:
1[label src/app/main.go]
2package main
3
4import (
5 "fmt"
6 "path/filepath"
7)
8
9func main() {
10 s := filepath.Join("a", "b", "c")
11 fmt.Println(s)
12}
该文件中的[strings](https://andsky.com/tech/tutorials/an-introduction-to-working-with-strings-in-go)()
函数使用filepath.Join()
将三个main连接在一起,并使用正确的、与平台相关的路径分隔符。
保存并退出文件,然后运行程序:
1go run main.go
运行此程序时,您将收到不同的输出,具体取决于您使用的平台。在Windows上,您将看到以\
分隔的字符串:
1[secondary_label Output]
2a\b\c
在MacOS和Linux等Unix系统上,您将收到以下内容:
1[secondary_label Output]
2a/b/c
这表明,由于这些操作系统上使用的文件系统协议不同,程序将不得不为不同的平台构建不同的代码。但由于它已经根据操作系统使用了不同的文件分隔符,所以我们知道filepath.Join()
已经解释了平台的差异。这是因为Go工具链会自动检测您的机器的GOOS‘和
GOARCH`,并使用这些信息来使用正确的Build tagsAnd文件分隔符]代码片段。
让我们考虑一下filepath.Join()
函数的分隔符是从哪里来的。运行以下命令以检查GO标准库中的相关代码片段:
1less /usr/local/go/src/os/path_unix.go
这将显示Path_unix.go
的内容。查找文件的以下部分:
1[label /usr/local/go/os/path_unix.go]
2. . .
3// +build aix darwin dragonfly freebsd js,wasm linux nacl netbsd openbsd solaris
4
5package os
6
7const (
8 PathSeparator = '/' // OS-specific path separator
9 PathListSeparator = ':' // OS-specific path list separator
10)
11. . .
本节定义了GO支持的所有类Unix系统的PathSeparator‘。请注意顶部的所有构建标记,它们都是与Unix相关联的每个可能的Unix
GOS‘平台。当`GOS‘匹配这些术语时,您的程序将生成Unix样式的文件路径分隔符。
按q
返回命令行。
接下来,打开定义filepath.Join()
在Windows上使用时的行为的文件:
1less /usr/local/go/src/os/path_windows.go
您将看到以下内容:
1[label /usr/local/go/src/os/path_windows.go]
2. . .
3package os
4
5const (
6 PathSeparator = '\\' // OS-specific path separator
7 PathListSeparator = ';' // OS-specific path list separator
8)
9. . .
虽然这里PathSeparator
的值是\\
,但代码将呈现Windows文件路径所需的单反斜杠(\
),因为第一个反斜杠只需要作为转义字符。
请注意,与Unix文件不同,顶部没有构建标记。这是因为GOOS
和GOARCH
也可以通过添加下划线(_
)和环境变量值作为文件名的后缀传递给go build
,我们将在[使用GOOS和GOARCH文件名后缀 ]一节(https://andsky.com/tech/tutorials/building-go-applications-for-different-operating-systems-and-architectures# using-goos-and-goarch-filename-suffixes)中详细介绍。在这里,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
文件:
1nano main.go
使用您自己的Join()
函数,将main.go
的内容替换为以下内容:
1[label src/app/main.go]
2package main
3
4import (
5 "fmt"
6 "strings"
7)
8
9func Join(parts ...string) string {
10 return strings.Join(parts, PathSeparator)
11}
12
13func main() {
14 s := Join("a", "b", "c")
15 fmt.Println(s)
16}
Join
函数获取多个Part
,并使用[
strings.Join()](https://godoc.org/strings.Join)
package中的方法将它们连接在一起,然后使用Path Separator
将Parts
连接在一起。
您还没有定义PathSeparator
,所以现在在另一个文件中定义。保存并退出main.go
,打开您喜欢的编辑器,创建一个名为path.go
的新文件:
1nano path.go
定义PathSeparator
,并设置为等于Unix文件路径分隔符/
:
1[label src/app/path.go]
2package main
3
4const PathSeparator = "/"
编译并运行应用程序:
1go build
2./app
您将收到以下输出:
1[secondary_label Output]
2a/b/c
这将成功运行以获得Unix样式的文件路径。但这还不是我们想要的:输出始终是a/b/c
,无论它运行在什么平台上。要添加创建Windows风格的文件路径的功能,您需要添加一个Windows版本的PathSeparator
,并告诉go Build
命令使用哪个版本。在下一节中,您将使用Build tags来完成此操作。
使用GOOS
或GOARCH
构建标签
为了适应Windows平台,您现在将为path.go
创建一个替代文件,并使用构建标签来确保代码片段仅在GOOS
和GOARCH
是合适的平台时运行。
但首先,在path.go
中添加一个构建标签,告诉它为除Windows之外的所有内容进行构建。打开文件:
1nano path.go
将以下突出显示的构建标记添加到文件中:
1[label src/app/path.go]
2// +build !windows
3
4package main
5
6const PathSeparator = "/"
Go Build标记允许倒置,这意味着您可以指示Go为除Windows以外的任何平台构建此文件。要反转构建标签,请在标签前放置一个!
。
保存并退出该文件。
现在,如果您要在Windows上运行此程序,您将收到以下错误:
1[secondary_label Output]
2./main.go:9:29: undefined: PathSeparator
在这种情况下,Go语言将无法包含path.go
来定义变量PathSeparator
。
现在您已经确保了GOOS
为Windows时不会运行path.go
,添加一个新文件windows.go
:
1nano windows.go
在windows.go
中,定义WindowsPathSeparator
,以及一个Build标签,让go Build
命令知道它是Windows实现:
1[label src/app/windows.go]
2// +build windows
3
4package main
5
6const 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系统,请在运行go Build
命令时,将GOOS
环境变量设置为windows
,构建app
的windows
二进制文件:
1GOOS=windows go build
现在列出当前目录中的文件:
1ls
列出目录的输出显示,项目目录中现在有一个app.exe
Windows可执行文件:
1[secondary_label Output]
2app app.exe main.go path.go windows.go
使用file
命令,您可以获取有关该文件的更多信息,以确认其版本:
1file app.exe
您将收到:
1[secondary_label Output]
2app.exe: PE32+ executable (console) x86-64 (stripped to external PDB), for MS Windows
您也可以在构建时设置一个或两个环境变量。运行以下命令:
1GOOS=linux GOARCH=ppc64 go build
您的app
可执行文件现在将被用于不同体系结构的文件替换。在此二进制文件上运行file
命令:
1file app
您将收到如下所示的输出:
1app: 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 architecture,]的Windows编译它,您可以将文件命名为filename_windows_arm64.go
。这种命名约定有助于保持代码的整洁组织。
更新您的程序以使用文件名后缀而不是构建标记。首先,重命名path.go
和windows.go
文件,以使用os
包中使用的约定:
1mv path.go path_unix.go
2mv windows.go path_windows.go
更改两个文件名后,您可以移除添加到Path_windows.go
中的构建标签:
1nano path_windows.go
删除// +build windows
,使文件看起来像这样:
1[label path_windows.go]
2package main
3
4const PathSeparator = "\\"
保存并退出文件。
因为unix
不是有效的GOOS
,所以_unix.go
后缀对Go编译器没有意义。但是,它确实传达了文件的预期目的。与os/path_unix.go
文件一样,您的path_unix.go
文件仍然需要使用构建标记,因此保持该文件不变。
通过使用文件名约定,您从源代码中删除了未成功的构建标记,并使文件系统更加干净和清晰。
结论
为不需要依赖关系的多个平台生成二进制文件的能力是Go工具链的一个强大功能。在本教程中,您通过添加构建标记和文件名后缀来标记仅针对特定体系结构编译的某些代码片段,从而使用了此功能。您创建了自己的依赖于平台的程序,然后操纵GOOS
和GOARCH
环境变量为当前平台以外的平台生成二进制文件。这是一项有价值的技能,因为有一个连续的集成过程可以自动运行这些环境变量来为所有平台构建二进制文件,这是一种常见的实践。
有关Go Build
的进一步研究,请查看我们的使用Build Tags定制Go二进制文件》教程。如果您想了解更多关于Go编程语言的一般信息,请查看完整的如何在Go series.中编码