Golang中debug.BuildInfo的Main.Version显示为devel的问题

Golang中debug.BuildInfo的Main.Version显示为devel的问题 我正在尝试从 debug.BuildInfo 获取“版本”信息:

package main

import (
	"fmt"
	"runtime/debug"
)

func main() {
	bi, ok := debug.ReadBuildInfo()
	if !ok {
		fmt.Println("not ok")
		return
	}
	fmt.Printf("Version: %s\n", bi.Main.Version)
}

输出:

Version: (devel)

我尝试了以下版本: go1.17.5 go1.18beta1

两个版本我都得到了相同的结果。

是否存在任何方法可以让 Go 编译器将 Main.Version 更新为 git 标签或 go.mod 中的版本(而不使用 -ldflags="-X path.to.variable=value")?


更多关于Golang中debug.BuildInfo的Main.Version显示为devel的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

在 Go 1.18beta1 中,只要你的代码位于模块中,并且已在 git 中标记,这应该就能正常工作。

更多关于Golang中debug.BuildInfo的Main.Version显示为devel的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我仍然得到版本“(devel)”

package main

import (
	"fmt"
	"runtime/debug"
)

func main() {
	bi, ok := debug.ReadBuildInfo()
	if !ok {
		panic("not ok")
	}
	fmt.Printf("%+v", bi)
}

输出:

&{GoVersion:go1.18beta1 Path:play.com/buildinfo Main:{Path:play.com/buildinfo Version:(devel) Sum: Replace:<nil>} Deps:[] Settings:[{Key:-compiler Value:gc} {Key:CGO_ENABLED Value:0} {Key:GOARCH Value:amd64} {Key:GOOS Value:darwin} {Key:GOAMD64 Value:v1} {Key:vcs Value:git} {Key:vcs.revision Value:bd21e4e6b920b1277fc1cbb3e02612220b0d4989} {Key:vcs.time Value:2022-01-11T14:46:27Z} {Key:vcs.modified Value:false}]}

运行:

git log
commit bd21e4e6b920b1277fc1cbb3e02612220b0d4989 (HEAD -> main, tag: v1.0.0)
Author: Micke
Date:   Tue Jan 11 15:46:27 2022 +0100

    intial

另请参阅

github.com/golang/go

cmd/go: stamp the pseudo-version in builds generated by go build

标签: Proposal, Proposal-Accepted, modules, Proposal-FinalCommentPeriod

cmd/go 将依赖版本信息嵌入到二进制文件中,这非常有用。从 Go 1.18 开始,cmd/go 还将 VCS 信息嵌入到二进制文件中,这使得它比以前更加有用。

如 #37475 所述,人们使用 -ldflags='-X foo=bar' 将版本信息放入二进制文件,这需要一个额外的构建包装器。cmd/go 的新 VCS 标记功能应该可以减少对外部包装器的需求,但我担心它做得还不够。

在执行 go build 时,主模块的版本信息(即 Go 伪版本意义上的信息)没有被记录下来:

: emerald:ver; go build
: emerald:ver; go version -m hello | grep 'mod.*hello'
	mod	mgk.ro/hello	(devel)	
: emerald:ver;

在执行 go install 时,版本信息会按预期被记录:

: emerald:ver; go install robpike.io/ivy@latest
go: downloading robpike.io/ivy v0.1.124
: emerald:ver; go version -m `which ivy` | grep 'mod.*ivy'
	mod	robpike.io/ivy	v0.1.12	h1:qI7dnEiXhorB+za07W6qX3sG+IvBK4EUl38vUHAf53Q=
: emerald:ver;
: emerald:ver;

我担心 cmd/go 的这个限制将继续迫使人们使用设置 -ldflags 的外部构建包装器,这是相当不幸的。

我并不是第一个希望在二进制文件中包含主模块版本信息的人,这已经在各种问题中被提出过,例如 #29814,该问题被标记为 #37475 的重复项,但它实际上并不是重复项,因为 #37475 是关于 VCS 信息的,而 #29814 是关于语义版本控制的。其他人要求此功能的例子还有 mvdan/sh#519 和 https://github.com/golang/go/issues/29228#issuecomment-449554128,其中提出了各种变通方法。

