2.1.3 Dependency Types

recommended

The edges in 2.1.1 Nodes, Edges & Acyclicity came from deps, srcs, and data attributes that you typed into a BUILD file. Those edges are real, but they are not the whole graph. Bazel adds edges that rules and toolchains require, and external modules bring in targets from outside the repository. Classify the resulting graph on two independent axes: who declared an edge, and which repository owns its target.1,2

Every dependency edge has two coordinates
Who declared the edge and where its target lives are independent questions
repository origin
declaration
source ↓
Main repository
External repository
Explicit Written by the BUILD author
EXPLICIT + MAIN
A normal in-repo dependency
deps = ["//lib:core"]
EXPLICIT + EXTERNAL
An external target named in BUILD
deps = ["@repo//pkg:lib"]
Implicit Injected by a rule or toolchain
IMPLICIT + MAIN
A private default label
_compiler → //tools:compiler
IMPLICIT + EXTERNAL
An externally supplied toolchain
toolchain → @rules//toolchain:impl
--noimplicit_deps filters the declaration-source axis. It does not mean “hide external repositories.”

Explicit dependencies

Explicit dependencies are the ones you declare directly in BUILD files. They are the edges from 2.1.1 Nodes, Edges & Acyclicitydeps, srcs, data, and other rule-specific attributes.1

java_binary(
    name = "server",
    srcs = ["Main.java"],
    deps = ["//lib:core"],
    data = ["config.yaml"],
)

Every edge here is visible in the BUILD file and under your control. When something changes in //lib:core, Bazel knows to rebuild server because you declared that relationship.3 When something changes in a package that has no path into this target's closure, Bazel ignores it — that is the laziness from 2.1.2 Laziness & Slicing at work.

The practical rule is: declare every direct dependency your code actually uses, and only direct dependencies. You do not need to list transitive dependencies because Bazel computes the full closure. But you also must not rely on a symbol just because some intermediate target happens to drag it in — that fragile shortcut is one of the issues in 2.1.4 Common Dependency Issues.4,5

Implicit dependencies

Not every edge in the graph comes from a BUILD file you wrote. Rules add their own dependencies — tools, compilers, runtime libraries — that the rule implementation needs to produce correct output. These are implicit dependencies: part of the target graph, but not specified by the user.6,7

The mechanism is straightforward. A rule definition can declare an attribute with a default value. That default creates an edge in the graph for every target of that rule type, whether or not the user knows about it. If the attribute name starts with an underscore, it is private and cannot be overridden. A public name with a default is also implicit but lets the user swap in a different value:6

example_library = rule(
    implementation = _example_library_impl,
    attrs = {
        "srcs": attr.label_list(allow_files = True),
        "deps": attr.label_list(),
        "_compiler": attr.label(
            default = "//tools:example_compiler",
        ),
    },
)

Every example_library target implicitly depends on //tools:example_compiler, even though no BUILD file mentions it. The rule implementation uses the compiler to generate actions, just like it uses srcs and deps.6

The most visible implicit dependency is the language toolchain. Every java_library implicitly depends on a Java compiler. If that compiler changes, every artifact that depends on it is rebuilt.8 For C++ targets, Bazel creates implicit edges to the C++ compiler, the standard library, the assembler, and the linker.2,7

When the tool does not live in the same repository as the rule, the rule should obtain it from a toolchain rather than a hard-coded label.6 Toolchains and their resolution are a 3.3 Configurable Builds & Platform Basics topic — for now, the key point is that they produce edges in the graph that you did not write.

Repository origin is a separate axis

External dependencies originate outside the main repository. Bzlmod modules normally have versions independent of your commit and may be fetched as prebuilt artifacts or sources. Other external repositories need not have a semantic version.5

In a Bzlmod project, MODULE.bazel declares external modules with bazel_dep(). Bazel resolves versions across the dependency graph using Minimal Version Selection, fetches the modules, and makes their targets available under @repo//... labels:9

# MODULE.bazel
bazel_dep(name = "rules_java", version = "8.6.4")
bazel_dep(name = "protobuf", version = "29.3")

Once available, external targets work like main-repository targets in BUILD files. An edge written as deps = ["@repo//pkg:lib"] is both explicit and external. A rule-injected compiler edge can likewise be both implicit and external. Repository origin overlaps the explicit/implicit axis rather than forming a third mutually exclusive category.5

Managing external dependencies well is a substantial topic: version resolution, registry infrastructure, overrides, and the legacy WORKSPACE model. That full story belongs to 3.1 Dependency Management.

Seeing both axes in bazel query

bazel query operates on the target graph and includes explicit and implicit edges across main and external repositories by default. Implicit edges from toolchains and private attributes appear alongside the explicit edges you declared:10

bazel query 'deps(//app:server)'

The output may include targets you never mentioned in any BUILD file — compiler toolchains, runtime libraries, standard-library bundles. To see only the edges that come from BUILD file declarations, filter out implicit dependencies:10

bazel query --noimplicit_deps 'deps(//app:server)'

The --noimplicit_deps flag suppresses edges from private attributes and toolchain requirements.10 The resulting graph is closer to what you typed in BUILD files — useful when debugging dependency structure without the noise of rule-injected edges.

One caveat: bazel query operates on the unconfigured target graph. It sees toolchain types (the requirement) but not resolved toolchain implementations (which depend on the target platform).10 bazel cquery resolves those, including filtering resolved toolchains with --noimplicit_deps — that is a 5.2 Query topic.

key takeaway

Use two axes. Explicit dependencies are edges you declare in BUILD files. Implicit dependencies are injected by rules and toolchains. Either kind can point into the main repository or an external repository. bazel query shows them together by default. --noimplicit_deps filters by declaration source, not by repository origin.1,2,10

Check your understanding · 3 questions

1.Classify each dependency edge on both axes:

Drag each answer onto the matching prompt, or click an answer and then click a prompt

Answers
deps = ["//lib:core"] in BUILD
deps = ["@repo//pkg:lib"] in BUILD
Private _compiler defaulting to //tools:compiler
Rule-selected toolchain implementation from @rules//toolchain:impl

2.What does the --noimplicit_deps flag do when passed to bazel query?

Select one answer

3.True or false: statements about implicit and external dependencies.

Choose True or False for each sentence

A rule can add an implicit dependency on a compiler by declaring an attribute with a default label, even if the user never specifies that attribute.
bazel query shows explicit dependencies only. You need bazel cquery to see implicit ones.
0 of 3 answered

Footnotes

  1. Dependencies — dependency relation as a DAG over targets, and the three generic dependency attributes srcs, deps, and data 1 2 3

  2. The anatomy of a dependency graph — explicit vs implicit dependency classification, compilers and system libraries as implicit build dependencies 1 2 3

  3. Artifact-Based Build Systems — BUILD files as a declarative manifest of artifacts and their dependencies, with Bazel computing the transitive graph

  4. Dependencies — transitive dependency hazard: relying on indirect imports that break when the intermediary changes

  5. Dependency Management — internal vs external dependencies, version management, and the One-Version Rule 1 2 3

  6. Rules — private attributes and implicit dependencies: default-valued attrs create edges the user does not specify in BUILD files 1 2 3 4

  7. Implicit Dependencies in Build Systems — system libraries and compiler toolchains as implicit dependencies that affect hermeticity and cache correctness 1 2

  8. Artifact-Based Build Systems — every java_library implicitly depends on a Java compiler. If the compiler changes, dependents rebuild

  9. Dependency ManagementMODULE.bazel for automatic transitive external dependency management

  10. Query language--noimplicit_deps flag suppresses edges from private attributes and toolchain requirements in query results 1 2 3 4 5