diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelLockFileFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelLockFileFunction.java index 41cff393000ac1..1451f431cf916f 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelLockFileFunction.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelLockFileFunction.java @@ -15,7 +15,6 @@ package com.google.devtools.build.lib.bazel.bzlmod; -import static com.google.devtools.build.lib.bazel.bzlmod.GsonTypeAdapterUtil.LOCKFILE_GSON; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableList; @@ -93,7 +92,13 @@ public static BazelLockFileValue getLockfileValue(RootedPath lockfilePath) throw BazelLockFileValue bazelLockFileValue; try { String json = FileSystemUtils.readContent(lockfilePath.asPath(), UTF_8); - bazelLockFileValue = LOCKFILE_GSON.fromJson(json, BazelLockFileValue.class); + bazelLockFileValue = + GsonTypeAdapterUtil.createLockFileGson( + lockfilePath + .asPath() + .getParentDirectory() + .getRelative(LabelConstants.MODULE_DOT_BAZEL_FILE_NAME)) + .fromJson(json, BazelLockFileValue.class); } catch (FileNotFoundException e) { bazelLockFileValue = EMPTY_LOCKFILE; } diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelLockFileModule.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelLockFileModule.java index 5bd3b40a06bd16..fd2bfddd42120a 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelLockFileModule.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelLockFileModule.java @@ -14,7 +14,6 @@ package com.google.devtools.build.lib.bazel.bzlmod; -import static com.google.devtools.build.lib.bazel.bzlmod.GsonTypeAdapterUtil.LOCKFILE_GSON; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableMap; @@ -136,7 +135,14 @@ private ImmutableMap combineModuleEx public static void updateLockfile(RootedPath lockfilePath, BazelLockFileValue updatedLockfile) { try { FileSystemUtils.writeContent( - lockfilePath.asPath(), UTF_8, LOCKFILE_GSON.toJson(updatedLockfile)); + lockfilePath.asPath(), + UTF_8, + GsonTypeAdapterUtil.createLockFileGson( + lockfilePath + .asPath() + .getParentDirectory() + .getRelative(LabelConstants.MODULE_DOT_BAZEL_FILE_NAME)) + .toJson(updatedLockfile)); } catch (IOException e) { logger.atSevere().withCause(e).log( "Error while updating MODULE.bazel.lock file: %s", e.getMessage()); diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/GsonTypeAdapterUtil.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/GsonTypeAdapterUtil.java index c558e806e9d6d3..313471c8468b85 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/GsonTypeAdapterUtil.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/GsonTypeAdapterUtil.java @@ -20,11 +20,13 @@ import static com.google.devtools.build.lib.bazel.bzlmod.DelegateTypeAdapterFactory.IMMUTABLE_MAP; import static com.google.devtools.build.lib.bazel.bzlmod.DelegateTypeAdapterFactory.IMMUTABLE_SET; +import com.google.auto.value.AutoValue; import com.google.common.base.Preconditions; import com.google.common.base.Splitter; import com.google.devtools.build.lib.bazel.bzlmod.Version.ParseException; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.LabelSyntaxException; +import com.google.devtools.build.lib.vfs.Path; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonParseException; @@ -42,6 +44,7 @@ import java.util.List; import java.util.Optional; import javax.annotation.Nullable; +import net.starlark.java.syntax.Location; /** * Utility class to hold type adapters and helper methods to get gson registered with type adapters @@ -188,24 +191,102 @@ public Optional read(JsonReader jsonReader) throws IOException { } } - public static final Gson LOCKFILE_GSON = - new GsonBuilder() - .setPrettyPrinting() - .disableHtmlEscaping() - .enableComplexMapKeySerialization() - .registerTypeAdapterFactory(GenerateTypeAdapter.FACTORY) - .registerTypeAdapterFactory(DICT) - .registerTypeAdapterFactory(IMMUTABLE_MAP) - .registerTypeAdapterFactory(IMMUTABLE_LIST) - .registerTypeAdapterFactory(IMMUTABLE_BIMAP) - .registerTypeAdapterFactory(IMMUTABLE_SET) - .registerTypeAdapterFactory(OPTIONAL) - .registerTypeAdapter(Version.class, VERSION_TYPE_ADAPTER) - .registerTypeAdapter(ModuleKey.class, MODULE_KEY_TYPE_ADAPTER) - .registerTypeAdapter(ModuleExtensionId.class, MODULE_EXTENSION_ID_TYPE_ADAPTER) - .registerTypeAdapter(AttributeValues.class, new AttributeValuesAdapter()) - .registerTypeAdapter(byte[].class, BYTE_ARRAY_TYPE_ADAPTER) - .create(); + /** + * A variant of {@link Location} that converts the absolute path to the root module file to a + * constant and back. + */ + // protected only for @AutoValue + @GenerateTypeAdapter + @AutoValue + protected abstract static class RootModuleFileEscapingLocation { + // This marker string is neither a valid absolute path nor a valid URL and thus cannot conflict + // with any real module file location. + private static final String ROOT_MODULE_FILE_LABEL = "@@//:MODULE.bazel"; + + public abstract String file(); + + public abstract int line(); + + public abstract int column(); + + public Location toLocation(String moduleFilePath) { + String file; + if (file().equals(ROOT_MODULE_FILE_LABEL)) { + file = moduleFilePath; + } else { + file = file(); + } + return Location.fromFileLineColumn(file, line(), column()); + } + + public static RootModuleFileEscapingLocation fromLocation( + Location location, String moduleFilePath) { + String file; + if (location.file().equals(moduleFilePath)) { + file = ROOT_MODULE_FILE_LABEL; + } else { + file = location.file(); + } + return new AutoValue_GsonTypeAdapterUtil_RootModuleFileEscapingLocation( + file, location.line(), location.column()); + } + } + + private static final class LocationTypeAdapterFactory implements TypeAdapterFactory { + + private final String moduleFilePath; + + public LocationTypeAdapterFactory(Path moduleFilePath) { + this.moduleFilePath = moduleFilePath.getPathString(); + } + + @Nullable + @Override + @SuppressWarnings("unchecked") + public TypeAdapter create(Gson gson, TypeToken typeToken) { + if (typeToken.getRawType() != Location.class) { + return null; + } + TypeAdapter relativizedLocationTypeAdapter = + gson.getAdapter(RootModuleFileEscapingLocation.class); + return (TypeAdapter) + new TypeAdapter() { + + @Override + public void write(JsonWriter jsonWriter, Location location) throws IOException { + relativizedLocationTypeAdapter.write( + jsonWriter, + RootModuleFileEscapingLocation.fromLocation(location, moduleFilePath)); + } + + @Override + public Location read(JsonReader jsonReader) throws IOException { + return relativizedLocationTypeAdapter.read(jsonReader).toLocation(moduleFilePath); + } + }; + } + } + + public static Gson createLockFileGson(Path moduleFilePath) { + return new GsonBuilder() + .setPrettyPrinting() + .disableHtmlEscaping() + .enableComplexMapKeySerialization() + .registerTypeAdapterFactory(GenerateTypeAdapter.FACTORY) + .registerTypeAdapterFactory(DICT) + .registerTypeAdapterFactory(IMMUTABLE_MAP) + .registerTypeAdapterFactory(IMMUTABLE_LIST) + .registerTypeAdapterFactory(IMMUTABLE_BIMAP) + .registerTypeAdapterFactory(IMMUTABLE_SET) + .registerTypeAdapterFactory(OPTIONAL) + .registerTypeAdapterFactory(new LocationTypeAdapterFactory(moduleFilePath)) + .registerTypeAdapter(Version.class, VERSION_TYPE_ADAPTER) + .registerTypeAdapter(ModuleKey.class, MODULE_KEY_TYPE_ADAPTER) + .registerTypeAdapter(ModuleExtensionId.class, MODULE_EXTENSION_ID_TYPE_ADAPTER) + .registerTypeAdapter(AttributeValues.class, new AttributeValuesAdapter()) + .registerTypeAdapter(byte[].class, BYTE_ARRAY_TYPE_ADAPTER) + .create(); + } private GsonTypeAdapterUtil() {} } diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileFunction.java index 89792cb41bec0c..3d622633e53c57 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileFunction.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileFunction.java @@ -24,7 +24,9 @@ import com.google.devtools.build.lib.actions.FileValue; import com.google.devtools.build.lib.bazel.bzlmod.ModuleFileValue.NonRootModuleFileValue; import com.google.devtools.build.lib.bazel.bzlmod.ModuleFileValue.RootModuleFileValue; +import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.LabelConstants; +import com.google.devtools.build.lib.cmdline.PackageIdentifier; import com.google.devtools.build.lib.cmdline.RepositoryName; import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.packages.StarlarkExportable; @@ -37,6 +39,7 @@ import com.google.devtools.build.lib.util.Fingerprint; import com.google.devtools.build.lib.vfs.FileSystemUtils; import com.google.devtools.build.lib.vfs.Path; +import com.google.devtools.build.lib.vfs.PathFragment; import com.google.devtools.build.lib.vfs.Root; import com.google.devtools.build.lib.vfs.RootedPath; import com.google.devtools.build.skyframe.SkyFunction; @@ -199,11 +202,11 @@ private SkyValue computeForRootModule(StarlarkSemantics starlarkSemantics, Envir if (env.getValue(FileValue.key(moduleFilePath)) == null) { return null; } - ModuleFile moduleFile = readModuleFile(moduleFilePath.asPath()); - String moduleFileHash = new Fingerprint().addBytes(moduleFile.getContent()).hexDigestAndReset(); + byte[] moduleFileContents = readModuleFile(moduleFilePath.asPath()); + String moduleFileHash = new Fingerprint().addBytes(moduleFileContents).hexDigestAndReset(); ModuleFileGlobals moduleFileGlobals = execModuleFile( - moduleFile, + ModuleFile.create(moduleFileContents, moduleFilePath.asPath().toString()), /* registry= */ null, ModuleKey.ROOT, /* ignoreDevDeps= */ Objects.requireNonNull(IGNORE_DEV_DEPS.get(env)), @@ -335,8 +338,15 @@ private GetModuleFileResult getModuleFile( if (env.getValue(FileValue.key(moduleFilePath)) == null) { return null; } + Label moduleFileLabel = + Label.createUnvalidated( + PackageIdentifier.create(key.getCanonicalRepoName(), PathFragment.EMPTY_FRAGMENT), + LabelConstants.MODULE_DOT_BAZEL_FILE_NAME.getBaseName()); GetModuleFileResult result = new GetModuleFileResult(); - result.moduleFile = readModuleFile(moduleFilePath.asPath()); + result.moduleFile = + ModuleFile.create( + readModuleFile(moduleFilePath.asPath()), + moduleFileLabel.getUnambiguousCanonicalForm()); return result; } @@ -392,10 +402,9 @@ private GetModuleFileResult getModuleFile( throw errorf(Code.MODULE_NOT_FOUND, "module not found in registries: %s", key); } - private static ModuleFile readModuleFile(Path path) throws ModuleFileFunctionException { + private static byte[] readModuleFile(Path path) throws ModuleFileFunctionException { try { - return ModuleFile.create( - FileSystemUtils.readWithKnownFileSize(path, path.getFileSize()), path.getPathString()); + return FileSystemUtils.readWithKnownFileSize(path, path.getFileSize()); } catch (IOException e) { throw errorf(Code.MODULE_NOT_FOUND, "MODULE.bazel expected but not found at %s", path); } diff --git a/src/test/py/bazel/bzlmod/bazel_lockfile_test.py b/src/test/py/bazel/bzlmod/bazel_lockfile_test.py index 03381c12e895e5..1bbed9274f7392 100644 --- a/src/test/py/bazel/bzlmod/bazel_lockfile_test.py +++ b/src/test/py/bazel/bzlmod/bazel_lockfile_test.py @@ -505,6 +505,70 @@ def testRemoveModuleExtensionsNotUsed(self): lockfile = json.loads(f.read().strip()) self.assertEqual(len(lockfile['moduleExtensions']), 0) + def testNoAbsoluteRootModuleFilePath(self): + self.ScratchFile( + 'MODULE.bazel', + [ + 'ext = use_extension("extension.bzl", "ext")', + 'ext.dep(generate = True)', + 'use_repo(ext, ext_hello = "hello")', + 'other_ext = use_extension("extension.bzl", "other_ext")', + 'other_ext.dep(generate = False)', + 'use_repo(other_ext, other_ext_hello = "hello")', + ], + ) + self.ScratchFile('BUILD.bazel') + 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 _module_ext_impl(ctx):', + ' for mod in ctx.modules:', + ' for dep in mod.tags.dep:', + ' if dep.generate:', + ' repo_rule(name="hello")', + '', + '_dep = tag_class(attrs={"generate": attr.bool()})', + 'ext = module_extension(', + ' implementation=_module_ext_impl,', + ' tag_classes={"dep": _dep},', + ')', + 'other_ext = module_extension(', + ' implementation=_module_ext_impl,', + ' tag_classes={"dep": _dep},', + ')', + ], + ) + + # Paths to module files in error message always use forward slashes as + # separators, even on Windows. + module_file_path = self.Path('MODULE.bazel').replace('\\', '/') + + self.RunBazel(['build', '--nobuild', '@ext_hello//:all']) + with open(self.Path('MODULE.bazel.lock'), 'r') as f: + self.assertNotIn(module_file_path, f.read()) + + self.RunBazel(['shutdown']) + exit_code, _, stderr = self.RunBazel( + ['build', '--nobuild', '@other_ext_hello//:all'], allow_failure=True + ) + self.AssertNotExitCode(exit_code, 0, stderr) + self.assertIn( + ( + 'ERROR: module extension "other_ext" from "//:extension.bzl" does ' + 'not generate repository "hello", yet it is imported as ' + '"other_ext_hello" in the usage at ' + + module_file_path + + ':4:26' + ), + stderr, + ) + if __name__ == '__main__': unittest.main() diff --git a/src/test/py/bazel/bzlmod/bazel_module_test.py b/src/test/py/bazel/bzlmod/bazel_module_test.py index 682e58a971b4a5..97d1455668af54 100644 --- a/src/test/py/bazel/bzlmod/bazel_module_test.py +++ b/src/test/py/bazel/bzlmod/bazel_module_test.py @@ -484,6 +484,258 @@ def testArchiveWithArchiveType(self): _, 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', + ], + ) + 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) + + def testWorkspaceToolchainRegistrationWithPlatformsConstraint(self): + """Regression test for https://github.com/bazelbuild/bazel/issues/17289.""" + self.ScratchFile('MODULE.bazel') + self.ScratchFile( + 'WORKSPACE', ['register_toolchains("//:my_toolchain_toolchain")'] + ) + os.remove(self.Path('WORKSPACE.bzlmod')) + + self.ScratchFile( + 'BUILD.bazel', + [ + 'load(":defs.bzl", "get_host_os", "my_consumer", "my_toolchain")', + 'toolchain_type(name = "my_toolchain_type")', + 'my_toolchain(', + ' name = "my_toolchain",', + ' my_value = "Hello, Bzlmod!",', + ')', + 'toolchain(', + ' name = "my_toolchain_toolchain",', + ' toolchain = ":my_toolchain",', + ' toolchain_type = ":my_toolchain_type",', + ' target_compatible_with = [', + ' "@platforms//os:" + get_host_os(),', + ' ],', + ')', + 'my_consumer(', + ' name = "my_consumer",', + ')', + ], + ) + + self.ScratchFile( + 'defs.bzl', + [ + ( + 'load("@local_config_platform//:constraints.bzl",' + ' "HOST_CONSTRAINTS")' + ), + 'def _my_toolchain_impl(ctx):', + ' return [', + ' platform_common.ToolchainInfo(', + ' my_value = ctx.attr.my_value,', + ' ),', + ' ]', + 'my_toolchain = rule(', + ' implementation = _my_toolchain_impl,', + ' attrs = {', + ' "my_value": attr.string(),', + ' },', + ')', + 'def _my_consumer(ctx):', + ' my_toolchain_info = ctx.toolchains["//:my_toolchain_type"]', + ' out = ctx.actions.declare_file(ctx.attr.name)', + ( + ' ctx.actions.write(out, "my_value =' + ' {}".format(my_toolchain_info.my_value))' + ), + ' return [DefaultInfo(files = depset([out]))]', + 'my_consumer = rule(', + ' implementation = _my_consumer,', + ' attrs = {},', + ' toolchains = ["//:my_toolchain_type"],', + ')', + 'def get_host_os():', + ' for constraint in HOST_CONSTRAINTS:', + ' if constraint.startswith("@platforms//os:"):', + ' return constraint.removeprefix("@platforms//os:")', + ], + ) + + self.RunBazel([ + 'build', + '//:my_consumer', + '--toolchain_resolution_debug=//:my_toolchain_type', + ]) + with open(self.Path('bazel-bin/my_consumer'), 'r') as f: + self.assertEqual(f.read().strip(), 'my_value = Hello, Bzlmod!') + + 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 in a BUILD file, ' + 'or a macro invoked from a BUILD file', + 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.createCcModule( + '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']) + self.ScratchFile('hello/WORKSPACE.bazel') + _, _, stderr = self.RunBazel(['build', '@what'], allow_failure=True) + self.assertIn('ERROR: @@hello~override//:MODULE.bazel', '\n'.join(stderr)) + if __name__ == '__main__': unittest.main()