build-docs.md
Path: @/ (root level - Makefile, .goreleaser.yml)
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.
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.go to establish the complete version reporting chain:
-X ldflagsInstallation 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 injectionbrew install beads./scripts/install.sh - User-friendly build-from-source helperRelease Automation: Goreleaser configuration integrates with GitHub Actions and the release process documented in @/RELEASING.md, ensuring released binaries have full version info.
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:
git rev-parse HEAD gets the full commit hashgit rev-parse --abbrev-ref HEAD gets the current branch namego install using the -X flag (variable assignment)main.Commit and main.Branch package variables@/cmd/bd/version.go at runtimeKey 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:
-buildmode=exe flagThe 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 contextGoreleaser 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 branchInstallation Script (@/scripts/install.sh):
Provides a user-friendly way to build from source with full version info:
go install with the same ldflags pattern as Makefilebd versionVersion Resolution Chain (@/cmd/bd/version.go, lines 116-163):
The version.go file implements functions that retrieve the injected information:
resolveCommitHash() (lines 116-130):
Commit package variable was set via ldflag (most reliable)runtime/debug.ReadBuildInfo() to extract VCS info automatically embedded by go buildresolveBranch() (lines 139-163):
Branch package variable was set via ldflag (most reliable)runtime/debug.ReadBuildInfo() to extract VCS branch infogit symbolic-ref --short HEAD for runtime detectionOutput Formatting (lines 52-58):
bd version 0.29.0 (dev: main@7e70940)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 automaticallyThe solution is to explicitly pass git information as ldflags in all build configurations. This ensures:
make install get full version infogo install ./cmd/bd need to explicitly set ldflags (via Makefile or script)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 variableThese 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
Runtime (fallback via git commands): Current branch of the source directory
Release Process Integration:
The build configuration integrates with @/RELEASING.md:
v0.29.0){{.Commit}} and {{.Branch}} template variablesTesting 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 valuesThese 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:
Dependency Requirements:
git and bash to be available at install timegit and go in PATHAll are standard tools in development and CI/CD environments.
Created and maintained by Nori.