介绍

将应用程序部署到生产环境时,使用版本信息和其他元数据构建二进制文件将通过添加标识信息来帮助跟踪随时间的生成来改进监视、日志记录和调试过程。此版本信息通常可以包括高度动态的数据,例如生成时间、计算机或用户生成二进制文件、生成二进制版本控制系统 (VCS)提交 ID 等。由于这些值不断变化,因此将这些数据直接编码到源代码中并在每次生成之前对其进行修改是繁琐且容易出错的:源文件可以四处移动,变量/常量可能在整个过程中切换文件开发,破坏构建过程。

在 Go 中解决此问题的一种方法是使用-ldflagsgo build命令在生成时将动态信息插入到二进制文件中,而无需修改源代码。在此标志ld代表链接器,即将编译的源代码的不同部分链接到最终二进制文件中的程序。ldflags,那么,代表链接器标志。之所以调用它,是因为它将一个标志传递给基础的 Go 工具链链接器cmd/link该链接允许您在生成时从命令行更改导入包的值。

在本教程中,您将使用-ldflags在生成时更改变量的值,并使用将版本信息打印到屏幕的示例应用程序将您自己的动态信息引入二进制文件。

先决条件

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

构建示例应用程序

在使用ldflags引入动态数据之前,首先需要一个应用程序将信息插入到 中。在此步骤中,您将制作此应用程序,在此阶段将仅打印静态版本控制信息。现在,让我们创建该应用程序。

src目录中,创建一个以应用程序命名的目录。本教程将使用应用程序名称app:

mkdir app

将工作目录更改为此文件夹:

cd app

接下来,使用您选择的文本编辑器,创建程序的输入点main.go:

nano main.go

现在,通过添加以下内容,使应用程序打印出版本信息:

应用/主.go

package main
import (
    "fmt"
)
var Version = "development"
func main() {
    fmt.Println("Version:\t", Version)
}

main()函数中,声明了Version变量,然后打印了字符串 Version:,后跟一个制表符\t然后是声明的变量。

此时,变量Version定义为development,这将是此应用程序的默认版本。稍后,您将此值更改为官方版本号,根据语义版本控制格式排列.

保存并退出文件。完成此操作后,生成并运行应用程序以确认它打印了正确的版本:

go build
./app

您将看到以下输出:

Output
Version:     development

现在,您有一个打印默认版本信息的应用程序,但您还没有方法在生成时传递当前版本信息。在下一步中,您将使用-ldflagsgo build来解决此问题。

ldflags与 gogo build一起使用

如前ldflags代表链接器标志,用于将标志传递到 Go 工具链中的底层链接器。这根据以下语法工作:

go build -ldflags="-flag"

在此示例中,我们将flag传递给作为go build一部分运行的底层go tool link命令。此命令在传递给ldflags的内容周围使用双引号,以避免其中的字符中断,或者命令行可能解释为不是我们想要的字符。从这里,你可以传递许多不同的link标志。在本教程中,我们将使用-X标志在链接时将信息写入变量,然后是变量的路径及其新值:

go build -ldflags="-X 'package_path.variable_name=new_value'"

在引号中,现在有-X选项和键值对,表示要更改的变量及其新值。.字符分隔包路径和变量名称,并且单引号用于避免键值对中的字符中断。

要替换示例应用程序中的Version变量,请使用最后一个命令块中的语法传递新值并生成新的二进制文件:

go build -ldflags="-X 'main.Version=v1.0.0'"

在此命令mainVersion变量的包路径,因为此变量位于main.go文件中。Version是您要写入的v1.0.0是新值。

