Bazel Book

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 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

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?

Answer all questions to check

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