| // Copyright 2025 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. | |
| package com.google.devtools.build.lib.skyframe; | |
| import static com.google.common.base.Preconditions.checkArgument; | |
| import static com.google.common.truth.Truth.assertThat; | |
| import static org.junit.Assert.assertThrows; | |
| import static org.junit.Assert.fail; | |
| import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; | |
| import com.google.devtools.build.lib.cmdline.PackageIdentifier; | |
| import com.google.devtools.build.lib.packages.NoSuchPackageException; | |
| import com.google.devtools.build.lib.packages.NoSuchPackagePieceException; | |
| import com.google.devtools.build.lib.packages.PackagePiece; | |
| import com.google.devtools.build.lib.packages.PackagePieceIdentifier; | |
| import com.google.devtools.build.lib.packages.Rule; | |
| import com.google.devtools.build.lib.packages.Types; | |
| import com.google.devtools.build.lib.skyframe.MacroInstanceFunction.NoSuchMacroInstanceException; | |
| import com.google.devtools.build.lib.skyframe.util.SkyframeExecutorTestUtils; | |
| import com.google.devtools.build.lib.vfs.ModifiedFileSet; | |
| import com.google.devtools.build.lib.vfs.PathFragment; | |
| import com.google.devtools.build.lib.vfs.Root; | |
| import com.google.devtools.build.skyframe.EvaluationResult; | |
| import com.google.errorprone.annotations.CanIgnoreReturnValue; | |
| import com.google.testing.junit.testparameterinjector.TestParameterInjector; | |
| import com.google.testing.junit.testparameterinjector.TestParameters; | |
| import java.util.List; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| /** Unit tests of {@link EvalMacroFunction}. */ | |
| @RunWith(TestParameterInjector.class) | |
| public final class EvalMacroFunctionTest extends BuildViewTestCase { | |
| private static PackagePieceIdentifier.ForBuildFile getBuildFileKey(String pkg) { | |
| PackageIdentifier pkgId = PackageIdentifier.createInMainRepo(pkg); | |
| return new PackagePieceIdentifier.ForBuildFile(pkgId); | |
| } | |
| /** | |
| * Returns the skykey for a {@link PackagePieceValue.ForMacro}. | |
| * | |
| * @param pkg the package name | |
| * @param macroInstances a list of macro instance names from the outermost to the innermost; for | |
| * example, ["foo", "foo_bar"] means the key for the package piece generated by expanding | |
| * macro instance "foo_bar" which is declared in macro instance "foo". | |
| */ | |
| private static PackagePieceIdentifier.ForMacro getMacroKey(String pkg, String... macroInstances) { | |
| checkArgument(macroInstances.length > 0); | |
| PackagePieceIdentifier.ForBuildFile buildFileKey = getBuildFileKey(pkg); | |
| PackagePieceIdentifier.ForMacro macroKey = null; | |
| for (String macroInstance : macroInstances) { | |
| macroKey = | |
| new PackagePieceIdentifier.ForMacro( | |
| buildFileKey.getPackageIdentifier(), | |
| macroKey != null ? macroKey : buildFileKey, | |
| macroInstance); | |
| } | |
| return macroKey; | |
| } | |
| private EvaluationResult<PackagePieceValue.ForMacro> evaluate( | |
| PackagePieceIdentifier.ForMacro skyKey) throws InterruptedException { | |
| return SkyframeExecutorTestUtils.evaluate( | |
| getSkyframeExecutor(), skyKey, /* keepGoing= */ false, reporter); | |
| } | |
| @CanIgnoreReturnValue | |
| private PackagePiece.ForMacro getPackagePiece(String pkg, String... macroInstances) | |
| throws InterruptedException { | |
| PackagePieceIdentifier.ForMacro skyKey = getMacroKey(pkg, macroInstances); | |
| EvaluationResult<PackagePieceValue.ForMacro> result = evaluate(skyKey); | |
| if (result.hasError()) { | |
| fail(result.getError(skyKey).getException().getMessage()); | |
| } | |
| PackagePiece.ForMacro value = result.get(skyKey).getPackagePiece(); | |
| assertThat(value.getIdentifier()).isEqualTo(skyKey); | |
| return value; | |
| } | |
| @CanIgnoreReturnValue | |
| private PackagePiece.ForMacro getPackagePieceWithoutErrors(String pkg, String... macroInstances) | |
| throws InterruptedException { | |
| PackagePiece.ForMacro value = getPackagePiece(pkg, macroInstances); | |
| assertThat(value.containsErrors()).isFalse(); | |
| return value; | |
| } | |
| @CanIgnoreReturnValue | |
| private <T> T getExceptionForPackagePiece( | |
| Class<T> exceptionClass, String pkg, String... macroInstances) throws InterruptedException { | |
| reporter.removeHandler(failFastHandler); | |
| PackagePieceIdentifier.ForMacro skyKey = getMacroKey(pkg, macroInstances); | |
| EvaluationResult<PackagePieceValue.ForMacro> result = evaluate(skyKey); | |
| assertThat(result.hasError()).isTrue(); | |
| Exception exception = result.getError(skyKey).getException(); | |
| assertThat(exception).isInstanceOf(exceptionClass); | |
| return exceptionClass.cast(exception); | |
| } | |
| @Test | |
| public void validMacro() throws Exception { | |
| scratch.file( | |
| "pkg/my_macro.bzl", | |
| """ | |
| def _impl(name, visibility): | |
| native.filegroup(name = name, visibility = visibility) | |
| my_macro = macro(implementation = _impl) | |
| """); | |
| scratch.file( | |
| "pkg/BUILD", | |
| """ | |
| load(":my_macro.bzl", "my_macro") | |
| my_macro(name = "foo") | |
| """); | |
| PackagePiece.ForMacro forMacro = getPackagePieceWithoutErrors("pkg", "foo"); | |
| assertThat(forMacro.getTargetOrNull("foo")).isNotNull(); | |
| } | |
| @Test | |
| @TestParameters({"{suffix: ''}", "{suffix: '_inner'}"}) | |
| public void validNestedMacro(String suffix) throws Exception { | |
| scratch.file( | |
| "pkg/inner_macro.bzl", | |
| """ | |
| def _impl(name, visibility): | |
| native.filegroup(name = name, visibility = visibility) | |
| inner_macro = macro(implementation = _impl) | |
| """); | |
| scratch.file( | |
| "pkg/outer_macro.bzl", | |
| String.format( | |
| """ | |
| load(":inner_macro.bzl", "inner_macro") | |
| def _impl(name, visibility): | |
| inner_macro(name = name + "%s", visibility = visibility) | |
| outer_macro = macro(implementation = _impl) | |
| """, | |
| suffix)); | |
| scratch.file( | |
| "pkg/BUILD", | |
| """ | |
| load(":outer_macro.bzl", "outer_macro") | |
| outer_macro(name = "foo") | |
| """); | |
| String innerMacroInstanceName = "foo" + suffix; | |
| PackagePiece.ForMacro forInnerMacro = | |
| getPackagePieceWithoutErrors("pkg", "foo", innerMacroInstanceName); | |
| assertThat(forInnerMacro.getTargetOrNull(innerMacroInstanceName)).isNotNull(); | |
| PackagePiece.ForMacro forOuterMacro = getPackagePieceWithoutErrors("pkg", "foo"); | |
| assertThat(forOuterMacro.getMacroByName(innerMacroInstanceName)) | |
| .isSameInstanceAs(forInnerMacro.getEvaluatedMacro()); | |
| } | |
| @Test | |
| public void innerAndSiblingMacros_notExpandedUnlessRequested() throws Exception { | |
| scratch.file( | |
| "pkg/inner_macro.bzl", | |
| """ | |
| def _impl(name, visibility): | |
| fail("This will fail if the inner macro is expanded") | |
| inner_macro = macro(implementation = _impl) | |
| """); | |
| scratch.file( | |
| "pkg/sibling_macro.bzl", | |
| """ | |
| def _impl(name, visibility): | |
| fail("This will fail if the sibling macro is expanded") | |
| sibling_macro = macro(implementation = _impl) | |
| """); | |
| scratch.file( | |
| "pkg/outer_macro.bzl", | |
| """ | |
| load(":inner_macro.bzl", "inner_macro") | |
| def _impl(name, visibility): | |
| inner_macro(name = name + "_inner", visibility = visibility) | |
| outer_macro = macro(implementation = _impl) | |
| """); | |
| scratch.file( | |
| "pkg/BUILD", | |
| """ | |
| load(":sibling_macro.bzl", "sibling_macro") | |
| load(":outer_macro.bzl", "outer_macro") | |
| sibling_macro(name = "bar") | |
| outer_macro(name = "foo") | |
| """); | |
| getPackagePieceWithoutErrors("pkg", "foo"); | |
| SkyframeExecutor.FailureToRetrieveIntrospectedValueException siblingPieceException = | |
| assertThrows( | |
| SkyframeExecutor.FailureToRetrieveIntrospectedValueException.class, | |
| () -> skyframeExecutor.getDoneSkyValueForIntrospection(getMacroKey("pkg", "bar"))); | |
| assertThat(siblingPieceException) | |
| .hasMessageThat() | |
| .contains( | |
| "<PackagePieceIdentifier.ForMacro name=//pkg:bar" | |
| + " declared_in=<PackagePieceIdentifier.ForBuildFile pkg=//pkg>> not found"); | |
| SkyframeExecutor.FailureToRetrieveIntrospectedValueException innerPieceException = | |
| assertThrows( | |
| SkyframeExecutor.FailureToRetrieveIntrospectedValueException.class, | |
| () -> | |
| skyframeExecutor.getDoneSkyValueForIntrospection( | |
| getMacroKey("pkg", "foo", "foo_inner"))); | |
| assertThat(innerPieceException) | |
| .hasMessageThat() | |
| .contains( | |
| "<PackagePieceIdentifier.ForMacro name=//pkg:foo_inner" | |
| + " declared_in=<PackagePieceIdentifier.ForMacro name=//pkg:foo" | |
| + " declared_in=<PackagePieceIdentifier.ForBuildFile pkg=//pkg>>> not found"); | |
| } | |
| // TODO(https://github.com/bazelbuild/bazel/issues/26128): also prune outer macro changes at an | |
| // inner macro instance. | |
| @Test | |
| public void buildFileChange_prunedAtMacroInstance() throws Exception { | |
| scratch.file( | |
| "pkg/my_macro.bzl", | |
| """ | |
| def _impl(name, visibility): | |
| native.filegroup(name = name, visibility = visibility) | |
| my_macro = macro(implementation = _impl) | |
| """); | |
| scratch.file( | |
| "pkg/BUILD", | |
| """ | |
| load(":my_macro.bzl", "my_macro") | |
| my_macro(name = "foo") | |
| """); | |
| PackagePiece.ForMacro packagePieceBeforeUpdate = getPackagePieceWithoutErrors("pkg", "foo"); | |
| // Edit and invalidate BUILD file. Note that for change pruning to work, the edit must preserve | |
| // line numbers in the macro call stack. | |
| // TODO(https://github.com/bazelbuild/bazel/issues/26128): relax this requirement. | |
| scratch.overwriteFile( | |
| "pkg/BUILD", | |
| """ | |
| load(":my_macro.bzl", "my_macro") | |
| my_macro(name = "foo") | |
| filegroup(name = "unrelated") | |
| """); | |
| getSkyframeExecutor() | |
| .invalidateFilesUnderPathForTesting( | |
| reporter, | |
| ModifiedFileSet.builder().modify(PathFragment.create("pkg/BUILD")).build(), | |
| Root.fromPath(rootDirectory)); | |
| // PackagePieceValue.ForMacro is a NotComparableSkyValue; if we get back the same instance after | |
| // update, it means all of the PackagePieceValue's deps were either change-pruned or unchanged. | |
| assertThat(getPackagePieceWithoutErrors("pkg", "foo")) | |
| .isSameInstanceAs(packagePieceBeforeUpdate); | |
| } | |
| @Test | |
| public void maxComputationSteps_enforced() throws Exception { | |
| scratch.file( | |
| "pkg/my_macro.bzl", | |
| """ | |
| def _impl(name, visibility): | |
| # exceed max_computation_steps | |
| for i in range(1000): | |
| pass | |
| native.filegroup(name = name, visibility = visibility) | |
| my_macro = macro(implementation = _impl) | |
| """); | |
| scratch.file( | |
| "pkg/BUILD", | |
| """ | |
| load(":my_macro.bzl", "my_macro") | |
| my_macro(name = "foo") | |
| """); | |
| setBuildLanguageOptions("--max_computation_steps=100"); // sufficient for BUILD but not my_macro | |
| assertThat(getExceptionForPackagePiece(NoSuchPackagePieceException.class, "pkg", "foo")) | |
| .hasMessageThat() | |
| .containsMatch( | |
| "symbolic macro evaluation took 1\\d{3} computation steps, but" | |
| + " --max_computation_steps=100"); | |
| } | |
| @Test | |
| public void noBuildFile_failsCleanly() throws Exception { | |
| assertThat(getExceptionForPackagePiece(NoSuchPackageException.class, "no_such_pkg", "foo")) | |
| .hasMessageThat() | |
| .contains("no such package 'no_such_pkg': BUILD file not found"); | |
| } | |
| @Test | |
| public void badBuildFile_failsCleanly() throws Exception { | |
| scratch.file( | |
| "pkg/my_macro.bzl", | |
| """ | |
| def _impl(name, visibility): | |
| native.filegroup(name = name, visibility = visibility) | |
| my_macro = macro(implementation = _impl) | |
| """); | |
| scratch.file( | |
| "pkg/BUILD", | |
| """ | |
| load(":my_macro.bzl", "my_macro") | |
| load(":bad_load.bzl", "bad_value") | |
| my_macro(name = "foo") | |
| """); | |
| assertThat(getExceptionForPackagePiece(NoSuchPackageException.class, "pkg", "foo")) | |
| .hasMessageThat() | |
| .contains("cannot load '//pkg:bad_load.bzl': no such file"); | |
| } | |
| @Test | |
| public void noMacroInstance_failsCleanly() throws Exception { | |
| scratch.file( | |
| "pkg/my_macro.bzl", | |
| """ | |
| def _impl(name, visibility): | |
| native.filegroup(name = name, visibility = visibility) | |
| my_macro = macro(implementation = _impl) | |
| """); | |
| scratch.file( | |
| "pkg/BUILD", | |
| """ | |
| load(":my_macro.bzl", "my_macro") | |
| my_macro(name = "foo") | |
| """); | |
| assertThat( | |
| getExceptionForPackagePiece(NoSuchMacroInstanceException.class, "pkg", "no_such_name")) | |
| .hasMessageThat() | |
| .contains("Macro instance 'no_such_name' not found in top-level package piece"); | |
| } | |
| @Test | |
| public void badMacroImplementation_producesPackagePieceWithErrors() throws Exception { | |
| scratch.file( | |
| "pkg/my_macro.bzl", | |
| """ | |
| def _impl(name, visibility): | |
| native.filegroup(name = name, visibility = visibility) | |
| fail("fail fail fail") | |
| my_macro = macro(implementation = _impl) | |
| """); | |
| scratch.file( | |
| "pkg/BUILD", | |
| """ | |
| load(":my_macro.bzl", "my_macro") | |
| my_macro(name = "foo") | |
| """); | |
| reporter.removeHandler(failFastHandler); | |
| PackagePiece.ForMacro forMacro = getPackagePiece("pkg", "foo"); | |
| assertThat(forMacro.containsErrors()).isTrue(); | |
| assertThat(((Rule) forMacro.getTarget("foo")).containsErrors()).isTrue(); | |
| assertContainsEvent( | |
| """ | |
| ERROR /workspace/pkg/my_macro.bzl:3:9: Traceback (most recent call last): | |
| \tFile "/workspace/pkg/BUILD", line 2, column 9, in <toplevel> | |
| \t\tmy_macro(name = "foo") | |
| \tFile "/workspace/pkg/my_macro.bzl", line 4, column 1, in my_macro | |
| \t\tmy_macro = macro(implementation = _impl) | |
| \tFile "/workspace/pkg/my_macro.bzl", line 3, column 9, in _impl | |
| \t\tfail("fail fail fail") | |
| Error: fail fail fail\ | |
| """); | |
| } | |
| @Test | |
| public void finalizers_seeNonFinalizerDefinedRulesOrderedByName() throws Exception { | |
| scratch.file( | |
| "pkg/my_finalizer.bzl", | |
| """ | |
| # Dummy rule used to save native.existing_rules() keys in a string list attribute. | |
| _existing_rules_saver = rule( | |
| implementation = lambda ctx: [], | |
| attrs = {"existing_rules": attr.string_list()}, | |
| ) | |
| def _impl(name, visibility): | |
| _existing_rules_saver(name = name, existing_rules = list(native.existing_rules())) | |
| my_finalizer = macro(implementation = _impl, finalizer = True) | |
| """); | |
| scratch.file( | |
| "pkg/other_macro.bzl", | |
| """ | |
| def _other_inner_macro_impl(name, visibility): | |
| native.filegroup(name = name, visibility = visibility) | |
| other_inner_macro = macro(implementation = _other_inner_macro_impl) | |
| def _other_macro_impl(name, visibility): | |
| other_inner_macro(name = name + "_c_inner", visibility = visibility) | |
| native.filegroup(name = name + "_b", visibility = visibility) | |
| other_inner_macro(name = name + "_a_inner", visibility = visibility) | |
| other_macro = macro(implementation = _other_macro_impl) | |
| """); | |
| scratch.file( | |
| "pkg/BUILD", | |
| """ | |
| load(":my_finalizer.bzl", "my_finalizer") | |
| load(":other_macro.bzl", "other_macro") | |
| my_finalizer(name = "finalize") | |
| other_macro(name = "macro_declared") | |
| filegroup(name = "a_top_level") | |
| filegroup(name = "z_top_level") | |
| """); | |
| // getPackagePieceWithoutErrors("pkg", "finalize"); | |
| PackagePiece.ForMacro finalizerPiece = getPackagePieceWithoutErrors("pkg", "finalize"); | |
| Rule existingRulesSaverRule = (Rule) finalizerPiece.getTarget("finalize"); | |
| List<String> existingRules = | |
| Types.STRING_LIST.cast(existingRulesSaverRule.getAttr("existing_rules")); | |
| assertThat(existingRules) | |
| .containsExactly( | |
| // Ordered by name. | |
| "a_top_level", | |
| "macro_declared_a_inner", | |
| "macro_declared_b", | |
| "macro_declared_c_inner", | |
| "z_top_level") | |
| .inOrder(); | |
| } | |
| @Test | |
| public void finalizers_doNotSeeFinalizerDefinedTargets() throws Exception { | |
| scratch.file( | |
| "pkg/my_finalizer.bzl", | |
| """ | |
| # Dummy rule used to save native.existing_rules() keys in a string list attribute. | |
| _existing_rules_saver = rule( | |
| implementation = lambda ctx: [], | |
| attrs = {"existing_rules": attr.string_list()}, | |
| ) | |
| def _impl(name, visibility): | |
| native.filegroup(name = name + "_dummy_rule") | |
| _existing_rules_saver(name = name, existing_rules = list(native.existing_rules())) | |
| my_finalizer = macro(implementation = _impl, finalizer = True) | |
| """); | |
| scratch.file( | |
| "pkg/BUILD", | |
| """ | |
| load(":my_finalizer.bzl", "my_finalizer") | |
| my_finalizer(name = "finalize") | |
| my_finalizer(name = "other_finalize") | |
| """); | |
| PackagePiece.ForMacro finalizerPiece = getPackagePieceWithoutErrors("pkg", "finalize"); | |
| assertThat( | |
| Types.STRING_LIST.cast( | |
| ((Rule) finalizerPiece.getTarget("finalize")).getAttr("existing_rules"))) | |
| .isEmpty(); | |
| PackagePiece.ForMacro otherFinalizerPiece = | |
| getPackagePieceWithoutErrors("pkg", "other_finalize"); | |
| assertThat( | |
| Types.STRING_LIST.cast( | |
| ((Rule) otherFinalizerPiece.getTarget("other_finalize")).getAttr("existing_rules"))) | |
| .isEmpty(); | |
| } | |
| @Test | |
| public void finalizers_notEvaluated_ifNonFinalizerPackagePieceInError() throws Exception { | |
| scratch.file( | |
| "pkg/my_finalizer.bzl", | |
| """ | |
| def _impl(name, visibility): | |
| native.filegroup(name = name + "_saw_rules", srcs = native.existing_rules()) | |
| my_finalizer = macro(implementation = _impl, finalizer = True) | |
| """); | |
| scratch.file( | |
| "pkg/fail_macro.bzl", | |
| """ | |
| def _impl(name, visibility): | |
| native.filegroup(name = name, visibility = visibility) | |
| fail("fail fail fail") | |
| fail_macro = macro(implementation = _impl) | |
| """); | |
| scratch.file( | |
| "pkg/BUILD", | |
| """ | |
| load(":fail_macro.bzl", "fail_macro") | |
| load(":my_finalizer.bzl", "my_finalizer") | |
| my_finalizer(name = "finalize") | |
| filegroup(name = "top_level_rule") | |
| fail_macro(name = "failing_macro") | |
| """); | |
| reporter.removeHandler(failFastHandler); | |
| PackagePiece.ForMacro finalizerPiece = getPackagePiece("pkg", "finalize"); | |
| assertThat(finalizerPiece.containsErrors()).isTrue(); | |
| assertThat(finalizerPiece.getTargets()).isEmpty(); | |
| assertThat(finalizerPiece.getFailureDetail().getMessage()) | |
| .contains( | |
| "cannot compute package piece for finalizer macro //pkg:finalize defined by" | |
| + " //pkg:my_finalizer.bzl%my_finalizer: error in package piece for macro" | |
| + " //pkg:failing_macro defined by //pkg:fail_macro.bzl%fail_macro"); | |
| assertThat(getPackagePiece("pkg", "failing_macro").containsErrors()).isTrue(); | |
| assertContainsEventsInOrder( | |
| """ | |
| Traceback (most recent call last): | |
| \tFile "/workspace/pkg/BUILD", line 5, column 11, in <toplevel> | |
| \t\tfail_macro(name = "failing_macro") | |
| \tFile "/workspace/pkg/fail_macro.bzl", line 5, column 1, in fail_macro | |
| \t\tfail_macro = macro(implementation = _impl) | |
| \tFile "/workspace/pkg/fail_macro.bzl", line 3, column 9, in _impl | |
| \t\tfail("fail fail fail") | |
| Error: fail fail fail\ | |
| """, | |
| "cannot compute package piece for finalizer macro //pkg:finalize defined by" | |
| + " //pkg:my_finalizer.bzl%my_finalizer"); | |
| } | |
| @Test | |
| public void finalizers_notEvaluated_ifNameConflictBetweenNonFinalizerPackagePieces() | |
| throws Exception { | |
| scratch.file( | |
| "pkg/my_finalizer.bzl", | |
| """ | |
| def _impl(name, visibility): | |
| native.filegroup(name = name + "_saw_rules", srcs = native.existing_rules()) | |
| my_finalizer = macro(implementation = _impl, finalizer = True) | |
| """); | |
| scratch.file( | |
| "pkg/name_conflict_macro.bzl", | |
| """ | |
| def _impl(name, suffix, visibility): | |
| native.filegroup(name = name + suffix, visibility = visibility) | |
| name_conflict_macro = macro( | |
| implementation = _impl, | |
| attrs = {"suffix": attr.string(configurable = False)}, | |
| ) | |
| """); | |
| scratch.file( | |
| "pkg/BUILD", | |
| """ | |
| load(":my_finalizer.bzl", "my_finalizer") | |
| load(":name_conflict_macro.bzl", "name_conflict_macro") | |
| my_finalizer(name = "finalize") | |
| filegroup(name = "top_level_rule") | |
| name_conflict_macro(name = "top", suffix = "_level_rule") | |
| """); | |
| reporter.removeHandler(failFastHandler); | |
| PackagePiece.ForMacro finalizerPiece = getPackagePiece("pkg", "finalize"); | |
| assertThat(finalizerPiece.containsErrors()).isTrue(); | |
| assertThat(finalizerPiece.getTargets()).isEmpty(); | |
| assertThat(finalizerPiece.getFailureDetail().getMessage()) | |
| .contains( | |
| "cannot compute package piece for finalizer macro //pkg:finalize defined by" | |
| + " //pkg:my_finalizer.bzl%my_finalizer: filegroup rule 'top_level_rule' conflicts" | |
| + " with existing filegroup rule"); | |
| // Note that individual non-finalizer package pieces are not in error - the conflict is in the | |
| // NonFinalizerPackagePiecesValue. | |
| getPackagePieceWithoutErrors("pkg", "top"); | |
| assertContainsEventsInOrder( | |
| """ | |
| Traceback (most recent call last): | |
| \tFile "/workspace/pkg/BUILD", line 5, column 20, in <toplevel> | |
| \t\tname_conflict_macro(name = "top", suffix = "_level_rule") | |
| \tFile "/workspace/pkg/name_conflict_macro.bzl", line 4, column 1, in name_conflict_macro | |
| \t\tname_conflict_macro = macro( | |
| \tFile "/workspace/pkg/name_conflict_macro.bzl", line 2, column 21, in _impl | |
| \t\tnative.filegroup(name = name + suffix, visibility = visibility) | |
| Error: filegroup rule 'top_level_rule' conflicts with existing filegroup rule, defined at /workspace/pkg/BUILD:4:10\ | |
| """, | |
| "cannot compute package piece for finalizer macro //pkg:finalize defined by" | |
| + " //pkg:my_finalizer.bzl%my_finalizer"); | |
| } | |
| } |