Understanding Bazel Concepts¶
This guide provides a deeper explanation of key Bazel concepts used in this tutorial.
Table of Contents¶
- MODULE.bazel and Bzlmod
- BUILD.bazel Files
- Targets and Labels
- Dependencies
- Caching and Incrementality
MODULE.bazel and Bzlmod¶
Bzlmod is Bazel's modern external dependency management system, replacing the legacy WORKSPACE approach.
Key Benefits:¶
- Version Resolution: Automatic conflict resolution for transitive dependencies
- Reproducibility: Lock files ensure consistent builds across machines
- Modularity: Each module declares its own dependencies
- Simpler Syntax: Cleaner than WORKSPACE's
http_archiveapproach
Structure:¶
module(
name = "my_project", # Your project name
version = "1.0.0", # Semantic version
)
# Declare dependencies
bazel_dep(name = "rules_python", version = "0.31.0")
bazel_dep(name = "rules_go", version = "0.46.0")
Extensions:¶
Extensions allow you to configure rules:
python = use_extension("@rules_python//python/extensions:python.bzl", "python")
python.toolchain(python_version = "3.11")
BUILD.bazel Files¶
BUILD.bazel files define targets — the buildable units in your project.
Common Target Types:¶
py_binary - Python Executable¶
go_binary - Go Executable¶
py_test - Python Test¶
go_test - Go Test¶
Targets and Labels¶
A label uniquely identifies a target in your workspace.
Label Syntax:¶
Examples:
- //go_service:server - The "server" target in the "go_service" package
- //py_service:py_service_test - The test target in "py_service"
- //:all - All targets in the root package
Special Labels:¶
//...- All targets in all packages (recursive)//py_service/...- All targets under py_service (recursive)
Dependencies¶
Direct Dependencies¶
Declared in the deps attribute:
py_binary(
name = "server",
deps = [
requirement("fastapi"), # External pip dependency
"//common:utils", # Internal dependency
],
)
Transitive Dependencies¶
Bazel automatically tracks transitive dependencies. If A depends on B, and B depends on C, Bazel knows A transitively depends on C.
Data Dependencies¶
Use data for runtime files:
Caching and Incrementality¶
How Bazel Caches Work:¶
- Content Hashing: Bazel hashes all inputs (source files, dependencies, build commands)
- Action Graph: Creates a graph of all build actions
- Cache Lookup: Before building, checks if outputs exist for the same inputs
- Incremental Builds: Only rebuilds what changed
Example:¶
# First build - builds everything
$ bazel build //...
# Change only Python code
$ echo "# comment" >> py_service/main.py
# Second build - only rebuilds Python targets
$ bazel build //...
# Go targets are cached and not rebuilt!
Cache Types:¶
Local Cache¶
Stored on disk in ~/.cache/bazel:
Remote Cache (Advanced)¶
Shared cache across teams/CI:
Viewing Cache Stats:¶
Hermetic Builds¶
Bazel ensures hermetic (isolated) builds: - Uses declared dependencies only - Ignores system-installed packages - Same inputs → same outputs (reproducible)
This is why we use:
- rules_python for Python toolchains (not system Python)
- rules_go for Go toolchains (not system Go)
Query Commands¶
Explore your build graph:
# List all targets
bazel query //...
# Find dependencies of a target
bazel query 'deps(//go_service:server)'
# Find what depends on a target
bazel query 'rdeps(//..., //py_service:server)'
# Visualize build graph
bazel query --output=graph //... > graph.dot
dot -Tpng graph.dot -o graph.png
Best Practices¶
- Keep BUILD files small: One package per directory
- Use visibility: Control who can depend on your targets
- Minimize dependencies: Faster builds, clearer design
- Use tests extensively:
bazel test //...is fast - Enable remote cache: Share build artifacts across team
- Pin versions: Use
.bazelversionfor reproducibility