Starlark Syntax Basics
By the time you reach this section, Bazel's filesystem model and label syntax have already taught you where things live and how Bazel refers to them. Then you open a BUILD.bazel file and hit the next trap: it looks enough like Python to invite the wrong reading.
load("//tools:defs.bzl", "my_rule")
package(default_visibility = ["//app:__subpackages__"])
my_rule(
name = "server",
deps = [":lib"],
)
The beginner instinct is to read that top to bottom as a little program: import something, run some setup, execute a function call. Bazel does evaluate the file, but that is not the mental model you want to carry around. What matters is declarative meaning: which symbols enter scope, which package-wide defaults apply, which targets exist, and what graph relationships their attributes describe. This section is about learning to read BUILD files as build declarations rather than as small scripts.
BUILD Files Are Code-Shaped, Not Program-Shaped
This is the first place where Bazel can feel stricter than expected. BUILD files have function-call syntax, named arguments, list literals, and a Python-like surface, but Bazel is deliberately not inviting you to write a little application inside them.
That restriction is not aesthetic. Bazel wants BUILD-file evaluation to stay predictable, tool-friendly, and easy to reason about. The more a BUILD file behaves like a data declaration with a small amount of syntax, the easier it is for Bazel to load packages in parallel, for tools to reformat or rewrite files automatically, and for humans to scan a package without reverse-engineering hidden control flow.
So the key move in this section is not "learn some syntax." It is "replace the wrong mental model." Instead of asking, "what does this line do now?", start asking, "what target does this declaration create?", "what package policy does this header set?", and "what kind of dependency edge does this attribute describe?" Once you ask those questions, the language gets much calmer.
Three Questions To Ask In Any BUILD File
Almost every article in this section sharpens one of three reading questions.
- What vocabulary entered scope before the target declarations? That is the job of 0.3.5 Load Statements.
- What is the overall shape of the file, and are there package-wide defaults in the header? That starts in 0.3.1 Anatomy of a BUILD File and connects back to 0.2.6 package() Function.
- What targets exist here, and what roles do their attributes play? That is the core of 0.3.2 Rules and 0.3.3 Attributes & Semantic Roles.
Around those three questions sit the helper concepts that make real BUILD files readable rather than repetitive. 0.3.4 Globs explains file selection inside one package. 0.3.6 filegroup Rule explains how a file set gets a reusable target name. 0.3.7 Aliases explains label indirection. 0.3.8 select() Awareness teaches you to recognize configuration-dependent values without needing the full configuration model yet.
The articles are separate because each concept deserves clarity, but together they train one habit: reading a BUILD file structurally before reading it line by line.
Why The Language Stays Smaller Than Python
One of the most important emotional shifts for beginners is to stop reading missing features as missing power.
The absence of top-level loops, top-level if statements, arbitrary I/O, and general script-like behavior is not Bazel being incomplete. It is Bazel protecting the package description from turning into an opaque program. That keeps the graph legible. It also keeps the language aligned with the job of the file: declare targets and the relationships between them, then let later phases decide what to build and execute.
This is also why "boring" BUILD files are often good BUILD files. Repetition is sometimes cheaper than abstraction when the thing being maintained is not just for humans, but for tools and for future readers trying to understand a package quickly. Level 0 is intentionally about reading comprehension before authorship. You do not need to become clever in BUILD files yet. You need to become fluent in what they are saying.
What This Section Is Really Training
The practical skill here is recognition before invention.
When you open an unfamiliar BUILD file, you should be able to orient yourself fast. Which names came from load()? Is there a package() declaration changing defaults for the whole package? What are the target names? Which calls look like libraries, binaries, or tests? Which attributes are compile-time inputs, target dependencies, runtime files, or execution-time arguments? Is a file list explicit, generated by glob(), or wrapped in a filegroup()? Is select() asking you to understand configuration deeply right now, or merely to recognize that this attribute is resolved later?
That reading skill pays off earlier than custom Starlark authoring does. Before you write macros or define rules, you will spend a lot of time reading BUILD files written by other people, debugging why a dependency edge exists, or figuring out why a package exports one thing and hides another. This section is the bridge from "the syntax looks familiar" to "the declarations make sense."
How To Read The Section
There is a clean progression here.
Start with 0.3.1 Anatomy of a BUILD File to get the file-level shape right. Move to 0.3.2 Rules and 0.3.3 Attributes & Semantic Roles to understand what the declarations are made of. Then read 0.3.4 Globs, 0.3.5 Load Statements, 0.3.6 filegroup Rule, and 0.3.7 Aliases as recurring building blocks that keep showing up in real repositories. Treat 0.3.8 select() Awareness as a recognition article: you do not need the full theory yet, only enough to avoid misreading configuration-dependent data as ordinary control flow.
That sequence mirrors how BUILD files become legible in practice. First you stop reading them as scripts. Then you learn the core nouns and roles. Then you learn the recurring patterns that make one package look different from another without changing the underlying model.
When a BUILD file feels dense, do one structural pass before any detailed reading. First scan the load() block. Then look for package(). Then list the target names and rule kinds. Only after that read srcs, deps, data, args, glob(), or select(). The file almost always becomes easier once you separate its zones.
This section is not mainly about memorizing Starlark syntax. It is about learning the declarative reading style that Bazel expects. BUILD files may look like code, but their job is to declare package contents, target kinds, and dependency relationships in a constrained language. Once you read them that way, the syntax stops fighting you and starts exposing the graph clearly.
Items in this section · 8
What a typical BUILD file looks like: load statements, package() defaults, rule calls. No imperative logic.
srcs (compile inputs), deps (library dependencies), data (runtime files) — distinct roles.
Grouping files under a named target for reuse across packages.
Creating alternative names for targets.
Conditional logic in BUILD files — covered in Level 3.2.