Project: /_project.yaml Book: /_book.yaml keywords: bzlmod
{% include “_buttons.html” %}
Due to the shortcomings of WORKSPACE, Bzlmod is going to replace the legacy WORKSPACE system in future Bazel releases. This guide helps you migrate your project to Bzlmod and drop WORKSPACE for fetching external dependencies.
Bazel's WORKSPACE and Bzlmod offer similar features with different syntax. This section explains how to migrate from specific WORKSPACE functionalities to Bzlmod.
The WORKSPACE file marks the source root of a Bazel project, this responsibility is replaced by MODULE.bazel in Bazel version 6.3 and later. With Bazel version prior to 6.3, there should still be a WORKSPACE
or WORKSPACE.bazel
file at your workspace root, maybe with comments like:
WORKSPACE
# This file marks the root of the Bazel workspace. # See MODULE.bazel for external dependencies setup.
.bazelrc
lets you set flags that apply every time your run Bazel. To enable Bzlmod, use the --enable_bzlmod
flag, and apply it to the common
command so it applies to every command:
.bazelrc
# Enable Bzlmod for every Bazel command common --enable_bzlmod
WORKSPACE
The workspace
function is used to specify a repository name for your workspace. This allows a target //foo:bar
in the workspace to be referenced as @<workspace name>//foo:bar
. If not specified, the default repository name for your workspace is __main__
.
## WORKSPACE workspace(name = "com_foo_bar")
Bzlmod
It's recommended to reference targets in the same workspace with the //foo:bar
syntax without @<repo name>
. But if you do need the old syntax , you can use the module name specified by the module
function as the repository name. If the module name is different from the needed repository name, you can use repo_name
attribute of the module
function to override the repository name.
## MODULE.bazel module( name = "bar", repo_name = "com_foo_bar", )
If your dependency is a Bazel project, you should be able to depend on it as a Bazel module when it also adopts Bzlmod.
WORKSPACE
With WORKSPACE, it's common to use the http_archive
or git_repository
repository rules to download the sources of the Bazel project.
## WORKSPACE load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") http_archive( name = "bazel_skylib", urls = ["https://github.com/bazelbuild/bazel-skylib/releases/download/1.4.2/bazel-skylib-1.4.2.tar.gz"], sha256 = "66ffd9315665bfaafc96b52278f57c7e2dd09f5ede279ea6d39b2be471e7e3aa", ) load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") bazel_skylib_workspace() http_archive( name = "rules_java", urls = ["https://github.com/bazelbuild/rules_java/releases/download/6.1.1/rules_java-6.1.1.tar.gz"], sha256 = "76402a50ae6859d50bd7aed8c1b8ef09dae5c1035bb3ca7d276f7f3ce659818a", ) load("@rules_java//java:repositories.bzl", "rules_java_dependencies", "rules_java_toolchains") rules_java_dependencies() rules_java_toolchains()
As you can see, it's a common pattern that users need to load transitive dependencies from a macro of the dependency. Assume both bazel_skylib
and rules_java
depends on platform
, the exact version of the platform
dependency is determined by the order of the macros.
Bzlmod
With Bzlmod, as long as the your dependency is available in Bazel Central Registry or your custom Bazel registry, you can simply depend on it with a bazel_dep
directive.
## MODULE.bazel bazel_dep(name = "bazel_skylib", version = "1.4.2") bazel_dep(name = "rules_java", version = "6.1.1")
Bzlmod resolves Bazel module dependencies transitively using the MVS algorithm. Therefore, the maximal required version of platform
is selected automatically.
As the root module, you can override Bazel module dependencies in different ways.
Please read the overrides section for more information.
You can find some example usages in the examples repository.
If your dependency is not a Bazel project or not yet available in any Bazel registry, you can introduce it using module extensions.
WORKSPACE
Download a file using the http_file
repository rule.
## WORKSPACE load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file") http_file( name = "data_file", url = "http://example.com/file", sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", )
Bzlmod
With Bzlmod, you have to move the definition into a .bzl
file, which also lets you share the definition between WORKSPACE and Bzlmod during the migration period.
## repositories.bzl load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file") def my_data_dependency(): http_file( name = "data_file", url = "http://example.com/file", sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", )
Implement a module extension to load the dependencies macro. You can define it in the same .bzl
file of the macro, but to keep compatibility with older Bazel versions, it's better to define it in a separate .bzl
file.
## extensions.bzl load("//:repositories.bzl", "my_data_dependency") def _non_module_dependencies_impl(_ctx): my_data_dependency() non_module_dependencies = module_extension( implementation = _non_module_dependencies_impl, )
To make the repository visible to the root project, you should declare the usages of the module extension and the repository in the MODULE.bazel file.
## MODULE.bazel non_module_dependencies = use_extension("//:extensions.bzl", "non_module_dependencies") use_repo(non_module_dependencies, "data_file")
A project can provide a macro that introduces external repositories based on inputs from its callers. But what if there are multiple callers in the dependency graph and they cause a conflict?
Assume the project foo
provides the following macro which takes version
as an argument.
## repositories.bzl in foo {:#repositories.bzl-foo} load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file") def data_deps(version = "1.0"): http_file( name = "data_file", url = "http://example.com/file-%s" % version, # Omitting the "sha256" attribute for simplicity )
WORKSPACE
With WORKSPACE, you can load the macro from @foo
and specify the version of the data dependency you need. Assume you have another dependency @bar
, which also depends on @foo
but requires a different version of the data dependency.
## WORKSPACE # Introduce @foo and @bar. ... load("@foo//:repositories.bzl", "data_deps") data_deps(version = "2.0") load("@bar//:repositories.bzl", "bar_deps") bar_deps() # -> which calls data_deps(version = "3.0")
In this case, the end user must carefully adjust the order of macros in the WORKSPACE to get the version they need. This is one of the biggest pain points with WORKSPACE since it doesn't really provide a sensible way to resolve dependencies.
Bzlmod
With Bzlmod, the author of project foo
can use module extension to resolve conflicts. For example, let's assume it makes sense to always select the maximal required version of the data dependency among all Bazel modules.
## extensions.bzl in foo load("//:repositories.bzl", "data_deps") data = tag_class(attrs={"version": attr.string()}) def _data_deps_extension_impl(module_ctx): # Select the maximal required version in the dependency graph. version = "1.0" for mod in module_ctx.modules: for data in mod.tags.data: version = max(version, data.version) data_deps(version) data_deps_extension = module_extension( implementation = _data_deps_extension_impl, tag_classes = {"data": data}, )
## MODULE.bazel in bar bazel_dep(name = "foo", version = "1.0") foo_data_deps = use_extension("@foo//:extensions.bzl", "data_deps_extension") foo_data_deps.data(version = "3.0") use_repo(foo_data_deps, "data_file")
## MODULE.bazel in root module bazel_dep(name = "foo", version = "1.0") bazel_dep(name = "bar", version = "1.0") foo_data_deps = use_extension("@foo//:extensions.bzl", "data_deps_extension") foo_data_deps.data(version = "2.0") use_repo(foo_data_deps, "data_file")
In this case, the root module requires data version 2.0
, while its dependency bar
requires 3.0
. The module extension in foo
can correctly resolve this conflict and automatically select version 3.0
for the data dependency.
Following the last section, since module extension provides a way to collect information from the dependency graph, perform custom logic to resolve dependencies and call repository rules to introduce external repositories, this provides a great way for rules authors to enhance the rulesets that integrate package managers for specific languages.
Please read the module extensions page to learn more about how to use module extensions.
Here is a list of the rulesets that already adopted Bzlmod to fetch dependencies from different package managers:
A minimal example that integrates a pseudo package manager is available at the examples repository.
When Bazel build rules need to detect what toolchains are available on your host machine, they use repository rules to inspect the host machine and generate toolchain info as external repositories.
WORKSPACE
Given the following repository rule to detect a shell toolchain.
## local_config_sh.bzl def _sh_config_rule_impl(repository_ctx): sh_path = get_sh_path_from_env("SH_BIN_PATH") if not sh_path: sh_path = detect_sh_from_path() if not sh_path: sh_path = "/shell/binary/not/found" repository_ctx.file("BUILD", """ load("@bazel_tools//tools/sh:sh_toolchain.bzl", "sh_toolchain") sh_toolchain( name = "local_sh", path = "{sh_path}", visibility = ["//visibility:public"], ) toolchain( name = "local_sh_toolchain", toolchain = ":local_sh", toolchain_type = "@bazel_tools//tools/sh:toolchain_type", ) """.format(sh_path = sh_path)) sh_config_rule = repository_rule( environ = ["SH_BIN_PATH"], local = True, implementation = _sh_config_rule_impl, )
You can load the repository rule in WORKSPACE.
## WORKSPACE load("//:local_config_sh.bzl", "sh_config_rule") sh_config_rule(name = "local_config_sh")
Bzlmod
With Bzlmod, you can introduce the same repository using a module extension, which is similar to introducing the @data_file
repository in the last section.
## local_config_sh_extension.bzl load("//:local_config_sh.bzl", "sh_config_rule") sh_config_extension = module_extension( implementation = lambda ctx: sh_config_rule(name = "local_config_sh"), )
Then use the extension in the MODULE.bazel file.
## MODULE.bazel sh_config_ext = use_extension("//:local_config_sh_extension.bzl", "sh_config_extension") use_repo(sh_config_ext, "local_config_sh")
Following the last section, after introducing a repository hosting toolchain information (e.g. local_config_sh
), you probably want to register the toolchain.
WORKSPACE
With WORKSPACE, you can register the toolchain in the following ways.
You can register the toolchain the .bzl
file and load the macro in the WORKSPACE file.
## local_config_sh.bzl def sh_configure(): sh_config_rule(name = "local_config_sh") native.register_toolchains("@local_config_sh//:local_sh_toolchain")
## WORKSPACE load("//:local_config_sh.bzl", "sh_configure") sh_configure()
Or register the toolchain in the WORKSPACE file directly.
## WORKSPACE load("//:local_config_sh.bzl", "sh_config_rule") sh_config_rule(name = "local_config_sh") register_toolchains("@local_config_sh//:local_sh_toolchain")
Bzlmod
With Bzlmod, the register_toolchains
and register_execution_platforms
APIs are only available in the MODULE.bazel file. You cannot call native.register_toolchains
in a module extension.
## MODULE.bazel sh_config_ext = use_extension("//:local_config_sh_extension.bzl", "sh_config_extension") use_repo(sh_config_ext, "local_config_sh") register_toolchains("@local_config_sh//:local_sh_toolchain")
The toolchains and execution platforms registered in WORKSPACE
, WORKSPACE.bzlmod
and each Bazel module's MODULE.bazel
file follow this order of precedence during toolchain selection (from highest to lowest):
MODULE.bazel
file.WORKSPACE
or WORKSPACE.bzlmod
file.WORKSPACE.bzlmod
: toolchains registered in the WORKSPACE
suffix.You may need to introduce a dependency as a local repository when you need a local version of the dependency for debugging or you want to incorporate a directory in your workspace as external repository.
WORKSPACE
With WORKSPACE, this is achieved by two native repository rules, local_repository
and new_local_repository
.
## WORKSPACE local_repository( name = "rules_java", path = "/Users/bazel_user/workspace/rules_java", )
Bzlmod
With Bzlmod, you can use local_path_override
to override a module with a local path.
## MODULE.bazel bazel_dep(name = "rules_java") local_path_override( module_name = "rules_java", path = "/Users/bazel_user/workspace/rules_java", )
Note: With local_path_override
, you can only introduce a local directory as a Bazel module, which means it should have a MODULE.bazel file and its transitive dependencies are taken into consideration during dependency resolution. In addition, all module override directives can only be used by the root module.
It is also possible to introduce a local repository with module extension. However, you cannot call native.local_repository
in module extension, there is ongoing effort on starlarkifying all native repository rules (check #18285 for progress). Then you can call the corresponding starlark local_repository
in a module extension. It's also trivial to implement a custom version of local_repository
repository rule if this is a blocking issue for you.
The bind
rule in WORKSPACE is deprecated and not supported in Bzlmod. It was introduced to give a target an alias in the special //external
package. All users depending on this should migrate away.
For example, if you have
## WORKSPACE bind( name = "openssl", actual = "@my-ssl//src:openssl-lib", )
This allows other targets to depend on //external:openssl
. You can migrate away from this by:
Replace all usages of //external:openssl
with @my-ssl//src:openssl-lib
.
Or use the alias
build rule
Define the following target in a package (e.g. //third_party
)
## third_party/BUILD alias( name = "openssl, actual = "@my-ssl//src:openssl-lib", )
Replace all usages of //external:openssl
with //third_party:openssl-lib
.
This section provides useful information and guidance for your Bzlmod migration process.
The first step of migration is to understand what dependencies you have. It could be hard to figure out what exact dependencies are introduced in the WORKSPACE file because transitive dependencies are often loaded with *_deps
macros.
Fortunately, the flag --experimental_repository_resolved_file
can help. This flag essentially generates a “lock file” of all fetched external dependencies in your last Bazel command. You can find more details in this blog post.
It can be used in two ways:
To fetch info of external dependencies needed for building certain targets.
bazel clean --expunge bazel build --nobuild --experimental_repository_resolved_file=resolved.bzl //foo:bar
To fetch info of all external dependencies defined in the WORKSPACE file.
bazel clean --expunge bazel sync --experimental_repository_resolved_file=resolved.bzl
With the bazel sync
command, you can fetch all dependencies defined in the WORKSPACE file, which include:
bind
usagesregister_toolchains
& register_execution_platforms
usagesHowever, if your project is cross platforms, bazel sync may break on certain platforms because some repository rules may only run correctly on supported platforms.
After running the command, you should have information of your external dependencies in the resolved.bzl
file.
bazel query
You may also know bazel query
can be used for inspecting repository rules with
bazel query --output=build //external:<repo name>
While it is more convenient and much faster, bazel query can lie about external dependency version, so be careful using it! Querying and inspecting external dependencies with Bzlmod is going to achieved by a new subcommand.
If you check the file generated by --experimental_repository_resolved_file
, you are going to find many dependencies that are not defined in your WORKSPACE. This is because Bazel in fact adds prefixes and suffixes to the user's WORKSPACE file content to inject some default dependencies, which are usually required by native rules (e.g. @bazel_tools
, @platforms
and @remote_java_tools
). With Bzlmod, those dependencies are introduced with a built-in module bazel_tools
, which is a default dependency for every other Bazel module.
Bzlmod and WORKSPACE can work side by side, which allows migrating dependencies from the WORKSPACE file to Bzlmod to be a gradual process.
During the migration, Bazel users may need to switch between builds with and without Bzlmod enabled. WORKSPACE.bzlmod support is implemented to make the process smoother.
WORKSPACE.bzlmod has the exact same syntax as WORKSPACE. When Bzlmod is enabled, if a WORKSPACE.bzlmod file also exists at the workspace root:
WORKSPACE.bzlmod
takes effect and the content of WORKSPACE
is ignored.Using the WORKSPACE.bzlmod file can make the migration easier because:
Note: WORKSPACE.bzlmod does NOT replace the functionality of WORKSPACE for identifying the workspace root, therefore you still need a WORKSPACE file at your workspace root.
Bzlmod is able to control which other repositories are visible from a given repository, check repository names and strict deps for more details.
Here is a summary of repository visibilities from different types of repositories when also taking WORKSPACE into consideration.
From the main repo | From Bazel module repos | From module extension repos | From WORKSPACE repos | |
---|---|---|---|---|
The main repo | Visible | If the root module is a direct dependency | If the root module is a direct dependency of the module hosting the module extension | Visible |
Bazel module repos | Direct deps | Direct deps | Direct deps of the module hosting the module extension | Direct deps of the root module |
Module extension repos | Direct deps | Direct deps | Direct deps of the module hosting the module extension + all repos generated by the same module extension | Direct deps of the root module |
WORKSPACE Repos | All visible | Not visible | Not visible | All visible |
Note: For the root module, if a repository @foo
is defined in WORKSPACE and @foo
is also used as an apparent repository name in MODULE.bazel, then @foo
refers to the one introduced in MODULE.bazel.
Note: For a module extension generated repository @bar
, if @foo
is used as an apparent repository name of another repository generated by the same module extension and direct dependencies of the module hosting the module extension, then for repository @bar
, @foo
refers to the latter.
A typical Bzlmod migration process can look like this:
There is an interactive Bzlmod migration helper script that can get you started.
The script does the following things:
To use it, make sure you have the latest Bazel release installed, and run the following command:
git clone https://github.com/bazelbuild/bazel-central-registry.git cd <your workspace root> <BCR repo root>/tools/migrate_to_bzlmod.py -t <your build targets>
Note: The migration script is not perfect and may not be up-to-date since Bzlmod is evolving, always double check if the recommended solution is correct.
If your Bazel project is a dependency for other projects, you can publish your project in the Bazel Central Registry.
To be able to check in your project in the BCR, you need a source archive URL of the project. Take note of a few things when creating the source archive:
Make sure the archive is pointing to a specific version.
The BCR can only accept versioned source archives because Bzlmod needs to conduct version comparison during dependency resolution.
Make sure the archive URL is stable.
Bazel verifies the content of the archive by a hash value, so you should make sure the checksum of the downloaded file never changes. If the URL is from GitHub, please create and upload a release archive in the release page. GitHub isn't going to guarantee the checksum of source archives generated on demand. In short, URLs in the form of https://github.com/<org>/<repo>/releases/download/...
is considered stable while https://github.com/<org>/<repo>/archive/...
is not. Check GitHub Archive Checksum Outage for more context.
Make sure the source tree follows the layout of the original repository.
In case your repository is very large and you want to create a distribution archive with reduced size by stripping out unnecessary sources, please make sure the stripped source tree is a subset of the original source tree. This makes it easier for end users to override the module to a non-release version by archive_override
and git_override
.
Include a test module in a subdirectory that tests your most common APIs.
A test module is a Bazel project with its own WORKSPACE and MODULE.bazel file located in a subdirectory of the source archive which depends on the actual module to be published. It should contain examples or some integration tests that cover your most common APIs. Check test module to learn how to set it up.
When you have your source archive URL ready, follow the BCR contribution guidelines to submit your module to the BCR with a GitHub Pull Request.
It is highly recommended to set up the Publish to BCR GitHub App for your repository to automate the process of submitting your module to the BCR.
This section documents a few best practices you should follow for better managing your external dependencies.
Check #12835, where dev dependencies for tests are forced to be fetched unnecessarily for building targets that don't need them. This is not actually not Bzlmod specific, but following this practices makes it easier to specify dev dependencies correctly.
You can set the dev_dependency
attribute to true for bazel_dep
and use_extension
directives so that they don't propagate to dependent projects. As the root module, you can use the --ignore_dev_dependency
flag to verify if your targets still build without dev dependencies.
{# More best practices here !!! #}
You can check the Bazel Central Registry to find out if your dependencies are already available. Otherwise feel free to join this GitHub discussion to upvote or post the dependencies that are blocking your migration.
Please check the Bazel GitHub issue list for known Bzlmod issues. Feel free to file new issues or feature requests that can help unblock your migration!