0.3.3 Attributes & Semantic Roles
Every rule call in a BUILD file takes a set of named arguments called 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 only the targets this code directly 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.
A missing direct dep often surfaces in the compiler's own language rather than as a Bazel label error:
java_binary(
name = "server_missing_dep",
srcs = ["Main.java"],
main_class = "app.Main",
)
app/Main.java:3: error: package lib does not exist
import lib.Helper;
^
app/Main.java:7: error: cannot find symbol
System.out.println(Helper.message());
^
That output means the Java source imported a symbol from another target, but this java_binary did not declare that target in deps. The fixed target adds the direct edge:
java_binary(
name = "server_fixed",
srcs = ["Main.java"],
main_class = "app.Main",
deps = ["//lib:helper"],
)
data — Runtime Files
Files needed when the binary or test runs, not when it compiles2,5. This includes configuration files, test fixtures, certificates, lookup tables, and anything else 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.
The runtime version of the same mistake looks like a successful build followed by a failing program:
sh_binary(
name = "broken_reader",
srcs = ["read_message.sh"],
args = ["_main/app/message.txt"],
deps = ["@bazel_tools//tools/bash/runfiles"],
)
missing runfile: _main/app/message.txt
declare it in data and pass it with $(rlocationpath ...)
The target built, so srcs and deps were enough for the executable itself. The runtime file still belongs in data, and the portable way to pass it is $(rlocationpath //app:message.txt).
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, such as main_class for java_binary, copts for cc_library, and 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.
Check your understanding · 2 questions
1.Match each attribute to how Bazel uses its values:
Drag each answer onto the matching prompt, or click an answer and then click a prompt
2.True or false: consequences of misusing rule attributes.
Choose True or False for each sentence
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↩