8.9 KiB
Noridoc: Build and Version Infrastructure
Path: @/ (root level - Makefile, .goreleaser.yml)
Overview
The beads project uses a coordinated build and version reporting system that ensures all installation methods (direct go install, make install, GitHub releases, Homebrew, npm) produce binaries with complete version information including git commit hash and branch name.
This infrastructure is critical for debugging, auditing, and user support - it allows anyone to identify exactly what code their binary was built from.
How it fits into the larger codebase
-
Build Entry Points: The Makefile and .goreleaser.yml are the authoritative build configurations that users and CI/CD systems interact with. They control how version information flows into binaries.
-
Version Pipeline: These files work with
@/cmd/bd/version.goto establish the complete version reporting chain:- Build time: Extract git info via shell commands (Makefile) or goreleaser templates
- Compilation: Pass info to Go compiler via
-Xldflags - Runtime: Resolve functions in version.go retrieve and display the info
-
Installation Methods: The build configuration enables multiple installation paths while maintaining version consistency:
make install- Used by developers building from sourcego install ./cmd/bd- Direct Go installation with embedded ldflag injection- GitHub releases - Goreleaser-built binaries for all platforms
- Homebrew - Pre-built binaries installed via
brew install bd - npm - Node.js package that downloads pre-built binaries via postinstall hook
./scripts/install.sh- User-friendly build-from-source helper
-
Release Automation: Goreleaser configuration integrates with GitHub Actions and the release process documented in
@/RELEASING.md, ensuring released binaries have full version info.
Core Implementation
Makefile (@/Makefile, lines 37-41 - install target):
The install target is the primary development build mechanism:
install:
@echo "Installing bd to $$(go env GOPATH)/bin..."
@bash -c 'commit=$$(git rev-parse HEAD 2>/dev/null || echo ""); \
branch=$$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo ""); \
go install -ldflags="-X main.Commit=$$commit -X main.Branch=$$branch" ./cmd/bd'
How it works:
- Uses bash subshell to extract git information at install time
git rev-parse HEADgets the full commit hashgit rev-parse --abbrev-ref HEADgets the current branch name- Passes both as ldflags to
go installusing the-Xflag (variable assignment) - The ldflags set
main.Commitandmain.Branchpackage variables - These variables are then retrieved by functions in
@/cmd/bd/version.goat runtime
Key implementation detail: The original target depended on build, but this was removed because go install is sufficient and handles compilation itself.
Goreleaser Configuration (@/.goreleaser.yml, lines 11-95):
Goreleaser builds binaries for all platforms and sets version information via ldflags. Each of the 5 build configurations uses identical ldflag patterns:
ldflags:
- -s -w
- -X main.Version={{.Version}}
- -X main.Build={{.ShortCommit}}
- -X main.Commit={{.Commit}}
- -X main.Branch={{.Branch}}
Platform configurations:
- bd-linux-amd64 (lines 12-26): Linux 64-bit Intel
- bd-linux-arm64 (lines 28-44): Linux 64-bit ARM (Apple Silicon support)
- bd-darwin-amd64 (lines 46-60): macOS 64-bit Intel
- bd-darwin-arm64 (lines 62-76): macOS 64-bit ARM (M1/M2/M3)
- bd-windows-amd64 (lines 78-95): Windows 64-bit Intel with additional
-buildmode=exeflag
The ldflags explained:
-s -w: Strip debug symbols to reduce binary size-X main.Version: Semantic version (e.g., "0.29.0") from git tag-X main.Build: Short commit hash for quick reference-X main.Commit: Full commit hash for precise identification-X main.Branch: Branch name for build context
Goreleaser template variables:
{{.Version}}: The release version from git tag{{.ShortCommit}}: First 7 characters of commit (used for Build variable){{.Commit}}: Full commit hash{{.Branch}}: Current git branch
Installation Script (@/scripts/install.sh):
Provides a user-friendly way to build from source with full version info:
- Extracts git commit and branch
- Calls
go installwith the same ldflags pattern as Makefile - Immediately verifies installation by running
bd version
Version Resolution Chain (@/cmd/bd/version.go, lines 116-163):
The version.go file implements functions that retrieve the injected information:
-
resolveCommitHash() (lines 116-130):
- First checks if
Commitpackage variable was set via ldflag (most reliable) - Falls back to
runtime/debug.ReadBuildInfo()to extract VCS info automatically embedded bygo build - Returns empty string if neither source has data
- First checks if
-
resolveBranch() (lines 139-163):
- First checks if
Branchpackage variable was set via ldflag (most reliable) - Falls back to
runtime/debug.ReadBuildInfo()to extract VCS branch info - Falls back to
git symbolic-ref --short HEADfor runtime detection - Returns empty string if none available
- First checks if
-
Output Formatting (lines 52-58):
- Displays commit and branch in human-readable format:
bd version 0.29.0 (dev: main@7e70940)
- Displays commit and branch in human-readable format:
Things to Know
Critical Design Decision - Why Explicit Ldflags:
The Go toolchain (as of 1.18+) can automatically embed VCS information when compiling with go build, but this does NOT happen with go install. This creates an asymmetry:
go build ./cmd/bd→ automatically embeds vcs.revision and vcs.branchgo install ./cmd/bd→ does NOT embed VCS info automatically
The solution is to explicitly pass git information as ldflags in all build configurations. This ensures:
- Users who run
make installget full version info - Users who run
go install ./cmd/bdneed to explicitly set ldflags (via Makefile or script) - Released binaries from goreleaser have full version info (handled by goreleaser templates)
- The version command is consistent regardless of installation method
Issue #503 Root Cause:
The original system relied on Go's automatic VCS embedding which only works with go build. When released binaries (built via goreleaser) or installed binaries (via go install) came without explicit ldflags, the bd version command couldn't report commit and branch information.
The fix adds explicit ldflag injection at all build points, creating a reliable pipeline independent of Go's automatic VCS embedding feature.
Ldflag Variable Names:
The variables in @/cmd/bd/version.go (lines 15-23) must match the ldflag paths in build configurations:
main.Version→ Version variablemain.Build→ Build variablemain.Commit→ Commit variablemain.Branch→ Branch variable
These are fully qualified with the package name (main) because the ldflag syntax is: -X package.Variable=value
Build-Time vs Runtime Information:
-
Build-Time (injected via ldflags): Git commit and branch at the moment of compilation
- Most reliable and consistent
- Does not change after binary is created
- Reflects the exact code in the binary
-
Runtime (fallback via git commands): Current branch of the source directory
- Used only if build-time info is not available
- Can differ from build-time info (e.g., if working directory changes)
- Useful for development but less reliable
Release Process Integration:
The build configuration integrates with @/RELEASING.md:
- Version tag is pushed to GitHub (e.g.,
v0.29.0) - GitHub Actions/goreleaser automatically builds binaries for all platforms
- Goreleaser uses git metadata to populate
{{.Commit}}and{{.Branch}}template variables - Released binaries include full version info
- Users installing via any channel get consistent version reporting
Testing Version Information:
The test file @/cmd/bd/version_test.go includes:
TestResolveCommitHash: Verifies ldflag values are prioritizedTestResolveBranch: Verifies ldflag values are prioritizedTestVersionOutputWithCommitAndBranch: Verifies output formatting with real values
These tests simulate build-time injection by directly setting the package variables, ensuring the resolution chain works correctly.
Multi-Platform Consistency:
All 5 goreleaser build configurations use identical ldflag patterns. This ensures:
- macOS (Intel and ARM) binaries have full version info
- Linux (Intel and ARM) binaries have full version info
- Windows binaries have full version info
- Users on any platform have equal access to version information
Dependency Requirements:
- Makefile: Requires
gitandbashto be available at install time - Goreleaser: Requires git tags and repository metadata (automatic in CI/CD)
- Scripts: Require
gitandgoin PATH
All are standard tools in development and CI/CD environments.
Created and maintained by Nori.