diff --git a/CHANGELOG.md b/CHANGELOG.md index 98fe658..e3367da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Fixed + +- **`--version` tells the truth for `go install` builds** - Binaries built outside GoReleaser reported `dev (commit: none, built: unknown)`; they now resolve the module version and VCS metadata from Go's embedded build info. GoReleaser-injected values still take precedence. + ## v1.1.0 ### Added diff --git a/cmd/sysgreet/main.go b/cmd/sysgreet/main.go index 8a4fda2..a9d5c29 100644 --- a/cmd/sysgreet/main.go +++ b/cmd/sysgreet/main.go @@ -38,7 +38,8 @@ func run() error { settings := parseFlags() if settings.Version { - fmt.Printf("sysgreet %s (commit: %s, built: %s)\n", version, commit, date) + v, c, d := buildInfo() + fmt.Printf("sysgreet %s (commit: %s, built: %s)\n", v, c, d) return nil } if settings.Disable { diff --git a/cmd/sysgreet/version.go b/cmd/sysgreet/version.go new file mode 100644 index 0000000..e16bbca --- /dev/null +++ b/cmd/sysgreet/version.go @@ -0,0 +1,44 @@ +package main + +import "runtime/debug" + +// buildInfo resolves the version metadata shown by --version. GoReleaser +// injects real values via ldflags for release binaries; go-install and +// plain go-build binaries keep the defaults, so fall back to the module +// build info Go embeds in every binary. +func buildInfo() (string, string, string) { + info, ok := debug.ReadBuildInfo() + if !ok { + return version, commit, date + } + return resolveBuildInfo(info, version, commit, date) +} + +func resolveBuildInfo(info *debug.BuildInfo, v, c, d string) (string, string, string) { + if v == "dev" && info.Main.Version != "" && info.Main.Version != "(devel)" { + v = info.Main.Version + } + fromVCS := false + dirty := false + for _, s := range info.Settings { + switch s.Key { + case "vcs.revision": + if c == "none" { + c = s.Value + fromVCS = true + } + case "vcs.time": + if d == "unknown" { + d = s.Value + } + case "vcs.modified": + dirty = s.Value == "true" + } + } + // A build from a tree with uncommitted changes must not claim to + // exactly match the reported commit. + if fromVCS && dirty { + c += "-dirty" + } + return v, c, d +} diff --git a/cmd/sysgreet/version_test.go b/cmd/sysgreet/version_test.go new file mode 100644 index 0000000..4484292 --- /dev/null +++ b/cmd/sysgreet/version_test.go @@ -0,0 +1,88 @@ +package main + +import ( + "runtime/debug" + "testing" +) + +func TestResolveBuildInfo(t *testing.T) { + tests := []struct { + name string + info *debug.BuildInfo + v, c, d string + wantV string + wantC string + wantD string + }{ + { + name: "go install binary resolves module version", + info: &debug.BuildInfo{ + Main: debug.Module{Version: "v1.2.0"}, + }, + v: "dev", c: "none", d: "unknown", + wantV: "v1.2.0", wantC: "none", wantD: "unknown", + }, + { + name: "local git build resolves vcs metadata", + info: &debug.BuildInfo{ + Main: debug.Module{Version: "(devel)"}, + Settings: []debug.BuildSetting{ + {Key: "vcs.revision", Value: "abc1234"}, + {Key: "vcs.time", Value: "2026-07-15T00:00:00Z"}, + }, + }, + v: "dev", c: "none", d: "unknown", + wantV: "dev", wantC: "abc1234", wantD: "2026-07-15T00:00:00Z", + }, + { + name: "goreleaser ldflags win over build info", + info: &debug.BuildInfo{ + Main: debug.Module{Version: "v1.2.0"}, + Settings: []debug.BuildSetting{ + {Key: "vcs.revision", Value: "abc1234"}, + }, + }, + v: "v1.2.0", c: "deadbeef", d: "2026-07-15", + wantV: "v1.2.0", wantC: "deadbeef", wantD: "2026-07-15", + }, + { + name: "dirty checkout marks the commit", + info: &debug.BuildInfo{ + Main: debug.Module{Version: "(devel)"}, + Settings: []debug.BuildSetting{ + {Key: "vcs.revision", Value: "abc1234"}, + {Key: "vcs.modified", Value: "true"}, + }, + }, + v: "dev", c: "none", d: "unknown", + wantV: "dev", wantC: "abc1234-dirty", wantD: "unknown", + }, + { + name: "ldflags commit is never marked dirty", + info: &debug.BuildInfo{ + Settings: []debug.BuildSetting{ + {Key: "vcs.revision", Value: "abc1234"}, + {Key: "vcs.modified", Value: "true"}, + }, + }, + v: "v1.2.0", c: "deadbeef", d: "2026-07-15", + wantV: "v1.2.0", wantC: "deadbeef", wantD: "2026-07-15", + }, + { + name: "empty build info keeps defaults", + info: &debug.BuildInfo{}, + v: "dev", c: "none", d: "unknown", + wantV: "dev", wantC: "none", wantD: "unknown", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotV, gotC, gotD := resolveBuildInfo(tt.info, tt.v, tt.c, tt.d) + if gotV != tt.wantV || gotC != tt.wantC || gotD != tt.wantD { + t.Errorf("resolveBuildInfo() = (%q, %q, %q), want (%q, %q, %q)", + gotV, gotC, gotD, tt.wantV, tt.wantC, tt.wantD) + } + }) + } +}