| --- | |
| title: 'Bazel Lockfile' | |
| --- | |
| The lockfile feature in Bazel enables the recording of specific versions or | |
| dependencies of software libraries or packages required by a project. It | |
| achieves this by storing the result of module resolution and extension | |
| evaluation. The lockfile promotes reproducible builds, ensuring consistent | |
| development environments. Additionally, it enhances build efficiency by allowing | |
| Bazel to skip the parts of the resolution process that are unaffected by changes | |
| in project dependencies. Furthermore, the lockfile improves stability by | |
| preventing unexpected updates or breaking changes in external libraries, thereby | |
| reducing the risk of introducing bugs. | |
| ## Lockfile Generation {#lockfile-generation} | |
| The lockfile is generated under the workspace root with the name | |
| `MODULE.bazel.lock`. It is created or updated during the build process, | |
| specifically after module resolution and extension evaluation. Importantly, it | |
| only includes dependencies that are included in the current invocation of the | |
| build. | |
| When changes occur in the project that affect its dependencies, the lockfile is | |
| automatically updated to reflect the new state. This ensures that the lockfile | |
| remains focused on the specific set of dependencies required for the current | |
| build, providing an accurate representation of the project's resolved | |
| dependencies. | |
| ## Lockfile Usage {#lockfile-usage} | |
| The lockfile can be controlled by the flag | |
| [`--lockfile_mode`](/versions/9.2.0/reference/command-line-reference#flag--lockfile_mode) to | |
| customize the behavior of Bazel when the project state differs from the | |
| lockfile. The available modes are: | |
| * `update` (Default): Use the information that is present in the lockfile to | |
| skip downloads of known registry files and to avoid re-evaluating extensions | |
| whose results are still up-to-date. If information is missing, it will | |
| be added to the lockfile. In this mode, Bazel also avoids refreshing | |
| mutable information, such as yanked versions, for dependencies that haven't | |
| changed. | |
| * `refresh`: Like `update`, but mutable information is always refreshed when | |
| switching to this mode and roughly every hour while in this mode. | |
| * `error`: Like `update`, but if any information is missing or out-of-date, | |
| Bazel will fail with an error. This mode never changes the lockfile or | |
| performs network requests during resolution. Module extensions that marked | |
| themselves as `reproducible` may still perform network requests, but are | |
| expected to always produce the same result. | |
| * `off`: The lockfile is neither checked nor updated. | |
| ## Lockfile Benefits {#lockfile-benefits} | |
| The lockfile offers several benefits and can be utilized in various ways: | |
| - **Reproducible builds.** By capturing the specific versions or dependencies | |
| of software libraries, the lockfile ensures that builds are reproducible | |
| across different environments and over time. Developers can rely on | |
| consistent and predictable results when building their projects. | |
| - **Fast incremental resolutions.** The lockfile enables Bazel to avoid | |
| downloading registry files that were already used in a previous build. | |
| This significantly improves build efficiency, especially in scenarios where | |
| resolution can be time-consuming. | |
| - **Stability and risk reduction.** The lockfile helps maintain stability by | |
| preventing unexpected updates or breaking changes in external libraries. By | |
| locking the dependencies to specific versions, the risk of introducing bugs | |
| due to incompatible or untested updates is reduced. | |
| ### Hidden lockfile {#hidden-lockfile} | |
| Bazel also maintains another lockfile at | |
| `"$(bazel info output_base)"/MODULE.bazel.lock`. The format and contents of this | |
| lockfile are explicitly unspecified. It is only used as a performance | |
| optimization. While it can be deleted together with the output base via | |
| `bazel clean --expunge`, any need to do so is a bug in either Bazel itself or a | |
| module extension. | |
| ## Lockfile Contents {#lockfile-contents} | |
| The lockfile contains all the necessary information to determine whether the | |
| project state has changed. It also includes the result of building the project | |
| in the current state. The lockfile consists of two main parts: | |
| 1. Hashes of all remote files that are inputs to module resolution. | |
| 2. For each module extension, the lockfile includes inputs that affect it, | |
| represented by `bzlTransitiveDigest`, `usagesDigest` and other fields, as | |
| well as the output of running that extension, referred to as | |
| `generatedRepoSpecs` | |
| Here is an example that demonstrates the structure of the lockfile, along with | |
| explanations for each section: | |
| ```json | |
| { | |
| "lockFileVersion": 10, | |
| "registryFileHashes": { | |
| "https://bcr.bazel.build/bazel_registry.json": "8a28e4af...5d5b3497", | |
| "https://bcr.bazel.build/modules/foo/1.0/MODULE.bazel": "7cd0312e...5c96ace2", | |
| "https://bcr.bazel.build/modules/foo/2.0/MODULE.bazel": "70390338... 9fc57589", | |
| "https://bcr.bazel.build/modules/foo/2.0/source.json": "7e3a9adf...170d94ad", | |
| "https://registry.mycorp.com/modules/foo/1.0/MODULE.bazel": "not found", | |
| ... | |
| }, | |
| "selectedYankedVersions": { | |
| "foo@2.0": "Yanked for demo purposes" | |
| }, | |
| "moduleExtensions": { | |
| "//:extension.bzl%lockfile_ext": { | |
| "general": { | |
| "bzlTransitiveDigest": "oWDzxG/aLnyY6Ubrfy....+Jp6maQvEPxn0pBM=", | |
| "usagesDigest": "aLmqbvowmHkkBPve05yyDNGN7oh7QE9kBADr3QIZTZs=", | |
| ..., | |
| "generatedRepoSpecs": { | |
| "hello": { | |
| "bzlFile": "@@//:extension.bzl", | |
| ... | |
| } | |
| } | |
| } | |
| }, | |
| "//:extension.bzl%lockfile_ext2": { | |
| "os:macos": { | |
| "bzlTransitiveDigest": "oWDzxG/aLnyY6Ubrfy....+Jp6maQvEPxn0pBM=", | |
| "usagesDigest": "aLmqbvowmHkkBPve05y....yDNGN7oh7r3QIZTZs=", | |
| ..., | |
| "generatedRepoSpecs": { | |
| "hello": { | |
| "bzlFile": "@@//:extension.bzl", | |
| ... | |
| } | |
| } | |
| }, | |
| "os:linux": { | |
| "bzlTransitiveDigest": "eWDzxG/aLsyY3Ubrto....+Jp4maQvEPxn0pLK=", | |
| "usagesDigest": "aLmqbvowmHkkBPve05y....yDNGN7oh7r3QIZTZs=", | |
| ..., | |
| "generatedRepoSpecs": { | |
| "hello": { | |
| "bzlFile": "@@//:extension.bzl", | |
| ... | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| ``` | |
| ### Registry File Hashes {#registry-file-hashes} | |
| The `registryFileHashes` section contains the hashes of all files from | |
| remote registries accessed during module resolution. Since the resolution | |
| algorithm is fully deterministic when given the same inputs and all remote | |
| inputs are hashed, this ensures a fully reproducible resolution result while | |
| avoiding excessive duplication of remote information in the lockfile. Note that | |
| this also requires recording when a particular registry didn't contain a certain | |
| module, but a registry with lower precedence did (see the "not found" entry in | |
| the example). This inherently mutable information can be updated via | |
| `bazel mod deps --lockfile_mode=refresh`. | |
| Bazel uses the hashes from the lockfile to look up registry files in the | |
| repository cache before downloading them, which speeds up subsequent | |
| resolutions. | |
| ### Selected Yanked Versions {#selected-yanked-versions} | |
| The `selectedYankedVersions` section contains the yanked versions of modules | |
| that were selected by module resolution. Since this usually result in an error | |
| when trying to build, this section is only non-empty when yanked versions are | |
| explicitly allowed via `--allow_yanked_versions` or | |
| `BZLMOD_ALLOW_YANKED_VERSIONS`. | |
| This field is needed since, compared to module files, yanked version information | |
| is inherently mutable and thus can't be referenced by a hash. This information | |
| can be updated via `bazel mod deps --lockfile_mode=refresh`. | |
| ### Module Extensions {#module-extensions} | |
| The `moduleExtensions` section is a map that includes only the extensions used | |
| in the current invocation or previously invoked, while excluding any extensions | |
| that are no longer utilized. In other words, if an extension is not being used | |
| anymore across the dependency graph, it is removed from the `moduleExtensions` | |
| map. | |
| If an extension is independent of the operating system or architecture type, | |
| this section features only a single "general" entry. Otherwise, multiple | |
| entries are included, named after the OS, architecture, or both, with each | |
| corresponding to the result of evaluating the extension on those specifics. | |
| Each entry in the extension map corresponds to a used extension and is | |
| identified by its containing file and name. The corresponding value for each | |
| entry contains the relevant information associated with that extension: | |
| 1. The `bzlTransitiveDigest` is the digest of the extension implementation | |
| and the .bzl files transitively loaded by it. | |
| 2. The `usagesDigest` is the digest of the _usages_ of the extension in the | |
| dependency graph, which includes all tags. | |
| 3. Further unspecified fields that track other inputs to the extension, | |
| such as contents of files or directories it reads or environment | |
| variables it uses. | |
| 4. The `generatedRepoSpecs` encode the repositories created by the | |
| extension with the current input. | |
| 5. The optional `moduleExtensionMetadata` field contains metadata provided by | |
| the extension such as whether certain repositories it created should be | |
| imported via `use_repo` by the root module. This information powers the | |
| `bazel mod tidy` command. | |
| Module extensions can opt out of being included in the lockfile by setting the | |
| returning metadata with `reproducible = True`. By doing so, they promise that | |
| they will always create the same repositories when given the same inputs. | |
| ## Best Practices {#best-practices} | |
| To maximize the benefits of the lockfile feature, consider the following best | |
| practices: | |
| * Regularly update the lockfile to reflect changes in project dependencies or | |
| configuration. This ensures that subsequent builds are based on the most | |
| up-to-date and accurate set of dependencies. To lock down all extensions | |
| at once, run `bazel mod deps --lockfile_mode=update`. | |
| * Include the lockfile in version control to facilitate collaboration and | |
| ensure that all team members have access to the same lockfile, promoting | |
| consistent development environments across the project. | |
| * Use [`bazelisk`](/versions/9.2.0/install/bazelisk) to run Bazel, and include a | |
| `.bazelversion` file in version control that specifies the Bazel version | |
| corresponding to the lockfile. Because Bazel itself is a dependency of | |
| your build, the lockfile is specific to the Bazel version, and will | |
| change even between [backwards compatible](/versions/9.2.0/release/backward-compatibility) | |
| Bazel releases. Using `bazelisk` ensures that all developers are using | |
| a Bazel version that matches the lockfile. | |
| By following these best practices, you can effectively utilize the lockfile | |
| feature in Bazel, leading to more efficient, reliable, and collaborative | |
| software development workflows. | |
| ## Merge Conflicts {#merge-conflicts} | |
| The lockfile format is designed to minimize merge conflicts, but they can still | |
| happen. | |
| ### Automatic Resolution {#automatic-resolution} | |
| Bazel provides a custom | |
| [git merge driver](https://git-scm.com/docs/gitattributes#_defining_a_custom_merge_driver) | |
| to help resolve these conflicts automatically. | |
| Set up the driver by adding this line to a `.gitattributes` file in the root of | |
| your git repository: | |
| ```gitattributes | |
| # A custom merge driver for the Bazel lockfile. | |
| # https://bazel.build/external/lockfile#automatic-resolution | |
| MODULE.bazel.lock merge=bazel-lockfile-merge | |
| ``` | |
| Then each developer who wants to use the driver has to register it once by | |
| following these steps: | |
| 1. Install [jq](https://jqlang.github.io/jq/download/) (1.5 or higher). | |
| 2. Run the following commands: | |
| ```bash | |
| jq_script=$(curl https://raw.githubusercontent.com/bazelbuild/bazel/master/scripts/bazel-lockfile-merge.jq) | |
| printf '%s\n' "${jq_script}" | less # to optionally inspect the jq script | |
| git config --global merge.bazel-lockfile-merge.name "Merge driver for the Bazel lockfile (MODULE.bazel.lock)" | |
| git config --global merge.bazel-lockfile-merge.driver "jq -s '${jq_script}' -- %O %A %B > %A.jq_tmp && mv %A.jq_tmp %A" | |
| ``` | |
| ### Manual Resolution {#manual-resolution} | |
| Simple merge conflicts in the `registryFileHashes` and `selectedYankedVersions` | |
| fields can be safely resolved by keeping all the entries from both sides of the | |
| conflict. | |
| Other types of merge conflicts should not be resolved manually. Instead: | |
| 1. Restore the previous state of the lockfile | |
| via `git reset MODULE.bazel.lock && git checkout MODULE.bazel.lock`. | |
| 2. Resolve any conflicts in the `MODULE.bazel` file. | |
| 3. Run `bazel mod deps` to update the lockfile. |