A Bazel module is a Bazel project that can have multiple versions, each of which publishes metadata about other modules that it depends on. This is analogous to familiar concepts in other dependency management systems, such as a Maven artifact, an npm package, a Go module, or a Cargo crate.
A module must have a MODULE.bazel
file at its repo root. This file is the
module's manifest, declaring its name, version, list of direct dependencies, and
other information. For a basic example:
module(name = "my-module", version = "1.0")
bazel_dep(name = "rules_cc", version = "0.0.1")
bazel_dep(name = "protobuf", version = "3.19.0")
See the full list of directives available in
MODULE.bazel
files.
To perform module resolution, Bazel starts by reading the root module's
MODULE.bazel
file, and then repeatedly requests any dependency's
MODULE.bazel
file from a Bazel registry until it
discovers the entire dependency graph.
By default, Bazel then selects one version of each module to use. Bazel represents each module with a repo, and consults the registry again to learn how to define each of the repos.
Version format
Bazel has a diverse ecosystem and projects use various versioning schemes. The
most popular by far is SemVer, but there are
also prominent projects using different schemes such as
Abseil, whose
versions are date-based, for example 20210324.2
).
For this reason, Bzlmod adopts a more relaxed version of the SemVer spec. The differences include:
- SemVer prescribes that the "release" part of the version must consist of 3
segments:
MAJOR.MINOR.PATCH
. In Bazel, this requirement is loosened so that any number of segments is allowed. - In SemVer, each of the segments in the "release" part must be digits only. In Bazel, this is loosened to allow letters too, and the comparison semantics match the "identifiers" in the "prerelease" part.
- Additionally, the semantics of major, minor, and patch version increases are not enforced. However, see compatibility level for details on how we denote backwards compatibility.
Any valid SemVer version is a valid Bazel module version. Additionally, two
SemVer versions a
and b
compare a < b
if and only if the same holds when
they're compared as Bazel module versions.
Version selection
Consider the diamond dependency problem, a staple in the versioned dependency management space. Suppose you have the dependency graph:
A 1.0
/ \
B 1.0 C 1.1
| |
D 1.0 D 1.1
Which version of D
should be used? To resolve this question, Bzlmod uses the
Minimal Version Selection
(MVS) algorithm introduced in the Go module system. MVS assumes that all new
versions of a module are backwards compatible, and so picks the highest version
specified by any dependent (D 1.1
in our example). It's called "minimal"
because D 1.1
is the earliest version that could satisfy our requirements —
even if D 1.2
or newer exists, we don't select them. Using MVS creates a
version selection process that is high-fidelity and reproducible.
Yanked versions
The registry can declare certain versions as yanked if they should be avoided
(such as for security vulnerabilities). Bazel throws an error when selecting a
yanked version of a module. To fix this error, either upgrade to a newer,
non-yanked version, or use the
--allow_yanked_versions
flag to explicitly allow the yanked version.
Compatibility level
In Go, MVS's assumption about backwards compatibility works because it treats
backwards incompatible versions of a module as a separate module. In terms of
SemVer, that means A 1.x
and A 2.x
are considered distinct modules, and can
coexist in the resolved dependency graph. This is, in turn, made possible by
encoding the major version in the package path in Go, so there aren't any
compile-time or linking-time conflicts.
Bazel, however, cannot provide such guarantees, so it needs the "major version"
number in order to detect backwards incompatible versions. This number is called
the compatibility level, and is specified by each module version in its
module()
directive. With this information, Bazel can throw an error when it
detects that versions of the same module with different compatibility levels
exist in the resolved dependency graph.
Overrides
Specify overrides in the MODULE.bazel
file to alter the behavior of Bazel
module resolution. Only the root module's overrides take effect — if a module is
used as a dependency, its overrides are ignored.
Each override is specified for a certain module name, affecting all of its versions in the dependency graph. Although only the root module's overrides take effect, they can be for transitive dependencies that the root module does not directly depend on.
Single-version override
The single_version_override
serves multiple purposes:
- With the
version
attribute, you can pin a dependency to a specific version, regardless of which versions of the dependency are requested in the dependency graph. - With the
registry
attribute, you can force this dependency to come from a specific registry, instead of following the normal registry selection process. - With the
patch*
attributes, you can specify a set of patches to apply to the downloaded module.
These attributes are all optional and can be mixed and matched with each other.
Multiple-version override
A multiple_version_override
can be specified to allow multiple versions of the same module to coexist in the
resolved dependency graph.
You can specify an explicit list of allowed versions for the module, which must all be present in the dependency graph before resolution — there must exist some transitive dependency depending on each allowed version. After resolution, only the allowed versions of the module remain, while Bazel upgrades other versions of the module to the nearest higher allowed version at the same compatibility level. If no higher allowed version at the same compatibility level exists, Bazel throws an error.
For example, if versions 1.1
, 1.3
, 1.5
, 1.7
, and 2.0
exist in the
dependency graph before resolution and the major version is the compatibility
level:
- A multiple-version override allowing
1.3
,1.7
, and2.0
results in1.1
being upgraded to1.3
,1.5
being upgraded to1.7
, and other versions remaining the same. - A multiple-version override allowing
1.5
and2.0
results in an error, as1.7
has no higher version at the same compatibility level to upgrade to. - A multiple-version override allowing
1.9
and2.0
results in an error, as1.9
is not present in the dependency graph before resolution.
Additionally, users can also override the registry using the registry
attribute, similarly to single-version overrides.
Non-registry overrides
Non-registry overrides completely remove a module from version resolution. Bazel
does not request these MODULE.bazel
files from a registry, but instead from
the repo itself.
Bazel supports the following non-registry overrides:
Define repos that don't represent Bazel modules
With bazel_dep
, you can define repos that represent other Bazel modules.
Sometimes there is a need to define a repo that does not represent a Bazel
module; for example, one that contains a plain JSON file to be read as data.
In this case, you could use the use_repo_rule
directive to directly define a repo
by invoking a repo rule. This repo will only be visible to the module it's
defined in.
Under the hood, this is implemented using the same mechanism as module extensions, which lets you define repos with more flexibility.
Repository names and strict deps
The apparent name of a repo backing a
module to its direct dependents defaults to its module name, unless the
repo_name
attribute of the bazel_dep
directive says otherwise. Note that this means a module can only find its direct
dependencies. This helps prevent accidental breakages due to changes in
transitive dependencies.
The canonical name of a repo backing a
module is either module_name+version
(for example, bazel_skylib+1.0.3
) or module_name+
(for example, bazel_features+
), depending on whether there are
multiple versions of the module in the entire dependency graph (see
multiple_version_override
).
Note that the canonical name format is not an API you should depend on and
is subject to change at any time. Instead of hard-coding the canonical name,
use a supported way to get it directly from Bazel:
- In BUILD and
.bzl
files, useLabel.repo_name
on aLabel
instance constructed from a label string given by the apparent name of the repo, e.g.,Label("@bazel_skylib").repo_name
. - When looking up runfiles, use
$(rlocationpath ...)
or one of the runfiles libraries in@bazel_tools//tools/{bash,cpp,java}/runfiles
or, for a rulesetrules_foo
, in@rules_foo//foo/runfiles
. - When interacting with Bazel from an external tool such as an IDE or language
server, use the
bazel mod dump_repo_mapping
command to get the mapping from apparent names to canonical names for a given set of repositories.
Module extensions can also introduce additional repos into the visible scope of a module.