Bake git commit into the binary and log it at startup
New internal/version package: ldflags-stamped Commit with a debug.ReadBuildInfo VCS fallback for host builds. Startup log line carries commit + Go version; --version prints the hash and exits. Makefile computes GIT_COMMIT (12 chars, -dirty on any local change) and passes it to docker-build/buildx; Dockerfile injects it via -ldflags and an org.opencontainers.image.revision label. make build now uses ./cmd — file-argument builds skip Go's automatic VCS stamp. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
47
internal/version/version.go
Normal file
47
internal/version/version.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Package version reports which commit the binary was built from. Docker
|
||||
// builds stamp it via -ldflags (the build context has no .git, so Go's
|
||||
// automatic VCS stamp is absent there); host builds fall back to that
|
||||
// automatic stamp.
|
||||
package version
|
||||
|
||||
import "runtime/debug"
|
||||
|
||||
// Commit is set at link time via
|
||||
// -ldflags "-X <module>/internal/version.Commit=<hash>".
|
||||
var Commit string
|
||||
|
||||
// Resolve returns the commit the binary was built from, or "unknown" when
|
||||
// neither the ldflags stamp nor build info is available (e.g. go test).
|
||||
func Resolve() string {
|
||||
return resolve(Commit, debug.ReadBuildInfo)
|
||||
}
|
||||
|
||||
func resolve(ldflagsCommit string, readBuildInfo func() (*debug.BuildInfo, bool)) string {
|
||||
if ldflagsCommit != "" {
|
||||
return ldflagsCommit
|
||||
}
|
||||
bi, ok := readBuildInfo()
|
||||
if !ok {
|
||||
return "unknown"
|
||||
}
|
||||
var revision string
|
||||
var modified bool
|
||||
for _, s := range bi.Settings {
|
||||
switch s.Key {
|
||||
case "vcs.revision":
|
||||
revision = s.Value
|
||||
case "vcs.modified":
|
||||
modified = s.Value == "true"
|
||||
}
|
||||
}
|
||||
if revision == "" {
|
||||
return "unknown"
|
||||
}
|
||||
if len(revision) > 12 {
|
||||
revision = revision[:12]
|
||||
}
|
||||
if modified {
|
||||
revision += "-dirty"
|
||||
}
|
||||
return revision
|
||||
}
|
||||
74
internal/version/version_test.go
Normal file
74
internal/version/version_test.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"runtime/debug"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func buildInfoWith(settings ...debug.BuildSetting) func() (*debug.BuildInfo, bool) {
|
||||
return func() (*debug.BuildInfo, bool) {
|
||||
return &debug.BuildInfo{Settings: settings}, true
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolve_precedenceAndFallback(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
noBuildInfo := func() (*debug.BuildInfo, bool) { return nil, false }
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ldflagsCommit string
|
||||
readBuildInfo func() (*debug.BuildInfo, bool)
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "ldflags stamp wins over build info",
|
||||
ldflagsCommit: "abc123def456-dirty",
|
||||
readBuildInfo: buildInfoWith(debug.BuildSetting{Key: "vcs.revision", Value: "ffffffffffffffffffffffffffffffffffffffff"}),
|
||||
want: "abc123def456-dirty",
|
||||
},
|
||||
{
|
||||
name: "no stamp, no build info",
|
||||
readBuildInfo: noBuildInfo,
|
||||
want: "unknown",
|
||||
},
|
||||
{
|
||||
name: "build info without vcs settings",
|
||||
readBuildInfo: buildInfoWith(),
|
||||
want: "unknown",
|
||||
},
|
||||
{
|
||||
name: "full revision truncated to 12 chars",
|
||||
readBuildInfo: buildInfoWith(
|
||||
debug.BuildSetting{Key: "vcs.revision", Value: "0123456789abcdef0123456789abcdef01234567"},
|
||||
debug.BuildSetting{Key: "vcs.modified", Value: "false"},
|
||||
),
|
||||
want: "0123456789ab",
|
||||
},
|
||||
{
|
||||
name: "modified tree gets dirty suffix",
|
||||
readBuildInfo: buildInfoWith(
|
||||
debug.BuildSetting{Key: "vcs.revision", Value: "0123456789abcdef0123456789abcdef01234567"},
|
||||
debug.BuildSetting{Key: "vcs.modified", Value: "true"},
|
||||
),
|
||||
want: "0123456789ab-dirty",
|
||||
},
|
||||
{
|
||||
name: "short revision kept as-is",
|
||||
readBuildInfo: buildInfoWith(
|
||||
debug.BuildSetting{Key: "vcs.revision", Value: "abc123"},
|
||||
),
|
||||
want: "abc123",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := resolve(tc.ldflagsCommit, tc.readBuildInfo); got != tc.want {
|
||||
t.Errorf("resolve() = %q, want %q", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user