blob: 878d3ec4fbbf517bf4377f220b4fb2d09f541ef7 [file] [log] [blame]
# 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 tempfile
import unittest
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 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.createCcModule('aaa', '1.0').createCcModule(
'aaa', '1.1'
).createCcModule(
'bbb', '1.0', {'aaa': '1.0'}, {'aaa': 'com_foo_aaa'}
).createCcModule(
'bbb', '1.1', {'aaa': '1.1'}
).createCcModule(
'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 --enable_bzlmod',
'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',
],
)
self.ScratchFile('WORKSPACE')
# The existence of WORKSPACE.bzlmod prevents WORKSPACE prefixes or suffixes
# from being used; this allows us to test built-in modules actually work
self.ScratchFile('WORKSPACE.bzlmod')
def writeMainProjectFiles(self):
self.ScratchFile('aaa.patch', [
'--- a/aaa.cc',
'+++ b/aaa.cc',
'@@ -1,6 +1,6 @@',
' #include <stdio.h>',
' #include "aaa.h"',
' void hello_aaa(const std::string& caller) {',
'- std::string lib_name = "aaa@1.0";',
'+ std::string lib_name = "aaa@1.0 (locally patched)";',
' printf("%s => %s\\n", caller.c_str(), lib_name.c_str());',
' }',
])
self.ScratchFile('BUILD', [
'cc_binary(',
' name = "main",',
' srcs = ["main.cc"],',
' deps = [',
' "@aaa//:lib_aaa",',
' "@bbb//:lib_bbb",',
' ],',
')',
])
self.ScratchFile('main.cc', [
'#include "aaa.h"',
'#include "bbb.h"',
'int main() {',
' hello_aaa("main function");',
' hello_bbb("main function");',
'}',
])
def testSimple(self):
self.ScratchFile('MODULE.bazel', [
'bazel_dep(name = "aaa", version = "1.0")',
])
self.ScratchFile('BUILD', [
'cc_binary(',
' name = "main",',
' srcs = ["main.cc"],',
' deps = ["@aaa//:lib_aaa"],',
')',
])
self.ScratchFile('main.cc', [
'#include "aaa.h"',
'int main() {',
' hello_aaa("main function");',
'}',
])
_, stdout, _ = self.RunBazel(['run', '//:main'], allow_failure=False)
self.assertIn('main function => aaa@1.0', stdout)
def testSimpleTransitive(self):
self.ScratchFile('MODULE.bazel', [
'bazel_dep(name = "bbb", version = "1.0")',
])
self.ScratchFile('BUILD', [
'cc_binary(',
' name = "main",',
' srcs = ["main.cc"],',
' deps = ["@bbb//:lib_bbb"],',
')',
])
self.ScratchFile('main.cc', [
'#include "bbb.h"',
'int main() {',
' hello_bbb("main function");',
'}',
])
_, stdout, _ = self.RunBazel(['run', '//:main'], allow_failure=False)
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")',
])
_, stdout, _ = self.RunBazel(['run', '//:main'], allow_failure=False)
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.cc',
'+++ b/aaa.cc',
'@@ -1,6 +1,6 @@',
' #include <stdio.h>',
' #include "aaa.h"',
' void hello_aaa(const std::string& caller) {',
'- std::string lib_name = "aaa@1.1-1";',
'+ std::string lib_name = "aaa@1.1-1 (remotely patched)";',
' printf("%s => %s\\n", caller.c_str(), lib_name.c_str());',
' }',
])
self.main_registry.createCcModule(
'aaa', '1.1-1', patches=[patch_file], patch_strip=1)
self.ScratchFile('MODULE.bazel', [
'bazel_dep(name = "aaa", version = "1.1-1")',
])
self.ScratchFile('BUILD', [
'cc_binary(',
' name = "main",',
' srcs = ["main.cc"],',
' deps = ["@aaa//:lib_aaa"],',
')',
])
self.ScratchFile('main.cc', [
'#include "aaa.h"',
'int main() {',
' hello_aaa("main function");',
'}',
])
_, stdout, _ = self.RunBazel(['run', '//:main'], allow_failure=False)
self.assertIn('main function => aaa@1.1-1 (remotely patched)', stdout)
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.ScratchFile('BUILD', [
'cc_binary(',
' name = "main",',
' srcs = ["main.cc"],',
' deps = [',
' "@my_repo_a_name//:lib_aaa",',
' "@bbb//:lib_bbb",',
' ],',
')',
])
_, stdout, _ = self.RunBazel(['run', '//:main'], allow_failure=False)
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")',
])
_, stdout, stderr = self.RunBazel(
['run', '//:main', '--check_direct_dependencies=warning'],
allow_failure=False)
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)
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 _repo_rule_impl(ctx):',
' ctx.file("WORKSPACE")',
'repo_rule = repository_rule(implementation = _repo_rule_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)
self.assertIn(
"ERROR: <builtin>: //pkg:_main~module_ext~foo: no such attribute 'invalid_attr' in 'repo_rule' rule",
stderr)
self.assertTrue(
any([
'/pkg/extension.bzl", line 3, column 14, in _module_ext_impl'
in line for line in 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('WORKSPACE')
self.ScratchFile('ext.bzl', [
'def _no_op_impl(ctx):',
' ctx.file("WORKSPACE")',
' 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'], allow_failure=False)
def setUpProjectWithLocalRegistryModule(self, dep_name, dep_version):
self.main_registry.generateCcSource(dep_name, dep_version)
self.main_registry.createLocalPathModule(dep_name, dep_version,
dep_name + '/' + dep_version)
self.ScratchFile('main.cc', [
'#include "%s.h"' % dep_name,
'int main() {',
' hello_%s("main function");' % dep_name,
'}',
])
self.ScratchFile('MODULE.bazel', [
'bazel_dep(name = "%s", version = "%s")' % (dep_name, dep_version),
])
self.ScratchFile('BUILD', [
'cc_binary(',
' name = "main",',
' srcs = ["main.cc"],',
' deps = ["@%s//:lib_%s"],' % (dep_name, dep_name),
')',
])
self.ScratchFile('WORKSPACE', [])
def testLocalRepoInSourceJsonAbsoluteBasePath(self):
self.main_registry.setModuleBasePath(str(self.main_registry.projects))
self.setUpProjectWithLocalRegistryModule('sss', '1.3')
_, stdout, _ = self.RunBazel(['run', '//:main'], allow_failure=False)
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'], allow_failure=False)
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('WORKSPACE')
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/WORKSPACE')
self.ScratchFile(
'bar/quux/BUILD',
[
'load("@bleb//:defs.bzl", "mac")',
'mac(name="book")',
],
)
_, _, stderr = self.RunBazel(
['build', '@bar//quux:book'], allow_failure=False
)
stderr = '\n'.join(stderr)
self.assertIn('1st: @@bar~override//quux:bleb', stderr)
self.assertIn('2nd: @@bar~override//bleb:bleb', stderr)
self.assertIn('3rd: @@//bleb:bleb', stderr)
self.assertIn('4th: @@bar~override//bleb:bleb', stderr)
self.assertIn('5th: @@bleb//bleb:bleb', stderr)
self.assertIn('6th: @@//bleb:bleb', stderr)
def testWorkspaceEvaluatedBzlCanSeeRootModuleMappings(self):
self.ScratchFile(
'MODULE.bazel',
[
'bazel_dep(name="aaa",version="1.0")',
'bazel_dep(name="bbb",version="1.0")',
],
)
self.ScratchFile(
'WORKSPACE.bzlmod',
[
'local_repository(name="foo", path="foo", repo_mapping={',
' "@bar":"@baz",',
' "@my_aaa":"@aaa",',
'})',
'load("@foo//:test.bzl", "test")',
'test()',
],
)
self.ScratchFile('foo/WORKSPACE')
self.ScratchFile('foo/BUILD', ['filegroup(name="test")'])
self.ScratchFile(
'foo/test.bzl',
[
'def test():',
' print("1st: " + str(Label("@bar//:z")))',
' print("2nd: " + str(Label("@my_aaa//:z")))',
' print("3rd: " + str(Label("@bbb//:z")))',
' print("4th: " + str(Label("@blarg//:z")))',
],
)
_, _, stderr = self.RunBazel(['build', '@foo//:test'], allow_failure=False)
stderr = '\n'.join(stderr)
# @bar is mapped to @@baz, which Bzlmod doesn't recognize, so we leave it be
self.assertIn('1st: @@baz//:z', stderr)
# @my_aaa is mapped to @@aaa, which Bzlmod remaps to @@aaa~1.0
self.assertIn('2nd: @@aaa~1.0//:z', stderr)
# @bbb isn't mapped in WORKSPACE, but Bzlmod maps it to @@bbb~1.0
self.assertIn('3rd: @@bbb~1.0//:z', stderr)
# @blarg isn't mapped by WORKSPACE or Bzlmod
self.assertIn('4th: @@blarg//:z', stderr)
def testWorkspaceItselfCanSeeRootModuleMappings(self):
self.ScratchFile(
'MODULE.bazel',
[
'bazel_dep(name="hello")',
'local_path_override(module_name="hello",path="hello")',
],
)
self.ScratchFile(
'WORKSPACE.bzlmod',
[
'load("@hello//:world.bzl", "message")',
'print(message)',
],
)
self.ScratchFile('BUILD', ['filegroup(name="a")'])
self.ScratchFile('hello/WORKSPACE')
self.ScratchFile('hello/BUILD')
self.ScratchFile('hello/MODULE.bazel', ['module(name="hello")'])
self.ScratchFile('hello/world.bzl', ['message="I LUV U!"'])
_, _, stderr = self.RunBazel(['build', ':a'], allow_failure=False)
self.assertIn('I LUV U!', '\n'.join(stderr))
def testArchiveWithArchiveType(self):
# make the archive without the .zip extension
self.main_registry.createCcModule(
'aaa', '1.2', archive_pattern='%s.%s', archive_type='zip'
)
self.ScratchFile(
'MODULE.bazel',
[
'bazel_dep(name = "aaa", version = "1.2")',
],
)
self.ScratchFile(
'BUILD',
[
'cc_binary(',
' name = "main",',
' srcs = ["main.cc"],',
' deps = ["@aaa//:lib_aaa"],',
')',
],
)
self.ScratchFile(
'main.cc',
[
'#include "aaa.h"',
'int main() {',
' hello_aaa("main function");',
'}',
],
)
_, stdout, _ = self.RunBazel(['run', '//:main'], allow_failure=False)
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('WORKSPACE')
self.ScratchFile(
'WORKSPACE.bzlmod', ['local_repository(name="quux",path="quux")']
)
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', 'WORKSPACE'))
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/WORKSPACE')
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()',
],
)
# quux: a repo defined by WORKSPACE
self.ScratchFile('quux/WORKSPACE')
self.ScratchFile(
'quux/BUILD',
[
'load("@foo//:report.bzl", "report")',
'report()',
],
)
_, _, stderr = self.RunBazel(
[
'build',
':a',
'@foo//:a',
'@report_repo//:a',
'@bar//:a',
'@quux//:a',
],
allow_failure=False,
)
stderr = '\n'.join(stderr)
self.assertIn('@@ reporting in: root@0.1', stderr)
self.assertIn('@@foo~1.0 reporting in: foo@1.0', stderr)
self.assertIn(
'@@foo~1.0~report_ext~report_repo reporting in: foo@1.0', stderr
)
self.assertIn('@@bar~override reporting in: bar@2.0', stderr)
self.assertIn('@@quux reporting in: None@None', stderr)
if __name__ == '__main__':
unittest.main()