Skip to content

Commit 72bddcc

Browse files
Patel230claude
andcommitted
fix(version): report the tagged module version for go install builds
`go install github.com/GrayCodeAI/rho/cmd/rho@vX.Y.Z` builds without ldflags, so main.Version stays "dev". DisplayVersion then fell back to any VERSION file in the working directory or its parents (so running rho inside another project could print that project's version) and otherwise "dev"; `rho update` compared the raw "dev" string, which never parses, and always reported an update. DisplayVersion now prefers, after the ldflags version, the main module version from debug.ReadBuildInfo when it is a clean release (e.g. v0.3.0 -> "0.3.0"); "(devel)", pseudo-versions (@main, untagged commits) and +dirty builds still fall back to the VERSION file, then "dev". `rho update` uses DisplayVersion, so go-install and source builds compare their real version. Tests stub the build info for a tagged module, each development form, and the ldflags precedence. Refs: F018, F025 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 3610e43 commit 72bddcc

3 files changed

Lines changed: 87 additions & 5 deletions

File tree

‎cmd/root.go‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -585,10 +585,9 @@ var updateCmd = &cobra.Command{
585585
Long: "Check GitHub for a newer rho release and print upgrade instructions.",
586586
Args: cobra.NoArgs,
587587
RunE: func(cmd *cobra.Command, args []string) error {
588-
ver := version
589-
if ver == "" {
590-
ver = "dev"
591-
}
588+
// Same resolution as `rho --version`, so `go install ...@vX.Y.Z` and
589+
// source builds compare their real version instead of "dev".
590+
ver := DisplayVersion()
592591
prog := NewCLIProgress("Update check", []string{"Checking GitHub for updates"})
593592
defer prog.Abort()
594593
prog.StartStep(0)

‎cmd/version_display.go‎

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ package cmd
33
import (
44
"os"
55
"path/filepath"
6+
"runtime/debug"
67
"strings"
8+
9+
"golang.org/x/mod/module"
10+
"golang.org/x/mod/semver"
711
)
812

913
// versionLine is the single user-facing version format shared by
@@ -21,12 +25,20 @@ func versionLine() string {
2125
}
2226

2327
// DisplayVersion returns the user-facing version string for banners and /version.
24-
// Release builds inject version via ldflags; local builds fall back to VERSION file.
28+
//
29+
// In order of preference:
30+
// 1. the version injected via ldflags (release archives, `make build`);
31+
// 2. the module version recorded by `go install .../cmd/rho@vX.Y.Z`;
32+
// 3. the repository VERSION file (local `go build` / `go run` checkouts);
33+
// 4. "dev".
2534
func DisplayVersion() string {
2635
v := strings.TrimSpace(version)
2736
if v != "" && v != "dev" {
2837
return v
2938
}
39+
if mv := releaseModuleVersion(); mv != "" {
40+
return mv
41+
}
3042
if fromFile := readRepoVERSIONFile(); fromFile != "" {
3143
return fromFile
3244
}
@@ -36,6 +48,26 @@ func DisplayVersion() string {
3648
return "dev"
3749
}
3850

51+
// readBuildInfo is debug.ReadBuildInfo, replaceable in tests.
52+
var readBuildInfo = debug.ReadBuildInfo
53+
54+
// releaseModuleVersion returns the main module's version without the leading
55+
// "v" when the binary was built from a tagged module (`go install
56+
// github.com/GrayCodeAI/rho/cmd/rho@v0.3.0`), and "" for development builds:
57+
// "(devel)", pseudo-versions (untagged commits such as @main) and versions
58+
// with build metadata (e.g. "+dirty" from a modified checkout).
59+
func releaseModuleVersion() string {
60+
info, ok := readBuildInfo()
61+
if !ok || info == nil {
62+
return ""
63+
}
64+
mv := info.Main.Version
65+
if !semver.IsValid(mv) || module.IsPseudoVersion(mv) || semver.Build(mv) != "" {
66+
return ""
67+
}
68+
return strings.TrimPrefix(mv, "v")
69+
}
70+
3971
func readRepoVERSIONFile() string {
4072
candidates := versionFileCandidates()
4173
for _, path := range candidates {

‎cmd/version_display_test.go‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cmd
33
import (
44
"os"
55
"path/filepath"
6+
"runtime/debug"
67
"testing"
78

89
rhoconfig "github.com/GrayCodeAI/rho/internal/config"
@@ -21,6 +22,56 @@ func TestDisplayVersion_FromVERSIONFile(t *testing.T) {
2122
}
2223
}
2324

25+
func stubBuildInfo(t *testing.T, mainVersion string) {
26+
t.Helper()
27+
prev := readBuildInfo
28+
readBuildInfo = func() (*debug.BuildInfo, bool) {
29+
return &debug.BuildInfo{Main: debug.Module{Path: "github.com/GrayCodeAI/rho", Version: mainVersion}}, true
30+
}
31+
t.Cleanup(func() { readBuildInfo = prev })
32+
}
33+
34+
func TestDisplayVersion_GoInstallTaggedModule(t *testing.T) {
35+
t.Chdir(t.TempDir()) // no VERSION file nearby
36+
SetVersion("dev")
37+
stubBuildInfo(t, "v0.3.0")
38+
if got := DisplayVersion(); got != "0.3.0" {
39+
t.Fatalf("DisplayVersion() = %q, want 0.3.0 for go install ...@v0.3.0", got)
40+
}
41+
}
42+
43+
func TestDisplayVersion_DevelopmentModuleVersionsFallBack(t *testing.T) {
44+
for _, mv := range []string{
45+
"(devel)",
46+
"v0.0.0-20260921005105-13e05ceb15e5", // go install ...@main
47+
"v0.3.1-0.20260930120000-abcdefabcdef", // untagged commit after v0.3.0
48+
"v0.3.0+dirty", // modified checkout at the tag
49+
"",
50+
} {
51+
t.Run(mv, func(t *testing.T) {
52+
dir := t.TempDir()
53+
if err := os.WriteFile(filepath.Join(dir, "VERSION"), []byte("0.3.0\n"), 0o644); err != nil {
54+
t.Fatal(err)
55+
}
56+
t.Chdir(dir)
57+
SetVersion("dev")
58+
stubBuildInfo(t, mv)
59+
if got := DisplayVersion(); got != "0.3.0" {
60+
t.Fatalf("DisplayVersion() = %q, want the VERSION file value for module version %q", got, mv)
61+
}
62+
})
63+
}
64+
}
65+
66+
func TestDisplayVersion_LdflagsWinOverModuleVersion(t *testing.T) {
67+
SetVersion("1.4.2")
68+
t.Cleanup(func() { SetVersion("dev") })
69+
stubBuildInfo(t, "v0.3.0")
70+
if got := DisplayVersion(); got != "1.4.2" {
71+
t.Fatalf("DisplayVersion() = %q, want the ldflags version 1.4.2", got)
72+
}
73+
}
74+
2475
func TestDisplayVersion_ReleaseBuild(t *testing.T) {
2576
SetVersion("1.4.2")
2677
if got := DisplayVersion(); got != "1.4.2" {

0 commit comments

Comments
 (0)