说到变通方法,我所知道的目前唯一有效的变通方法是创建一个本地模块代理并将 GOPROXY 传递给 go install,但这是一个开销极高的变通方法,而且 go install 无论如何也不能替代 go build,因为 go install 在 vendoring 的工作方式以及你可以在 go.mod 中放置什么内容方面有一些相当严格的限制,并且 go install 在交叉编译时不支持控制 GOBIN

我意识到 Git 标签是一个本地概念,通过执行“错误”的 git 操作,可能会为相同的源代码生成不同的伪版本。对于 Git 的这个缺陷,我恐怕没有任何解决方案或建议,除了注意到即使在这种情况下哈希信息也被正确记录,并且在所有情况下,由于可以访问本地源代码,程序员总是可以执行某些本地操作,这有可能导致版本标签错误。Git 只是更容易意外地发生这种情况,但这种可能性始终存在。

我没有任何统计数据来支持这一点,但根据我的经验,大多数企业源代码都是通过 go build 构建的,而不是 go install,如果 Go 的版本概念能以某种方式通过 go build 进行标记,那就太好了。

CC @bcmills @mvdan @rsc

这是一个常见问题,debug.BuildInfoMain.Version 字段显示为 (devel) 是因为你的程序不是通过 go installgo buildgo run 使用模块版本信息构建的。

当使用 go install 安装特定版本时,Main.Version 会自动填充。例如:

# 安装特定版本
go install github.com/example/project@v1.2.3

# 或者从本地模块安装
go install ./cmd/myapp

但如果你直接使用 go buildgo run 构建当前目录的代码,而没有指定版本,Go 会使用 (devel) 作为占位符。

要获取实际的版本信息,你可以检查 debug.BuildInfoSettings 字段,其中包含了 vcs.revisionvcs.time 等信息:

package main

import (
	"fmt"
	"runtime/debug"
)

func main() {
	bi, ok := debug.ReadBuildInfo()
	if !ok {
		fmt.Println("not ok")
		return
	}
	
	fmt.Printf("Main Version: %s\n", bi.Main.Version)
	
	// 检查构建设置中的版本控制信息
	for _, setting := range bi.Settings {
		switch setting.Key {
		case "vcs.revision":
			fmt.Printf("Git Commit: %s\n", setting.Value)
		case "vcs.time":
			fmt.Printf("Commit Time: %s\n", setting.Value)
		case "vcs.modified":
			fmt.Printf("Modified: %s\n", setting.Value)
		}
	}
}

要确保版本信息被正确设置,你需要在构建时启用版本控制信息。从 Go 1.18 开始,这变得更加简单:

# Go 1.18+ 自动包含版本控制信息
go build -o myapp

# 或者显式启用
go build -ldflags="-buildvcs=true" -o myapp

# 对于 Go 1.17,需要显式启用
go build -ldflags="-buildvcs=true" -o myapp

如果你使用 go install 安装带有版本标签的模块,Main.Version 会被正确设置:

# 假设你的模块有 v1.0.0 标签
go install github.com/your/project@v1.0.0

然后运行程序时,Main.Version 会显示为 v1.0.0

另外,你也可以直接从 go.mod 文件中读取版本信息作为备选方案:

package main

import (
	"fmt"
	"io/ioutil"
	"runtime/debug"
	"strings"
)

func getVersionFromGoMod() string {
	data, err := ioutil.ReadFile("go.mod")
	if err != nil {
		return ""
	}
	
	lines := strings.Split(string(data), "\n")
	if len(lines) > 0 && strings.HasPrefix(lines[0], "module ") {
		// 解析模块路径和版本
		for _, line := range lines {
			if strings.Contains(line, "// indirect") {
				continue
			}
			// 这里可以添加更复杂的解析逻辑
		}
	}
	return ""
}

func main() {
	bi, ok := debug.ReadBuildInfo()
	if !ok {
		return
	}
	
	version := bi.Main.Version
	if version == "(devel)" {
		// 尝试从其他来源获取版本
		for _, setting := range bi.Settings {
			if setting.Key == "vcs.revision" && setting.Value != "" {
				version = setting.Value[:8] // 使用前8位提交哈希
				break
			}
		}
	}
	
	fmt.Printf("Version: %s\n", version)
}

总结:(devel) 是正常行为,表示开发构建。要获取具体的版本信息,需要使用 go install 安装带标签的版本,或者从 Settings 字段中提取版本控制信息。

回到顶部