From f6f094356ee20355c545c4e751245a840e3356f4 Mon Sep 17 00:00:00 2001 From: hushamsaeed Date: Fri, 1 May 2026 18:46:19 +0500 Subject: [PATCH] fix(version): fall back to debug.ReadBuildInfo for go-install builds When the binary is installed via `go install github.com/plinth-dev/cli/cmd/plinth@vX.Y.Z` the Makefile ldflags don't run, so Version and Commit stay empty. Read the module version + vcs.revision from runtime/debug.ReadBuildInfo so those binaries report a real version instead of "dev (commit none)". The Makefile-built path still wins when both fields are set explicitly. Co-Authored-By: Claude Opus 4.7 (1M context) --- internal/cli/version.go | 47 +++++++++++++++++++++++++++++++++--- internal/cli/version_test.go | 19 +++++++++++++++ 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/internal/cli/version.go b/internal/cli/version.go index 7d14956..b96eb30 100644 --- a/internal/cli/version.go +++ b/internal/cli/version.go @@ -4,15 +4,54 @@ import ( "fmt" "io" "runtime" + "runtime/debug" ) -// Set via -ldflags "-X github.com/plinth-dev/cli/internal/cli.Version=..." at build. +// Set via -ldflags "-X github.com/plinth-dev/cli/internal/cli.Version=..." at +// build (see Makefile). When unset — e.g. when the user installs via +// `go install ...@v0.1.0` — runtime/debug.ReadBuildInfo provides the module +// version and VCS revision Go's toolchain stamped into the binary. var ( - Version = "dev" - Commit = "none" + Version = "" + Commit = "" ) func runVersion(stdout io.Writer) int { - fmt.Fprintf(stdout, "plinth %s (commit %s, %s)\n", Version, Commit, runtime.Version()) + v, c := resolveVersion(Version, Commit) + fmt.Fprintf(stdout, "plinth %s (commit %s, %s)\n", v, c, runtime.Version()) return 0 } + +func resolveVersion(version, commit string) (string, string) { + if version != "" && commit != "" { + return version, commit + } + info, ok := debug.ReadBuildInfo() + if !ok { + return fallback(version, "dev"), fallback(commit, "none") + } + if version == "" { + if v := info.Main.Version; v != "" && v != "(devel)" { + version = v + } + } + if commit == "" { + for _, s := range info.Settings { + if s.Key == "vcs.revision" && s.Value != "" { + commit = s.Value + if len(commit) > 12 { + commit = commit[:12] + } + break + } + } + } + return fallback(version, "dev"), fallback(commit, "none") +} + +func fallback(s, def string) string { + if s == "" { + return def + } + return s +} diff --git a/internal/cli/version_test.go b/internal/cli/version_test.go index 952f4c8..ea235d2 100644 --- a/internal/cli/version_test.go +++ b/internal/cli/version_test.go @@ -23,3 +23,22 @@ func TestVersionPrints(t *testing.T) { t.Errorf("missing commit: %s", got) } } + +func TestResolveVersionPrefersExplicit(t *testing.T) { + v, c := resolveVersion("0.2.0", "deadbeef") + if v != "0.2.0" || c != "deadbeef" { + t.Errorf("explicit args lost: v=%q c=%q", v, c) + } +} + +func TestResolveVersionFallback(t *testing.T) { + // In `go test` runs, debug.ReadBuildInfo reports Main.Version="(devel)" + // and no vcs.revision setting, so both should land on the dev/none defaults. + v, c := resolveVersion("", "") + if v != "dev" { + t.Errorf("unset version did not fall back to dev: %q", v) + } + if c == "" { + t.Errorf("commit must not be empty after fallback") + } +}