为了使用ldflags要更改的值必须存在,并且是类型string的包级变量。此变量可以导出或取消导出。该值不能是const也不能由函数调用的结果设置其值。Version符合所有这些要求:已在main.go文件中声明为变量,当前值 (development和所需值v1.0.0都是字符串。

生成新app二进制文件后,运行应用程序:

./app

您将收到以下输出:

Output
Version:     v1.0.0

使用-ldflags您成功地将Version变量从development更改为v1.0.0.

现在,您已经修改了简单应用程序内部在生成时的string变量。使用ldflags,您可以使用命令行将版本详细信息、许可信息等嵌入到可供分发的二进制文件中。

在此示例中,您更改的变量位于main程序中,从而降低了确定路径名称的难度。但有时找到这些变量的路径会更加复杂。在下一步中,您将向子包中的变量写入值,以演示确定更复杂的包路径的最佳方式。

定位子包变量

在最后一节中,您操作了Version变量,该变量位于应用程序的顶级包中。但情况并非总是如此。通常,将这些变量放在另一个包中更为实际,因为main不是可导入的包。为了在示例应用程序中模拟这一点,您将创建一个新的子包、app/build,它将存储有关生成二进制文件的时间和发出生成命令的用户的名称的信息。

要添加新的子包,请先向名为build的项目添加新目录:

mkdir -p build

然后创建一个名为build.go的新文件以保存新变量:

nano build/build.go

在文本编辑器中,为TimeUser添加新变量:

应用/生成/生成.

package build
var Time string
var User string

Time变量将保存生成二进制文件的时间的字符串表示形式。User变量将保存生成二进制文件的用户的名称。由于这两个变量始终具有值,因此无需像Version默认值一样初始化这些变量.

保存并退出文件。

接下来,打开main.go以将这些变量添加到应用程序中:

nano main.go

main.go内部,添加以下突出显示的行:

主.go

package main
import (
    "app/build"
    "fmt"
)
var Version = "development"
func main() {
    fmt.Println("Version:\t", Version)
    fmt.Println("build.Time:\t", build.Time)
    fmt.Println("build.User:\t", build.User)
}

在这些行中,您首先导入app/build包,然后打印build.Timebuild.User打印Version的方式相同.

保存文件,然后从文本编辑器退出。

接下来,要使用ldflags定位这些变量,可以使用导入路径app/build后跟.User.Time因为您已经知道导入路径。但是,为了模拟一种更复杂的情况,即变量的路径不明显,让我们改用 Go 工具链中的nm命令。

go tool nm命令将输出给定可执行文件、对象文件或存档中涉及的符号。在这种情况下,符号是指代码中的对象,如已定义或导入的变量或函数。通过生成带有nm的符号表并使用grep搜索变量,您可以快速查找有关其路径的信息。

注意:如果包名称具有任何非ASCII字符或"或% "nm命令不会帮助您查找变量的路径,因为这是工具本身的限制。

要使用此命令,首先为应用生成二进制app:

go build

现在,app已生成,将nm工具指向它并搜索输出:

go tool nm ./app | grep app

运行nm工具将输出大量数据。因此,前面的命令使用|将输出管道到grep命令,grep 命令随后搜索标题中具有顶级app的术语。

您将收到类似于此的输出:

Output
  55d2c0 D app/build.Time
  55d2d0 D app/build.User
  4069a0 T runtime.appendIntStr
  462580 T strconv.appendEscapedRune
. . .

在这种情况下,结果集的前两行包含要查找的两个变量的路径:app/build.Timeapp/build.User.

现在您已经了解了路径,请再次生成应用程序,这一次在生成时更改VersionUserTime为此,将多个-X标志传递给-ldflags:

go build -v -ldflags="-X 'main.Version=v1.0.0' -X 'app/build.User=$(id -u -n)' -X 'app/build.Time=$(date)'"

在这里,您传递了id -u -n Bash 命令来列出当前用户,并传递了列出当前日期date命令。

生成可执行文件后,运行程序:

./app

此命令在 Unix 系统上运行时,将生成与以下内容类似的输出:

Output
Version:     v1.0.0
build.Time:  Fri Oct  4 19:49:19 UTC 2019
build.User:  sammy

现在,您有一个二进制文件,其中包含版本控制并生成信息,可在解决问题时在生产中提供重要的帮助。

结论

本教程演示了在正确应用ldflags如何成为在生成时将有价值的信息注入二进制文件的强大工具。这样,您可以控制功能标志、环境信息、版本控制信息等,而无需对源代码进行更改。通过将ldflags添加到当前生成工作流,您可以最大化 Go 自包含二进制分发格式的优势。

如果您想了解有关 Go 编程语言的更多详细信息,请查看我们完整的"如何编写 Go"系列。如果您正在寻找更多版本控制解决方案,请尝试我们的如何使用 Git参考指南。