From dd152d0fa432062a4a8ebe2bdd9c59ceb3d8ed2d Mon Sep 17 00:00:00 2001 From: VeteranBV Date: Wed, 15 Jul 2026 03:53:31 +0000 Subject: [PATCH 1/2] fix: resolve --version from build info for go-install builds The version/commit/date variables are only injected by GoReleaser's ldflags, so binaries from go install (or plain go build) reported dev (commit: none, built: unknown) no matter what version they were. Go embeds the module version and VCS metadata in every binary; when the ldflags defaults are detected, --version now falls back to debug.ReadBuildInfo, so a fleet box always knows what it's running. GoReleaser-injected values still win when present. --- CHANGELOG.md | 6 ++++ cmd/sysgreet/main.go | 3 +- cmd/sysgreet/version.go | 34 +++++++++++++++++++ cmd/sysgreet/version_test.go | 65 ++++++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 cmd/sysgreet/version.go create mode 100644 cmd/sysgreet/version_test.go 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..129ca8e --- /dev/null +++ b/cmd/sysgreet/version.go @@ -0,0 +1,34 @@ +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 + } + for _, s := range info.Settings { + switch s.Key { + case "vcs.revision": + if c == "none" { + c = s.Value + } + case "vcs.time": + if d == "unknown" { + d = s.Value + } + } + } + return v, c, d +} diff --git a/cmd/sysgreet/version_test.go b/cmd/sysgreet/version_test.go new file mode 100644 index 0000000..1494f5f --- /dev/null +++ b/cmd/sysgreet/version_test.go @@ -0,0 +1,65 @@ +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: "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) + } + }) + } +} From 40bbfd97e60e0d2dcc7fe60a7e5e8e6f2e4980d6 Mon Sep 17 00:00:00 2001 From: VeteranBV Date: Wed, 15 Jul 2026 04:01:03 +0000 Subject: [PATCH 2/2] fix: mark dirty checkouts in the version fallback A binary built from a tree with uncommitted changes reported the clean commit hash as if it matched exactly. The vcs.modified build setting now appends -dirty to the commit when the hash came from the VCS fallback; ldflags-injected values remain untouched. --- cmd/sysgreet/version.go | 10 ++++++++++ cmd/sysgreet/version_test.go | 23 +++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/cmd/sysgreet/version.go b/cmd/sysgreet/version.go index 129ca8e..e16bbca 100644 --- a/cmd/sysgreet/version.go +++ b/cmd/sysgreet/version.go @@ -18,17 +18,27 @@ func resolveBuildInfo(info *debug.BuildInfo, v, c, d string) (string, string, st 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 index 1494f5f..4484292 100644 --- a/cmd/sysgreet/version_test.go +++ b/cmd/sysgreet/version_test.go @@ -45,6 +45,29 @@ func TestResolveBuildInfo(t *testing.T) { 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{},