blob: 180f2d0cd7bd1af051a8eb964db1645cacae32c2 [file] [edit]
# pylint: disable=g-backslash-continuation
# Copyright 2021 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 pathlib
import shutil
import subprocess
import sys
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
from src.test.py.bazel.bzlmod.test_utils import integrity
from src.test.py.bazel.bzlmod.test_utils import read
from src.test.py.bazel.bzlmod.test_utils import scratchFile
class BazelModuleTest(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.main_registry.start()
self.main_registry.createShModule('aaa', '1.0').createShModule(
'aaa', '1.1'
).createShModule(
'bbb', '1.0', {'aaa': '1.0'}, {'aaa': 'com_foo_aaa'}
).createShModule(
'bbb', '1.1', {'aaa': '1.1'}
).createShModule(
'ccc', '1.1', {'aaa': '1.1', 'bbb': '1.1'}
)
self.ScratchFile(
'.bazelrc',
[
# In ipv6 only network, this has to be enabled.
# 'startup --host_jvm_args=-Djava.net.preferIPv6Addresses=true',
'build --registry=' + self.main_registry.getURL(),
# We need to have BCR here to make sure built-in modules like
# bazel_tools can work.
'build --registry=https://bcr.bazel.build',
'build --verbose_failures',
# Set an explicit Java language version
'build --java_language_version=8',
'build --tool_java_language_version=8',
'build --lockfile_mode=update',
( # fmt: skip pylint: disable=line-too-long
'build'
' --extra_toolchains=@bazel_tools//tools/python:autodetecting_toolchain'
),
# TODO(bazel-team): Remove once rules_python exports runtime_env_toolchain_interpreter.sh
# See https://github.com/bazel-contrib/rules_python/pull/3471
'build --noincompatible_no_implicit_file_export',
],
)
def tearDown(self):
self.main_registry.stop()
test_base.TestBase.tearDown(self)
def writeMainProjectFiles(self):
self.ScratchFile(
'aaa.patch',
[
'--- a/aaa.sh',
'+++ b/aaa.sh',
'@@ -1,5 +1,5 @@',
' function hello_aaa {',
' caller_name="${1}"',
'- lib_name="aaa@1.0"',
'+ lib_name="aaa@1.0 (locally patched)"',
' echo "${caller_name} => ${lib_name}"',
' }',
],
)
self.ScratchFile(
'BUILD',
[
'load("@rules_shell//shell:sh_binary.bzl", "sh_binary")',
'sh_binary(',
' name = "main",',
' srcs = ["main.sh"],',
' deps = [',
' "@aaa//:lib_aaa",',
' "@bbb//:lib_bbb",',
' ],',
' use_bash_launcher = True,',
')',
],
)
self.ScratchFile(
'main.sh',
[
'source $(rlocation aaa/aaa.sh)',
'source $(rlocation bbb/bbb.sh)',
'hello_aaa "main function"',
'hello_bbb "main function"',
],
executable=True,
)
def testSimple(self):
self.ScratchFile('MODULE.bazel', [
'bazel_dep(name = "aaa", version = "1.0")',
])
self.AddBazelDep('rules_shell')
self.ScratchFile(
'BUILD',
[
'load("@rules_shell//shell:sh_binary.bzl", "sh_binary")',
'sh_binary(',
' name = "main",',
' srcs = ["main.sh"],',
' deps = ["@aaa//:lib_aaa"],',
' use_bash_launcher = True,',
')',
],
)
self.ScratchFile(
'main.sh',
[
'source $(rlocation aaa/aaa.sh)',
'hello_aaa "main function"',
],
executable=True,
)
_, stdout, _ = self.RunBazel(['run', '//:main'])
self.assertIn('main function => aaa@1.0', stdout)
def testSimpleTransitive(self):
self.ScratchFile('MODULE.bazel', [
'bazel_dep(name = "bbb", version = "1.0")',
])
self.AddBazelDep('rules_shell')
self.ScratchFile(
'BUILD',
[
'load("@rules_shell//shell:sh_binary.bzl", "sh_binary")',
'sh_binary(',
' name = "main",',
' srcs = ["main.sh"],',
' deps = ["@bbb//:lib_bbb"],',
' use_bash_launcher = True,',
')',
],
)
self.ScratchFile(
'main.sh',
[
'source $(rlocation bbb/bbb.sh)',
'hello_bbb "main function"',
],
executable=True,
)
_, stdout, _ = self.RunBazel(['run', '//:main'])
self.assertIn('main function => bbb@1.0', stdout)
self.assertIn('bbb@1.0 => aaa@1.0', stdout)
def testSimpleDiamond(self):
self.writeMainProjectFiles()
self.ScratchFile(
'MODULE.bazel',
[
'bazel_dep(name = "aaa", version = "1.1")',
# bbb@1.0 has to depend on aaa@1.1 after MVS.
'bazel_dep(name = "bbb", version = "1.0")',
])
self.AddBazelDep('rules_shell')
_, stdout, _ = self.RunBazel(['run', '//:main'])
self.assertIn('main function => aaa@1.1', stdout)
self.assertIn('main function => bbb@1.0', stdout)
self.assertIn('bbb@1.0 => aaa@1.1', stdout)
def testRemotePatchForBazelDep(self):
patch_file = self.ScratchFile(
'aaa.patch',
[
'--- a/aaa.sh',
'+++ b/aaa.sh',
'@@ -1,5 +1,5 @@',
' function hello_aaa {',
' caller_name="${1}"',
'- lib_name="aaa@1.1-1"',
'+ lib_name="aaa@1.1-1 (remotely patched)"',
' echo "${caller_name} => ${lib_name}"',
' }',
],
)
self.main_registry.createShModule(
'aaa', '1.1-1', patches=[patch_file], patch_strip=1
)
self.ScratchFile('MODULE.bazel', [
'bazel_dep(name = "aaa", version = "1.1-1")',
])
self.AddBazelDep('rules_shell')
self.ScratchFile(
'BUILD',
[
'load("@rules_shell//shell:sh_binary.bzl", "sh_binary")',
'sh_binary(',
' name = "main",',
' srcs = ["main.sh"],',
' deps = ["@aaa//:lib_aaa"],',
' use_bash_launcher = True,',
')',
],
)
self.ScratchFile(
'main.sh',
[
'source $(rlocation aaa/aaa.sh)',
'hello_aaa "main function"',
],
executable=True,
)
_, stdout, _ = self.RunBazel(['run', '//:main'])
self.assertIn('main function => aaa@1.1-1 (remotely patched)', stdout)
def testArchiveOverrideWithMainRepoLabelPatch(self):
self.ScratchFile(
'aaa.patch',
[
'--- a/aaa.sh',
'+++ b/aaa.sh',
'@@ -1,5 +1,5 @@',
' function hello_aaa {',
' caller_name="${1}"',
'- lib_name="aaa@lol"',
'+ lib_name="aaa@lol (locally patched)"',
' echo "${caller_name} => ${lib_name}"',
' }',
],
)
self.main_registry.createShModule('aaa', 'lol')
integ = integrity(read(self.main_registry.archives.joinpath('aaa.lol.zip')))
self.ScratchFile(
'MODULE.bazel',
[
'module(repo_name="foo")',
'bazel_dep(name = "aaa")',
'archive_override(',
' module_name="aaa",',
' urls=["%s/archives/aaa.lol.zip"],' % self.main_registry.getURL(),
' integrity="%s",' % integ,
' patches=["@foo//:aaa.patch"],',
' patch_strip=1,',
')',
],
)
self.AddBazelDep('rules_shell')
self.ScratchFile(
'BUILD',
[
'load("@rules_shell//shell:sh_binary.bzl", "sh_binary")',
'sh_binary(',
' name = "main",',
' srcs = ["main.sh"],',
' deps = ["@aaa//:lib_aaa"],',
' use_bash_launcher = True,',
')',
],
)
self.ScratchFile(
'main.sh',
[
'source $(rlocation aaa/aaa.sh)',
'hello_aaa "main function"',
],
executable=True,
)
_, stdout, _ = self.RunBazel(['run', '//:main'])
self.assertIn('main function => aaa@lol (locally patched)', stdout)
def testArchiveOverrideWithBadLabelPatch(self):
self.main_registry.createShModule('aaa', '1')
self.main_registry.createShModule('bbb', '1')
integ = integrity(read(self.main_registry.archives.joinpath('aaa.1.zip')))
self.ScratchFile(
'MODULE.bazel',
[
'module(repo_name="foo")',
'bazel_dep(name = "aaa")',
'bazel_dep(name = "bbb", version = "1")',
'archive_override(',
' module_name="aaa",',
' urls=["%s/archives/aaa.1.zip"],' % self.main_registry.getURL(),
' integrity="%s",' % integ,
' patches=["@bbb//:aaa.patch"])',
],
)
self.AddBazelDep('rules_shell')
self.ScratchFile(
'BUILD',
[
'load("@rules_shell//shell:sh_binary.bzl", "sh_binary")',
'sh_binary(',
' name = "main",',
' srcs = ["main.sh"],',
' deps = ["@aaa//:lib_aaa"],',
' use_bash_launcher = True,',
')',
],
)
self.ScratchFile(
'main.sh',
[
'source $(rlocation aaa/aaa.sh)',
'hello_aaa "main function"',
],
executable=True,
)
exit_code, _, stderr = self.RunBazel(['run', '//:main'], allow_failure=True)
self.AssertNotExitCode(exit_code, 0, stderr)
self.assertIn(
"Error in archive_override: invalid label in 'patches': only patches in"
" the main repository can be applied, not from '@bbb'",
'\n'.join(stderr),
)
def testRepoNameForBazelDep(self):
self.writeMainProjectFiles()
self.ScratchFile(
'MODULE.bazel',
[
'bazel_dep(name = "aaa", version = "1.0", repo_name = "my_repo_a_name")',
# bbb should still be able to access aaa as com_foo_aaa
'bazel_dep(name = "bbb", version = "1.0")',
])
self.AddBazelDep('rules_shell')
self.ScratchFile(
'BUILD',
[
'load("@rules_shell//shell:sh_binary.bzl", "sh_binary")',
'sh_binary(',
' name = "main",',
' srcs = ["main.sh"],',
' deps = [',
' "@my_repo_a_name//:lib_aaa",',
' "@bbb//:lib_bbb",',
' ],',
' use_bash_launcher = True,',
')',
],
)
_, stdout, _ = self.RunBazel(['run', '//:main'])
self.assertIn('main function => aaa@1.0', stdout)
self.assertIn('main function => bbb@1.0', stdout)
self.assertIn('bbb@1.0 => aaa@1.0', stdout)
def testCheckDirectDependencies(self):
self.writeMainProjectFiles()
self.ScratchFile('MODULE.bazel', [
'bazel_dep(name = "aaa", version = "1.0")',
'bazel_dep(name = "bbb", version = "1.0")',
'bazel_dep(name = "ccc", version = "1.1")',
])
self.AddBazelDep('rules_shell')
_, stdout, stderr = self.RunBazel(
['run', '//:main', '--check_direct_dependencies=warning']
)
stderr = '\n'.join(stderr)
self.assertIn(
'WARNING: For repository \'aaa\', the root module requires module version aaa@1.0, but got aaa@1.1 in the resolved dependency graph.',
stderr)
self.assertIn(
'WARNING: For repository \'bbb\', the root module requires module version bbb@1.0, but got bbb@1.1 in the resolved dependency graph.',
stderr)
self.assertIn('main function => aaa@1.1', stdout)
self.assertIn('main function => bbb@1.1', stdout)
self.assertIn('bbb@1.1 => aaa@1.1', stdout)
exit_code, _, stderr = self.RunBazel(
['run', '//:main', '--check_direct_dependencies=error'],
allow_failure=True)
self.AssertExitCode(exit_code, 48, stderr)
stderr = '\n'.join(stderr)
self.assertIn(
'ERROR: For repository \'aaa\', the root module requires module version aaa@1.0, but got aaa@1.1 in the resolved dependency graph.',
stderr)
self.assertIn(
'ERROR: For repository \'bbb\', the root module requires module version bbb@1.0, but got bbb@1.1 in the resolved dependency graph.',
stderr)
def testRepositoryRuleErrorInModuleExtensionFailsTheBuild(self):
self.ScratchFile('MODULE.bazel', [
'module_ext = use_extension("//pkg:extension.bzl", "module_ext")',
'use_repo(module_ext, "foo")',
])
self.ScratchFile('pkg/BUILD.bazel')
self.ScratchFile(
'pkg/rules.bzl',
[
'def impl(ctx):',
' pass',
'repo_rule = repository_rule(implementation = impl)',
],
)
self.ScratchFile('pkg/extension.bzl', [
'load(":rules.bzl", "repo_rule")',
'def _module_ext_impl(ctx):',
' repo_rule(name = "foo", invalid_attr = "value")',
'module_ext = module_extension(implementation = _module_ext_impl)',
])
exit_code, _, stderr = self.RunBazel(['run', '@foo//...'],
allow_failure=True)
self.AssertExitCode(exit_code, 48, stderr)
stderr = '\n'.join(stderr)
self.assertIn(
"Error: in call to 'repo_rule' repo rule with name 'foo', unknown"
" attribute 'invalid_attr' provided",
stderr,
)
self.assertIn(
'/pkg/extension.bzl", line 3, column 14, in _module_ext_impl',
stderr,
)
def testDownload(self):
data_path = self.ScratchFile('data.txt', ['some data'])
data_url = pathlib.Path(data_path).resolve().as_uri()
self.ScratchFile('MODULE.bazel', [
'data_ext = use_extension("//:ext.bzl", "data_ext")',
'use_repo(data_ext, "no_op")',
])
self.ScratchFile('BUILD')
self.ScratchFile('ext.bzl', [
'def _no_op_impl(ctx):',
' ctx.file("BUILD", "filegroup(name=\\"no_op\\")")',
'no_op = repository_rule(_no_op_impl)',
'def _data_ext_impl(ctx):',
' if not ctx.download(url="%s", output="data.txt").success:' %
data_url,
' fail("download failed")',
' if ctx.read("data.txt").strip() != "some data":',
' fail("unexpected downloaded content: %s" % ctx.read("data.txt").strip())',
' no_op(name="no_op")',
'data_ext = module_extension(_data_ext_impl)',
])
self.RunBazel(['build', '@no_op//:no_op'])
def testNoRestart(self):
self.ScratchFile(
'MODULE.bazel',
[
'data_ext = use_extension("//:ext.bzl", "data_ext")',
'use_repo(data_ext, "no_op")',
],
)
self.ScratchFile('BUILD')
self.ScratchFile('foo.txt', ['abc'])
self.ScratchFile(
'ext.bzl',
[
'def _no_op_impl(ctx):',
' ctx.file("BUILD", "filegroup(name=\\"no_op\\")")',
'no_op = repository_rule(_no_op_impl)',
'def _data_ext_impl(ctx):',
' print("I AM HERE")',
' ctx.watch(Label("//:foo.txt"))',
' no_op(name="no_op")',
'data_ext = module_extension(_data_ext_impl)',
],
)
_, _, stderr = self.RunBazel(['build', '@no_op//:no_op'])
stderr = '\n'.join(stderr)
self.assertIn('I AM HERE', stderr)
self.assertEqual(
stderr.count('I AM HERE'),
1,
'expected "I AM HERE" once, but got %s times:\n%s'
% (stderr.count('I AM HERE'), stderr),
)
def setUpProjectWithLocalRegistryModule(self, dep_name, dep_version):
self.main_registry.generateShSource(dep_name, dep_version)
self.main_registry.createLocalPathModule(dep_name, dep_version,
dep_name + '/' + dep_version)
self.writeShProjectFiles(dep_name, dep_version)
def writeShProjectFiles(self, dep_name, dep_version):
self.ScratchFile(
'main.sh',
[
'source $(rlocation %s/%s.sh)' % (dep_name, dep_name),
'hello_%s "main function"' % dep_name,
],
executable=True,
)
self.ScratchFile('MODULE.bazel', [
'bazel_dep(name = "%s", version = "%s")' % (dep_name, dep_version),
])
self.AddBazelDep('rules_shell')
self.ScratchFile(
'BUILD',
[
'load("@rules_shell//shell:sh_binary.bzl", "sh_binary")',
'sh_binary(',
' name = "main",',
' srcs = ["main.sh"],',
' deps = ["@%s//:lib_%s"],' % (dep_name, dep_name),
' use_bash_launcher = True,',
')',
],
)
def setUpProjectWithGitRegistryModule(self, dep_name, dep_version):
src_dir = self.main_registry.generateShSource(dep_name, dep_version)
# Move the src_dir to a temp dir and make that temp dir a git repo.
repo_dir = os.path.join(self.registries_work_dir, 'git_repo', dep_name)
os.makedirs(repo_dir)
shutil.move(
# Workaround https://bugs.python.org/issue32689 for Python < 3.9
str(src_dir),
repo_dir,
)
repo_dir = os.path.join(repo_dir, os.path.basename(src_dir))
subprocess.check_output(['git', 'init'], cwd=repo_dir)
subprocess.check_output(
['git', 'config', 'user.email', 'example@bazel-dev.org'], cwd=repo_dir
)
subprocess.check_output(
['git', 'config', 'user.name', 'example'], cwd=repo_dir
)
subprocess.check_output(['git', 'add', '--all'], cwd=repo_dir)
subprocess.check_output(['git', 'commit', '-m', 'Initialize'], cwd=repo_dir)
commit = subprocess.check_output(
['git', 'rev-parse', 'HEAD'], cwd=repo_dir, universal_newlines=True
).strip()
self.main_registry.createGitRepoModule(
dep_name,
dep_version,
repo_dir,
commit=commit,
verbose=True,
shallow_since='2000-01-02',
)
self.writeShProjectFiles(dep_name, dep_version)
def testLocalRepoInSourceJsonAbsoluteBasePath(self):
self.main_registry.setModuleBasePath(str(self.main_registry.projects))
self.setUpProjectWithLocalRegistryModule('sss', '1.3')
_, stdout, _ = self.RunBazel(['run', '//:main'])
self.assertIn('main function => sss@1.3', stdout)
def testLocalRepoInSourceJsonRelativeBasePath(self):
self.main_registry.setModuleBasePath('projects')
self.setUpProjectWithLocalRegistryModule('sss', '1.3')
_, stdout, _ = self.RunBazel(['run', '//:main'])
self.assertIn('main function => sss@1.3', stdout)
def testGitRepoAbsoluteBasePath(self):
self.main_registry.setModuleBasePath(str(self.main_registry.projects))
self.setUpProjectWithGitRegistryModule('sss', '1.3')
_, stdout, _ = self.RunBazel(['run', '//:main'])
self.assertIn('main function => sss@1.3', stdout)
def testNativePackageRelativeLabel(self):
self.ScratchFile(
'MODULE.bazel',
[
'module(name="foo")',
'bazel_dep(name="bar")',
'local_path_override(module_name="bar",path="bar")',
],
)
self.ScratchFile('BUILD')
self.ScratchFile(
'defs.bzl',
[
'def mac(name):',
' native.filegroup(name=name)',
' print("1st: " + str(native.package_relative_label(":bleb")))',
' print("2nd: " + str(native.package_relative_label('
+ '"//bleb:bleb")))',
' print("3rd: " + str(native.package_relative_label('
+ '"@bleb//bleb:bleb")))',
' print("4th: " + str(native.package_relative_label("//bleb")))',
' print("5th: " + str(native.package_relative_label('
+ '"@@bleb//bleb:bleb")))',
' print("6th: " + str(native.package_relative_label(Label('
+ '"//bleb"))))',
],
)
self.ScratchFile(
'bar/MODULE.bazel',
[
'module(name="bar")',
'bazel_dep(name="foo", repo_name="bleb")',
],
)
self.ScratchFile(
'bar/quux/BUILD',
[
'load("@bleb//:defs.bzl", "mac")',
'mac(name="book")',
],
)
_, _, stderr = self.RunBazel(['build', '@bar//quux:book'])
stderr = '\n'.join(stderr)
self.assertIn('1st: @@bar+//quux:bleb', stderr)
self.assertIn('2nd: @@bar+//bleb:bleb', stderr)
self.assertIn('3rd: @@//bleb:bleb', stderr)
self.assertIn('4th: @@bar+//bleb:bleb', stderr)
self.assertIn('5th: @@bleb//bleb:bleb', stderr)
self.assertIn('6th: @@//bleb:bleb', stderr)
def testCtxPackageRelativeLabel(self):
self.ScratchFile(
'MODULE.bazel',
[
'module(name="foo")',
'bazel_dep(name="bar")',
'local_path_override(module_name="bar",path="bar")',
],
)
self.ScratchFile('BUILD')
self.ScratchFile(
'defs.bzl',
[
'def _my_rule_impl(ctx):',
' print("1st: " + str(ctx.package_relative_label(":bleb")))',
' print("2nd: " + str(ctx.package_relative_label('
+ '"//bleb:bleb")))',
' print("3rd: " + str(ctx.package_relative_label('
+ '"@bleb//bleb:bleb")))',
' print("4th: " + str(ctx.package_relative_label("//bleb")))',
' print("5th: " + str(ctx.package_relative_label('
+ '"@@bleb//bleb:bleb")))',
' print("6th: " + str(ctx.package_relative_label(Label('
+ '"//bleb"))))',
'my_rule = rule(_my_rule_impl)',
],
)
self.ScratchFile(
'bar/MODULE.bazel',
[
'module(name="bar")',
'bazel_dep(name="foo", repo_name="bleb")',
],
)
self.ScratchFile(
'bar/quux/BUILD',
[
'load("@bleb//:defs.bzl", "my_rule")',
'my_rule(name="book")',
],
)
_, _, stderr = self.RunBazel(['build', '@bar//quux:book'])
stderr = '\n'.join(stderr)
self.assertIn('1st: @@bar+//quux:bleb', stderr)
self.assertIn('2nd: @@bar+//bleb:bleb', stderr)
self.assertIn('3rd: @@//bleb:bleb', stderr)
self.assertIn('4th: @@bar+//bleb:bleb', stderr)
self.assertIn('5th: @@bleb//bleb:bleb', stderr)
self.assertIn('6th: @@//bleb:bleb', stderr)
def testArchiveWithArchiveType(self):
# make the archive without the .zip extension
self.main_registry.createShModule(
'aaa', '1.2', archive_pattern='%s.%s', archive_type='zip'
)
self.ScratchFile(
'MODULE.bazel',
[
'bazel_dep(name = "aaa", version = "1.2")',
],
)
self.AddBazelDep('rules_shell')
self.ScratchFile(
'BUILD',
[
'load("@rules_shell//shell:sh_binary.bzl", "sh_binary")',
'sh_binary(',
' name = "main",',
' srcs = ["main.sh"],',
' deps = ["@aaa//:lib_aaa"],',
' use_bash_launcher = True,',
')',
],
)
self.ScratchFile(
'main.sh',
[
'source $(rlocation aaa/aaa.sh)',
'hello_aaa "main function"',
],
executable=True,
)
_, stdout, _ = self.RunBazel(['run', '//:main'])
self.assertIn('main function => aaa@1.2', stdout)
def testNativeModuleNameAndVersion(self):
self.main_registry.setModuleBasePath('projects')
projects_dir = self.main_registry.projects
self.ScratchFile(
'MODULE.bazel',
[
'module(name="root",version="0.1")',
'bazel_dep(name="foo",version="1.0")',
'report_ext = use_extension("@foo//:ext.bzl", "report_ext")',
'use_repo(report_ext, "report_repo")',
'bazel_dep(name="bar")',
'local_path_override(module_name="bar",path="bar")',
],
)
self.ScratchFile(
'BUILD',
[
'load("@foo//:report.bzl", "report")',
'report()',
],
)
# foo: a repo defined by a normal Bazel module. Also hosts the extension
# `report_ext` which generates a repo `report_repo`.
self.main_registry.createLocalPathModule('foo', '1.0', 'foo')
projects_dir.joinpath('foo').mkdir(exist_ok=True)
scratchFile(
projects_dir.joinpath('foo', 'BUILD'),
[
'load(":report.bzl", "report")',
'report()',
],
)
scratchFile(
projects_dir.joinpath('foo', 'report.bzl'),
[
'def report():',
' repo = native.repository_name()',
' name = str(native.module_name())',
' version = str(native.module_version())',
' print("@" + repo + " reporting in: " + name + "@" + version)',
' native.filegroup(name="a")',
],
)
scratchFile(
projects_dir.joinpath('foo', 'ext.bzl'),
[
'def _report_repo(rctx):',
' rctx.file("BUILD",',
' "load(\\"@foo//:report.bzl\\", \\"report\\")\\n" +',
' "report()")',
'report_repo = repository_rule(_report_repo)',
'report_ext = module_extension(',
' lambda mctx: report_repo(name="report_repo"))',
],
)
# bar: a repo defined by a Bazel module with a non-registry override
self.ScratchFile(
'bar/MODULE.bazel',
[
'module(name="bar", version="2.0")',
'bazel_dep(name="foo",version="1.0")',
],
)
self.ScratchFile(
'bar/BUILD',
[
'load("@foo//:report.bzl", "report")',
'report()',
],
)
_, _, stderr = self.RunBazel(
[
'build',
':a',
'@foo//:a',
'@report_repo//:a',
'@bar//:a',
],
)
stderr = '\n'.join(stderr)
self.assertIn('@@ reporting in: root@0.1', stderr)
self.assertIn('@@foo+ reporting in: foo@1.0', stderr)
self.assertIn('@@foo++report_ext+report_repo reporting in: foo@1.0', stderr)
self.assertIn('@@bar+ reporting in: bar@2.0', stderr)
def testModuleExtensionWithRuleError(self):
self.ScratchFile(
'MODULE.bazel',
[
'ext = use_extension("extensions.bzl", "ext")',
'use_repo(ext, "ext")',
],
)
self.ScratchFile('BUILD')
self.ScratchFile(
'extensions.bzl',
[
'def _rule_impl(ctx):',
' print("RULE CALLED")',
'init_rule = rule(_rule_impl)',
'def ext_impl(module_ctx):',
' init_rule()',
'ext = module_extension(implementation = ext_impl,)',
],
)
exit_code, _, stderr = self.RunBazel(
['build', '--nobuild', '@ext//:all'],
allow_failure=True,
)
self.AssertExitCode(exit_code, 48, stderr)
self.assertIn(
'Error in init_rule: a rule can only be instantiated while evaluating a'
' BUILD file or a legacy or symbolic macro',
stderr,
)
def testLocationRoot(self):
"""Tests that the reported location of the MODULE.bazel file of the root module is as expected."""
self.ScratchFile('MODULE.bazel', ['wat'])
_, _, stderr = self.RunBazel(['build', '@what'], allow_failure=True)
self.assertIn(
'ERROR: ' + self.Path('MODULE.bazel').replace('\\', '/'),
'\n'.join(stderr).replace('\\', '/'),
)
def testLocationRegistry(self):
"""Tests that the reported location of the MODULE.bazel file of a module from a registry is as expected."""
self.ScratchFile('MODULE.bazel', ['bazel_dep(name="hello",version="1.0")'])
self.main_registry.createShModule(
'hello', '1.0', extra_module_file_contents=['wat']
)
_, _, stderr = self.RunBazel(['build', '@what'], allow_failure=True)
self.assertIn(
'ERROR: '
+ self.main_registry.getURL()
+ '/modules/hello/1.0/MODULE.bazel',
'\n'.join(stderr),
)
def testLocationNonRegistry(self):
"""Tests that the reported location of the MODULE.bazel file of a module with a non-registry override is as expected."""
self.ScratchFile(
'MODULE.bazel',
[
'bazel_dep(name="hello")',
'local_path_override(module_name="hello",path="hello")',
],
)
self.ScratchFile('hello/MODULE.bazel', ['wat'])
_, _, stderr = self.RunBazel(['build', '@what'], allow_failure=True)
self.assertIn('ERROR: @@hello+//:MODULE.bazel', '\n'.join(stderr))
def testLoadRulesJavaSymbolThroughBazelTools(self):
"""Tests that loads from @bazel_tools that delegate to other modules resolve."""
self.ScratchFile(
'MODULE.bazel',
[
'ext = use_extension("//:ext.bzl", "ext")',
'use_repo(ext, "data")',
],
)
self.ScratchFile('BUILD')
self.ScratchFile(
'ext.bzl',
[
(
"load('@bazel_tools//tools/jdk:toolchain_utils.bzl',"
" 'find_java_toolchain')"
),
'def _repo_impl(ctx):',
" ctx.file('BUILD', 'exports_files([\"data.txt\"])')",
" ctx.file('data.txt', 'hi')",
'repo = repository_rule(implementation = _repo_impl)',
'def _ext_impl(ctx):',
" repo(name='data')",
'ext = module_extension(implementation = _ext_impl)',
],
)
self.RunBazel(['build', '@data//:data.txt'])
def testHttpJar(self):
"""Tests that using http_jar works with a bazel_dep on rules_java."""
my_jar_path = self.ScratchFile('my_jar.jar')
my_jar_uri = pathlib.Path(my_jar_path).as_uri()
self.ScratchFile(
'MODULE.bazel',
[
(
'http_jar ='
' use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl",'
' "http_jar")'
),
'http_jar(',
' name = "my_jar",',
' url = "%s",' % my_jar_uri,
(
' sha256 ='
' "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",'
),
')',
],
)
self.AddBazelDep('rules_java')
self.RunBazel(['build', '@my_jar//jar'])
def testInclude(self):
self.ScratchFile(
'MODULE.bazel',
[
'module(name="foo")',
'bazel_dep(name="bbb", version="1.0")',
'include("//java:java.MODULE.bazel")',
],
)
self.AddBazelDep('rules_shell')
self.ScratchFile('java/BUILD')
self.ScratchFile(
'java/java.MODULE.bazel',
[
'bazel_dep(name="aaa", version="1.0", repo_name="lol")',
],
)
self.ScratchFile(
'BUILD',
[
'load("@rules_shell//shell:sh_binary.bzl", "sh_binary")',
'sh_binary(',
' name = "main",',
' srcs = ["main.sh"],',
' deps = ["@lol//:lib_aaa"],',
' use_bash_launcher = True,',
')',
],
)
self.ScratchFile(
'main.sh',
[
'source $(rlocation aaa+/aaa.sh)',
'hello_aaa "main function"',
],
executable=True,
)
_, stdout, _ = self.RunBazel(['run', '//:main'])
self.assertIn('main function => aaa@1.0', stdout)
def testNonRegistryOverrideModuleInclude(self):
"""Tests that include() works in non-root modules with a non-registry override."""
self.ScratchFile(
'MODULE.bazel',
[
'module(name="aaa", version="0.1")',
'bazel_dep(name="bbb", version="1.0")',
'local_path_override(module_name="bbb", path="code_for_b")',
],
)
self.AddBazelDep('rules_shell')
self.ScratchFile(
'code_for_b/MODULE.bazel',
[
'module(name="bbb", version="1.0")',
'include("//subdir:bbb.MODULE.bazel")',
],
)
self.AddBazelDep('rules_shell', path='code_for_b')
self.ScratchFile('code_for_b/subdir/BUILD')
self.ScratchFile(
'code_for_b/subdir/bbb.MODULE.bazel',
[
'include("//subdir/nested:ccc.MODULE.bazel")',
],
)
self.ScratchFile('code_for_b/subdir/nested/BUILD')
self.ScratchFile(
'code_for_b/subdir/nested/ccc.MODULE.bazel',
[
'bazel_dep(name="ccc", version="2.0")',
],
)
self.ScratchFile(
'code_for_b/BUILD',
[
'load("@rules_shell//shell:sh_library.bzl", "sh_library")',
'sh_library(',
' name = "lib_bbb",',
' srcs = ["bbb.sh"],',
' deps = ["@ccc//:lib_ccc"],',
' visibility = ["//visibility:public"],',
')',
],
)
self.ScratchFile(
'code_for_b/bbb.sh',
[
'source $(rlocation ccc+/ccc.sh)',
'function hello_bbb {',
' caller_name="${1}"',
' lib_name="bbb@1.0"',
' echo "${caller_name} => ${lib_name}"',
' hello_ccc ${lib_name}',
'}',
],
executable=True,
)
self.main_registry.createShModule('ccc', '2.0', {'ddd': '3.0'})
self.main_registry.createShModule('ddd', '3.0')
self.ScratchFile(
'BUILD',
[
'load("@rules_shell//shell:sh_binary.bzl", "sh_binary")',
'sh_binary(',
' name = "main",',
' srcs = ["main.sh"],',
' deps = ["@bbb//:lib_bbb"],',
' use_bash_launcher = True,',
')',
],
)
self.ScratchFile(
'main.sh',
[
'source $(rlocation bbb+/bbb.sh)',
'hello_bbb "main function"',
],
executable=True,
)
_, stdout, _ = self.RunBazel(['run', '//:main'])
self.assertIn('main function => bbb@1.0', stdout)
self.assertIn('bbb@1.0 => ccc@2.0', stdout)
self.assertIn('ccc@2.0 => ddd@3.0', stdout)
def testRegistryModuleInclude(self):
"""Tests that include() is not allowed in registry modules."""
self.main_registry.createShModule(
'foo',
'1.0',
extra_module_file_contents=['include("//java:MODULE.bazel.segment")'],
)
self.ScratchFile(
'MODULE.bazel',
[
'bazel_dep(name="foo", version="1.0")',
],
)
self.ScratchFile('BUILD')
exit_code, _, stderr = self.RunBazel(
['build', '--nobuild', '//:all'], allow_failure=True
)
self.AssertNotExitCode(exit_code, 0, stderr)
self.assertIn(
'include() directive found at '
+ self.main_registry.getURL()
+ '/modules/foo/1.0/MODULE.bazel:5:1, but it can only be used in the '
+ 'root module or in modules with non-registry overrides',
'\n'.join(stderr),
)
def testLabelDebugPrint(self):
"""Tests that print emits labels in display form."""
self.ScratchFile(
'MODULE.bazel',
[
'bazel_dep(name="other_module", version="1.0")',
(
'local_path_override(module_name="other_module",'
' path="other_module")'
),
'ext = use_extension("//:defs.bzl", "my_ext")',
'use_repo(ext, "repo")',
],
)
self.ScratchFile(
'other_module/MODULE.bazel',
['module(name="other_module", version="1.0")'],
)
self.ScratchFile(
'defs.bzl',
[
(
'print("toplevel", Label("//:foo"),'
' Label("@other_module//:bar"),'
' Label("@@canonical_name//:baz"))'
),
'def _repo_impl(ctx):',
(
' print("repo", Label("//:foo"), Label("@other_module//:bar"),'
' Label("@@canonical_name//:baz"))'
),
' ctx.file("BUILD")',
' ctx.file("data.bzl", "repo_data = \\"repo\\"")',
'my_repo = repository_rule(implementation=_repo_impl)',
'def _ext_impl(ctx):',
(
' print("ext", Label("//:foo"), Label("@other_module//:bar"),'
' Label("@@canonical_name//:baz"))'
),
' my_repo(name = "repo")',
'my_ext = module_extension(implementation=_ext_impl)',
'def _rule_impl(ctx):',
(
' print("rule", Label("//:foo"), Label("@other_module//:bar"),'
' Label("@@canonical_name//:baz"))'
),
'my_rule = rule(implementation=_rule_impl)',
],
)
self.ScratchFile(
'BUILD',
[
'load("@repo//:data.bzl", "repo_data")',
'load(":defs.bzl", "my_rule")',
'my_rule(name = "my_rule")',
],
)
_, _, stderr = self.RunBazel(['build', '//:my_rule'])
stderr = '\n'.join(stderr)
self.assertIn(
'toplevel //:foo @other_module//:bar @@canonical_name//:baz', stderr
)
self.assertIn(
'repo //:foo @other_module//:bar @@canonical_name//:baz', stderr
)
self.assertIn(
'ext //:foo @other_module//:bar @@canonical_name//:baz', stderr
)
self.assertIn(
'rule //:foo @other_module//:bar @@canonical_name//:baz', stderr
)
def testPendingDownloadDetected(self):
self.ScratchFile(
'MODULE.bazel',
[
'ext = use_extension("extensions.bzl", "ext")',
'use_repo(ext, "ext")',
],
)
self.ScratchFile('BUILD')
self.ScratchFile(
'extensions.bzl',
[
'repo_rule = repository_rule(lambda _: None)',
'def ext_impl(module_ctx):',
' repo_rule(name = "ext")',
(
' module_ctx.download(url = "https://bcr.bazel.build", output'
' = "download", block = False)'
),
'ext = module_extension(implementation = ext_impl)',
],
)
exit_code, _, stderr = self.RunBazel(
['build', '--nobuild', '@ext//:all'],
allow_failure=True,
)
self.AssertExitCode(exit_code, 48, stderr)
self.assertIn(
'ERROR: Pending asynchronous work after module extension'
' @@//:extensions.bzl%ext finished execution',
stderr,
)
def testRegression22754(self):
"""Regression test for issue #22754."""
self.ScratchFile('BUILD.bazel', ['print(glob(["testdata/**"]))'])
self.ScratchFile('testdata/WORKSPACE')
self.RunBazel(['build', ':all'])
def testUnicodePaths(self):
if sys.getfilesystemencoding() != 'utf-8':
self.skipTest('Test requires UTF-8 by default (Python 3.7+)')
unicode_dir = 'äöüÄÖÜß'
self.ScratchFile(unicode_dir + '/MODULE.bazel', ['module(name = "module")'])
self.ScratchFile(
unicode_dir + '/BUILD',
[
'filegroup(name = "choose_me")',
],
)
self.writeMainProjectFiles()
self.ScratchFile(
'MODULE.bazel',
[
'bazel_dep(name = "module")',
'local_path_override(',
' module_name = "module",',
' path = "%s",' % unicode_dir,
')',
],
)
self.AddBazelDep('rules_shell')
self.RunBazel(['build', '@module//:choose_me'])
def testUnicodeTags(self):
unicode_str = 'äöüÄÖÜß'
self.ScratchFile(
'MODULE.bazel',
[
'ext = use_extension("extensions.bzl", "ext")',
'ext.tag(attr = "%s")' % unicode_str,
'use_repo(ext, "ext")',
],
)
self.ScratchFile('BUILD')
self.ScratchFile(
'extensions.bzl',
[
'def repo_rule_impl(ctx):',
' ctx.file("BUILD")',
' print("DATA: " + ctx.attr.tag)',
'repo_rule = repository_rule(',
' implementation = repo_rule_impl,',
' attrs = {',
' "tag": attr.string(),',
' },',
')',
'def ext_impl(module_ctx):',
' repo_rule(',
' name = "ext",',
' tag = module_ctx.modules[0].tags.tag[0].attr,',
' )',
'tag = tag_class(',
' attrs = {',
' "attr": attr.string(),',
' },',
')',
'ext = module_extension( implementation = ext_impl,',
' tag_classes = {',
' "tag": tag,',
' },',
')',
],
)
_, _, stderr = self.RunBazel(['build', '@ext//:all'])
self.assertIn('DATA: ' + unicode_str, '\n'.join(stderr))
def testDefaultModuleMirrors(self):
"""Tests that default module mirrors are applied to all registries."""
self.ScratchFile(
'MODULE.bazel',
[
'bazel_dep(name = "aaa", version = "1.0")',
],
)
self.ScratchFile('BUILD')
_, stdout, _ = self.RunBazel([
'mod',
'show_repo',
'@@aaa+',
'--registry=' + self.main_registry.getURL(),
'--registry=https://bcr.bazel.build',
'--module_mirrors=https://default-mirror1.example.com,https://default-mirror2.example.com',
])
stdout = '\n'.join(stdout)
self.assertIn('https://default-mirror1.example.com', stdout)
self.assertIn('https://default-mirror2.example.com', stdout)
def testPerRegistryModuleMirrors(self):
"""Tests that per-registry module mirrors are applied only to the specified registry."""
second_registry = BazelRegistry(
os.path.join(self.registries_work_dir, 'second')
)
second_registry.start()
second_registry.createShModule('ddd', '2.0')
self.ScratchFile(
'MODULE.bazel',
[
'bazel_dep(name = "aaa", version = "1.0")',
'bazel_dep(name = "ddd", version = "2.0")',
],
)
self.ScratchFile('BUILD')
_, stdout, _ = self.RunBazel([
'mod',
'show_repo',
'@@aaa+',
'--registry=' + self.main_registry.getURL(),
'--registry=' + second_registry.getURL(),
'--registry=https://bcr.bazel.build',
'--module_mirrors=%s=https://main-mirror.example.com'
% self.main_registry.getURL(),
'--module_mirrors=%s=https://second-mirror.example.com'
% second_registry.getURL(),
])
stdout = '\n'.join(stdout)
self.assertIn('https://main-mirror.example.com', stdout)
self.assertNotIn('https://second-mirror.example.com', stdout)
second_registry.stop()
def testPerRegistryModuleMirrorsWithDefault(self):
"""Tests that per-registry mirrors override default mirrors for that registry."""
second_registry = BazelRegistry(
os.path.join(self.registries_work_dir, 'second')
)
second_registry.start()
second_registry.createShModule('ddd', '2.0')
self.ScratchFile(
'MODULE.bazel',
[
'bazel_dep(name = "aaa", version = "1.0")',
'bazel_dep(name = "ddd", version = "2.0")',
],
)
self.ScratchFile('BUILD')
_, stdout_aaa, _ = self.RunBazel([
'mod',
'show_repo',
'@@aaa+',
'--registry=' + self.main_registry.getURL(),
'--registry=' + second_registry.getURL(),
'--registry=https://bcr.bazel.build',
'--module_mirrors=https://default-mirror.example.com',
'--module_mirrors=%s=https://main-specific-mirror.example.com'
% self.main_registry.getURL(),
])
stdout_aaa = '\n'.join(stdout_aaa)
self.assertIn('https://main-specific-mirror.example.com', stdout_aaa)
self.assertNotIn('https://default-mirror.example.com', stdout_aaa)
_, stdout_ddd, _ = self.RunBazel([
'mod',
'show_repo',
'@@ddd+',
'--registry=' + self.main_registry.getURL(),
'--registry=' + second_registry.getURL(),
'--registry=https://bcr.bazel.build',
'--module_mirrors=https://default-mirror.example.com',
'--module_mirrors=%s=https://main-specific-mirror.example.com'
% self.main_registry.getURL(),
])
stdout_ddd = '\n'.join(stdout_ddd)
self.assertIn('https://default-mirror.example.com', stdout_ddd)
self.assertNotIn('https://main-specific-mirror.example.com', stdout_ddd)
second_registry.stop()
def testUnknownRegistryInModuleMirrors(self):
"""Tests that specifying an unknown registry in module_mirrors results in an error."""
self.ScratchFile(
'MODULE.bazel',
[
'bazel_dep(name = "aaa", version = "1.0")',
],
)
self.ScratchFile('BUILD')
exit_code, _, stderr = self.RunBazel(
[
'mod',
'show_repo',
'@@aaa+',
'--registry=' + self.main_registry.getURL(),
'--registry=https://bcr.bazel.build',
'--module_mirrors=https://unknown-registry.example.com=https://mirror.example.com',
],
allow_failure=True,
)
self.AssertNotExitCode(exit_code, 0, stderr)
stderr = '\n'.join(stderr)
self.assertIn(
'--module_mirrors references registries not listed in --registries',
stderr,
)
self.assertIn('https://unknown-registry.example.com', stderr)
def testInvalidRepoRuleReferencedByTargetDoesNotCrash(self):
self.ScratchFile(
'MODULE.bazel',
[
'repo = use_repo_rule("//:repo.bzl", "repo")',
'repo(name = "my_repo")',
],
)
self.ScratchFile(
'BUILD',
[
'genrule(',
' name = "gen",',
' srcs = ["@my_repo//:a.txt"],',
' outs = ["out.txt"],',
' cmd = "cat $(SRCS) > $(OUTS)",',
')',
],
)
self.ScratchFile('repo.bzl', ['nonsense'])
exit_code, _, stderr = self.RunBazel(
['build', '//:gen'],
allow_failure=True,
)
self.AssertNotExitCode(exit_code, 0, stderr)
stderr = '\n'.join(stderr)
self.assertNotIn('FATAL', stderr)
self.assertIn("compilation of module 'repo.bzl' failed", stderr)
def testReverseDependencyDirection(self):
# Set up two module extensions that retain their predeclared input hashes
# across two builds but still reverse their dependency direction. Depending
# on how repo cache candidates are checked, this could lead to a Skyframe
# cycle.
self.ScratchFile(
'MODULE.bazel',
[
'foo_ext = use_extension("//:repo.bzl", "foo_ext")',
'use_repo(foo_ext, "foo")',
'bar_ext = use_extension("//:repo.bzl", "bar_ext")',
'use_repo(bar_ext, "bar")',
],
)
self.ScratchFile('BUILD.bazel')
self.ScratchFile(
'repo.bzl',
[
'def _repo_impl(rctx):',
' rctx.file("output.txt", rctx.attr.content)',
' rctx.file("BUILD", "exports_files([\'output.txt\'])")',
' print("JUST FETCHED: %s" % rctx.original_name)',
' return rctx.repo_metadata(reproducible=True)',
'repo = repository_rule(',
' implementation = _repo_impl,',
' attrs = {"content": attr.string()},',
')',
'def _foo_ext_impl(mctx):',
' deps = mctx.read(Label("@//:foo_deps.txt")).splitlines()',
' content = "foo"',
' for dep in deps:',
' if dep:',
' content += " + " + mctx.read(Label(dep))',
' repo(name = "foo", content = content)',
'foo_ext = module_extension(implementation = _foo_ext_impl)',
'def _bar_ext_impl(mctx):',
' deps = mctx.read(Label("@//:bar_deps.txt")).splitlines()',
' content = "bar"',
' for dep in deps:',
' if dep:',
' content += " + " + mctx.read(Label(dep))',
' repo(name = "bar", content = content)',
'bar_ext = module_extension(implementation = _bar_ext_impl)',
],
)
self.ScratchFile('foo_deps.txt', ['@bar//:output.txt'])
self.ScratchFile('bar_deps.txt')
# First fetch: not cached
_, _, stderr = self.RunBazel(['build', '@foo//:output.txt'])
self.assertIn('JUST FETCHED: bar', '\n'.join(stderr))
self.assertIn('JUST FETCHED: foo', '\n'.join(stderr))
# After expunging and reversing: cached
self.RunBazel(['clean', '--expunge'])
self.ScratchFile('foo_deps.txt')
self.ScratchFile('bar_deps.txt', ['@foo//:output.txt'])
self.RunBazel(['build', '@foo//:output.txt'])
if __name__ == '__main__':
absltest.main()