// 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.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType.ABSTRACT; import static com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType.TEST; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.devtools.build.lib.actions.ActionEnvironment; import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoFactory; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.analysis.config.BuildConfiguration.Fragment; import com.google.devtools.build.lib.analysis.config.BuildOptions; import com.google.devtools.build.lib.analysis.config.ComposingRuleTransitionFactory; import com.google.devtools.build.lib.analysis.config.ConfigurationFragmentFactory; import com.google.devtools.build.lib.analysis.config.DefaultsPackage; import com.google.devtools.build.lib.analysis.config.FragmentOptions; import com.google.devtools.build.lib.analysis.config.transitions.PatchTransition; import com.google.devtools.build.lib.analysis.constraints.ConstraintSemantics; import com.google.devtools.build.lib.analysis.skylark.SkylarkModules; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.LabelSyntaxException; import com.google.devtools.build.lib.cmdline.PackageIdentifier; import com.google.devtools.build.lib.events.EventHandler; import com.google.devtools.build.lib.graph.Digraph; import com.google.devtools.build.lib.graph.Node; import com.google.devtools.build.lib.packages.Attribute; import com.google.devtools.build.lib.packages.NativeAspectClass; import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper; import com.google.devtools.build.lib.packages.OutputFile; import com.google.devtools.build.lib.packages.Rule; import com.google.devtools.build.lib.packages.RuleClass; import com.google.devtools.build.lib.packages.RuleClassProvider; import com.google.devtools.build.lib.packages.RuleErrorConsumer; import com.google.devtools.build.lib.packages.RuleTransitionFactory; import com.google.devtools.build.lib.packages.Target; import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.InvocationPolicy; import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData; import com.google.devtools.build.lib.skylarkbuildapi.Bootstrap; import com.google.devtools.build.lib.skylarkinterface.SkylarkInterfaceUtils; import com.google.devtools.build.lib.skylarkinterface.SkylarkModule; import com.google.devtools.build.lib.syntax.Environment; import com.google.devtools.build.lib.syntax.Environment.Extension; import com.google.devtools.build.lib.syntax.Environment.GlobalFrame; import com.google.devtools.build.lib.syntax.Environment.Phase; import com.google.devtools.build.lib.syntax.Mutability; import com.google.devtools.build.lib.syntax.Runtime; import com.google.devtools.build.lib.syntax.SkylarkSemantics; import com.google.devtools.build.lib.syntax.SkylarkUtils; import com.google.devtools.build.lib.syntax.Type; import com.google.devtools.common.options.OptionsClassProvider; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import javax.annotation.Nullable; /** * Knows about every rule Blaze supports and the associated configuration options. * *
This class is initialized on server startup and the set of rules, build info factories * and configuration options is guaranteed not to change over the life time of the Blaze server. */ public class ConfiguredRuleClassProvider implements RuleClassProvider { /** * Custom dependency validation logic. */ public interface PrerequisiteValidator { /** * Checks whether the rule in {@code contextBuilder} is allowed to depend on {@code * prerequisite} through the attribute {@code attribute}. * *
Can be used for enforcing any organization-specific policies about the layout of the
* workspace.
*/
void validate(
RuleContext.Builder contextBuilder,
ConfiguredTargetAndData prerequisite,
Attribute attribute);
}
/** Validator to check for and warn on the deprecation of dependencies. */
public static final class DeprecationValidator implements PrerequisiteValidator {
/** Checks if the given prerequisite is deprecated and prints a warning if so. */
@Override
public void validate(
RuleContext.Builder contextBuilder,
ConfiguredTargetAndData prerequisite,
Attribute attribute) {
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.
*/
public static boolean isSameLogicalPackage(
PackageIdentifier thisPackage, PackageIdentifier prerequisitePackage) {
if (thisPackage.equals(prerequisitePackage)) {
// If the packages are equal, they are the same logical package (and just the same package).
return true;
}
if (!thisPackage.getRepository().equals(prerequisitePackage.getRepository())) {
// If the packages are in different repositories, they are not the same logical package.
return false;
}
// If the packages are in the same repository, it's allowed iff this package is the javatests
// companion to the prerequisite java package.
String thisPackagePath = thisPackage.getPackageFragment().getPathString();
String prerequisitePackagePath = prerequisitePackage.getPackageFragment().getPathString();
return thisPackagePath.startsWith("javatests/")
&& prerequisitePackagePath.startsWith("java/")
&& thisPackagePath.substring("javatests/".length()).equals(
prerequisitePackagePath.substring("java/".length()));
}
/** Returns whether a deprecation warning should be printed for the prerequisite described. */
private static boolean shouldEmitDeprecationWarningFor(
String thisDeprecation, PackageIdentifier thisPackage,
String prerequisiteDeprecation, PackageIdentifier prerequisitePackage,
boolean forAspect) {
// Don't report deprecation edges from javatests to java or within a package;
// otherwise tests of deprecated code generate nuisance warnings.
// Don't report deprecation if the current target is also deprecated,
// or if the current context is evaluating an aspect,
// as the base target would have already printed the deprecation warnings.
return (!forAspect
&& prerequisiteDeprecation != null
&& !isSameLogicalPackage(thisPackage, prerequisitePackage)
&& thisDeprecation == null);
}
/** Checks if the given prerequisite is deprecated and prints a warning if so. */
public static void validateDirectPrerequisiteForDeprecation(
RuleErrorConsumer errors,
Rule rule,
ConfiguredTargetAndData prerequisite,
boolean forAspect) {
Target prerequisiteTarget = prerequisite.getTarget();
Label prerequisiteLabel = prerequisiteTarget.getLabel();
PackageIdentifier thatPackage = prerequisiteLabel.getPackageIdentifier();
PackageIdentifier thisPackage = rule.getLabel().getPackageIdentifier();
if (prerequisiteTarget instanceof Rule) {
Rule prerequisiteRule = (Rule) prerequisiteTarget;
String thisDeprecation =
NonconfigurableAttributeMapper.of(rule).has("deprecation", Type.STRING)
? NonconfigurableAttributeMapper.of(rule).get("deprecation", Type.STRING)
: null;
String thatDeprecation =
NonconfigurableAttributeMapper.of(prerequisiteRule).has("deprecation", Type.STRING)
? NonconfigurableAttributeMapper.of(prerequisiteRule)
.get("deprecation", Type.STRING)
: null;
if (shouldEmitDeprecationWarningFor(
thisDeprecation, thisPackage, thatDeprecation, thatPackage, forAspect)) {
errors.ruleWarning("target '" + rule.getLabel() + "' depends on deprecated target '"
+ prerequisiteLabel + "': " + thatDeprecation);
}
}
if (prerequisiteTarget instanceof OutputFile) {
Rule generatingRule = ((OutputFile) prerequisiteTarget).getGeneratingRule();
String thisDeprecation =
NonconfigurableAttributeMapper.of(rule).get("deprecation", Type.STRING);
String thatDeprecation =
NonconfigurableAttributeMapper.of(generatingRule).get("deprecation", Type.STRING);
if (shouldEmitDeprecationWarningFor(
thisDeprecation, thisPackage, thatDeprecation, thatPackage, forAspect)) {
errors.ruleWarning("target '" + rule.getLabel() + "' depends on the output file "
+ prerequisiteLabel + " of a deprecated rule " + generatingRule.getLabel()
+ "': " + thatDeprecation);
}
}
}
}
/**
* A coherent set of options, fragments, aspects and rules; each of these may declare a dependency
* on other such sets.
*/
public interface RuleSet {
/** Add stuff to the configured rule class provider builder. */
void init(ConfiguredRuleClassProvider.Builder builder);
/** List of required modules. */
ImmutableList Note that configuration fragments annotated with a Skylark name must have a unique
* name; no two different configuration fragments can share the same name.
*/
public Builder addConfig(
Class extends FragmentOptions> options, ConfigurationFragmentFactory factory) {
// Enforce that the factory requires the options.
Preconditions.checkState(factory.requiredOptions().contains(options));
this.configurationOptions.add(options);
this.configurationFragmentFactories.add(factory);
return this;
}
public Builder addConfigurationOptions(
Collection Note that configuration fragments annotated with a Skylark name must have a unique
* name; no two different configuration fragments can share the same name.
*/
public Builder addConfigurationFragment(ConfigurationFragmentFactory factory) {
configurationFragmentFactories.add(factory);
return this;
}
public Builder addUniversalConfigurationFragment(
Class extends BuildConfiguration.Fragment> fragment) {
this.universalFragments.add(fragment);
return this;
}
public Builder addSkylarkBootstrap(Bootstrap bootstrap) {
this.skylarkBootstraps.add(bootstrap);
return this;
}
public Builder addSkylarkAccessibleTopLevels(String name, Object object) {
this.skylarkAccessibleTopLevels.put(name, object);
return this;
}
public Builder addSkylarkModule(Class>... modules) {
this.skylarkModules.add(modules);
return this;
}
public Builder addReservedActionMnemonic(String mnemonic) {
this.reservedActionMnemonics.add(mnemonic);
return this;
}
public Builder setActionEnvironmentProvider(
BuildConfiguration.ActionEnvironmentProvider actionEnvironmentProvider) {
this.actionEnvironmentProvider = actionEnvironmentProvider;
return this;
}
/**
* Sets the logic that lets rules declare which environments they support and validates rules
* don't depend on rules that aren't compatible with the same environments. Defaults to
* {@ConstraintSemantics}. See {@ConstraintSemantics} for more details.
*/
public Builder setConstraintSemantics(ConstraintSemantics constraintSemantics) {
this.constraintSemantics = constraintSemantics;
return this;
}
/**
* Sets the C++ LIPO data transition, as defined in {@link
* com.google.devtools.build.lib.rules.cpp.transitions.DisableLipoTransition}.
*
* This is language-specific, so doesn't really belong here. But since non-C++ rules declare
* this transition, we need universal access to it. The need for this interface should go away
* on the deprecation of LIPO for
* ThinLTO.
*/
public Builder setLipoDataTransition(PatchTransition transition) {
Preconditions.checkState(lipoDataTransition == null, "LIPO data transition already set");
lipoDataTransition = Preconditions.checkNotNull(transition);
return this;
}
/**
* Adds a transition factory that produces a trimming transition to be run over all targets
* after other transitions.
*
* Transitions are run in the order they're added.
*
* This is a temporary measure for supporting trimming of test rules and manual trimming of
* feature flags, and support for this transition factory will likely be removed at some point
* in the future (whenever automatic trimming is sufficiently workable).
*/
public Builder addTrimmingTransitionFactory(RuleTransitionFactory factory) {
if (trimmingTransitionFactory == null) {
trimmingTransitionFactory = Preconditions.checkNotNull(factory);
} else {
trimmingTransitionFactory = new ComposingRuleTransitionFactory(
trimmingTransitionFactory, Preconditions.checkNotNull(factory));
}
return this;
}
/**
* Overrides the transition factory run over all targets.
*
* @see #setTrimmingTransitionFactory(RuleTransitionFactory)
*/
@VisibleForTesting(/* for testing trimming transition factories without relying on prod use */)
public Builder overrideTrimmingTransitionFactoryForTesting(RuleTransitionFactory factory) {
trimmingTransitionFactory = null;
return this.addTrimmingTransitionFactory(factory);
}
@Override
public PatchTransition getLipoDataTransition() {
Preconditions.checkState(lipoDataTransition != null);
return lipoDataTransition;
}
private RuleConfiguredTargetFactory createFactory(
Class extends RuleConfiguredTargetFactory> factoryClass) {
try {
Constructor extends RuleConfiguredTargetFactory> ctor = factoryClass.getConstructor();
return ctor.newInstance();
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException
| InvocationTargetException e) {
throw new IllegalStateException(e);
}
}
private RuleClass commitRuleDefinition(Class extends RuleDefinition> definitionClass) {
RuleDefinition instance = checkNotNull(ruleDefinitionMap.get(definitionClass.getName()),
"addRuleDefinition(new %s()) should be called before build()", definitionClass.getName());
RuleDefinition.Metadata metadata = instance.getMetadata();
checkArgument(
ruleClassMap.get(metadata.name()) == null,
"The rule " + metadata.name() + " was committed already, use another name");
List This is language-specific, so doesn't really belong here. But since non-C++ rules declare
* this transition, we need universal access to it. The need for this interface should go away on
* the deprecation of LIPO for ThinLTO.
*/
public PatchTransition getLipoDataTransition() {
return lipoDataTransition;
}
/**
* Returns the transition factory used to produce the transition to trim targets.
*
* This is a temporary measure for supporting manual trimming of feature flags, and support
* for this transition factory will likely be removed at some point in the future (whenever
* automatic trimming is sufficiently workable
*/
@Nullable
public RuleTransitionFactory getTrimmingTransitionFactory() {
return trimmingTransitionFactory;
}
/**
* Returns the set of configuration options that are supported in this module.
*/
public ImmutableList