diff --git a/docs/external/vendor.mdx b/docs/external/vendor.mdx index ae7317486f9db5..60b094d025acac 100644 --- a/docs/external/vendor.mdx +++ b/docs/external/vendor.mdx @@ -6,8 +6,8 @@ keywords: product:Bazel,Bzlmod,vendor {# disableFinding("repo") #} --- -title: 'Vendor Mode' ---- + +## title: 'Vendor Mode' Vendor mode is a feature that lets you create a local copy of external dependencies. This is useful for offline builds, or when you want to @@ -96,9 +96,9 @@ bazel vendor --vendor_dir=vendor_src Note that vendoring all dependencies has a few **disadvantages**: -- Fetching all repos, including those introduced transitively, can be time-consuming. -- The vendor directory can become very large. -- Some repos may fail to fetch if they are not compatible with the current platform or environment. +- Fetching all repos, including those introduced transitively, can be time-consuming. +- The vendor directory can become very large. +- Some repos may fail to fetch if they are not compatible with the current platform or environment. Therefore, consider vendoring for specific targets first. @@ -125,12 +125,12 @@ pin("@@bazel_skylib+") With this configuration -- Both repos will be excluded from subsequent vendor commands. -- Repo `bazel_skylib` will be overridden to the source located under the - vendor directory. -- The user can safely modify the vendored source of `bazel_skylib`. -- To re-vendor `bazel_skylib`, the user has to disable the pin statement - first. +- Both repos will be excluded from subsequent vendor commands. +- Repo `bazel_skylib` will be overridden to the source located under the + vendor directory. +- The user can safely modify the vendored source of `bazel_skylib`. +- To re-vendor `bazel_skylib`, the user has to disable the pin statement + first. Note: Repository rules with [`local`](/rules/lib/globals/bzl#repository_rule.local) or @@ -146,8 +146,8 @@ vendored source for later builds. The content being vendored includes: -- The repo directory -- The repo marker file +- The repo directory +- The repo marker file During a build, if the vendored marker file is up-to-date or the repo is pinned in the VENDOR.bazel file, then Bazel uses the vendored source by creating @@ -174,12 +174,12 @@ External repositories may contain symlinks pointing to other files or directories. To make sure symlinks work correctly, Bazel uses the following strategy to rewrite symlinks in the vendored source: -- Create a symlink `/bazel-external` that points to `$(bazel info - output_base)/external`. It is refreshed by every Bazel command - automatically. -- For the vendored source, rewrite all symlinks that originally point to a - path under `$(bazel info output_base)/external` to a relative path under - `/bazel-external`. +- Create a symlink `/bazel-external` that points to `$(bazel info +output_base)/external`. It is refreshed by every Bazel command + automatically. +- For the vendored source, rewrite all symlinks that originally point to a + path under `$(bazel info output_base)/external` to a relative path under + `/bazel-external`. For example, if the original symlink is diff --git a/site/en/external/vendor.md b/site/en/external/vendor.md index c4155f84a0c3dd..2796ed7639b23a 100644 --- a/site/en/external/vendor.md +++ b/site/en/external/vendor.md @@ -104,6 +104,22 @@ Note that vendoring all dependencies has a few **disadvantages**: Therefore, consider vendoring for specific targets first. +### Vendor tools for Bazel subcommands {:#vendor-tools-for-subcommands} + +Some Bazel subcommands (such as `bazel mod tidy`) have implicit tool +dependencies that are not reachable from user build targets, so they are +**not** included by `bazel vendor //...`. To vendor those tools as well, add +the `@bazel_tools//tools:tools_for_bazel_subcommands` filegroup to your +vendor invocation: + +```none +bazel vendor //... @bazel_tools//tools:tools_for_bazel_subcommands +``` + +This is required if you plan to run commands like `bazel mod tidy` in an +offline or hermetic environment (for example with `--vendor_dir` and +`--nofetch`). + ## Configure vendor mode with VENDOR.bazel {:#configure-vendor-mode} You can control how given repos are handled with the VENDOR.bazel file located diff --git a/src/test/py/bazel/bzlmod/bazel_vendor_test.py b/src/test/py/bazel/bzlmod/bazel_vendor_test.py index 476a49d1c59c78..5ebeb4ddd8477a 100644 --- a/src/test/py/bazel/bzlmod/bazel_vendor_test.py +++ b/src/test/py/bazel/bzlmod/bazel_vendor_test.py @@ -874,6 +874,67 @@ def testVendorAliasTarget(self): # Regression test for https://github.com/bazelbuild/bazel/issues/23300 self.RunBazel(['vendor', '//foo/...', '--vendor_dir=vendor']) + def testVendorToolsForBazelSubcommands(self): + # Regression test for https://github.com/bazelbuild/bazel/issues/29222: + # `bazel vendor //...` alone doesn't pull in tools needed by Bazel + # subcommands (e.g. buildozer for `bazel mod tidy`). Users must explicitly + # vendor @bazel_tools//tools:tools_for_bazel_subcommands for those + # subcommands to work under `--nofetch`. + self.ScratchFile( + 'MODULE.bazel', + [ + 'ext = use_extension("//:extension.bzl", "ext")', + 'use_repo(ext, "dep", "indirect_dep")', + ], + ) + self.ScratchFile('BUILD.bazel') + self.ScratchFile( + 'extension.bzl', + [ + 'def _repo_impl(ctx):', + ' ctx.file("WORKSPACE")', + ' ctx.file("BUILD", "filegroup(name=\'lala\')")', + 'repo_rule = repository_rule(implementation=_repo_impl)', + '', + 'def _ext_impl(ctx):', + ' repo_rule(name="dep")', + ' repo_rule(name="missing_dep")', + ' repo_rule(name="indirect_dep")', + ' return ctx.extension_metadata(', + ' root_module_direct_deps=["dep", "missing_dep"],', + ' root_module_direct_dev_deps=[],', + ' )', + '', + 'ext = module_extension(implementation=_ext_impl)', + ], + ) + + # Vendor the main target set plus the tools filegroup so that + # `bazel mod tidy` (which invokes buildozer) can run offline. + self.RunBazel([ + 'vendor', + '--vendor_dir=vendor', + '//...', + '@bazel_tools//tools:tools_for_bazel_subcommands', + ]) + + # Run `bazel mod tidy` under `--nofetch`. Without the filegroup being + # vendored above, this would fail because buildozer can't be fetched. + self.RunBazel([ + 'mod', + 'tidy', + '--vendor_dir=vendor', + '--nofetch', + ]) + + # Verify that mod tidy actually rewrote MODULE.bazel based on the + # extension's root_module_direct_deps metadata. + with open(self.Path('MODULE.bazel'), 'r', encoding='utf-8') as f: + contents = f.read() + self.assertIn('"dep"', contents) + self.assertIn('"missing_dep"', contents) + self.assertNotIn('"indirect_dep"', contents) + if __name__ == '__main__': absltest.main() diff --git a/tools/BUILD.tools b/tools/BUILD.tools index 3778f708f8c4d5..c072804eeee009 100644 --- a/tools/BUILD.tools +++ b/tools/BUILD.tools @@ -29,3 +29,21 @@ filegroup( "//tools/test:bzl_srcs", ], ) + +# Tools that Bazel subcommands (e.g. `bazel mod tidy`) depend on implicitly, +# but that aren't in the transitive closure of user build targets. Users who +# need to run these subcommands offline (e.g. with `--vendor_dir` and +# `--nofetch`) should include this target in their `bazel vendor` invocation: +# +# bazel vendor //... @bazel_tools//tools:tools_for_bazel_subcommands +# +# See https://github.com/bazelbuild/bazel/issues/29222. +filegroup( + name = "tools_for_bazel_subcommands", + srcs = [ + # Required by `bazel mod tidy`. Keep in sync with the label resolved + # in src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ + # BazelModTidyFunction.java (search for "buildozer.exe"). + "@buildozer_binary//:buildozer.exe", + ], +)