From 725fb694de68c024352bb7332830be327a97bc44 Mon Sep 17 00:00:00 2001 From: Abhishek Choudhary Date: Wed, 3 Jun 2026 13:06:19 +0800 Subject: [PATCH] fix(version): report module version for go-install builds Binaries installed with 'go install github.com/api7/a7/cmd/a7@' bypass the Makefile/GoReleaser ldflags, so 'a7 version' reported 'dev' with unknown commit and date. Fall back to runtime/debug.ReadBuildInfo: the Go module version fills Version, and vcs.revision/vcs.time fill Commit/Date for source builds. Values injected via ldflags still take precedence. This also fixes 'a7 update' treating go-installed binaries as dev builds when comparing against the latest release. Audited for the same bug class: internal/version is the only ldflags-injected metadata in the repo. --- internal/version/version.go | 41 +++++++++++++++++++ internal/version/version_test.go | 68 ++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 internal/version/version_test.go diff --git a/internal/version/version.go b/internal/version/version.go index 1804c5f..d7eeae6 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -1,5 +1,7 @@ package version +import "runtime/debug" + // These variables are set at build time via ldflags. var ( // Version is the semantic version (e.g., "v0.1.0"). @@ -9,3 +11,42 @@ var ( // Date is the build date in UTC. Date = "unknown" ) + +// init falls back to the Go module build info for any value that was not +// injected via ldflags, so that binaries installed with +// `go install github.com/api7/a7/cmd/a7@` report the module version +// instead of "dev". +func init() { + if info, ok := debug.ReadBuildInfo(); ok { + Version, Commit, Date = resolve(Version, Commit, Date, info) + } +} + +// resolve returns version, commit, and date with unset (default) values +// filled in from build info. Values already set via ldflags take precedence. +func resolve(version, commit, date string, info *debug.BuildInfo) (string, string, string) { + if version == "dev" && info.Main.Version != "" && info.Main.Version != "(devel)" { + version = info.Main.Version + } + + var revision, vcsTime string + for _, s := range info.Settings { + switch s.Key { + case "vcs.revision": + revision = s.Value + case "vcs.time": + vcsTime = s.Value + } + } + if commit == "unknown" && revision != "" { + if len(revision) > 7 { + revision = revision[:7] + } + commit = revision + } + if date == "unknown" && vcsTime != "" { + date = vcsTime + } + + return version, commit, date +} diff --git a/internal/version/version_test.go b/internal/version/version_test.go new file mode 100644 index 0000000..e645063 --- /dev/null +++ b/internal/version/version_test.go @@ -0,0 +1,68 @@ +package version + +import ( + "runtime/debug" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestResolve_GoInstallModuleVersion(t *testing.T) { + // `go install .../cmd/a7@v1.0.0` embeds the module version but no ldflags. + info := &debug.BuildInfo{Main: debug.Module{Version: "v1.0.0"}} + + v, c, d := resolve("dev", "unknown", "unknown", info) + + assert.Equal(t, "v1.0.0", v) + assert.Equal(t, "unknown", c) + assert.Equal(t, "unknown", d) +} + +func TestResolve_DevelBuildStaysDev(t *testing.T) { + // `go build` from a source checkout reports "(devel)" as the module version. + info := &debug.BuildInfo{Main: debug.Module{Version: "(devel)"}} + + v, _, _ := resolve("dev", "unknown", "unknown", info) + + assert.Equal(t, "dev", v) +} + +func TestResolve_LdflagsTakePrecedence(t *testing.T) { + info := &debug.BuildInfo{ + Main: debug.Module{Version: "v9.9.9"}, + Settings: []debug.BuildSetting{ + {Key: "vcs.revision", Value: "ffffffffffffffffffffffffffffffffffffffff"}, + {Key: "vcs.time", Value: "2026-01-01T00:00:00Z"}, + }, + } + + v, c, d := resolve("v1.2.3", "abc1234", "2026-05-27T03:23:51Z", info) + + assert.Equal(t, "v1.2.3", v) + assert.Equal(t, "abc1234", c) + assert.Equal(t, "2026-05-27T03:23:51Z", d) +} + +func TestResolve_VCSSettingsFallback(t *testing.T) { + info := &debug.BuildInfo{ + Main: debug.Module{Version: "(devel)"}, + Settings: []debug.BuildSetting{ + {Key: "vcs.revision", Value: "0123456789abcdef0123456789abcdef01234567"}, + {Key: "vcs.time", Value: "2026-06-03T00:00:00Z"}, + }, + } + + v, c, d := resolve("dev", "unknown", "unknown", info) + + assert.Equal(t, "dev", v) + assert.Equal(t, "0123456", c, "commit should be shortened to 7 characters") + assert.Equal(t, "2026-06-03T00:00:00Z", d) +} + +func TestResolve_EmptyBuildInfo(t *testing.T) { + v, c, d := resolve("dev", "unknown", "unknown", &debug.BuildInfo{}) + + assert.Equal(t, "dev", v) + assert.Equal(t, "unknown", c) + assert.Equal(t, "unknown", d) +}