跳转到主要内容

介绍


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

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

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

先决条件


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

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


构建您的示例应用程序


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

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

mkdir app


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

cd app


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

nano main.go


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

app/main.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


您现在有一个打印默认版本信息的应用程序,但是您还没有办法在构建时传递当前版本信息。在下一步中,您将使用 -ldflags 并 go build 来解决此问题。

在 go build 中使用 ldflags


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

go build -ldflags="-flag"


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

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 文件中。版本是您要写入的变量,而 v1.0.0 是新值。

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

构建新的应用程序二进制文件后,运行应用程序:

./app


您将收到以下输出:

Output

Version:     v1.0.0


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

您现在已经在构建时修改了一个简单应用程序内部的字符串变量。使用 ldflags,您可以仅使用命令行将版本详细信息、许可信息等嵌入到准备分发的二进制文件中。

在本例中,您更改的变量在主程序中,降低了确定路径名的难度。但有时这些变量的路径更难找到。在下一步中,您将向子包中的变量写入值,以演示确定更复杂包路径的最佳方法。

定位子包变量


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

要添加一个新的子包,首先在你的项目中添加一个名为 build 的新目录:

mkdir -p build


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

nano build/build.go


在您的文本编辑器中,为时间和用户添加新变量:

app/build/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 包,然后以与打印版本相同的方式打印 build.Time 和 build.User。

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

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

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

注意:如果包名包含任何非 ASCII 字符、" 或 % 字符,nm 命令将无法帮助您找到变量的路径,因为这是工具本身的限制。

要使用此命令,首先为 app 构建二进制文件:

go build


现在该应用程序已构建,将 nm 工具指向它并搜索输出:

go tool nm ./app | grep app


运行时,nm 工具会输出大量数据。因此,前面的命令使用了 |将输出传递给 grep 命令,然后该命令搜索标题中包含顶级应用程序的术语。

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

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。

现在您知道了路径,再次构建应用程序,这一次在构建时更改版本、用户和时间。为此,将多个 -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 的自包含二进制分发格式的优势。

文章链接