blob: 75410326e4744d27098636f251154802b95fbbea [file] [log] [blame]
// Copyright 2024 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.analysis;
import static com.google.common.truth.Truth.assertWithMessage;
import com.google.common.base.Preconditions;
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import com.google.testing.junit.testparameterinjector.TestParameters;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Tests for the how the visibility system works with respect to symbolic macros, i.e. the
* Macro-Aware Visibility design.
*
* <p>This does *not* include tests of how the {@code visibility} attribute's value gets determined
* and threaded through macros. See SymbolicMacroTest.java for that.
*/
@RunWith(TestParameterInjector.class)
public final class MacroVisibilityTest extends BuildViewTestCase {
@Override
protected String getDefaultVisibility() {
// We're testing visibility. Avoid having to litter our test cases with `visibility=` attribute
// declarations, by using the same behavior seen in production.
return "private";
}
/**
* Returns a substring expected to be in the error event reported for a visibility violation from
* {@code consumer} (expressed as a label string) to {@code dependency} (expressed as a label
* string, or for {@code alias} targets, the string returned by {@link
* AliasProvider#describeTargetWithAliases}).
*/
private String visibilityErrorMessage(
String consumer, String dependency, boolean dependencyIsAlias) {
if (!dependencyIsAlias) {
dependency = String.format("target '%s'", dependency);
}
return String.format(
"Visibility error:\n%s is not visible from\ntarget '%s'", dependency, consumer);
}
/**
* Convenience wrapper for {@link #visibilityErrorMessage} for when {@code dependency} is not an
* {@code alias}.
*/
private String visibilityErrorMessage(String consumer, String dependency) {
return visibilityErrorMessage(consumer, dependency, /* dependencyIsAlias= */ false);
}
/**
* Requests the evaluation of the configured target identified by the label {@code consumer}, and
* asserts that the target analyzes successfully and has a dependency on the target identified by
* the label {@code dependency}.
*
* <p>Does not work when {@code dependency} is an {@code alias} target.
*/
private void assertVisibilityPermits(String consumer, String dependency) throws Exception {
ConfiguredTarget consumerTarget = getConfiguredTarget(consumer);
// If the dependency is in fact *not* visible, consumerTarget will be null. So we assert on the
// absence of the visibility error event first, to deliver a better error message than just
// saying consumerTarget was null. (We can't just attach a nicer message to the null check
// because consumerTarget could be null due to other unexpected errors.)
//
// This nicer handling is moot if the failFastHandler is present, in which case we'll see the
// visibility error event in a traceback.
assertDoesNotContainEvent(visibilityErrorMessage(consumer, dependency));
assertWithMessage(
String.format(
"%s had an error (other than a visibility violation on %s)", consumer, dependency))
.that(consumerTarget)
.isNotNull();
assertWithMessage(String.format("%s does not have a dependency on %s", consumer, dependency))
.that(getDirectPrerequisite(consumerTarget, dependency))
.isNotNull();
}
/**
* Requests the evaluation of the configured target identified by the label {@code consumer}, and
* asserts that the target is in error due to a visibility violation on the target identified by
* the label {@code dependency}.
*
* <p>Removes the {@code failFastHandler}.
*/
private void assertVisibilityDisallows(
String consumer, String dependency, boolean dependencyIsAlias) throws Exception {
reporter.removeHandler(failFastHandler);
ConfiguredTarget consumerTarget = getConfiguredTarget(consumer);
assertWithMessage(
String.format(
"%s unexpectedly did not have an error (no visibility violation due to %s)",
consumer, dependency))
.that(consumerTarget)
.isNull();
assertContainsEvent(visibilityErrorMessage(consumer, dependency, dependencyIsAlias));
}
/**
* Convenience wrapper for {@link #assertVisibilityDisallows} for when {@code dependency} is not
* an {@code alias}.
*/
private void assertVisibilityDisallows(String consumer, String dependency) throws Exception {
assertVisibilityDisallows(consumer, dependency, /* dependencyIsAlias= */ false);
}
/**
* Creates definition of {@code //rules:simple_rule.bzl%simple_rule}, a rule that has a label
* attribute {@code dep} and implicit output {@code <NAME>.bin}.
*/
private void defineSimpleRule() throws Exception {
scratch.file("rules/BUILD");
scratch.file(
"rules/simple_rule.bzl",
"""
def _impl(ctx):
ctx.actions.write(ctx.outputs.out, "")
simple_rule = rule(
implementation = _impl,
attrs = {"dep": attr.label(mandatory=False, allow_files=True)},
outputs = {"out": "%{name}.bin"},
)
""");
}
/**
* Creates a package {@code //common} defining several {@code simple_rule} targets with the given
* names and visibility declarations.
*
* <p>The argument list must have even length, and contains pairs of (unquoted) target names and
* Starlark expression strings that evaluate to visibility lists (e.g. pass the string {@code
* "[]"} for no visibility).
*/
private void defineCommonPackageWithSimpleTargets(String... targetNamesAndVisibilities)
throws Exception {
Preconditions.checkArgument(targetNamesAndVisibilities.length % 2 == 0);
StringBuilder s = new StringBuilder();
s.append(
"""
load("//rules:simple_rule.bzl", "simple_rule")
""");
for (int i = 0; i < targetNamesAndVisibilities.length; i += 2) {
s.append(
String.format(
"""
simple_rule(
name = "%s",
visibility = %s,
)
""",
targetNamesAndVisibilities[i], targetNamesAndVisibilities[i + 1]));
}
scratch.file("common/BUILD", s.toString());
}
@Test
public void buildFileAccessToMacroTargetsIsControlled() throws Exception {
defineSimpleRule();
scratch.file("lib/BUILD");
scratch.file(
"lib/macro.bzl",
"""
load("//rules:simple_rule.bzl", "simple_rule")
def _impl(name, visibility):
simple_rule(
name = name + "_exported",
visibility = ["//pkg:__pkg__"],
)
simple_rule(
name = name + "_internal",
)
native.exports_files([name + "_exported_input"], visibility = ["//pkg:__pkg__"])
native.exports_files([name + "_internal_input"], visibility = ["//visibility:private"])
my_macro = macro(implementation=_impl)
""");
scratch.file(
"pkg/BUILD",
"""
load("//rules:simple_rule.bzl", "simple_rule")
load("//lib:macro.bzl", "my_macro")
my_macro(name = "foo")
simple_rule(name = "consumes_exported_ruletarget", dep = ":foo_exported")
simple_rule(name = "consumes_exported_output", dep = ":foo_exported.bin")
simple_rule(name = "consumes_exported_input", dep = ":foo_exported_input")
simple_rule(name = "consumes_internal_ruletarget", dep = ":foo_internal")
simple_rule(name = "consumes_internal_output", dep = ":foo_internal.bin")
simple_rule(name = "consumes_internal_input", dep = ":foo_internal_input")
""");
assertVisibilityPermits("//pkg:consumes_exported_ruletarget", "//pkg:foo_exported");
assertVisibilityPermits("//pkg:consumes_exported_output", "//pkg:foo_exported.bin");
assertVisibilityPermits("//pkg:consumes_exported_input", "//pkg:foo_exported_input");
assertVisibilityDisallows("//pkg:consumes_internal_ruletarget", "//pkg:foo_internal");
assertVisibilityDisallows("//pkg:consumes_internal_output", "//pkg:foo_internal.bin");
assertVisibilityDisallows("//pkg:consumes_internal_input", "//pkg:foo_internal_input");
}
@Test
public void macroAccessToBuildFileTargetsIsControlled() throws Exception {
defineSimpleRule();
scratch.file("lib/BUILD");
scratch.file(
"lib/macro.bzl",
"""
load("//rules:simple_rule.bzl", "simple_rule")
def _impl(name, visibility):
simple_rule(name = name + "_consumes_exported_ruletarget", dep = "//pkg:exported")
simple_rule(name = name + "_consumes_exported_output", dep = "//pkg:exported.bin")
simple_rule(name = name + "_consumes_exported_input", dep = "//pkg:exported_input")
simple_rule(name = name + "_consumes_internal_ruletarget", dep = "//pkg:internal")
simple_rule(name = name + "_consumes_internal_output", dep = "//pkg:internal.bin")
simple_rule(name = name + "_consumes_internal_input", dep = "//pkg:internal_input")
my_macro = macro(implementation=_impl)
""");
scratch.file(
"pkg/BUILD",
"""
load("//rules:simple_rule.bzl", "simple_rule")
load("//lib:macro.bzl", "my_macro")
my_macro(name = "foo")
simple_rule(
name = "exported",
visibility = ["//lib:__pkg__"],
)
simple_rule(
name = "internal",
)
exports_files(["exported_input"], visibility = ["//lib:__pkg__"])
exports_files(["internal_input"], visibility = ["//visibility:private"])
""");
assertVisibilityPermits("//pkg:foo_consumes_exported_ruletarget", "//pkg:exported");
assertVisibilityPermits("//pkg:foo_consumes_exported_output", "//pkg:exported.bin");
assertVisibilityPermits("//pkg:foo_consumes_exported_input", "//pkg:exported_input");
assertVisibilityDisallows("//pkg:foo_consumes_internal_ruletarget", "//pkg:internal");
assertVisibilityDisallows("//pkg:foo_consumes_internal_output", "//pkg:internal.bin");
assertVisibilityDisallows("//pkg:foo_consumes_internal_input", "//pkg:internal_input");
}
@Test
public void macroAccessToOtherMacrosInSameBuildFileIsControlled() throws Exception {
defineSimpleRule();
scratch.file("producer/BUILD");
scratch.file(
"producer/macro.bzl",
"""
load("//rules:simple_rule.bzl", "simple_rule")
def _impl(name, visibility):
simple_rule(
name = name + "_internal",
)
simple_rule(
name = name + "_visible_to_pkg",
visibility = ["//pkg:__pkg__"],
)
simple_rule(
name = name + "_visible_to_consumer",
visibility = ["//consumer:__pkg__"],
)
producer_macro = macro(implementation=_impl)
""");
scratch.file("consumer/BUILD");
scratch.file(
"consumer/macro.bzl",
"""
load("//rules:simple_rule.bzl", "simple_rule")
def _impl(name, visibility):
simple_rule(
name = name + "_consumes_internal",
dep = "//pkg:foo_internal",
)
simple_rule(
name = name + "_consumes_visible_to_pkg",
dep = "//pkg:foo_visible_to_pkg",
)
simple_rule(
name = name + "_consumes_visible_to_consumer",
dep = "//pkg:foo_visible_to_consumer",
)
consumer_macro = macro(implementation=_impl)
""");
scratch.file(
"pkg/BUILD",
"""
load("//producer:macro.bzl", "producer_macro")
load("//consumer:macro.bzl", "consumer_macro")
producer_macro(name = "foo")
consumer_macro(name = "bar")
""");
assertVisibilityDisallows("//pkg:bar_consumes_internal", "//pkg:foo_internal");
assertVisibilityDisallows("//pkg:bar_consumes_visible_to_pkg", "//pkg:foo_visible_to_pkg");
assertVisibilityPermits(
"//pkg:bar_consumes_visible_to_consumer", "//pkg:foo_visible_to_consumer");
}
@Test
public void siblingsInSameMacroCanSeeEachOther() throws Exception {
defineSimpleRule();
scratch.file("lib/BUILD");
scratch.file(
"lib/macro.bzl",
"""
load("//rules:simple_rule.bzl", "simple_rule")
def _impl(name, visibility):
simple_rule(
name = name + "_ruletarget",
)
simple_rule(
name = name + "_consumes_ruletarget",
dep = name + "_ruletarget",
)
native.exports_files([name + "_input"], visibility = ["//visibility:private"])
simple_rule(
name = name + "_consumes_input",
dep = name + "_input",
)
my_macro = macro(implementation=_impl)
""");
scratch.file(
"pkg/BUILD",
"""
load("//rules:simple_rule.bzl", "simple_rule")
load("//lib:macro.bzl", "my_macro")
my_macro(name = "foo")
simple_rule(
name = "control",
dep = ":foo_ruletarget",
)
""");
assertVisibilityPermits("//pkg:foo_consumes_ruletarget", "//pkg:foo_ruletarget");
assertVisibilityPermits("//pkg:foo_consumes_input", "//pkg:foo_input");
assertVisibilityDisallows("//pkg:control", "//pkg:foo_ruletarget");
}
@Test
public void buildFileTargetIsVisibleToTargetsDefinedByMacroFromSamePackage() throws Exception {
// This test is needed because the declaration location of a target is only materialized in its
// visibility attribute when the target is declared inside a symbolic macro. We need to
// confirm that we're still allowing the edge from a target declared by a macro whose code is in
// //pkg, to a dependency declared in //pkg:BUILD, despite the dependency not materializing
// //pkg in its own visibility.
defineSimpleRule();
scratch.file(
"pkg/macro.bzl",
"""
load("//rules:simple_rule.bzl", "simple_rule")
def _impl(name, visibility):
simple_rule(name = name + "_macro_target", dep = "//pkg:build_target")
my_macro = macro(implementation=_impl)
""");
scratch.file(
"other_pkg/BUILD",
"""
load("//pkg:macro.bzl", "my_macro")
my_macro(name = "foo")
""");
scratch.file(
"pkg/BUILD",
"""
load("//rules:simple_rule.bzl", "simple_rule")
simple_rule(name = "build_target")
""");
// Allowed because my_macro's code was defined in //pkg, even though it was instantiated in
// //other_pkg.
assertVisibilityPermits("//other_pkg:foo_macro_target", "//pkg:build_target");
}
@Test
public void macroDefinitionLocationIsDeterminedByExportAssignment() throws Exception {
// Consider a target declared by a helper func in //A, which is called from an impl func in //B,
// which is used in a macro() definition in //C, which is loaded and exported by //D, and called
// by a legacy macro defined in //E, itself called in a BUILD file in //F. Then the declaration
// location of the target is considered to be //D, where the export occurs, rather than any of
// those other packages.
//
// This test defines six targets in //pkg, visible to each of //A through //F. The helper
// function in //A declares six targets that try to consume each of the respective targets from
// //pkg. Only the one that consumes the target visible to //D is allowed to succeed.
defineSimpleRule();
scratch.file("A/BUILD");
scratch.file(
"A/helper.bzl",
"""
load("//rules:simple_rule.bzl", "simple_rule")
def helper(name):
for c in ["A", "B", "C", "D", "E", "F"]:
simple_rule(
name = name + "_wants_vis_to_%s" % c,
dep = "//pkg:vis_to_%s" % c,
)
""");
scratch.file("B/BUILD");
scratch.file(
"B/impl.bzl",
"""
load("//A:helper.bzl", "helper")
def impl(name, visibility):
helper(name)
""");
scratch.file("C/BUILD");
scratch.file(
"C/metamacro.bzl",
"""
load("//B:impl.bzl", "impl")
def metamacro():
return macro(implementation = impl)
""");
scratch.file("D/BUILD");
scratch.file(
"D/my_macro.bzl",
"""
load("//C:metamacro.bzl", "metamacro")
my_macro = metamacro()
""");
scratch.file("E/BUILD");
scratch.file(
"E/legacy_macro.bzl",
"""
load("//D:my_macro.bzl", "my_macro")
def legacy_macro(name):
my_macro(name = name)
""");
scratch.file(
"F/BUILD",
"""
load("//E:legacy_macro.bzl", "legacy_macro")
legacy_macro(name = "consumer")
""");
scratch.file(
"pkg/BUILD",
"""
load("//rules:simple_rule.bzl", "simple_rule")
[
simple_rule(
name = "vis_to_%s" % c,
visibility = ["//%s:__pkg__" % c])
for c in ["A", "B", "C", "D", "E", "F"]
]
""");
assertVisibilityDisallows("//F:consumer_wants_vis_to_A", "//pkg:vis_to_A");
assertVisibilityDisallows("//F:consumer_wants_vis_to_B", "//pkg:vis_to_B");
assertVisibilityDisallows("//F:consumer_wants_vis_to_C", "//pkg:vis_to_C");
assertVisibilityPermits("//F:consumer_wants_vis_to_D", "//pkg:vis_to_D");
assertVisibilityDisallows("//F:consumer_wants_vis_to_E", "//pkg:vis_to_E");
assertVisibilityDisallows("//F:consumer_wants_vis_to_F", "//pkg:vis_to_F");
}
@Test
public void locationOfTargetDeclarationIsInnermostMacro() throws Exception {
// The //common package has a target visible to an outer macro and a target visible to an inner
// macro. In //pkg, the outer macro is instantiated as "foo" and the inner as "foo_inner". We
// check each combination of foo and foo_inner trying to access the two common targets.
defineSimpleRule();
defineCommonPackageWithSimpleTargets(
"vis_to_inner", "['//inner:__pkg__']", "vis_to_outer", "['//outer:__pkg__']");
scratch.file("inner/BUILD");
scratch.file(
"inner/macro.bzl",
"""
load("//rules:simple_rule.bzl", "simple_rule")
def _impl(name, visibility):
simple_rule(
name = name + "_wants_vis_to_inner",
dep = "//common:vis_to_inner",
)
simple_rule(
name = name + "_wants_vis_to_outer",
dep = "//common:vis_to_outer",
)
inner_macro = macro(implementation=_impl)
""");
scratch.file("outer/BUILD");
scratch.file(
"outer/macro.bzl",
"""
load("//rules:simple_rule.bzl", "simple_rule")
load("//inner:macro.bzl", "inner_macro")
def _impl(name, visibility):
inner_macro(name = name + "_inner")
simple_rule(
name = name + "_wants_vis_to_inner",
dep = "//common:vis_to_inner",
)
simple_rule(
name = name + "_wants_vis_to_outer",
dep = "//common:vis_to_outer",
)
outer_macro = macro(implementation=_impl)
""");
scratch.file(
"pkg/BUILD",
"""
load("//outer:macro.bzl", "outer_macro")
outer_macro(name = "foo")
""");
assertVisibilityPermits("//pkg:foo_inner_wants_vis_to_inner", "//common:vis_to_inner");
assertVisibilityDisallows("//pkg:foo_inner_wants_vis_to_outer", "//common:vis_to_outer");
assertVisibilityDisallows("//pkg:foo_wants_vis_to_inner", "//common:vis_to_inner");
assertVisibilityPermits("//pkg:foo_wants_vis_to_outer", "//common:vis_to_outer");
}
// This is the simplest version of the caller delegating visibility privileges to a macro.
// Subsequent test cases don't bother to test opting out of select() promotion by setting
// configurable=False.
@Test
@TestParameters({"{depIsConfigurable: false}", "{depIsConfigurable: true}"})
public void buildFileCanDelegateVisibilityPrivilegesToMacro(boolean depIsConfigurable)
throws Exception {
defineSimpleRule();
defineCommonPackageWithSimpleTargets("vis_to_pkg", "['//pkg:__pkg__']");
scratch.file("macros/BUILD");
scratch.file(
"macros/simple_rule_wrapper.bzl",
String.format(
"""
load("//rules:simple_rule.bzl", "simple_rule")
def _impl(name, visibility, dep):
simple_rule(
name = name,
dep = dep,
)
simple_rule_wrapper = macro(
implementation = _impl,
attrs = {"dep": attr.label(configurable=%s)},
)
""",
depIsConfigurable ? "True" : "False"));
scratch.file(
"pkg/BUILD",
"""
load("//macros:simple_rule_wrapper.bzl", "simple_rule_wrapper")
simple_rule_wrapper(
name = "foo",
dep = "//common:vis_to_pkg",
)
""");
// Allowed, even though //pkg:foo's declaration location is not //pkg, because the macro that
// //pkg:foo is declared in is itself declared in //pkg and passes vis_to_pkg in as an
// attribute.
assertVisibilityPermits("//pkg:foo", "//common:vis_to_pkg");
}
/**
* Defines a macro {@code name} whose body calls symbol {@code wraps} to instantiate its main
* target or submacro, passing along {@code attrExpr}.
*
* <p>The new macro is declared in {@code //<name>:macro.bzl}. It takes an optional {@code dep}
* attribute.
*
* @param name the macro being introduced, e.g. "my_macro", which would be loaded from {@code
* //my_macro:macro.bzl}
* @param wraps the rule or macro to be called by this macro, in {@code bzlLabel%symbol} form,
* e.g. "//some_pkg:defs.bzl%my_rule"
* @param attrExpr an attribute argument expression for the call site of {@code wraps}, e.g.
* "some_attr = dep"
*/
private void defineWrappingMacro(String name, String wraps, String attrExpr) throws Exception {
int i = wraps.indexOf("%");
Preconditions.checkArgument(i != -1);
String bzlToLoad = wraps.substring(0, i);
String symbolName = wraps.substring(i + 1);
scratch.file(String.format("%s/BUILD", name));
scratch.file(
String.format("%s/macro.bzl", name),
String.format(
"""
load("%2$s", "%3$s")
def _impl(name, visibility, dep):
%3$s(
name = name,
%4$s,
)
%1$s = macro(
implementation = _impl,
attrs = {"dep": attr.label(mandatory=False)},
)
""",
name, bzlToLoad, symbolName, attrExpr));
}
/**
* Defines a macro {@code name} whose body calls {@code wraps} to instantiate its main target or
* submacro, forwarding along the {@code dep} attribute (if supplied) unchanged.
*
* <p>The new macro is declared in {@code //<name>:macro.bzl}.
*
* @param name the macro being introduced, e.g. "my_macro", which would be loaded from {@code
* //my_macro:macro.bzl}
* @param wraps the rule or macro to be called by this macro, in {@code bzlLabel%symbol} form,
* e.g. "//some_pkg:defs.bzl%my_rule"
*/
private void defineWrappingMacroWithSameDep(String name, String wraps) throws Exception {
defineWrappingMacro(name, wraps, "dep = dep");
}
/**
* Defines a macro {@code name} whose body calls {@code wraps} to instantiate its main target or
* submacro, passing {@code hardcodedDep} as the value of the {@code dep} attribute.
*
* <p>The new macro is declared in {@code //<name>:macro.bzl}. It takes an optional {@code dep}
* attribute itself, which gets ignored.
*
* @param name the macro being introduced, e.g. "my_macro", which would be loaded from {@code
* //my_macro:macro.bzl}
* @param wraps the rule or macro to be called by this macro, in {@code bzlLabel%symbol} form,
* e.g. "//some_pkg:defs.bzl%my_rule"
* @param hardcodedDep a label string to pass as {@code dep = <hardcodedDep>} at the call site of
* {@code wraps}, e.g. "//some_pkg:my_target"
*/
private void defineWrappingMacroWithHardcodedDep(String name, String wraps, String hardcodedDep)
throws Exception {
defineWrappingMacro(name, wraps, String.format("dep = \"%s\"", hardcodedDep));
}
@Test
public void outerMacroCanDelegateVisibilityPrivilegesToSubmacro() throws Exception {
defineSimpleRule();
defineCommonPackageWithSimpleTargets("vis_to_outer", "['//outer:__pkg__']");
defineWrappingMacroWithSameDep("inner", "//rules:simple_rule.bzl%simple_rule");
defineWrappingMacroWithHardcodedDep(
"outer", "//inner:macro.bzl%inner", "//common:vis_to_outer");
scratch.file(
"pkg/BUILD",
"""
load("//outer:macro.bzl", "outer")
outer(name = "foo")
""");
// Allowed by same reasoning as buildFileCanDelegateVisibilityPrivilegesToMacro(), but with the
// privilege originating from an outer macro rather than from the BUILD file.
assertVisibilityPermits("//pkg:foo", "//common:vis_to_outer");
}
@Test
public void delegationCanBeTransitiveThroughAnIntermediaryMacro() throws Exception {
defineSimpleRule();
defineCommonPackageWithSimpleTargets("vis_to_pkg", "['//pkg:__pkg__']");
defineWrappingMacroWithSameDep("inner", "//rules:simple_rule.bzl%simple_rule");
defineWrappingMacroWithSameDep("outer", "//inner:macro.bzl%inner");
scratch.file(
"pkg/BUILD",
"""
load("//outer:macro.bzl", "outer")
outer(
name = "foo",
dep = "//common:vis_to_pkg",
)
""");
// Allowed because the package delegates its privilege to the outer macro, which in turn
// delegates its privilege to the inner macro.
assertVisibilityPermits("//pkg:foo", "//common:vis_to_pkg");
}
@Test
public void callerPermissionIsCheckedEvenIfCalleeHasItsOwnPermission() throws Exception {
defineSimpleRule();
defineCommonPackageWithSimpleTargets("vis_to_macro", "['//macros:__pkg__']");
defineWrappingMacroWithSameDep("my_macro", "//rules:simple_rule.bzl%simple_rule");
scratch.file(
"pkg/BUILD",
"""
load("//my_macro:macro.bzl", "my_macro")
my_macro(
name = "foo",
dep = "//common:vis_to_macro",
)
""");
// Even though the macro has visibility on the dep, we still fail because the caller tried to
// pass it in and the caller has no such permission.
assertVisibilityDisallows("//pkg:foo", "//common:vis_to_macro");
}
@Test
public void noDelegationIfCallerCannotSeeDep() throws Exception {
defineSimpleRule();
defineCommonPackageWithSimpleTargets("not_visible", "[]");
defineWrappingMacroWithSameDep("my_macro", "//rules:simple_rule.bzl%simple_rule");
scratch.file(
"pkg/BUILD",
"""
load("//my_macro:macro.bzl", "my_macro")
my_macro(
name = "foo",
dep = "//common:not_visible",
)
""");
// Passed in from caller, but caller had no permission to delegate.
assertVisibilityDisallows("//pkg:foo", "//common:not_visible");
}
@Test
public void noDelegationIfCallerDoesNotPassInTarget() throws Exception {
defineSimpleRule();
defineCommonPackageWithSimpleTargets(
// Read: "v2[P|M]" -> "visible to [package|macro]"
"v2P_hardcoded",
"['//pkg:__pkg__']",
"v2P_implicitdep",
"['//pkg:__pkg__']",
"v2M_hardcoded",
"['//macros:__pkg__']",
"v2M_implicitdep",
"['//macros:__pkg__']");
scratch.file("macros/BUILD");
scratch.file(
"macros/my_macro.bzl",
"""
load("//rules:simple_rule.bzl", "simple_rule")
def _impl(name, visibility, _v2P_implicitdep, _v2M_implicitdep):
simple_rule(
name = name + "_consumes_v2P_hardcoded",
dep = "//common:v2P_hardcoded",
)
simple_rule(
name = name + "_consumes_v2P_implicitdep",
dep = _v2P_implicitdep,
)
simple_rule(
name = name + "_consumes_v2M_hardcoded",
dep = "//common:v2M_hardcoded",
)
simple_rule(
name = name + "_consumes_v2M_implicitdep",
dep = _v2M_implicitdep,
)
my_macro = macro(
implementation = _impl,
attrs = {
"_v2P_implicitdep": attr.label(
default="//common:v2P_implicitdep"),
"_v2M_implicitdep": attr.label(
default="//common:v2M_implicitdep"),
},
)
""");
scratch.file(
"pkg/BUILD",
"""
load("//macros:my_macro.bzl", "my_macro")
my_macro(name = "foo")
""");
// Macro can't see it. Caller didn't pass it in, so caller's permission is not delegated to the
// macro.
assertVisibilityDisallows("//pkg:foo_consumes_v2P_hardcoded", "//common:v2P_hardcoded");
// Having an implicit dep doesn't count as the caller passing it in.
assertVisibilityDisallows("//pkg:foo_consumes_v2P_implicitdep", "//common:v2P_implicitdep");
// Macro can see it. Caller didn't pass it in, so it's fine that the caller can't see it.
assertVisibilityPermits("//pkg:foo_consumes_v2M_hardcoded", "//common:v2M_hardcoded");
// Having an implicit dep doesn't count as the caller passing it in.
assertVisibilityPermits("//pkg:foo_consumes_v2M_implicitdep", "//common:v2M_implicitdep");
// Theoretically another behavior we could try to demonstrate might be that having a label in
// the "visibility" attribute does not qualify as the caller passing it in. However, the only
// non-synthetic labels that can appear in visibility are package_groups or rules that imitate
// them by providing PackageSpecificationProvider. package_groups always have public visibility,
// so we can't manufacture a situation demonstrating that delegation has failed unless we make
// an alternate rule class for that purpose, which doens't seem worth it. See also
// SymbolicMacroTest#labelVisitation which covers this case at a lower level.
}
@Test
public void delegationCannotSkipOverAGap() throws Exception {
// We have a chain of macro calls A -> B -> C -> D, with a target in D consuming a dep passed in
// by A. But the dep is not actually threaded all the way through; C hardcodes it, instead of
// taking it as an argument from B.
//
// In this case, when validating the dep's visibility w.r.t. D, we don't consider A at all, even
// though both A and D participate in passing or receiving this dep, respectively. In
// particular, 1) we don't get the benefit of A's privileges passed on to us, and 2) if A is
// violating the dep's visibility, we don't actually enforce that check (because macro
// visibility violations are only discovered when actually relying on them for visibility
// delegation).
defineSimpleRule();
defineCommonPackageWithSimpleTargets(
"some_dep", "['//A:__pkg__']", "irrelevant", "['//B:__pkg__']");
// D calls simple_rule with args unchanged.
defineWrappingMacroWithSameDep("D", "//rules:simple_rule.bzl%simple_rule");
// C calls D with hardcoded dep = "//common:some_dep".
defineWrappingMacroWithHardcodedDep("C", "//D:macro.bzl%D", "//common:some_dep");
// B calls C with a different hardcoded dep = "//common:irrelevant"
defineWrappingMacroWithHardcodedDep("B", "//C:macro.bzl%C", "//common:irrelevant");
// A calls B with hardcoded dep = "//common:some_dep".
defineWrappingMacroWithHardcodedDep("A", "//B:macro.bzl%B", "//common:some_dep");
scratch.file(
"pkg/BUILD",
"""
load("//A:macro.bzl", "A")
A(name = "foo")
""");
// No privileges delegated through gap.
assertVisibilityDisallows("//pkg:foo", "//common:some_dep");
// Let's retry it with the visibility rewritten to allow C, but not A.
scratch.deleteFile("common/BUILD");
defineCommonPackageWithSimpleTargets("some_dep", "['//C:__pkg__']");
invalidatePackages(); // to pick up the changes to common/BUILD
eventCollector.clear(); // to avoid the assertion matching events emitted above
// Now D has its own privileges, and we *don't* discover that A is illegally referring to it.
assertVisibilityPermits("//pkg:foo", "//common:some_dep");
}
@Test
public void delegationDoesNotFollowAliases() throws Exception {
// The whole visibility system considers alias targets to have distinct permissions from the
// underlying target they refer to. So check that these are separate entities for the purpose
// of a caller delegating permission to a macro.
defineSimpleRule();
scratch.file(
"common/BUILD",
"""
load("//rules:simple_rule.bzl", "simple_rule")
simple_rule(
name = "vis_to_pkg",
visibility = ["//pkg:__pkg__"],
)
alias(
name = "aliasof_vis_to_pkg",
actual = ":vis_to_pkg",
)
""");
defineWrappingMacroWithHardcodedDep(
"my_macro", "//rules:simple_rule.bzl%simple_rule", "//common:vis_to_pkg");
scratch.file(
"pkg/BUILD",
"""
load("//my_macro:macro.bzl", "my_macro")
my_macro(
name = "foo",
dep = "//common:aliasof_vis_to_pkg",
)
""");
// Delegation doesn't work, needed the actual but passed the alias.
assertVisibilityDisallows("//pkg:foo", "//common:vis_to_pkg");
// Try it the other way around.
scratch.deleteFile("my_macro/BUILD");
scratch.deleteFile("my_macro/macro.bzl");
defineWrappingMacroWithHardcodedDep(
"my_macro", "//rules:simple_rule.bzl%simple_rule", "//common:aliasof_vis_to_pkg");
scratch.overwriteFile(
"pkg/BUILD",
"""
load("//my_macro:macro.bzl", "my_macro")
my_macro(
name = "foo",
dep = "//common:vis_to_pkg",
)
""");
invalidatePackages(); // to pick up the changes to common/BUILD
eventCollector.clear(); // to avoid the assertion matching events emitted above
// Delegation doesn't work, needed the alias but passed the actual.
assertVisibilityDisallows(
"//pkg:foo",
"alias '//common:aliasof_vis_to_pkg' referring to target '//common:vis_to_pkg'",
/* dependencyIsAlias= */ true);
}
@Test
public void packageDefaultVisibilityDoesNotPropagateToInsideMacro() throws Exception {
defineSimpleRule();
defineWrappingMacroWithSameDep("my_macro", "//rules:simple_rule.bzl%simple_rule");
defineWrappingMacroWithSameDep("inner", "//rules:simple_rule.bzl%simple_rule");
defineWrappingMacroWithSameDep("outer", "//inner:macro.bzl%inner");
scratch.file(
"lib/BUILD",
"""
load("//rules:simple_rule.bzl", "simple_rule")
load("//my_macro:macro.bzl", "my_macro")
load("//outer:macro.bzl", "outer")
package(default_visibility=["//pkg:__pkg__"])
simple_rule(name = "foo")
my_macro(name = "bar")
outer(name = "baz")
""");
scratch.file(
"pkg/BUILD",
"""
load("//rules:simple_rule.bzl", "simple_rule")
simple_rule(
name = "consumes_foo",
dep = "//lib:foo",
)
simple_rule(
name = "consumes_bar",
dep = "//lib:bar",
)
simple_rule(
name = "consumes_baz",
dep = "//lib:baz",
)
""");
// Not in a macro, default_visibility applies.
assertVisibilityPermits("//pkg:consumes_foo", "//lib:foo");
// In a macro, default_visibility does not propagate.
assertVisibilityDisallows("//pkg:consumes_bar", "//lib:bar");
// In more than one macro, default_visibility still does not propagate.
assertVisibilityDisallows("//pkg:consumes_baz", "//lib:baz");
}
/*
* TODO: #19922 - Tests cases to add:
*
* ---- Implicit deps ----
*
* - If a rule's implicit dep is defined in a macro, the check of the rule's def loc against the
* dep takes into account the macro's location. In particular, a rule can see implicit deps
* defined by macros whose defs are in the same package as the rule's def (when the macro is
* called from a totally different package), even if the dep is otherwise private.
*
* - If a macro has an implicit dep, that dep's visibility is checked against the macro def's
* location, not its instantiation location. So pass even when the instance doesn't have
* permission. And fail if the macro def location doesn't have permission, even if the instance
* does have permission (either because of its declaration location or because its caller
* received permission delegated by its own caller via an explicit attr value).
*
* ---- Visibility attr usage ----
*
* - Can compose public and private visibilities with other visibilities via concatenation.
* Visibility attr is normalized. (Unclear whether to apply normalization to targets defined
* outside symbolic macros.)
*
* ---- Accounting for CommonPrerequisiteValidator#isSameLogicalPackage() ----
*
* - Targets of a macro defined in //javatests/foo can see targets defined in //java/foo, even
* when the macro is instantiated in //bar. (Rationale: We just look at the consumer's location,
* irrespective of whether that location comes from being in a macro or a BUILD file.)
*
* - Targets in //javatest/foo cannot automatically see unexported targets created by a symbolic
* macro defined in //java/foo, even when that macro is also instantiated in //java/foo.
* (Rationale: Additional visibilities granted by same-logical-package apply only at the
* top-level, similar to default_visibility. Macros can still propagate the additional
* visibility down to exported targets.)
*
* - A target exported from a macro instantiated in //java/foo can be seen in //javatests/foo,
* even when the macro is defined in //bar. (Rationale: Exported targets behave similarly to
* declaring the target in the BUILD file.)
*/
// TODO: #19922 - Consider any other edge cases regarding exotic dependencies and other
// PrerequisiteValidator code paths, e.g. implicit deps, toolchain resolution, etc.
// TODO: #19922 - Consider correctness for alias targets. Probably just means auditing anywhere we
// use a PackageIdentifier in visibility logic without going through
// AliasProvider.getDependencyLabel().
}