使用ldflags为Go应用程序设置版本信息
介绍
在将应用程序部署到生产环境中时,使用版本信息和其他元数据构建二进制文件将通过添加标识信息来帮助跟踪您的构建,从而改进您的监控、日志记录和调试过程。 此版本信息通常可以包括高度动态的数据,例如构建时间、构建二进制文件的机器或用户、构建它的 版本控制系统 (VCS) 提交 ID 等等。 因为这些值是不断变化的,所以将这些数据直接编码到源代码中并在每次新构建之前对其进行修改是乏味的并且容易出错:源文件可以四处移动,变量/常量可以在整个开发过程中切换文件,打破构建过程。
在 Go 中解决此问题的一种方法是使用 -ldflags
和 go build
命令在构建时将动态信息插入二进制文件,而无需修改源代码。 在此标志中,ld
代表 linker,该程序将编译后的源代码的不同部分链接到最终二进制文件中。 因此,ldflags
代表 链接器标志 。 之所以这样称呼它,是因为它将一个标志传递给底层的 Go 工具链链接器 cmd/link,它允许您在构建时从命令行更改导入包的值。
在本教程中,您将使用 -ldflags
在构建时更改变量的值,并将您自己的动态信息引入二进制文件,使用将版本信息打印到屏幕的示例应用程序。
先决条件
要遵循本文中的示例,您将需要:
- 按照 如何安装 Go 并设置本地编程环境 设置的 Go 工作区。
构建您的示例应用程序
在您可以使用 ldflags
引入动态数据之前,您首先需要一个应用程序来插入信息。 在此步骤中,您将制作此应用程序,该应用程序在此阶段仅打印静态版本信息。 现在让我们创建该应用程序。
在您的 src
目录中,创建一个以您的应用程序命名的目录。 本教程将使用应用程序名称 app
:
mkdir app
将您的工作目录更改为此文件夹:
cd app
接下来,使用您选择的文本编辑器,创建程序的入口点 main.go
:
nano main.go
现在,通过添加以下内容使您的应用程序打印出版本信息:
应用程序/main.go
package main import ( "fmt" ) var Version = "development" func main() { fmt.Println("Version:\t", Version) }
在 main()
函数内部,您声明了 Version
变量,然后打印了 string Version:
,后跟一个制表符 [X148X ],然后是声明的变量。
此时,变量 Version
被定义为 development
,这将是这个应用程序的默认版本。 稍后,您将将此值更改为正式版本号,按照语义版本控制格式排列。
保存并退出文件。 完成后,构建并运行应用程序以确认它打印了正确的版本:
go build ./app
您将看到以下输出:
OutputVersion: development
您现在有一个打印默认版本信息的应用程序,但是您还没有办法在构建时传递当前版本信息。 在下一步中,您将使用 -ldflags
和 go build
来解决这个问题。
将 ldflags
与 go build
一起使用
如前所述,ldflags
代表 linker flags,用于将标志传递给 Go 工具链中的底层链接器。 这根据以下语法工作:
go build -ldflags="-flag"
在此示例中,我们将 flag
传递给作为 go build
的一部分运行的底层 go tool link
命令。 此命令在传递给 ldflags
的内容周围使用双引号,以避免破坏其中的字符,或者命令行可能将其解释为我们想要的内容之外的字符。 从这里,您可以传入 许多不同的链接标志 。 出于本教程的目的,我们将使用 -X
标志在链接时将信息写入变量,然后是变量的 package 路径及其新值:
go build -ldflags="-X 'package_path.variable_name=new_value'"
在引号内,现在有 -X
选项和 键值对 表示要更改的变量及其新值。 .
字符分隔包路径和变量名,单引号用于避免键值对中的字符中断。
要替换示例应用程序中的 Version
变量,请使用最后一个命令块中的语法传入一个新值并构建新的二进制文件:
go build -ldflags="-X 'main.Version=v1.0.0'"
在此命令中,main
是 Version
变量的包路径,因为该变量在 main.go
文件中。 Version
是您正在写入的变量,v1.0.0
是新值。
为了使用 ldflags
,您要更改的值必须存在并且是 string
类型的包级变量。 此变量可以导出或不导出。 该值不能是 const
或由函数调用的结果设置其值。 幸运的是,Version
符合所有这些要求:它已经在 main.go
文件中声明为变量,当前值 (development
) 和期望值 (v1.0.0
) 都是字符串。
构建新的 app
二进制文件后,运行应用程序:
./app
您将收到以下输出:
OutputVersion: 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
在您的文本编辑器中,为 Time
和 User
添加新变量:
应用程序/构建/build.go
package build var Time string var User string
Time
变量将保存二进制文件构建时间的字符串表示形式。 User
变量将保存构建二进制文件的用户的名称。 因为这两个变量总是有值的,所以你不需要像为 Version
那样用默认值初始化这些变量。
保存并退出文件。
接下来,打开 main.go
将这些变量添加到您的应用程序中:
nano main.go
在 main.go
内部,添加以下突出显示的行:
main.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
包,然后以与打印 Version
相同的方式打印 build.Time
和 build.User
。
保存文件,然后退出文本编辑器。
接下来,要使用 ldflags
来定位这些变量,您可以使用导入路径 app/build
后跟 .User
或 .Time
,因为您已经知道导入路径。 但是,为了模拟变量路径不明显的更复杂情况,让我们改用 Go 工具链中的 nm
命令。
go tool nm
命令将输出给定的可执行文件、目标文件或存档中涉及的 符号 。 在这种情况下,符号指代代码中的对象,例如定义或导入的变量或函数。 通过 nm
生成符号表,并使用 grep
搜索变量,您可以快速找到有关其路径的信息。
注意: 如果包名包含任何非 ASCII 字符或 [X154X ] 或 %
字符,因为这是工具本身的限制。
要使用此命令,首先为 app
构建二进制文件:
go build
现在 app
已构建,将 nm
工具指向它并搜索输出:
go tool nm ./app | grep app
运行时,nm
工具会输出大量数据。 因此,前面的命令使用 |
将输出传送到 grep
命令,然后搜索标题中具有顶级 app
的术语。
您将收到与此类似的输出:
Output 55d2c0 D app/build.Time 55d2d0 D app/build.User 4069a0 T runtime.appendIntStr 462580 T strconv.appendEscapedRune . . .
在这种情况下,结果集的前两行包含您要查找的两个变量的路径:app/build.Time
和 app/build.User
。
现在您知道了路径,再次构建应用程序,这次在构建时更改 Version
、User
和 Time
。 为此,请将多个 -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 系统上运行时,将生成与以下类似的输出:
OutputVersion: v1.0.0 build.Time: Fri Oct 4 19:49:19 UTC 2019 build.User: sammy
现在您有了一个包含版本控制和构建信息的二进制文件,可以在解决问题时为生产提供重要帮助。
结论
本教程展示了在正确应用的情况下,ldflags
如何成为在构建时将有价值的信息注入二进制文件的强大工具。 这样,您可以控制功能标志、环境信息、版本信息等,而无需对源代码进行更改。 通过将 ldflags
添加到您当前的构建工作流程中,您可以最大限度地利用 Go 的自包含二进制分发格式的优势。
如果您想了解有关 Go 编程语言的更多信息,请查看我们完整的 如何在 Go 系列中编码 。 如果您正在寻找更多版本控制解决方案,请尝试我们的 如何使用 Git 参考指南。