blob: 711d7f56e78a3f73e3c29c248bf367415db51688 [file] [log] [blame] [edit]
// Copyright 2014 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.devtools.build.lib.packages.NonconfigurableAttributeMapper.attributeOrNull;
import com.google.devtools.build.lib.analysis.AliasProvider.TargetMode;
import com.google.devtools.build.lib.analysis.RuleContext.PrerequisiteValidator;
import com.google.devtools.build.lib.analysis.configuredtargets.PackageGroupConfiguredTarget;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.packages.Aspect;
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.FunctionSplitTransitionAllowlist;
import com.google.devtools.build.lib.packages.InputFile;
import com.google.devtools.build.lib.packages.MacroInstance;
import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper;
import com.google.devtools.build.lib.packages.PackageSpecification.PackageGroupContents;
import com.google.devtools.build.lib.packages.RawAttributeMapper;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.RuleClass;
import com.google.devtools.build.lib.packages.RuleOrMacroInstance;
import com.google.devtools.build.lib.packages.StarlarkAspectClass;
import com.google.devtools.build.lib.packages.Type;
import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
/**
* A base implementation of {@link PrerequisiteValidator} that performs common checks based on
* definitions of what is considered the same logical package and what is considered "experimental"
* code, which has may have relaxed checks for visibility and deprecation.
*/
public abstract class CommonPrerequisiteValidator implements PrerequisiteValidator {
@Override
public void validate(
RuleContext.Builder contextBuilder,
ConfiguredTargetAndData prerequisite,
Attribute attribute) {
validateDirectPrerequisiteLocation(contextBuilder, prerequisite);
checkForMisplacedPackageGroups(contextBuilder, prerequisite, attribute);
validateDirectPrerequisiteVisibility(contextBuilder, prerequisite, attribute);
validateDirectPrerequisiteForTestOnly(contextBuilder, prerequisite);
validateDirectPrerequisiteForDeprecation(
contextBuilder, contextBuilder.getRule(), prerequisite, contextBuilder.forAspect());
}
/**
* Returns whether two packages are considered the same for purposes of deprecation warnings.
* Dependencies within the same package do not print deprecation warnings; a package in the
* javatests directory may also depend on its corresponding java package without a warning.
*/
// TODO: #19922 - Rename this method to not imply that it is symmetric across its arguments.
protected abstract boolean isSameLogicalPackage(
PackageIdentifier thisPackage, PackageIdentifier prerequisitePackage);
protected abstract boolean checkVisibilityForExperimental(RuleContext.Builder context);
protected abstract boolean checkVisibilityForToolchains(
RuleContext.Builder context, Label prerequisite);
protected abstract boolean allowExperimentalDeps(RuleContext.Builder context);
/**
* Encapsulates the state of the visibility check for a single dependency edge.
*
* <p>This makes it easier to retain intermediate information for detailed diagnostics.
*
* <p>Throughout, if this edge is for an implicit dep of a rule or aspect, we call the latter the
* "owning rule" or "owning aspect" respectively. Normal edges have no owner in this sense.
*/
private static class VisibilityCheckState {
/** Dependency target. */
ConfiguredTargetAndData prerequisite;
/** Consuming target. */
Rule consumer;
/** The rule that this edge is an implicit dep of (if applicable). */
@Nullable RuleClass owningRule;
/**
* The aspect that this edge is an implicit dep of (if applicable).
*
* <p>(Mutually exclusive with {@code owningRule}.)
*/
@Nullable StarlarkAspectClass owningAspect;
/**
* A series of macros, innermost first, that the prerequisite was passed to.
*
* <p>Empty if no delegation occurs. Otherwise, the first entry is the macro that declared the
* consumer, and the last entry is the macro whose declaration location is tested against the
* prerequisite's visibility.
*/
final ArrayList<MacroInstance> delegatedThrough = new ArrayList<>();
boolean verboseVisibilityErrors = false;
/** Whether this edge is for an implicit dep. */
boolean isImplicitDep() {
return owningRule != null || owningAspect != null;
}
/** The type of owner (if applicable). */
@Nullable
String getOwnerKind() {
return owningRule != null ? "rule" : owningAspect != null ? "aspect" : null;
}
/**
* The exported identifier of the owning rule or aspect (if applicable), e.g. {@code "my_rule"}.
*/
@Nullable
String getOwnerName() {
return owningRule != null
? owningRule.getName()
: owningAspect != null ? owningAspect.getExportedName() : null;
}
/** The .bzl of the owning rule or aspect (if applicable). */
@Nullable
Label getOwnerDefinitionBzl() {
return owningRule != null
? owningRule.getRuleDefinitionEnvironmentLabel()
: owningAspect != null ? owningAspect.getExtensionLabel() : null;
}
/** The definition location of the owning rule or aspect (if applicable). */
@Nullable
PackageIdentifier getOwnerDefinitionLocation() {
Label bzlLabel = getOwnerDefinitionBzl();
return bzlLabel != null ? bzlLabel.getPackageIdentifier() : null;
}
}
private void validateDirectPrerequisiteVisibility(
RuleContext.Builder context, ConfiguredTargetAndData prerequisite, Attribute attribute) {
String attrName = attribute.getName();
Rule rule = context.getRule();
if (!context.getConfiguration().checkVisibility()) {
return;
}
// We don't check the visibility of late-bound attributes, because it would break some
// features.
if (Attribute.isAnalysisDependent(attrName)) {
return;
}
// Determine whether we should check toolchain target visibility.
if (attrName.equals(RuleContext.TOOLCHAIN_ATTR_NAME)
&& !checkVisibilityForToolchains(context, prerequisite.getTargetLabel())) {
return;
}
// Only verify visibility of implicit dependencies of the current aspect.
// Dependencies of other aspects as well as the rule itself are checked when they are
// evaluated.
Aspect mainAspect = context.getMainAspect();
if (mainAspect != null) {
if (!attribute.isImplicit()
|| !mainAspect.getDefinition().getAttributes().containsKey(attrName)) {
return;
}
}
boolean checkExperimental = checkVisibilityForExperimental(context);
// Normally visibility is validated with respect to the location of the consuming target. But
// implicit attributes of Starlark-defined rules and aspects get validated primarily with
// respect to the .bzl where the rule or aspect is exported, with the location of the target
// serving only as a fallback for backwards compatibility purposes.
//
// (We don't do the same for default values of non-implicit attributes. That would introduce a
// semantic difference between omitting the attribute (allowing it to be populated by default),
// vs. explicitly passing in a value that happens to be the same as its default.)
boolean isImplicitDep = attribute.isImplicit() && context.isStarlarkRuleOrAspect();
// Also, the special $config_dependencies attribute is always validated as a normal dependency
// even though it's technically implicit.
isImplicitDep &= !attribute.getName().equals(RuleClass.CONFIG_SETTING_DEPS_ATTRIBUTE);
VisibilityCheckState state = new VisibilityCheckState();
state.prerequisite = prerequisite;
state.consumer = rule;
state.verboseVisibilityErrors = context.getConfiguration().verboseVisibilityErrors();
if (isImplicitDep) {
// Populate the state with the relevant rule or aspect's info.
if (mainAspect != null) {
state.owningAspect = ((StarlarkAspectClass) mainAspect.getAspectClass());
} else {
state.owningRule = rule.getRuleClassObject();
}
if (!isVisibleToLocation(
prerequisite, state.getOwnerDefinitionLocation(), checkExperimental)) {
// Failed. Validate with respect to the target anyway, for backwards compatibility.
// TODO(bazel-team): When can this fallback be removed?
if (!isVisibleToConsumer(prerequisite, rule, checkExperimental, state.delegatedThrough)) {
// True failure. In the error message, always suggest making the prerequisite visible from
// the definition, not the target.
context.ruleError(generateVisibilityConflictMessage(state));
}
}
} else {
// Normal case: Validate with respect to the target, only.
if (!isVisibleToConsumer(prerequisite, rule, checkExperimental, state.delegatedThrough)) {
context.ruleError(generateVisibilityConflictMessage(state));
}
}
}
/**
* Returns whether {@code prerequisite} is visible to {@code consumer}, which can be either a
* {@link Rule} target or a {@link MacroInstance}.
*
* <p>In general, this passes if {@code consumer}'s declaration location is allowed by {@code
* prerequisite}'s visibility provider or the same-logical-package condition.
*
* <p>In this context, the "declaration location" of a target means the package containing the
* defining bzl (i.e. export label) of the symbolic macro that directly declares the target; or
* the target's package if it was not declared within any symbolic macro. Likewise for a macro
* instance.
*
* <p>As a special case, if {@code consumer} was directly created by a symbolic macro that takes
* in the {@code prerequisite}'s label (not following {@code alias}es), then before running the
* above logic we first substitute the symbolic macro for {@code consumer}. This reflects how the
* usage of the prerequisite was not really by the given declaration but rather its parent. In
* this case, the symbolic macro is appended to {@code delegatedThrough}. This process can repeat
* recursively.
*/
private boolean isVisibleToConsumer(
ConfiguredTargetAndData prerequisite,
RuleOrMacroInstance consumer,
boolean checkExperimental,
List<MacroInstance> delegatedThrough) {
@Nullable MacroInstance declaringMacro = consumer.getDeclaringMacro();
// Visibility delegation: If we're directly declared by a macro that took this prereq as an
// argument from its own caller, then our location is moot, and it's the macro's usage that we
// have to validate instead.
if (declaringMacro != null) {
// Don't conflate an alias with its target.
Label prereqLabel = AliasProvider.getDependencyLabel(prerequisite.getConfiguredTarget());
boolean[] declaringMacroWasGivenPrereqByCaller = {false};
declaringMacro.visitExplicitAttributeLabels(
label -> declaringMacroWasGivenPrereqByCaller[0] |= label.equals(prereqLabel));
if (declaringMacroWasGivenPrereqByCaller[0]) {
delegatedThrough.add(declaringMacro);
return isVisibleToConsumer(
prerequisite, declaringMacro, checkExperimental, delegatedThrough);
}
}
// No delegation. Check visibility on our own merits.
PackageIdentifier packageOfConsumer = consumer.getPackageMetadata().packageIdentifier();
// Finalizers, in addition to their normal visibility privileges, also get the privileges of the
// BUILD file of the package they live in.
if (declaringMacro != null && declaringMacro.getMacroClass().isFinalizer()) {
if (isVisibleToLocation(prerequisite, packageOfConsumer, checkExperimental)) {
return true;
}
}
PackageIdentifier declaringLocation =
declaringMacro != null ? declaringMacro.getDefinitionPackage() : packageOfConsumer;
return isVisibleToLocation(prerequisite, declaringLocation, checkExperimental);
}
/**
* Returns whether {@code prerequisite} is visible to {@code location}, based on {@code
* prerequisite}'s visibility provider and the same-logical-package condition.
*/
private boolean isVisibleToLocation(
ConfiguredTargetAndData prerequisite, PackageIdentifier location, boolean checkExperimental) {
if (packageUnderExperimental(location) && !checkExperimental) {
return true;
}
VisibilityProvider visibility =
prerequisite.getConfiguredTarget().getProvider(VisibilityProvider.class);
// For prerequisite targets that are created in symbolic macros, the visibility provider is
// authoritative and we can move on to checking its package specifications one by one.
//
// For prerequisite targets that are *not* created in symbolic macros, the visibility provider
// does not necessarily list the target's own declaration location (which is the same as the
// package it lives in). In addition, the target should be visible to other packages that are
// same-logical-package as this location, a property that doesn't apply to targets created in
// symbolic macros. Calling isSameLogicalPackage() takes care of both of these checks. Note that
// we don't need to worry about the package's default_visibility at this stage because
// it is already accounted for at loading time by the target's getVisibility() accessor.
//
// TODO: #19922 - The same-logical-package logic should also be applied in the loading phase, to
// the propagated visibility attribute inside symbolic macros, so that it applies to targets
// exported from symbolic macros (i.e. targets that pass `visibility = visibility`).
if (!visibility.isCreatedInSymbolicMacro()) {
if (isSameLogicalPackage(
location,
// In the case of a prerequisite that is an alias rule, we check whether we can see the
// alias itself, not the actual target it points to. In other words, alias re-exports
// targets under its own visibility.
AliasProvider.getDependencyLabel(prerequisite.getConfiguredTarget())
.getPackageIdentifier())) {
return true;
}
}
// Not same-package / same-logical-package. Check the actual visibility contents.
for (PackageGroupContents specification : visibility.getVisibility().toList()) {
if (specification.containsPackage(location)) {
return true;
}
}
return false;
}
/**
* Registers an attribute error if a {@code package_group} target is detected in a context where
* it is not allowed.
*/
private void checkForMisplacedPackageGroups(
RuleContext.Builder context, ConfiguredTargetAndData prerequisite, Attribute attribute) {
String attrName = attribute.getName();
Rule rule = context.getRule();
// TODO(bazel-team): The instanceof check seems pretty suspect, and should maybe be phrased in
// terms of a provider check that would work with the `alias` rule. Then again, the string
// matching on PackageSpecification[Provider|Info] is probably more suspect.
if (prerequisite.getConfiguredTarget().unwrapIfMerged()
instanceof PackageGroupConfiguredTarget) {
Attribute configuredAttribute = RawAttributeMapper.of(rule).getAttributeDefinition(attrName);
if (configuredAttribute == null) { // handles aspects
configuredAttribute = attribute;
}
String description = configuredAttribute.getRequiredProviders().getDescription();
boolean containsPackageSpecificationProvider =
description.contains("PackageSpecificationProvider")
|| description.contains("PackageSpecificationInfo");
// TODO(plf): Add the PackageSpecificationProvider to the 'visibility' attribute.
if (!attrName.equals("visibility")
&& !attrName.equals(FunctionSplitTransitionAllowlist.ATTRIBUTE_NAME)
&& !containsPackageSpecificationProvider) {
context.attributeError(
attrName,
"in "
+ attrName
+ " attribute of "
+ rule.getRuleClass()
+ " rule "
+ rule.getLabel()
+ ": "
+ AliasProvider.describeTargetWithAliases(prerequisite, TargetMode.WITH_KIND)
+ " is misplaced here (they are only allowed in the visibility attribute)");
}
}
}
private String generateVisibilityConflictMessage(VisibilityCheckState state) {
String errorMessage;
if (!state.verboseVisibilityErrors) {
// TODO: https://github.com/bazelbuild/bazel/issues/25941 - Streamline this error message to
// eliminate redundancy, label quoting, newlines, the recommendation, and alias expansion, and
// to include a suggestion to pass --verbose_visibility_errors. Also make it so we don't emit
// "target 'foo.bzl'" when referring to the definition location of an attribute.
Label consumerOrOwnerLocation =
state.isImplicitDep() ? state.getOwnerDefinitionBzl() : state.consumer.getLabel();
errorMessage =
String.format(
"Visibility error:\n"
+ "%s is not visible from\n"
+ "target '%s'\n"
+ "Recommendation: modify the visibility declaration if you think the dependency"
+ " is legitimate. For more info see https://bazel.build/concepts/visibility",
AliasProvider.describeTargetWithAliases(state.prerequisite, TargetMode.WITHOUT_KIND),
consumerOrOwnerLocation.getCanonicalForm());
if (state.prerequisite.getTargetKind().equals(InputFile.targetKind())) {
errorMessage +=
". To set the visibility of that source file target, use the exports_files() function";
}
} else {
String dependencyDesc = state.prerequisite.getTargetLabel().getCanonicalForm();
errorMessage =
String.format(
"dependency on target %s violates its visibility. Additional diagnostics:",
dependencyDesc);
ArrayList<String> bullets = new ArrayList<>();
maybeAddImplicitDepBullet(state, bullets);
addConsumingLocationBullet(state, bullets);
// TODO: https://github.com/bazelbuild/bazel/issues/25933 - Add bullet point for explaining
// the dependency's declared visibility with and without package group expansion. This can be
// pretty spammy. To show this info without package group expansion, we'd also need a way of
// accessing the attribute info from TargetData.
maybeAddAliasDisclaimerBullet(state, bullets);
maybeAddSamePackageDisclaimerBullet(state, bullets);
// TODO: https://github.com/bazelbuild/bazel/issues/25933 - Add bullet point for explaining
// that the dependency could've been exported to make it visible to the consumer. This applies
// when the dependency was declared in a transitive child macro of the consuming location.
// This requires that we know the macro ancestors of the prerequisite object, but that
// probably means adding MacroInstance parentage information to the TargetData interface.
maybeAddMoreDelegationNeededBullet(state, bullets);
addEditVisibilityBullet(state, bullets);
StringBuilder details = new StringBuilder();
for (String bullet : bullets) {
details.append("\n\n * ");
details.append(bullet);
}
errorMessage += details.toString();
}
// TODO: https://github.com/bazelbuild/bazel/issues/25940 - ruleError() prefixes the message
// with a location that is the outermost stack frame of the innermost symbolic macro. Even
// absent any symbolic macros, this is a BUILD file location even though the target declaration
// may be many levels deep. Consider a more principled approach to reporting error locations for
// targets.
return errorMessage;
}
private void maybeAddImplicitDepBullet(VisibilityCheckState state, List<String> bullets) {
if (!state.isImplicitDep()) {
return;
}
bullets.add(
String.format(
"""
The dependency is an implicit dependency of the consuming target's %s, %s, which is \
defined in %s. Since that file's package, %s, does not match the dependency's \
visibility, we are falling back on checking the consuming target itself. The following \
bullet points explain why the consuming target also did not match.\
""",
state.getOwnerKind(),
state.getOwnerName(),
state.getOwnerDefinitionBzl(),
state.getOwnerDefinitionLocation().getCanonicalForm()));
}
private void addConsumingLocationBullet(VisibilityCheckState state, List<String> bullets) {
Rule consumer = state.consumer;
if (state.delegatedThrough.isEmpty()) {
// Simple case, report that we're checking the target's declaration location, which is the
// innermost macro or the BUILD file if not in a macro.
MacroInstance declaringMacro = consumer.getDeclaringMacro();
if (declaringMacro == null) {
bullets.add(
String.format(
"The location being checked is the package where the consuming target lives, %s.",
consumer.getDeclaringPackage().getCanonicalForm()));
} else {
bullets.add(
String.format(
"""
Because the consuming target was declared in the body of the symbolic macro %s \
defined in %s, the location being checked is this file's package, %s.\
""",
declaringMacro.getMacroClass().getName(),
declaringMacro.getMacroClass().getDefiningBzlLabel().getCanonicalForm(),
consumer.getDeclaringPackage().getCanonicalForm()));
}
} else {
// Delegation case. Get the outermost macro the dep was delegated through. The parent of that
// macro, which is either another macro or else the BUILD file, is the location we're
// checking.
MacroInstance outermostDelegated = state.delegatedThrough.getLast();
MacroInstance delegationParent = outermostDelegated.getParent();
String consumingLocation;
if (delegationParent == null) {
consumingLocation =
String.format(
"package %s",
outermostDelegated.getPackageMetadata().packageIdentifier().getCanonicalForm());
} else {
consumingLocation =
String.format(
"the body of the calling macro %s, defined in %s of package %s",
delegationParent.getMacroClass().getName(),
delegationParent.getMacroClass().getDefiningBzlLabel(),
delegationParent.getDefinitionPackage().getCanonicalForm());
}
bullets.add(
String.format(
"""
Because the dependency was%s passed to the consuming target from an attribute of the \
symbolic macro %s, the location being checked is the place where this macro is \
declared: %s.\
""",
state.delegatedThrough.size() > 1 ? " transitively" : "",
outermostDelegated.getLabel(),
consumingLocation));
}
}
private void maybeAddAliasDisclaimerBullet(VisibilityCheckState state, List<String> bullets) {
if (AliasProvider.isAlias(state.prerequisite.getConfiguredTarget())) {
bullets.add(
"""
The dependency is an alias target. Note that it is the visibility of the alias we care \
about, not the visibility of the underlying target it refers to.\
""");
}
}
private void maybeAddSamePackageDisclaimerBullet(
VisibilityCheckState state, List<String> bullets) {
if (state.isImplicitDep()) {
// Don't emit the same-package message for implicit deps. That message is referring to the
// consuming target, but we only checked the consuming target as a fallback after the real
// problem was encountered: that the rule or aspect couldn't see the dep.
return;
}
PackageIdentifier dependencyLocation =
state.prerequisite.getTargetLabel().getPackageIdentifier();
PackageIdentifier consumerLocation = state.consumer.getLabel().getPackageIdentifier();
if (!consumerLocation.equals(dependencyLocation)) {
return;
}
bullets.add(
"""
Although both targets live in the same package, they cannot automatically see each other \
because they are declared by different symbolic macros.\
""");
}
private void maybeAddMoreDelegationNeededBullet(
VisibilityCheckState state, List<String> bullets) {
// Check whether any of the macro ancestors of the consuming location match the dep's
// visibility. If so, additional delegation from that match down to the consuming location
// could've avoided the error.
RuleOrMacroInstance outermostDelegated =
state.delegatedThrough.isEmpty() ? state.consumer : state.delegatedThrough.getLast();
MacroInstance delegationParent = outermostDelegated.getDeclaringMacro();
if (delegationParent == null) {
// The visibility failure occurred at the BUILD file level. There's no one to delegate to us.
return;
}
// Visibility failed at the delegationParent, so start the search one level up.
boolean moreThanOneLevelUp = false;
for (MacroInstance m = delegationParent.getParent(); m != null; m = m.getParent()) {
// TODO: https://github.com/bazelbuild/bazel/issues/25933 - We're using isVisibleToLocation()
// for simplicity. But consider the macro call stack A -> B -> C, where A can see the dep and
// A passes the dep to B but B does not pass the dep to C. We report that A can see it, and
// tell the user maybe A or a transitive child of A (i.e. really B in this case) should've
// passed it on to C. Ideally we should instead use isVisibleToConsumer() and notice that B
// can see the dep thanks to delegation from A, and report specifically that B should've
// passed it on to C.
if (isVisibleToLocation(
state.prerequisite,
m.getDefinitionPackage(),
// Don't make suggestions based on the experimental loophole.
/* checkExperimental= */ true)) {
bullets.add(
String.format(
"""
Although the dependency is not visible to the location being checked, it is \
visible to this location's%s caller, %s, a %s macro defined in %s. (Perhaps %s \
needs to pass in the dependency as an argument?)\
""",
moreThanOneLevelUp ? " transitive" : "",
m.getLabel(),
m.getMacroClass().getName(),
m.getDefinitionPackage().getCanonicalForm(),
moreThanOneLevelUp ? "this caller, or an intermediate caller," : "the caller"));
return;
}
moreThanOneLevelUp = true;
}
// One last check, for whether the BUILD file itself should've delegated.
if (isVisibleToLocation(
state.prerequisite,
state.consumer.getPackageMetadata().packageIdentifier(),
/* checkExperimental= */ true)) {
bullets.add(
String.format(
"""
Although the dependency is not visible to the location being checked, it is visible \
to this location's%s caller, the BUILD file of package %s. (Perhaps %s needs to pass \
in the dependency as an argument?)\
""",
moreThanOneLevelUp ? " transitive" : "",
state.consumer.getPackageMetadata().packageIdentifier().getCanonicalForm(),
moreThanOneLevelUp ? "this caller, or an intermediate caller," : "the caller"));
}
}
private void addEditVisibilityBullet(VisibilityCheckState state, List<String> bullets) {
boolean isSourceFile = state.prerequisite.getTargetKind().equals(InputFile.targetKind());
bullets.add(
String.format(
"""
If you think the dependency%s is legitimate, consider updating its visibility \
declaration%s. For more info see https://bazel.build/concepts/visibility.\
""",
isSourceFile ? " on this source file" : "",
isSourceFile ? " using exports_files()" : ""));
}
private void validateDirectPrerequisiteLocation(
RuleContext.Builder context, ConfiguredTargetAndData prerequisite) {
Rule rule = context.getRule();
Label prerequisiteLabel = prerequisite.getTargetLabel();
if (packageUnderExperimental(prerequisiteLabel.getPackageIdentifier())
&& !packageUnderExperimental(rule.getLabel().getPackageIdentifier())) {
String message =
"non-experimental target '"
+ rule.getLabel()
+ "' depends on experimental target '"
+ prerequisiteLabel
+ "'";
if (allowExperimentalDeps(context)) {
context.ruleWarning(
message + " (ignored due to --experimental_deps_ok;" + " do not submit)");
} else {
context.ruleError(
message
+ " (you may not check in such a dependency,"
+ " though you can test "
+ "against it by passing --experimental_deps_ok)");
}
}
}
/** Checks if the given prerequisite is deprecated and prints a warning if so. */
private void validateDirectPrerequisiteForDeprecation(
RuleErrorConsumer errors,
Rule rule,
ConfiguredTargetAndData prerequisite,
boolean forAspect) {
if (forAspect || attributeOrNull(rule, "deprecation", Type.STRING) != null) {
// No warning for aspects because the base target would already have the warning.
// No warning if the current target is already deprecated.
return;
}
String warning = prerequisite.getDeprecationWarning();
if (warning == null) {
return; // No warning if it's not deprecated.
}
PackageIdentifier thisPackage = rule.getLabel().getPackageIdentifier();
Label prerequisiteLabel = prerequisite.getTargetLabel();
PackageIdentifier thatPackage = prerequisiteLabel.getPackageIdentifier();
// TODO: #19922 - What to do about this check, when one or both targets are in a macro?
if (isSameLogicalPackage(thisPackage, thatPackage)) {
return; // Doesn't report deprecation edges within a package.
}
Label generatingRuleLabel = prerequisite.getGeneratingRuleLabel();
if (generatingRuleLabel != null) {
errors.ruleWarning(
"target '"
+ rule.getLabel()
+ "' depends on the output file "
+ prerequisiteLabel
+ " of a deprecated rule "
+ generatingRuleLabel
+ "': "
+ warning);
} else {
errors.ruleWarning(
"target '"
+ rule.getLabel()
+ "' depends on deprecated target '"
+ prerequisiteLabel
+ "': "
+ warning);
}
}
/** Check that the dependency is not test-only, or the current rule is test-only. */
private void validateDirectPrerequisiteForTestOnly(
RuleContext.Builder context, ConfiguredTargetAndData prerequisite) {
Rule rule = context.getRule();
if (rule.getRuleClassObject().getAdvertisedProviders().canHaveAnyProvider()) {
// testonly-ness will be checked directly between the depender and the target of the alias;
// getTarget() called by the depender will not return the alias rule, but its actual target
return;
}
if (!prerequisite.isTestOnly() || isTestOnlyRule(rule)) {
return;
}
String message;
Label generatingRuleLabel = prerequisite.getGeneratingRuleLabel();
if (generatingRuleLabel == null) {
message =
"non-test target '"
+ rule.getLabel()
+ "' depends on testonly "
+ AliasProvider.describeTargetWithAliases(prerequisite, TargetMode.WITHOUT_KIND)
+ " and doesn't have testonly attribute set";
} else if (context.getConfiguration().checkTestonlyForOutputFiles()) {
message =
"non-test target '"
+ rule.getLabel()
+ "' depends on the output file "
+ AliasProvider.describeTargetWithAliases(prerequisite, TargetMode.WITHOUT_KIND)
+ " of a testonly rule "
+ generatingRuleLabel
+ " and doesn't have testonly attribute set";
} else {
return;
}
PackageIdentifier thisPackage = rule.getLabel().getPackageIdentifier();
if (packageUnderExperimental(thisPackage)) {
context.ruleWarning(message);
} else {
context.ruleError(message);
}
}
private static boolean isTestOnlyRule(Rule rule) {
NonconfigurableAttributeMapper mapper = NonconfigurableAttributeMapper.of(rule);
return mapper.has("testonly", Type.BOOLEAN) && mapper.get("testonly", Type.BOOLEAN);
}
}