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:

Won't build
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"],
)

Reproduce this error

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:

Won't build
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).

Reproduce this error

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 srcs instead of data — 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.
key takeaway

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

Answers
srcs
deps
data
args

2.True or false: consequences of misusing rule attributes.

Choose True or False for each sentence

A runtime file placed in srcs instead of data may cause unnecessary build-cache invalidations when the file changes.
Relying on a symbol provided by a transitive dependency rather than declaring it in deps is safe as long as the intermediate library is stable.
A file omitted from data may cause a test to pass locally but fail under sandboxing or in CI.
args can be used to declare another build dependency edge, the same way deps does.
0 of 2 answered

Footnotes

  1. Sponsored Session: Writing Bazel Rules - Instructor: Jay Conrod — "attributes are arguments to a rule". Rule declarations create targets from named attribute values

  2. Dependencies — defines srcs, deps, and data as the three generic dependency types common to most build rules 1 2 3 4 5 6

  3. Bazel Training 101 (Part 9): Packages, Rules, Targets, and Labels — common attributes: name, srcs, deps, data, env, and args 1 2 3 4

  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

  5. BUILD files*_binary rules create a runfiles directory containing files from the data attribute and its transitive closure. *_test rules restrict file access to runfiles at runtime 1 2 3 4

  6. Writing Bazel rules: data and runfilesdata attribute makes files available at runtime to executables started with bazel run