| // 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.cc_library(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.getTargets()).containsKey("foo"); |
| } |
| |
| @Test |
| @TestParameters({"{suffix: ''}", "{suffix: '_inner'}"}) |
| public void validNestedMacro(String suffix) throws Exception { |
| scratch.file( |
| "pkg/inner_macro.bzl", |
| """ |
| def _impl(name, visibility): |
| native.cc_library(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.getTargets()).containsKey(innerMacroInstanceName); |
| 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.cc_library(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") |
| cc_library(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.cc_library(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.cc_library(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.cc_library(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.cc_library(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 in fail: 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.cc_library(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.cc_library(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") |
| cc_library(name = "a_top_level") |
| cc_library(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.cc_library(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.cc_library(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") |
| cc_library(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 in fail: 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.cc_library(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") |
| cc_library(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: cc_library rule 'top_level_rule' conflicts" |
| + " with existing cc_library 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 22, in _impl |
| \t\tnative.cc_library(name = name + suffix, visibility = visibility) |
| Error: cc_library rule 'top_level_rule' conflicts with existing cc_library rule, defined at /workspace/pkg/BUILD:4:11\ |
| """, |
| "cannot compute package piece for finalizer macro //pkg:finalize defined by" |
| + " //pkg:my_finalizer.bzl%my_finalizer"); |
| } |
| } |