2.1.1 Nodes, Edges & Acyclicity
P.2.4 Five Properties named five big properties: laziness, slicing, hermeticity, incrementality, and parallelism. The structure underneath several of them is simpler: Bazel reads BUILD declarations as a dependency graph of targets. When you request //app:server, Bazel follows edges from that target to everything it needs first. Because those edges have direction and the graph stays acyclic, Bazel can compute reachability, derive a valid prerequisite order, and expose independent work for parallel execution.1,2,3
Starting target
Direct edge via deps
Direct edge via data
Transitive edge via core
Build analysis rejects the loop instead of guessing an order.
Nodes are labels, edges are dependency declarations
The labels from 0.2.1 Label Anatomy name the graph's nodes. The attribute roles from 0.3.3 Attributes & Semantic Roles explain what each edge means. In a tiny example like this, //app:server depends on another target for compiled code and on a file needed at runtime:
java_library(
name = "core",
srcs = ["Core.java"],
)
java_binary(
name = "server",
srcs = ["Main.java"],
deps = [":core"],
data = ["config.yaml"],
)
deps says "this target needs another target's output." srcs says "this rule reads these source files." data says "this file or target must be present when the binary or test runs." They are all dependency declarations, but they are not interchangeable.1,4
Direct edges are local, transitive reachability is global
A direct dependency is one edge away. A transitive dependency is reached by following a chain of edges. If //app:server depends on //lib:core and //lib:core depends on //config:defaults, then //app:server transitively depends on //config:defaults. Bazel walks that full transitive closure when deciding what must be up to date for the requested target.1,2
That is why BUILD files should name actual direct dependencies and stop there. You do not copy the whole transitive closure into every rule, because Bazel already computes it. But you also do not get to omit a direct edge just because some intermediate library happens to drag it in today. That shortcut creates fragile builds that break as soon as the intermediate target is refactored, which is the failure mode behind many cases in 2.1.4 Common Dependency Issues.1,5
Once you have this graph, Bazel can ignore unreachable branches entirely. That reachability story is the subject of 2.1.2 Laziness & Slicing. The graph also gets richer than what you typed once Bazel adds implicit rule and toolchain edges, which 2.1.3 Dependency Types covers.6
Acyclic means no dependency loops
A buildable target graph has to stay acyclic. If target analysis or configuration encounters a dependency loop, Bazel reports it as an error rather than guessing an order.7
bazel query is slightly looser because it inspects the unconfigured target graph: query algorithms can still operate in the presence of some erroneous cycles, so query output is useful for inspection but not the final word on what a build accepts.6
In practice, breaking a cycle usually means changing the dependency boundary, not adding flags. Extract shared code into a third library, split an API from its implementation, or move data and resources so the edge only points one way.8
This is only the first graph
This article is about the target graph: the directed acyclic graph over targets that BUILD declarations create and that bazel query can inspect.3,9 Analysis later turns reachable targets into build actions in 2.2.1 Loading, Analysis & Execution, and Bazel's incremental engine tracks a wider dependency graph of evaluation nodes in 2.4.1 Skyframe & Incrementality.10
To look at the target graph without building anything, start with:
bazel query --noimplicit_deps 'deps(//app:server)'
bazel query --noimplicit_deps 'deps(//app:server)' --output graph
The first command prints the reachable closure. The second emits the same structure in Graphviz form. The beginner command tour comes in 2.1.5 Inspecting the Graph — Query Preview. The larger analytical patterns come later in 5.3 Graph Analysis.9,11
The target graph is Bazel's first real mental model: labels name nodes, attributes declare directed edges, transitive closure tells Bazel what the request actually needs, and acyclicity keeps the order valid. The rest of this level explains what Bazel does with that structure: it slices the reachable subgraph in 2.1.2 Laziness & Slicing, transforms it into actions in 2.2.1 Loading, Analysis & Execution, and tracks it incrementally in 2.4.1 Skyframe & Incrementality.1,2,10
Check your understanding · 3 questions
1.Match each dependency attribute to its semantic role:
Drag each answer onto the matching prompt, or click an answer and then click a prompt
2.Why must a BUILD file declare a direct dependency explicitly, even when a transitive path already provides it?
Select one answer
3.True or false: properties of the Bazel target graph.
Choose True or False for each sentence
bazel query operates on the configured target graph and can definitively validate that a cycle-free build is correct.Footnotes
-
Dependencies — dependency relation as a DAG over targets, direct vs transitive dependencies, and the semantic roles of
srcs,deps, anddata↩1 ↩2 ↩3 ↩4 ↩5 -
Artifact-Based Build Systems —
BUILDfiles define artifacts and their dependencies, and Bazel uses that graph to compute transitive prerequisites and build order ↩1 ↩2 ↩3 -
Labels — the directed acyclic graph over targets is the target graph or build dependency graph, the domain of
bazel query↩1 ↩2 -
Dependencies — generic dependency attributes and how Bazel distinguishes build-time and runtime inputs ↩
-
Dependency Management — transitive dependency examples and why strict direct-dependency declarations matter as a codebase grows ↩
-
Query language — build dependency graphs should be acyclic, query can expose implicit dependencies, and
bazel queryoperates on the target graph abstraction ↩1 ↩2 -
Rules — target analysis follows declared dependency edges, and cycles in the dependency graph of targets are errors ↩
-
Introduction to the dependency graph — why cycles break dependency ordering and the usual refactoring move of restoring a one-way DAG ↩
-
Query guide —
deps()semantics and--output graphfor visualizing dependency structure ↩1 ↩2 -
Skyframe — Bazel's wider dependency graph of evaluation nodes and the DAG formed while evaluating a build request ↩1 ↩2
-
Query quickstart —
--noimplicit_depsfor a cleaner beginner view of the graph ↩