Understanding Bazel's Build Model
Determinism and Hermeticity
Bazel's core philosophy is deterministic builds: the same input always yields the same output. It enforces hermeticity by sandboxing build actions and tracking all dependencies explicitly.
Incrementality and Caching
Bazel reuses outputs from previous builds using content-based hashing. Problems arise when rules are misconfigured or tools produce inconsistent outputs, leading to invalid cache hits or misses.
Common Enterprise-Level Issues
1. Non-Deterministic Outputs
Some build tools write timestamps or absolute paths into outputs, violating hermeticity and causing cache invalidation.
jar cf build/libs/myapp.jar *.class
Fix: Strip timestamps or use --normalize
options if supported.
2. Action Conflicts
Two targets trying to write to the same output file cause action conflicts.
Error: file already being written by //lib:foo and //lib:bar
Solution: Refactor targets to write to unique paths or consolidate outputs.
3. Remote Cache Inconsistencies
When remote cache is shared across platforms or branches without isolation, corrupted builds may occur.
Use cache keys with platform suffixes or separate instances per major branch.
4. Genrule Side Effects
Genrules can produce side effects outside the declared outputs, leading to unreliable builds.
genrule( name = "generate", outs = ["out.txt"], cmd = "./generate.sh > out.txt; cp out.txt ../somewhere" )
Fix: Avoid accessing directories outside declared outputs.
Diagnostics and Logging
1. Enable Verbose Logs
Use flags for detailed analysis of build actions and cache keys.
bazel build --experimental_execution_log_file=log.json --sandbox_debug //target
2. Use Bazel Query and cquery
Inspect the dependency graph or rule attributes:
bazel query "deps(//myapp:target)" --output=graph bazel cquery "//myapp:target" --output=starlark
3. Analyze Output Base
Explore Bazel's output base to diagnose lingering or conflicting build artifacts:
bazel info output_base ls -l $OUTPUT_BASE/execroot/
Advanced Fixes and Techniques
1. Enforce Output Consistency
Use bazel-diff
or third-party reproducibility tools to detect output drift.
2. Declare All Inputs and Tools
Ensure all tools used in genrules are declared in tools
or inputs
:
genrule( name = "compile", srcs = ["schema.json"], tools = ["//tools:jsonc"], outs = ["output.h"], cmd = "$(location //tools:jsonc) $(SRCS) > $@" )
3. Fix External Dependency Flakiness
Lock external repositories with commit hashes and mirror URLs to avoid resolution failures.
http_archive( name = "zlib", urls = ["https://mirror.example.com/zlib.tar.gz"], sha256 = "..." )
Remote Execution Considerations
1. Platform Mismatch
Set execution platform flags to avoid cache poisoning across incompatible OS or architectures.
--platforms=@local_config_platform//:host
2. Missing Toolchain Declarations
Remote builds fail when toolchains are implicitly assumed. Use toolchains
attribute explicitly in rules.
3. Monitor Execution Logs
Use remote build event protocol (BEP) logs to visualize action execution times and remote cache hit/miss ratios.
Best Practices
- Write hermetic rules with declared inputs, outputs, and tools.
- Isolate remote cache per branch or platform.
- Use
bazel analyze-profile
to tune CI builds. - Enforce strict visibility to prevent dependency sprawl.
- Regularly audit genrules and legacy macro behaviors.
Conclusion
Bazel's high-performance and deterministic builds are invaluable for scaling large codebases. However, its power comes with complexity. Subtle misconfigurations can cascade into opaque failures or performance degradation. By adopting hermetic design, strict dependency declaration, and vigilant remote execution hygiene, teams can harness Bazel effectively in enterprise CI/CD workflows and maintain long-term build reliability.
FAQs
1. Why are my builds flaky even with remote caching enabled?
Likely due to non-hermetic rules or mismatched platforms. Ensure output consistency and platform tagging.
2. How do I debug cache misses in Bazel?
Use --experimental_execution_log_file
and bazel aquery
to trace input digests and cache keys.
3. Can I use Bazel with Docker for full isolation?
Yes. Use rules_docker
or run actions in containerized toolchains for reproducibility and OS-level isolation.
4. How do I handle external dependencies safely?
Pin versions with commit hashes, provide mirror URLs, and verify checksums to avoid repo flakiness.
5. What is the best way to track build performance regressions?
Use bazel analyze-profile
and remote build event protocol logs to compare build time deltas over time.