| # 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() |