跳转到主要内容

介绍

在软件开发中,重要的是要考虑您希望为其编译二进制文件的操作系统和底层处理器架构。由于在不同的操作系统/架构平台上运行二进制文件通常很慢或不可能,因此为许多不同的平台构建最终二进制文件以最大化程序的受众是一种常见的做法。但是,当您用于开发的平台与您要将程序部署到的平台不同时,这可能会很困难。例如,在过去,在 Windows 上开发程序并将其部署到 Linux 或 macOS 机器将涉及为您想要二进制文件的每个环境设置构建机器。除了会增加成本并使协作测试和分发更加困难的其他考虑因素之外,您还需要保持工具同步。

Go 通过直接在 Go 构建工具以及 Go 工具链的其余部分中构建对多个平台的支持来解决这个问题。通过使用环境变量和构建标签,您可以控制构建最终二进制文件的操作系统和架构,此外还可以组合一个工作流,该工作流可以在不更改代码库的情况下快速切换平台相关代码的包含。

在本教程中,您将组装一个示例应用程序,它将字符串连接到一个文件路径中,创建并选择性地包含平台相关的片段,并为您自己的系统上的多个操作系统和系统架构构建二进制文件,向您展示如何使用这个强大的Go 编程语言的能力。

先决条件


要遵循本文中的示例,您将需要:

  • 按照如何安装 Go 和设置本地编程环境设置的 Go 工作区。


GOOS 和 GOARCH 的可能平台


在展示如何控制构建过程以为不同平台构建二进制文件之前,让我们首先检查 Go 能够构建哪些类型的平台,以及 Go 如何使用环境变量 GOOS 和 GOARCH 引用这些平台。

Go 工具有一个命令可以打印 Go 可以构建的可能平台列表。这个列表会随着每个新的 Go 版本而改变,所以这里讨论的组合在另一个版本的 Go 上可能不一样。在编写本教程时,当前的 Go 版本是 1.13。

要查找此可能平台列表,请运行以下命令:

go tool dist list


您将收到类似于以下内容的输出:

Output
aix/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 操作系统。 /之后的第二部分是架构。和以前一样,这些都是环境变量的可能值: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 也可以覆盖不太主流的平台,比如代表 Google 的 Native Client 的 nacl。

当您运行 go build 之类的命令时,Go 使用当前平台的 GOOS 和 GOARCH 来确定如何构建二进制文件。要找出您的平台是什么组合,您可以使用 go env 命令并将 GOOS 和 GOARCH 作为参数传递:

go env GOOS GOARCH


在测试这个例子时,我们在一台 AMD64 架构的机器上在 macOS 上运行了这个命令,所以我们将收到以下输出:

Output
darwin
amd64


这里命令的输出告诉我们我们的系统有 GOOS=darwin 和 GOARCH=amd64。

您现在知道 Go 中的 GOOS 和 GOARCH 是什么,以及它们可能的值。接下来,您将编写一个程序以用作示例,说明如何使用这些环境变量和构建标记来构建其他平台的二进制文件。

使用 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 上,您将看到由 \ 分隔的字符串:




Output
a\b\c


在 macOS 和 Linux 等 Unix 系统上,您将收到以下信息:

Output
a/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 返回命令行。

接下来,打开在 Windows 上使用时定义 filepath.Join() 行为的文件:

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 的标准库如何实现特定于平台的代码,您可以在自己的应用程序中使用构建标签来执行此操作。为此,您将编写自己的 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 函数采用多个部分并使用 strings 包中的 strings.Join() 方法将它们连接在一起,以使用 PathSeparator 将这些部分连接在一起。

您还没有定义 PathSeparator,所以现在在另一个文件中定义。保存并退出 main.go,打开您喜欢的编辑器,并创建一个名为 path.go 的新文件:

nano path.go


定义 PathSeparator 并将其设置为等于 Unix 文件路径分隔符 /:

src/app/path.go

package main

const PathSeparator = "/"

编译并运行应用程序:

go build
./app


您将收到以下输出:

Output
a/b/c


这成功运行以获取 Unix 样式的文件路径。但这还不是我们想要的:输出总是 a/b/c,不管它运行在什么平台上。要添加创建 Windows 样式文件路径的功能,您需要添加 Windows 版本的 PathSeparator 并告诉 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。

现在您已经确保当 GOOS 为 Windows 时 path.go 不会运行,添加一个新文件 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 系统,请在运行 go build 命令时通过将 GOOS 环境变量设置为 windows 来构建 app 的 windows 二进制文件:

GOOS=windows go build


现在列出当前目录中的文件:

ls


列出目录的输出显示项目目录中现在有一个 app.exe Windows 可执行文件:

Output
app  app.exe  main.go  path.go  windows.go


使用 file 命令,您可以获得有关此文件的更多信息,并确认其构建:

file app.exe


您将收到:

Output
app.exe: PE32+ executable (console) x86-64 (stripped to external PDB), for MS Windows


您还可以在构建时设置一个或两个环境变量。运行以下命令:

GOOS=linux GOARCH=ppc64 go build


您的应用程序可执行文件现在将替换为不同架构的文件。在这个二进制文件上运行 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 环境变量为当前平台之外的平台生成二进制文件。这是一项宝贵的技能,因为有一个持续集成过程是一种常见的做法,该过程会自动运行这些环境变量以构建适用于所有平台的二进制文件。

 

文章链接