Attributes & Semantic Roles
Every rule call in a BUILD file takes a set of named arguments — these are the rule's attributes1. The name attribute identifies the target; beyond that, the most important beginner distinction is between build-time inputs, target dependencies, runtime files, and execution-time arguments.
java_binary(
name = "server",
srcs = ["Main.java"],
deps = [":lib"],
data = ["config.yaml"],
args = ["--port", "8080"],
)
Three generic dependency roles appear across most rule families: srcs, deps, and data2. Executable rules often add args as a separate execution-time attribute3. They look like simple lists, but each one tells Bazel something different about how the value is consumed.
srcs — Compile-Time Inputs
Files consumed directly by the rule's build action2. For a java_library, these are the .java files that the compiler reads. For a genrule, they are the input files that the generation command processes.
java_library(
name = "lib",
srcs = ["Lib.java"],
)
srcs typically contains source files owned by the same package — the app/BUILD.bazel in the target-patterns snippet shows this pattern. If the rule needs files from another package, those files must be explicitly shared (via 0.2.7 exports_files() or a rule target in the other package).
deps — Library Dependencies
References to other targets that provide compiled code, headers, or modules2,3. Where srcs points at raw files, deps points at other targets — the output of another rule becomes an input to this one.
java_binary(
name = "server",
srcs = ["Main.java"],
deps = [":lib"],
)
The distinction matters for dependency tracking. When a file in srcs changes, Bazel knows this target's own build inputs changed. When a target in deps changes, Bazel knows the dependency graph downstream may need rebuilding.
deps must list direct dependencies only — the targets this code actually imports or links against4. Relying on transitive dependencies (a target your dep happens to pull in) creates fragile builds that break when someone refactors an intermediate library4.
data — Runtime Files
Files needed when the binary or test runs, not when it compiles2,5. Configuration files, test fixtures, certificates, lookup tables — anything the program opens or reads at execution time.
sh_test(
name = "regtest",
srcs = ["regtest.sh"],
data = [
"//data:file1.txt",
"//data:file2.txt",
],
)
Bazel sandboxes test execution: only files listed in data (or transitively via dependencies) are available in the test's working directory2,5. A test that reads a file not declared in data may appear to work in an ad hoc local run, but it will fail once Bazel enforces the runfiles boundary in a sandboxed or remote environment.
Files from data are collected into a runfiles tree — a directory next to the binary that makes runtime files available at predictable paths5,6. The details of how bazel run assembles and uses the runfiles tree are covered in 1.1.2 bazel run & Runfiles.
args — Execution-Time Arguments
Many executable rules expose an args attribute for values passed on the command line when the program or test starts3. Unlike srcs, deps, and data, args does not declare another build dependency edge — it provides string values for execution.
java_test(
name = "integration_test",
srcs = ["IntegrationTest.java"],
args = ["--env", "staging"],
)
Many rules also have additional attributes specific to their purpose — main_class for java_binary, copts for cc_library, stamp for binaries. args is a common part of the execution interface for binaries and tests, while srcs, deps, and data are the more general roles you will see across rule families2,3.
Roles Are Not Interchangeable
Each attribute triggers different behavior in Bazel's build and execution pipeline. Confusing them causes subtle problems:
- Runtime file in
srcsinstead ofdata— Bazel now treats that file as a build-time input instead of runtime state. That can invalidate build actions when the file changes, and it does not communicate the program's runtime needs clearly. - Missing from
deps— the build may succeed if a transitive dependency happens to provide the symbol, but a future refactor of that intermediate target will break the build unexpectedly4. - Missing from
data— the test passes locally but fails under sandboxing or on CI, because the file is not in the runfiles tree5.
srcs feeds build actions, deps connects targets in the dependency graph, data bundles files for runtime, and args passes execution-time strings. These roles are easy to skim past in a BUILD file, but they are not interchangeable: Bazel tracks them differently, and using the right attribute keeps the graph precise.
1.What is the role of the deps attribute?
2.A test reads a config.yaml file at runtime. Which attribute should list it?
3.What happens if a target relies on a transitive dependency instead of declaring it in deps?
Footnotes
-
Sponsored Session: Writing Bazel Rules - Instructor: Jay Conrod — "attributes are arguments to a rule"; rule declarations create targets from named attribute values ↩
-
Dependencies — defines
srcs,deps, anddataas the three generic dependency types common to most build rules ↩1 ↩2 ↩3 ↩4 ↩5 ↩6 -
Bazel Training 101 (Part 9): Packages, Rules, Targets, and Labels — common attributes:
name,srcs,deps,data,env, andargs↩1 ↩2 ↩3 ↩4 -
Dependencies — actual vs declared dependencies, the danger of undeclared transitive dependencies, and the 3-step timeline showing how indirect deps silently break builds ↩1 ↩2 ↩3
-
BUILD files —
*_binaryrules create a runfiles directory containing files from thedataattribute and its transitive closure;*_testrules restrict file access to runfiles at runtime ↩1 ↩2 ↩3 ↩4 -
Writing Bazel rules: data and runfiles —
dataattribute makes files available at runtime to executables started withbazel run↩