0.3.5 Load Statements
load() is the import boundary between a BUILD file and reusable Starlark definitions in .bzl files1,2. It brings selected names into the current file's scope, which is why it sits in the header we saw in 0.3.1 Anatomy of a BUILD File. load() imports symbols. It does not "run the build" or make the rest of the file less declarative1,2.
What load() Actually Imports
load("//foo/bar:file.bzl", "some_library")
load("//foo:defs.bzl", my_bin = "bin")
The first argument is a label pointing at a .bzl file. The later arguments name the specific symbols that should become available in the current file1. Those imported names can be rules, macros, functions, or constants1. Aliasing lets the current file use a different local name, as in my_bin = "bin" above1,2.
load() is more precise than "import this file". It exposes only the names you ask for, and symbols starting with _ are private and cannot be loaded from another file1.
The First Argument Is A Bazel Label
load(":my_rules.bzl", "some_rule", nice_alias = "some_other_rule")
load("@rules_shell//shell:defs.bzl", "sh_binary", "sh_library")
The path in load() uses Bazel label syntax, not Python module syntax1,2. In a BUILD file, :my_rules.bzl means "the .bzl file in this package". A form like //foo/bar:file.bzl points at another package in the main repository. A form like @rules_shell//shell:defs.bzl points into an external repository1,2. So load() is another place where the label model from 0.2.1 Label Anatomy matters before any target declarations begin.
That label meaning belongs to Bazel, not to the core Starlark language. The implementation-independent specification defines symbol imports, aliases, private-name rejection, and module initialization, but deliberately leaves interpretation of the first string to the embedding application.3 Bazel supplies the label-based module resolver described here. Another Starlark host can use a different module-identifier scheme.
Why It Lives In The Header
BUILD files are evaluated as a sequential list of statements, so a name has to exist before later code can use it1. load() statements must appear at top level, and typical BUILD-file structure puts them before the optional package() call from 0.2.6 package() Function and before any target declarations1,2. That keeps the file readable: first import the external vocabulary, then declare the package metadata and targets that use it.
Because BUILD.bazel is Starlark, a syntax error stops package loading before
Bazel can trust any target declarations in that package. The example below is
not a load() error: the srcs list is missing its closing ] before the
rule call's ). It demonstrates what happens when parsing fails anywhere in
the file during package loading:
filegroup(
name = "demo",
srcs = ["message.txt",
)
ERROR: .../broken/BUILD.bazel:4:1: syntax error at ')': expected ]
WARNING: Target pattern parsing failed.
ERROR: Skipping '//broken:demo': no such target '//broken:demo': target 'demo' not declared in package 'broken' ...
The first line is the cause. The later missing-target wording is just fallout from the package never loading successfully. Fix the Starlark syntax first, then re-run the build.
This placement also helps preserve the declarative style from 0.3.1 Anatomy of a BUILD File. After the load() block, the rest of the file is still a flat list of declarations using the imported names. If a symbol such as sh_binary or my_bin appears later in the file, the header tells you where that name came from before you start reading attributes or dependencies.
load() Is Not Only For Rules
The same mechanism is used for plain constants shared across multiple BUILD files4:
load("//path/to:variables.bzl", "COPTS")
load() brings a public symbol from a .bzl file into the current file's scope. That symbol may be a rule, macro, or value that several packages share1,4.
Not every symbol in a BUILD file comes from load(). Some rules are native
and already available, while others are defined in Starlark and loaded from
built-in or external repositories5. For now, the useful skill is recognizing
that import boundary. When you need to write the abstractions yourself, continue
with 4.1.2 Legacy Macros, 4.1.3 Symbolic Macros (Bazel 8+), and
4.2.1 Rule Function.
A Later Performance Caveat
As of Bazel 8, load statements are eagerly evaluated transitively, so deep load
chains can slow the loading phase2. 2.2.1 Loading, Analysis & Execution and
3.1.5 Eager Fetch Anti-pattern explain the architecture and performance consequences.
Place load() mentally in package loading, not in action execution.
load() makes a BUILD file's external vocabulary explicit1,2. The first argument says where the .bzl file lives, the later arguments say which public names enter scope, and the rest of the file can use those names as ordinary declarations. If a BUILD file feels mysterious, read the load() block first: it tells you which rules, macros, and shared constants the package depends on before any target definitions start.
Check your understanding · 2 questions
1.True or false: how load() works in BUILD files.
Choose True or False for each sentence
2.A BUILD file contains load(":my_rules.bzl", "some_rule"). What does :my_rules.bzl refer to?
Select one answer
Footnotes
-
BUILD files —
load()syntax, label-based.bzllookup, aliasing, top-level restriction, and_-prefixed symbols not being exportable ↩1 ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 -
Bazel Training 101 (Part 12): Manually using rules in BUILD files —
load()as the common BUILD-file opening pattern, file-scope imports, BUILD vs.bzldistinction, and the eager transitive evaluation note ↩1 ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 -
Starlark repository map —
spec.md#load-statementsdefines the portable import contract and leaves module identifiers to the embedding application. ↩ -
Sharing Variables — using
.bzlfiles plusload()to share constants across multiple BUILD files ↩1 ↩2 -
Rules — distinction between native rules that are already available and Starlark-defined rules loaded from built-in or external repositories ↩