Build tooling
ADR: Choosing Bazel over Gradle for Building Go Applications
Status: Accepted
Context
Our collaborators build and maintain several Go-based services, many of which share dependencies and communicate via protobuf-based APIs. These services are part of a larger ecosystem that includes components written in other languages, such as Java and TypeScript. To improve build consistency, speed, and reproducibility across projects, we evaluated multiple build systems for long-term standardization.
After assessing Bazel and Gradle (with Go support through community plugins), we decided to adopt Bazel as our primary build system for Go applications. The decision was driven by the need for hermetic builds, deterministic outputs, efficient caching, and first-class support for protobuf and gRPC integration.
Decision
The team will use Bazel as the standard build system for Go projects. We will rely on rules_go for Go integration, rules_proto for protobuf generation, and Gazelle for automatic BUILD file generation. Gradle and related Go plugins will not be used for production builds.
Consequences
Advantages
- Reproducible and isolated builds: Bazel’s sandboxing model ensures that builds are consistent across environments and reproducible over time.
- Controlled toolchains: With
rules_go, the Go compiler and dependencies are versioned and pinned, removing discrepancies between local and CI environments. - Scalable build performance: Bazel’s remote caching and execution capabilities reduce build and test durations, especially in CI pipelines.
- Support for multi-language repositories: Bazel provides a unified way to build Go, Java, TypeScript, and protobuf artifacts within a single repository.
- Automated build metadata management: Gazelle keeps BUILD files in sync with source structure and module definitions.
- Protobuf integration: Using
rules_protoandrules_go, teams can define.protocontracts once and generate consistent server and client code. This approach standardizes server-to-server communication, ensures type safety, and simplifies maintaining cross-service APIs.
Trade-offs
- Learning curve: Engineers will need time to become comfortable with Bazel’s concepts, including WORKSPACE/BUILD files and rule definitions.
- Integration complexity: Some third-party Go modules may require additional configuration for Bazel compatibility.
- Build verbosity: BUILD files and rules can feel more complex compared to a simple
go buildcommand or small Gradle script.
Alternatives Considered
- Gradle with Go Plugins (e.g., Gogradle)
- Pros: Familiar syntax for teams already using Gradle; flexible scripting; native integration with other Gradle tasks.
- Cons: Plugins for Go are community-maintained with inconsistent activity levels; lack of hermetic builds and reproducible toolchains; weaker support for large-scale or multi-language projects.
- Native Go Tooling (
go buildwith modules)
- Pros: Simple and idiomatic for Go developers; minimal external tooling.
- Cons: No native support for remote caching or hermetic builds; harder to integrate consistently across polyglot repositories; more complex to maintain large-scale build pipelines.
Rationale
The decision to adopt Bazel was based on the following priorities:
Hermetic, reproducible builds Bazel’s sandboxed execution guarantees that builds only depend on declared inputs. This eliminates environment drift and ensures consistency between developer machines and CI agents.
Deterministic toolchain management
rules_goallows us to fix the Go toolchain version and dependency graph within Bazel, producing identical binaries across environments.Remote caching and execution Bazel’s native caching and distributed execution significantly reduce build and test times across large teams and CI environments.
Multi-language and monorepo compatibility Bazel supports different languages under a unified build graph. This reduces friction when combining Go, protobufs, and frontend code within the same repository.
Protobuf and gRPC integration With
rules_protoandrules_go, service contracts defined in.protofiles are shared between teams and languages. The same definitions can generate Go clients, servers, and gRPC stubs, standardizing communication between services and minimizing integration issues.Automated build maintenance Gazelle automatically generates and updates BUILD files based on source structure, reducing manual upkeep.
Mature ecosystem The
rules_go,rules_proto, and Gazelle projects are actively maintained and widely used in production environments, providing stability and community support.
Why Not Gradle?
Gradle’s Go support depends on third-party plugins that vary in quality and maintenance. These plugins lack the deep integration and hermetic guarantees provided by Bazel. While Gradle offers an expressive build language and good support for Java ecosystems, its model is not optimized for reproducible, sandboxed builds across multiple languages.
Migration Plan
- Proof of Concept: Migrate a small service to Bazel using
rules_go,rules_proto, and Gazelle to verify reproducibility, caching, and protobuf integration. - Pilot Training: Provide documentation and training sessions to familiarize engineers with Bazel conventions, workspace setup, and common rules.
- Incremental Rollout: Gradually migrate services while maintaining Gradle or native Go builds during the transition period.
- CI Integration: Configure and optimize Bazel’s remote cache and execution environment for production pipelines.
Operational Considerations
- Provide templates, example WORKSPACE and BUILD files, and editor support to ease adoption.
- Establish standards for managing dependencies and regenerating BUILD files with Gazelle.
- Track known issues and patterns related to
rules_goandrules_prototo help teams troubleshoot consistently.
Appendices
Diagrams
High-Level Build Flow (Bazel)
flowchart LR
subgraph Developer
A[Source: pkg/, cmd/, proto/, go.mod] --> B[Gazelle]
B --> C[BUILD.bazel]
end
subgraph Bazel
C --> D[Analysis Phase]
D --> E[Execution Phase: sandboxed compile/test/proto-gen]
E --> F[Local cache]
E --> G[Remote cache / remote execution]
end
G --> CI[CI agents reuse cached artifacts]
F --> Developer
Protobuf-Based Communication Flow
sequenceDiagram
participant ServiceA
participant Proto
participant ServiceB
ServiceA->>Proto: Define contract (service + messages)
Proto-->>ServiceA: Generate Go server and client stubs
Proto-->>ServiceB: Generate Go server and client stubs
ServiceA->>ServiceB: gRPC or HTTP communication using protobuf
Alternative (Gradle + Go Plugin)
flowchart LR
Dev[Developer sources] --> Gradle[Gradle build scripts + Go plugin]
Gradle --> LocalGo[Invokes go tool or manages GOPATH/modules]
LocalGo --> Artifacts
Gradle -.-> RemoteCache[Optional Gradle cache]
Decision Log
- 2025-11-04: Decision recorded and accepted, including protobuf integration considerations.