# 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.go` to 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 `-X` ldflags - 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 source - `go 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: ```makefile 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: 1. Uses bash subshell to extract git information at install time 2. `git rev-parse HEAD` gets the full commit hash 3. `git rev-parse --abbrev-ref HEAD` gets the current branch name 4. Passes both as ldflags to `go install` using the `-X` flag (variable assignment) 5. The ldflags set `main.Commit` and `main.Branch` package variables 6. These variables are then retrieved by functions in `@/cmd/bd/version.go` at 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: ```yaml ldflags: - -s -w - -X main.Version={{.Version}} - -X main.Build={{.ShortCommit}} - -X main.Commit={{.Commit}} - -X main.Branch={{.Branch}} ``` Platform configurations: 1. **bd-linux-amd64** (lines 12-26): Linux 64-bit Intel 2. **bd-linux-arm64** (lines 28-44): Linux 64-bit ARM (Apple Silicon support) 3. **bd-darwin-amd64** (lines 46-60): macOS 64-bit Intel 4. **bd-darwin-arm64** (lines 62-76): macOS 64-bit ARM (M1/M2/M3) 5. **bd-windows-amd64** (lines 78-95): Windows 64-bit Intel with additional `-buildmode=exe` flag 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 install` with 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: 1. **resolveCommitHash()** (lines 116-130): - First checks if `Commit` package variable was set via ldflag (most reliable) - Falls back to `runtime/debug.ReadBuildInfo()` to extract VCS info automatically embedded by `go build` - Returns empty string if neither source has data 2. **resolveBranch()** (lines 139-163): - First checks if `Branch` package 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 HEAD` for runtime detection - Returns empty string if none available 3. **Output Formatting** (lines 52-58): - Displays commit and branch in human-readable format: `bd version 0.29.0 (dev: main@7e70940)` ### 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.branch - `go 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 install` get full version info - Users who run `go install ./cmd/bd` need 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 variable - `main.Build` → Build variable - `main.Commit` → Commit variable - `main.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`: 1. Version tag is pushed to GitHub (e.g., `v0.29.0`) 2. GitHub Actions/goreleaser automatically builds binaries for all platforms 3. Goreleaser uses git metadata to populate `{{.Commit}}` and `{{.Branch}}` template variables 4. Released binaries include full version info 5. 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 prioritized - `TestResolveBranch`: Verifies ldflag values are prioritized - `TestVersionOutputWithCommitAndBranch`: 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 `git` and `bash` to be available at install time - **Goreleaser**: Requires git tags and repository metadata (automatic in CI/CD) - **Scripts**: Require `git` and `go` in PATH All are standard tools in development and CI/CD environments. Created and maintained by Nori.