From f85febc7902956fd5715e580d302fae49aac4616 Mon Sep 17 00:00:00 2001 From: Selim Date: Sat, 1 Aug 2026 01:41:55 +0300 Subject: [PATCH] fix: wire release build info into the version the binaries report MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GoReleaser injected -X main.version/commit/date, but package main only holds func main() — the values were dropped and every released binary reported "Commit: dev, Built: unknown". The desktop build passed no ldflags at all, and pkg/api hardcoded its own copy of the version string, so three places had to be edited by hand for each release. - Add internal/buildinfo as the single source, read by the CLI and the API - Point GoReleaser and the desktop (wails) builds at it - Fall back to the toolchain's embedded build info when no ldflags are given, so `go install` and local builds report the real revision and build time - Strip build metadata ("+dirty") in the update check, which otherwise parsed 0.4.10+dirty as 0.4.0 Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/release.yml | 40 ++++++++-- .goreleaser.yml | 8 +- cmd/version.go | 17 +++-- internal/buildinfo/buildinfo.go | 108 +++++++++++++++++++++++++++ internal/buildinfo/buildinfo_test.go | 71 ++++++++++++++++++ pkg/api/api.go | 7 +- 6 files changed, 233 insertions(+), 18 deletions(-) create mode 100644 internal/buildinfo/buildinfo.go create mode 100644 internal/buildinfo/buildinfo_test.go diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dcc3c5a..73dbe15 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -69,7 +69,13 @@ jobs: - name: Build Desktop App working-directory: ./desktop run: | - wails build -platform darwin/amd64 -clean + # Stamp the same build info the CLI gets from GoReleaser. + VERSION=${GITHUB_REF#refs/tags/v} + BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) + LDFLAGS="-X github.com/ganbitlabs/walgo/internal/buildinfo.version=${VERSION}" + LDFLAGS="${LDFLAGS} -X github.com/ganbitlabs/walgo/internal/buildinfo.commit=${GITHUB_SHA}" + LDFLAGS="${LDFLAGS} -X github.com/ganbitlabs/walgo/internal/buildinfo.buildDate=${BUILD_DATE}" + wails build -platform darwin/amd64 -clean -ldflags "$LDFLAGS" - name: Sign and Package App working-directory: ./desktop/build/bin @@ -121,7 +127,13 @@ jobs: - name: Build Desktop App working-directory: ./desktop run: | - wails build -platform darwin/arm64 -clean + # Stamp the same build info the CLI gets from GoReleaser. + VERSION=${GITHUB_REF#refs/tags/v} + BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) + LDFLAGS="-X github.com/ganbitlabs/walgo/internal/buildinfo.version=${VERSION}" + LDFLAGS="${LDFLAGS} -X github.com/ganbitlabs/walgo/internal/buildinfo.commit=${GITHUB_SHA}" + LDFLAGS="${LDFLAGS} -X github.com/ganbitlabs/walgo/internal/buildinfo.buildDate=${BUILD_DATE}" + wails build -platform darwin/arm64 -clean -ldflags "$LDFLAGS" - name: Sign and Package App working-directory: ./desktop/build/bin @@ -173,7 +185,13 @@ jobs: - name: Build Desktop App working-directory: ./desktop run: | - wails build -platform windows/amd64 -clean + # Stamp the same build info the CLI gets from GoReleaser. + VERSION=${GITHUB_REF#refs/tags/v} + BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) + LDFLAGS="-X github.com/ganbitlabs/walgo/internal/buildinfo.version=${VERSION}" + LDFLAGS="${LDFLAGS} -X github.com/ganbitlabs/walgo/internal/buildinfo.commit=${GITHUB_SHA}" + LDFLAGS="${LDFLAGS} -X github.com/ganbitlabs/walgo/internal/buildinfo.buildDate=${BUILD_DATE}" + wails build -platform windows/amd64 -clean -ldflags "$LDFLAGS" - name: Package App working-directory: ./desktop/build/bin @@ -218,7 +236,13 @@ jobs: - name: Build Desktop App working-directory: ./desktop run: | - wails build -platform windows/arm64 -clean + # Stamp the same build info the CLI gets from GoReleaser. + VERSION=${GITHUB_REF#refs/tags/v} + BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) + LDFLAGS="-X github.com/ganbitlabs/walgo/internal/buildinfo.version=${VERSION}" + LDFLAGS="${LDFLAGS} -X github.com/ganbitlabs/walgo/internal/buildinfo.commit=${GITHUB_SHA}" + LDFLAGS="${LDFLAGS} -X github.com/ganbitlabs/walgo/internal/buildinfo.buildDate=${BUILD_DATE}" + wails build -platform windows/arm64 -clean -ldflags "$LDFLAGS" - name: Package App working-directory: ./desktop/build/bin @@ -268,7 +292,13 @@ jobs: - name: Build Desktop App working-directory: ./desktop run: | - wails build -platform linux/amd64 -clean + # Stamp the same build info the CLI gets from GoReleaser. + VERSION=${GITHUB_REF#refs/tags/v} + BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) + LDFLAGS="-X github.com/ganbitlabs/walgo/internal/buildinfo.version=${VERSION}" + LDFLAGS="${LDFLAGS} -X github.com/ganbitlabs/walgo/internal/buildinfo.commit=${GITHUB_SHA}" + LDFLAGS="${LDFLAGS} -X github.com/ganbitlabs/walgo/internal/buildinfo.buildDate=${BUILD_DATE}" + wails build -platform linux/amd64 -clean -ldflags "$LDFLAGS" - name: Package App working-directory: ./desktop/build/bin diff --git a/.goreleaser.yml b/.goreleaser.yml index 9c447ad..e9b9807 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -19,11 +19,13 @@ builds: - amd64 - arm64 # All OS + all architectures supported + # main only holds func main(); the version lives in internal/buildinfo, which + # the CLI, the API and the desktop app all read. ldflags: - -s -w - - -X main.version={{.Version}} - - -X main.commit={{.Commit}} - - -X main.date={{.Date}} + - -X github.com/ganbitlabs/walgo/internal/buildinfo.version={{.Version}} + - -X github.com/ganbitlabs/walgo/internal/buildinfo.commit={{.FullCommit}} + - -X github.com/ganbitlabs/walgo/internal/buildinfo.buildDate={{.Date}} archives: - id: walgo-archives diff --git a/cmd/version.go b/cmd/version.go index 70ecf0c..93a0251 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -10,17 +10,17 @@ import ( "strings" "time" + "github.com/ganbitlabs/walgo/internal/buildinfo" "github.com/ganbitlabs/walgo/internal/ui" "github.com/spf13/cobra" ) +// Build details, resolved from ldflags or the embedded build info. +// See internal/buildinfo. var ( - // Version will be set during build time via ldflags - Version = "0.4.0" - // GitCommit will be set during build time via ldflags - GitCommit = "dev" - // BuildDate will be set during build time via ldflags - BuildDate = "unknown" + Version = buildinfo.Version() + GitCommit = buildinfo.Commit() + BuildDate = buildinfo.BuildDate() ) const ( @@ -121,7 +121,10 @@ func checkForUpdates() { func compareSemver(a, b string) int { parse := func(input string) [3]int { var result [3]int - clean := strings.SplitN(input, "-", 2)[0] + // Drop pre-release ("-rc1") and build metadata ("+dirty"), which the + // build-info fallback appends when the working tree was modified. + clean, _, _ := strings.Cut(input, "-") + clean, _, _ = strings.Cut(clean, "+") parts := strings.Split(clean, ".") for i := 0; i < len(result) && i < len(parts); i++ { if n, err := strconv.Atoi(parts[i]); err == nil { diff --git a/internal/buildinfo/buildinfo.go b/internal/buildinfo/buildinfo.go new file mode 100644 index 0000000..7503f35 --- /dev/null +++ b/internal/buildinfo/buildinfo.go @@ -0,0 +1,108 @@ +// Package buildinfo is the single source of the version reported by the CLI, +// the API and the desktop app. +package buildinfo + +import ( + "runtime/debug" + "strings" + "time" +) + +// Values injected at build time. GoReleaser and the desktop build set these +// through -X flags; see .goreleaser.yml and .github/workflows/release.yml. +// +// Keep releaseVersion in sync with the current release: it is what a plain +// `go build ./...` reports, and what tags are cut from. +var ( + version string + commit string + buildDate string +) + +const ( + releaseVersion = "0.4.0" + unknownCommit = "dev" + unknownDate = "unknown" +) + +// Version returns the release version, without a leading "v". +func Version() string { + if version != "" { + return strings.TrimPrefix(version, "v") + } + + // `go install github.com/ganbitlabs/walgo@v1.2.3` records the version in the + // build info even though no ldflags were passed. + if info, ok := debug.ReadBuildInfo(); ok { + if v := info.Main.Version; v != "" && v != "(devel)" { + return strings.TrimPrefix(v, "v") + } + } + + return releaseVersion +} + +// Commit returns the git revision the binary was built from, suffixed with +// "-dirty" when the working tree had uncommitted changes. +func Commit() string { + if commit != "" { + return commit + } + + revision, modified := vcsInfo() + if revision == "" { + return unknownCommit + } + if modified { + return revision + "-dirty" + } + + return revision +} + +// BuildDate returns when the binary was built, in RFC 3339 form. +func BuildDate() string { + if buildDate != "" { + return buildDate + } + + if _, _, buildTime := vcsSettings(); buildTime != "" { + return buildTime + } + + return unknownDate +} + +// vcsInfo reports the revision recorded by the Go toolchain and whether the +// working tree was dirty. Both are empty for builds outside a repository. +func vcsInfo() (revision string, modified bool) { + revision, dirty, _ := vcsSettings() + return revision, dirty == "true" +} + +// vcsSettings extracts the vcs.* build settings the toolchain stamps into +// binaries built from a repository. +func vcsSettings() (revision, modified, buildTime string) { + info, ok := debug.ReadBuildInfo() + if !ok { + return "", "", "" + } + + for _, setting := range info.Settings { + switch setting.Key { + case "vcs.revision": + revision = setting.Value + case "vcs.modified": + modified = setting.Value + case "vcs.time": + // Normalize so it matches the format used by the ldflags path. + if parsed, err := time.Parse(time.RFC3339, setting.Value); err == nil { + buildTime = parsed.UTC().Format(time.RFC3339) + } else { + buildTime = setting.Value + } + } + } + + return revision, modified, buildTime +} diff --git a/internal/buildinfo/buildinfo_test.go b/internal/buildinfo/buildinfo_test.go new file mode 100644 index 0000000..11be30e --- /dev/null +++ b/internal/buildinfo/buildinfo_test.go @@ -0,0 +1,71 @@ +package buildinfo + +import ( + "strings" + "testing" +) + +func TestVersionPrefersLdflags(t *testing.T) { + defer restore(version, commit, buildDate) + + version = "v1.2.3" + if got := Version(); got != "1.2.3" { + t.Errorf("Version() = %q, want 1.2.3 (leading v stripped)", got) + } +} + +func TestVersionFallsBack(t *testing.T) { + defer restore(version, commit, buildDate) + + version = "" + got := Version() + if got == "" { + t.Fatal("Version() is empty without ldflags") + } + if strings.HasPrefix(got, "v") { + t.Errorf("Version() = %q, should not carry a leading v", got) + } +} + +func TestCommitPrefersLdflags(t *testing.T) { + defer restore(version, commit, buildDate) + + commit = "abc1234" + if got := Commit(); got != "abc1234" { + t.Errorf("Commit() = %q, want abc1234", got) + } +} + +func TestCommitFallsBack(t *testing.T) { + defer restore(version, commit, buildDate) + + commit = "" + // Tests build from the repository, so the toolchain stamps a revision; a + // build from outside one reports the placeholder instead. + if got := Commit(); got == "" { + t.Error("Commit() is empty without ldflags") + } +} + +func TestBuildDatePrefersLdflags(t *testing.T) { + defer restore(version, commit, buildDate) + + buildDate = "2026-08-01T00:00:00Z" + if got := BuildDate(); got != "2026-08-01T00:00:00Z" { + t.Errorf("BuildDate() = %q", got) + } +} + +func TestBuildDateFallsBack(t *testing.T) { + defer restore(version, commit, buildDate) + + buildDate = "" + if got := BuildDate(); got == "" { + t.Error("BuildDate() is empty without ldflags") + } +} + +// restore puts the injected build values back after a test mutates them. +func restore(v, c, d string) { + version, commit, buildDate = v, c, d +} diff --git a/pkg/api/api.go b/pkg/api/api.go index bd6bf4d..09a2da4 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -13,6 +13,7 @@ import ( "time" "github.com/ganbitlabs/walgo/internal/ai" + "github.com/ganbitlabs/walgo/internal/buildinfo" "github.com/ganbitlabs/walgo/internal/compress" "github.com/ganbitlabs/walgo/internal/config" "github.com/ganbitlabs/walgo/internal/deployer" @@ -560,9 +561,9 @@ type VersionResult struct { // GetVersion returns current version information func GetVersion() VersionResult { return VersionResult{ - Version: "0.4.0", - GitCommit: "dev", - BuildDate: "unknown", + Version: buildinfo.Version(), + GitCommit: buildinfo.Commit(), + BuildDate: buildinfo.BuildDate(), } }