blob: e171cd8b8504003fb4c6504690969904df93653a [file] [log] [blame]
# pylint: disable=g-backslash-continuation
# Copyright 2024 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# pylint: disable=g-long-ternary
import os
import tempfile
from absl.testing import absltest
from src.test.py.bazel import test_base
from src.test.py.bazel.bzlmod.test_utils import BazelRegistry
class BazelVendorTest(test_base.TestBase):
def setUp(self):
test_base.TestBase.setUp(self)
self.registries_work_dir = tempfile.mkdtemp(dir=self._test_cwd)
self.main_registry = BazelRegistry(
os.path.join(self.registries_work_dir, 'main')
)
self.ScratchFile(
'.bazelrc',
[
# In ipv6 only network, this has to be enabled.
# 'startup --host_jvm_args=-Djava.net.preferIPv6Addresses=true',
'common --noenable_workspace',
'common --experimental_isolated_extension_usages',
'common --registry=' + self.main_registry.getURL(),
'common --registry=https://bcr.bazel.build',
'common --verbose_failures',
# Set an explicit Java language version
'common --java_language_version=8',
'common --tool_java_language_version=8',
'common --lockfile_mode=update',
'startup --windows_enable_symlinks' if self.IsWindows() else '',
],
)
self.ScratchFile('MODULE.bazel')
self.generateBuiltinModules()
def generateBuiltinModules(self):
self.ScratchFile('platforms_mock/BUILD')
self.ScratchFile(
'platforms_mock/MODULE.bazel', ['module(name="local_config_platform")']
)
self.ScratchFile('tools_mock/BUILD')
self.ScratchFile('tools_mock/MODULE.bazel', ['module(name="bazel_tools")'])
self.ScratchFile('tools_mock/tools/build_defs/repo/BUILD')
self.CopyFile(
self.Rlocation('io_bazel/tools/build_defs/repo/cache.bzl'),
'tools_mock/tools/build_defs/repo/cache.bzl',
)
self.CopyFile(
self.Rlocation('io_bazel/tools/build_defs/repo/http.bzl'),
'tools_mock/tools/build_defs/repo/http.bzl',
)
self.CopyFile(
self.Rlocation('io_bazel/tools/build_defs/repo/utils.bzl'),
'tools_mock/tools/build_defs/repo/utils.bzl',
)
def testBasicVendoring(self):
self.main_registry.createCcModule('aaa', '1.0').createCcModule(
'bbb', '1.0', {'aaa': '1.0'}
)
self.ScratchFile(
'MODULE.bazel',
[
'bazel_dep(name = "bbb", version = "1.0")',
'local_path_override(module_name="bazel_tools", path="tools_mock")',
'local_path_override(module_name="local_config_platform", ',
'path="platforms_mock")',
],
)
self.ScratchFile('BUILD')
self.RunBazel(['vendor', '--vendor_dir=vendor'])
# Assert repos are vendored with marker files and .vendorignore is created
repos_vendored = os.listdir(self._test_cwd + '/vendor')
self.assertIn('aaa~', repos_vendored)
self.assertIn('bbb~', repos_vendored)
self.assertIn('@aaa~.marker', repos_vendored)
self.assertIn('@bbb~.marker', repos_vendored)
self.assertIn('.vendorignore', repos_vendored)
def testVendorFailsWithNofetch(self):
self.ScratchFile('MODULE.bazel')
self.ScratchFile('BUILD')
_, _, stderr = self.RunBazel(
['vendor', '--vendor_dir=vendor', '--nofetch'], allow_failure=True
)
self.assertIn('ERROR: You cannot run vendor with --nofetch', stderr)
def testVendoringMultipleTimes(self):
self.main_registry.createCcModule('aaa', '1.0')
self.ScratchFile(
'MODULE.bazel',
[
'bazel_dep(name = "aaa", version = "1.0")',
'local_path_override(module_name="bazel_tools", path="tools_mock")',
'local_path_override(module_name="local_config_platform", ',
'path="platforms_mock")',
],
)
self.ScratchFile('BUILD')
self.RunBazel(['vendor', '--vendor_dir=vendor'])
# Clean the external cache
self.RunBazel(['clean', '--expunge'])
# Re-vendoring should NOT re-fetch, but only create symlinks
# We need to check this because the vendor logic depends on the fetch logic,
# but we don't want to re-fetch if our vendored repo is already up-to-date!
self.RunBazel(['vendor', '--vendor_dir=vendor'])
_, stdout, _ = self.RunBazel(['info', 'output_base'])
repo_path = stdout[0] + '/external/aaa~'
if self.IsWindows():
self.assertTrue(self.IsJunction(repo_path))
else:
self.assertTrue(os.path.islink(repo_path))
def testVendorRepo(self):
self.main_registry.createCcModule('aaa', '1.0').createCcModule(
'bbb', '1.0', {'aaa': '1.0'}
).createCcModule('ccc', '1.0')
self.ScratchFile(
'MODULE.bazel',
[
'bazel_dep(name = "bbb", version = "1.0")',
'bazel_dep(name = "ccc", version = "1.0", repo_name = "my_repo")',
'local_path_override(module_name="bazel_tools", path="tools_mock")',
'local_path_override(module_name="local_config_platform", ',
'path="platforms_mock")',
],
)
self.ScratchFile('BUILD')
# Test canonical/apparent repo names & multiple repos
self.RunBazel(
['vendor', '--vendor_dir=vendor', '--repo=@@bbb~', '--repo=@my_repo']
)
repos_vendored = os.listdir(self._test_cwd + '/vendor')
self.assertIn('bbb~', repos_vendored)
self.assertIn('ccc~', repos_vendored)
self.assertNotIn('aaa~', repos_vendored)
def testVendorExistingRepo(self):
self.main_registry.createCcModule('aaa', '1.0')
self.ScratchFile(
'MODULE.bazel',
[
'bazel_dep(name = "aaa", version = "1.0", repo_name = "my_repo")',
'local_path_override(module_name="bazel_tools", path="tools_mock")',
'local_path_override(module_name="local_config_platform", ',
'path="platforms_mock")',
],
)
self.ScratchFile('BUILD')
# Test canonical/apparent repo names & multiple repos
self.RunBazel(['vendor', '--vendor_dir=vendor', '--repo=@my_repo'])
self.assertIn('aaa~', os.listdir(self._test_cwd + '/vendor'))
# Delete repo from external cache
self.RunBazel(['clean', '--expunge'])
# Vendoring again should find that it is already up-to-date and exclude it
# from vendoring not fail
self.RunBazel(['vendor', '--vendor_dir=vendor', '--repo=@my_repo'])
def testVendorInvalidRepo(self):
# Invalid repo name (not canonical or apparent)
exit_code, _, stderr = self.RunBazel(
['vendor', '--vendor_dir=vendor', '--repo=hello'], allow_failure=True
)
self.AssertExitCode(exit_code, 8, stderr)
self.assertIn(
'ERROR: Invalid repo name: The repo value has to be either apparent'
" '@repo' or canonical '@@repo' repo name",
stderr,
)
# Repo does not exist
self.ScratchFile(
'MODULE.bazel',
[
'local_path_override(module_name="bazel_tools", path="tools_mock")',
'local_path_override(module_name="local_config_platform", ',
'path="platforms_mock")',
],
)
exit_code, _, stderr = self.RunBazel(
['vendor', '--vendor_dir=vendor', '--repo=@@nono', '--repo=@nana'],
allow_failure=True,
)
self.AssertExitCode(exit_code, 8, stderr)
self.assertIn(
"ERROR: Vendoring some repos failed with errors: [Repository '@@nono'"
" is not defined, No repository visible as '@nana' from main"
' repository]',
stderr,
)
# Remove this test when workspace is removed
def testVendorDirIsNotCheckedForWorkspaceRepos(self):
self.ScratchFile(
'MODULE.bazel',
[
'local_path_override(module_name="bazel_tools", path="tools_mock")',
'local_path_override(module_name="local_config_platform", ',
'path="platforms_mock")',
],
)
self.ScratchFile(
'WORKSPACE.bzlmod',
['load("//:main.bzl", "dump_env")', 'dump_env(name = "dummyRepo")'],
)
self.ScratchFile('BUILD')
self.ScratchFile(
'main.bzl',
[
'def _dump_env(ctx):',
' ctx.file("BUILD")',
'dump_env = repository_rule(implementation = _dump_env)',
],
)
_, _, stderr = self.RunBazel([
'fetch',
'@@dummyRepo//:all',
'--enable_workspace=true',
'--vendor_dir=blabla',
])
self.assertNotIn(
"Vendored repository 'dummyRepo' is out-of-date.", '\n'.join(stderr)
)
def testBuildingWithVendoredRepos(self):
self.main_registry.createCcModule('aaa', '1.0')
self.ScratchFile(
'MODULE.bazel',
[
'bazel_dep(name = "aaa", version = "1.0")',
],
)
self.ScratchFile('BUILD')
self.RunBazel(['vendor', '--vendor_dir=vendor'])
self.assertIn('aaa~', os.listdir(self._test_cwd + '/vendor'))
# Empty external & build with vendor
self.RunBazel(['clean', '--expunge'])
_, _, stderr = self.RunBazel(['build', '@aaa//:all', '--vendor_dir=vendor'])
self.assertNotIn(
"Vendored repository '_main~ext~justRepo' is out-of-date.",
'\n'.join(stderr),
)
# Assert repo aaa in {OUTPUT_BASE}/external is a symlink (junction on
# windows, this validates it was created from vendor and not fetched)=
_, stdout, _ = self.RunBazel(['info', 'output_base'])
repo_path = stdout[0] + '/external/aaa~'
if self.IsWindows():
self.assertTrue(self.IsJunction(repo_path))
else:
self.assertTrue(os.path.islink(repo_path))
def testIgnoreFromVendoring(self):
# Repos should be excluded from vendoring:
# 1.Local Repos, 2.Config Repos, 3.Repos declared in .vendorignore file
self.main_registry.createCcModule('aaa', '1.0').createCcModule(
'bbb', '1.0', {'aaa': '1.0'}
)
self.ScratchFile(
'MODULE.bazel',
[
'bazel_dep(name = "bbb", version = "1.0")',
'ext = use_extension("extension.bzl", "ext")',
'use_repo(ext, "regularRepo")',
'use_repo(ext, "localRepo")',
'use_repo(ext, "configRepo")',
'local_path_override(module_name="bazel_tools", path="tools_mock")',
'local_path_override(module_name="local_config_platform", ',
'path="platforms_mock")',
],
)
self.ScratchFile('BUILD')
self.ScratchFile(
'extension.bzl',
[
'def _repo_rule_impl(ctx):',
' ctx.file("WORKSPACE")',
' ctx.file("BUILD")',
'',
'repo_rule1 = repository_rule(implementation=_repo_rule_impl)',
'repo_rule2 = repository_rule(implementation=_repo_rule_impl, ',
'local=True)',
'repo_rule3 = repository_rule(implementation=_repo_rule_impl, ',
'configure=True)',
'',
'def _ext_impl(ctx):',
' repo_rule1(name="regularRepo")',
' repo_rule2(name="localRepo")',
' repo_rule3(name="configRepo")',
'ext = module_extension(implementation=_ext_impl)',
],
)
os.makedirs(self._test_cwd + '/vendor', exist_ok=True)
with open(self._test_cwd + '/vendor/.vendorignore', 'w') as f:
f.write('aaa~\n')
self.RunBazel(['vendor', '--vendor_dir=vendor'])
repos_vendored = os.listdir(self._test_cwd + '/vendor')
# Assert bbb and the regularRepo are vendored with marker files
self.assertIn('bbb~', repos_vendored)
self.assertIn('@bbb~.marker', repos_vendored)
self.assertIn('_main~ext~regularRepo', repos_vendored)
self.assertIn('@_main~ext~regularRepo.marker', repos_vendored)
# Assert aaa (from .vendorignore), local and config repos are not vendored
self.assertNotIn('aaa~', repos_vendored)
self.assertNotIn('bazel_tools', repos_vendored)
self.assertNotIn('local_config_platform', repos_vendored)
self.assertNotIn('_main~ext~localRepo', repos_vendored)
self.assertNotIn('_main~ext~configRepo', repos_vendored)
def testBuildingOutOfDateVendoredRepo(self):
self.ScratchFile(
'MODULE.bazel',
[
'ext = use_extension("extension.bzl", "ext")',
'use_repo(ext, "justRepo")',
],
)
self.ScratchFile('BUILD')
self.ScratchFile(
'extension.bzl',
[
'def _repo_rule_impl(ctx):',
' ctx.file("WORKSPACE")',
' ctx.file("BUILD", "filegroup(name=\'lala\')")',
'repo_rule = repository_rule(implementation=_repo_rule_impl)',
'',
'def _ext_impl(ctx):',
' repo_rule(name="justRepo")',
'ext = module_extension(implementation=_ext_impl)',
],
)
# Vendor, assert and build with no problems
self.RunBazel(['vendor', '--vendor_dir=vendor'])
self.assertIn('_main~ext~justRepo', os.listdir(self._test_cwd + '/vendor'))
_, _, stderr = self.RunBazel(
['build', '@justRepo//:all', '--vendor_dir=vendor']
)
self.assertNotIn(
"WARNING: <builtin>: Vendored repository '_main~ext~justRepo' is"
' out-of-date. The up-to-date version will be fetched into the external'
' cache and used. To update the repo in the vendor directory, run'
" 'bazel vendor'",
stderr,
)
# Make updates in repo definition
self.ScratchFile(
'extension.bzl',
[
'def _repo_rule_impl(ctx):',
' ctx.file("WORKSPACE")',
' ctx.file("BUILD", "filegroup(name=\'haha\')")',
'repo_rule = repository_rule(implementation=_repo_rule_impl)',
'',
'def _ext_impl(ctx):',
' repo_rule(name="justRepo")',
'ext = module_extension(implementation=_ext_impl)',
],
)
# Clean cache, and re-build with vendor
self.RunBazel(['clean', '--expunge'])
_, _, stderr = self.RunBazel(
['build', '@justRepo//:all', '--vendor_dir=vendor']
)
# Assert repo in vendor is out-of-date, and the new one is fetched into
# external and not a symlink
self.assertIn(
"WARNING: <builtin>: Vendored repository '_main~ext~justRepo' is"
' out-of-date. The up-to-date version will be fetched into the external'
' cache and used. To update the repo in the vendor directory, run'
" 'bazel vendor'",
stderr,
)
_, stdout, _ = self.RunBazel(['info', 'output_base'])
self.assertFalse(os.path.islink(stdout[0] + '/external/bbb~1.0'))
# Assert vendoring again solves the problem
self.RunBazel(['vendor', '--vendor_dir=vendor'])
self.RunBazel(['clean', '--expunge'])
_, _, stderr = self.RunBazel(
['build', '@justRepo//:all', '--vendor_dir=vendor']
)
self.assertNotIn(
"WARNING: <builtin>: Vendored repository '_main~ext~justRepo' is"
' out-of-date. The up-to-date version will be fetched into the external'
' cache and used. To update the repo in the vendor directory, run'
" 'bazel vendor'",
stderr,
)
def testBuildingVendoredRepoInOfflineMode(self):
self.ScratchFile(
'MODULE.bazel',
[
'ext = use_extension("extension.bzl", "ext")',
'use_repo(ext, "venRepo")',
],
)
self.ScratchFile(
'extension.bzl',
[
'def _repo_rule_impl(ctx):',
' ctx.file("WORKSPACE")',
' ctx.file("BUILD", "filegroup(name=\'lala\')")',
'repo_rule = repository_rule(implementation=_repo_rule_impl)',
'',
'def _ext_impl(ctx):',
' repo_rule(name="venRepo")',
'ext = module_extension(implementation=_ext_impl)',
],
)
self.ScratchFile('BUILD')
# Vendor, assert and build with no problems
self.RunBazel(['vendor', '--vendor_dir=vendor'])
self.assertIn('_main~ext~venRepo', os.listdir(self._test_cwd + '/vendor'))
# Make updates in repo definition
self.ScratchFile(
'MODULE.bazel',
[
'ext = use_extension("extension.bzl", "ext")',
'use_repo(ext, "venRepo")',
'use_repo(ext, "noVenRepo")',
],
)
self.ScratchFile(
'extension.bzl',
[
'def _repo_rule_impl(ctx):',
' ctx.file("WORKSPACE")',
' ctx.file("BUILD", "filegroup(name=\'haha\')")',
'repo_rule = repository_rule(implementation=_repo_rule_impl)',
'',
'def _ext_impl(ctx):',
' repo_rule(name="venRepo")',
' repo_rule(name="noVenRepo")',
'ext = module_extension(implementation=_ext_impl)',
],
)
# Building a repo that is not vendored in offline mode, should fail
_, _, stderr = self.RunBazel(
['build', '@noVenRepo//:all', '--vendor_dir=vendor', '--nofetch'],
allow_failure=True,
)
self.assertIn(
'ERROR: Vendored repository _main~ext~noVenRepo not found under the'
" vendor directory and fetching is disabled. To fix run 'bazel"
" vendor' or build without the '--nofetch'",
stderr,
)
# Building out-of-date repo in offline mode, should build the out-dated one
# and emit a warning
_, _, stderr = self.RunBazel(
['build', '@venRepo//:all', '--vendor_dir=vendor', '--nofetch'],
)
self.assertIn(
"WARNING: <builtin>: Vendored repository '_main~ext~venRepo' is"
' out-of-date and fetching is disabled. Run build without the'
" '--nofetch' option or run `bazel vendor` to update it",
stderr,
)
# Assert the out-dated repo is the one built with
self.assertIn(
'Target @@_main~ext~venRepo//:lala up-to-date (nothing to build)',
stderr,
)
if __name__ == '__main__':
absltest.main()