// 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 com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableBiMap; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; import com.google.common.collect.ListMultimap; import com.google.common.collect.Multimaps; import com.google.common.collect.Sets; import com.google.devtools.build.lib.actions.Action; import com.google.devtools.build.lib.actions.ActionAnalysisMetadata; import com.google.devtools.build.lib.actions.ActionOwner; import com.google.devtools.build.lib.actions.ActionRegistry; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.ArtifactOwner; import com.google.devtools.build.lib.actions.Root; import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider.PrerequisiteValidator; import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext; import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoFactory.BuildInfoKey; 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.ConfigMatchingProvider; import com.google.devtools.build.lib.analysis.config.FragmentCollection; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.collect.ImmutableSortedKeyListMultimap; import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.packages.Attribute; import com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition; import com.google.devtools.build.lib.packages.Attribute.SplitTransition; import com.google.devtools.build.lib.packages.AttributeMap; import com.google.devtools.build.lib.packages.BuildType; import com.google.devtools.build.lib.packages.ConfigurationFragmentPolicy; import com.google.devtools.build.lib.packages.FileTarget; import com.google.devtools.build.lib.packages.FilesetEntry; import com.google.devtools.build.lib.packages.ImplicitOutputsFunction; import com.google.devtools.build.lib.packages.InputFile; import com.google.devtools.build.lib.packages.OutputFile; import com.google.devtools.build.lib.packages.PackageSpecification; 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.RuleClass.ConfiguredTargetFactory.RuleErrorException; import com.google.devtools.build.lib.packages.RuleErrorConsumer; import com.google.devtools.build.lib.packages.Target; import com.google.devtools.build.lib.packages.TargetUtils; import com.google.devtools.build.lib.rules.AliasProvider; import com.google.devtools.build.lib.rules.fileset.FilesetProvider; import com.google.devtools.build.lib.shell.ShellUtils; import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable; import com.google.devtools.build.lib.syntax.EvalException; import com.google.devtools.build.lib.syntax.Type; import com.google.devtools.build.lib.util.FileTypeSet; import com.google.devtools.build.lib.util.OrderedSetMultimap; import com.google.devtools.build.lib.util.Preconditions; import com.google.devtools.build.lib.util.StringUtil; import com.google.devtools.build.lib.vfs.FileSystemUtils; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; /** * The totality of data available during the analysis of a rule. * *

These objects should not outlast the analysis phase. Do not pass them to {@link Action} * objects or other persistent objects. There are internal tests to ensure that RuleContext objects * are not persisted that check the name of this class, so update those tests if you change this * class's name. * * @see com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory */ public final class RuleContext extends TargetContext implements ActionConstructionContext, ActionRegistry, RuleErrorConsumer { /** * The configured version of FilesetEntry. */ @Immutable public static final class ConfiguredFilesetEntry { private final FilesetEntry entry; private final TransitiveInfoCollection src; private final ImmutableList files; ConfiguredFilesetEntry(FilesetEntry entry, TransitiveInfoCollection src) { this.entry = entry; this.src = src; this.files = null; } ConfiguredFilesetEntry(FilesetEntry entry, ImmutableList files) { this.entry = entry; this.src = null; this.files = files; } public FilesetEntry getEntry() { return entry; } public TransitiveInfoCollection getSrc() { return src; } /** * Targets from FilesetEntry.files, or null if the user omitted it. */ @Nullable public ImmutableList getFiles() { return files; } } private static final String HOST_CONFIGURATION_PROGRESS_TAG = "for host"; private final Rule rule; private final ListMultimap targetMap; private final ListMultimap filesetEntryMap; private final ImmutableMap configConditions; private final AttributeMap attributes; private final ImmutableSet features; private final String ruleClassNameForLogging; private final ImmutableMap aspectAttributes; private final BuildConfiguration hostConfiguration; private final ConfigurationFragmentPolicy configurationFragmentPolicy; private final Class universalFragment; private final ErrorReporter reporter; private final ImmutableBiMap> skylarkProviderRegistry; private ActionOwner actionOwner; /* lazily computed cache for Make variables, computed from the above. See get... method */ private transient ConfigurationMakeVariableContext configurationMakeVariableContext = null; private RuleContext( Builder builder, AttributeMap attributes, ListMultimap targetMap, ListMultimap filesetEntryMap, ImmutableMap configConditions, Class universalFragment, String ruleClassNameForLogging, ImmutableMap aspectAttributes) { super(builder.env, builder.rule, builder.configuration, builder.prerequisiteMap.get(null), builder.visibility); this.rule = builder.rule; this.configurationFragmentPolicy = builder.configurationFragmentPolicy; this.universalFragment = universalFragment; this.targetMap = targetMap; this.filesetEntryMap = filesetEntryMap; this.configConditions = configConditions; this.attributes = attributes; this.features = getEnabledFeatures(); this.ruleClassNameForLogging = ruleClassNameForLogging; this.aspectAttributes = aspectAttributes; this.skylarkProviderRegistry = builder.skylarkProviderRegistry; this.hostConfiguration = builder.hostConfiguration; reporter = builder.reporter; } private ImmutableSet getEnabledFeatures() { Set globallyEnabled = new HashSet<>(); Set globallyDisabled = new HashSet<>(); parseFeatures(getConfiguration().getDefaultFeatures(), globallyEnabled, globallyDisabled); for (ImmutableMap.Entry, Fragment> entry : getConfiguration().getAllFragments().entrySet()) { if (isLegalFragment(entry.getKey())) { globallyEnabled.addAll(entry.getValue().configurationEnabledFeatures(this)); } } Set packageEnabled = new HashSet<>(); Set packageDisabled = new HashSet<>(); parseFeatures(getRule().getPackage().getFeatures(), packageEnabled, packageDisabled); Set packageFeatures = Sets.difference(Sets.union(globallyEnabled, packageEnabled), packageDisabled); Set ruleFeatures = packageFeatures; if (attributes().has("features", Type.STRING_LIST)) { Set ruleEnabled = new HashSet<>(); Set ruleDisabled = new HashSet<>(); parseFeatures(attributes().get("features", Type.STRING_LIST), ruleEnabled, ruleDisabled); ruleFeatures = Sets.difference(Sets.union(packageFeatures, ruleEnabled), ruleDisabled); } return ImmutableSortedSet.copyOf(Sets.difference(ruleFeatures, globallyDisabled)); } private void parseFeatures(Iterable features, Set enabled, Set disabled) { for (String feature : features) { if (feature.startsWith("-")) { disabled.add(feature.substring(1)); } else if (feature.equals("no_layering_check")) { // TODO(bazel-team): Remove once we do not have BUILD files left that contain // 'no_layering_check'. disabled.add(feature.substring(3)); } else { enabled.add(feature); } } } @Override public Rule getRule() { return rule; } /** * Returns a rule class name suitable for log messages, including an aspect name if applicable. */ public String getRuleClassNameForLogging() { return ruleClassNameForLogging; } /** * Returns the workspace name for the rule. */ public String getWorkspaceName() { return rule.getPackage().getWorkspaceName(); } /** * The configuration conditions that trigger this rule's configurable attributes. */ ImmutableMap getConfigConditions() { return configConditions; } /** * Returns the host configuration for this rule. */ public BuildConfiguration getHostConfiguration() { return hostConfiguration; } /** * Attributes from aspects. */ public ImmutableMap getAspectAttributes() { return aspectAttributes; } /** * Accessor for the Rule's attribute values. */ public AttributeMap attributes() { return attributes; } /** * Returns a map that indicates which providers should be exported to skylark under the key * (map key). These provider types will also be exportable by skylark rules under (map key). */ public ImmutableBiMap> getSkylarkProviderRegistry() { return skylarkProviderRegistry; } /** * Returns whether this instance is known to have errors at this point during analysis. Do not * call this method after the initializationHook has returned. */ public boolean hasErrors() { return getAnalysisEnvironment().hasErrors(); } /** * No-op if {@link #hasErrors} is false, throws {@link RuleErrorException} if it is true. * This provides a convenience to early-exit of configured target creation if there are errors. */ public void assertNoErrors() throws RuleErrorException { if (hasErrors()) { throw new RuleErrorException(); } } /** * Returns an immutable map from attribute name to list of configured targets for that attribute. */ public ListMultimap getConfiguredTargetMap() { return targetMap; } /** * Returns an immutable map from attribute name to list of fileset entries. */ public ListMultimap getFilesetEntryMap() { return filesetEntryMap; } @Override public ActionOwner getActionOwner() { if (actionOwner == null) { actionOwner = createActionOwner(rule, getConfiguration()); } return actionOwner; } /** * Returns a configuration fragment for this this target. */ @Nullable public T getFragment(Class fragment, ConfigurationTransition config) { return getFragment(fragment, fragment.getSimpleName(), "", config); } @Nullable protected T getFragment(Class fragment, String name, String additionalErrorMessage, ConfigurationTransition config) { // TODO(bazel-team): The fragments can also be accessed directly through BuildConfiguration. // Can we lock that down somehow? Preconditions.checkArgument(isLegalFragment(fragment, config), "%s has to declare '%s' as a required fragment " + "in %s configuration in order to access it.%s", getRuleClassNameForLogging(), name, FragmentCollection.getConfigurationName(config), additionalErrorMessage); return getConfiguration(config).getFragment(fragment); } @Nullable public T getFragment(Class fragment) { // NONE means target configuration. return getFragment(fragment, ConfigurationTransition.NONE); } @Nullable public Fragment getSkylarkFragment(String name, ConfigurationTransition config) { Class fragmentClass = getConfiguration(config).getSkylarkFragmentByName(name); if (fragmentClass == null) { return null; } return getFragment(fragmentClass, name, String.format( " Please update the '%1$sfragments' argument of the rule definition " + "(for example: %1$sfragments = [\"%2$s\"])", (config == ConfigurationTransition.HOST) ? "host_" : "", name), config); } public ImmutableCollection getSkylarkFragmentNames(ConfigurationTransition config) { return getConfiguration(config).getSkylarkFragmentNames(); } public boolean isLegalFragment( Class fragment, ConfigurationTransition config) { return fragment == universalFragment || configurationFragmentPolicy.isLegalConfigurationFragment(fragment, config); } public boolean isLegalFragment(Class fragment) { // NONE means target configuration. return isLegalFragment(fragment, ConfigurationTransition.NONE); } protected BuildConfiguration getConfiguration(ConfigurationTransition config) { return config.equals(ConfigurationTransition.HOST) ? hostConfiguration : getConfiguration(); } @Override public ArtifactOwner getOwner() { return getAnalysisEnvironment().getOwner(); } public ImmutableList getBuildInfo(BuildInfoKey key) throws InterruptedException { return getAnalysisEnvironment().getBuildInfo(this, key, getConfiguration()); } @VisibleForTesting public static ActionOwner createActionOwner(Rule rule, BuildConfiguration configuration) { return new ActionOwner( rule.getLabel(), rule.getLocation(), configuration.getMnemonic(), rule.getTargetKind(), configuration.checksum(), configuration.isHostConfiguration() ? HOST_CONFIGURATION_PROGRESS_TAG : null); } @Override public void registerAction(ActionAnalysisMetadata... action) { getAnalysisEnvironment().registerAction(action); } /** * Convenience function for subclasses to report non-attribute-specific * errors in the current rule. */ @Override public void ruleError(String message) { reporter.ruleError(message); } /** * Convenience function to report non-attribute-specific errors in the current rule and then * throw a {@link RuleErrorException}, immediately exiting the build invocation. Alternatively, * invoke {@link #ruleError} instead to collect additional error information before ending the * invocation. */ public void throwWithRuleError(String message) throws RuleErrorException { reporter.ruleError(message); throw new RuleErrorException(); } /** * Convenience function for subclasses to report non-attribute-specific * warnings in the current rule. */ @Override public void ruleWarning(String message) { reporter.ruleWarning(message); } /** * Convenience function for subclasses to report attribute-specific errors in * the current rule. * *

If the name of the attribute starts with $ * it is replaced with a string (an implicit dependency). */ @Override public void attributeError(String attrName, String message) { reporter.attributeError(attrName, message); } /** * Convenience function to report attribute-specific errors in the current rule, and then throw a * {@link RuleErrorException}, immediately exiting the build invocation. Alternatively, invoke * {@link #attributeError} instead to collect additional error information before ending the * invocation. * *

If the name of the attribute starts with $ * it is replaced with a string (an implicit dependency). */ public RuleErrorException throwWithAttributeError(String attrName, String message) throws RuleErrorException { reporter.attributeError(attrName, message); throw new RuleErrorException(); } /** * Like attributeError, but does not mark the configured target as errored. * *

If the name of the attribute starts with $ * it is replaced with a string (an implicit dependency). */ @Override public void attributeWarning(String attrName, String message) { reporter.attributeWarning(attrName, message); } /** * Returns an artifact beneath the root of either the "bin" or "genfiles" * tree, whose path is based on the name of this target and the current * configuration. The choice of which tree to use is based on the rule with * which this target (which must be an OutputFile or a Rule) is associated. */ public Artifact createOutputArtifact() { return internalCreateOutputArtifact(getTarget()); } /** * Returns the output artifact of an {@link OutputFile} of this target. * * @see #createOutputArtifact() */ public Artifact createOutputArtifact(OutputFile out) { return internalCreateOutputArtifact(out); } /** * Implementation for {@link #createOutputArtifact()} and * {@link #createOutputArtifact(OutputFile)}. This is private so that * {@link #createOutputArtifact(OutputFile)} can have a more specific * signature. */ private Artifact internalCreateOutputArtifact(Target target) { Preconditions.checkState( target.getLabel().getPackageIdentifier().equals(getLabel().getPackageIdentifier()), "Creating output artifact for target '%s' in different package than the rule '%s' " + "being analyzed", target.getLabel(), getLabel()); Root root = getBinOrGenfilesDirectory(); return getPackageRelativeArtifact(target.getName(), root); } /** * Returns the root of either the "bin" or "genfiles" * tree, based on this target and the current configuration. * The choice of which tree to use is based on the rule with * which this target (which must be an OutputFile or a Rule) is associated. */ public Root getBinOrGenfilesDirectory() { return rule.hasBinaryOutput() ? getBinDirectory() : getGenfilesDirectory(); } /** * Returns the bin directory for this build configuration. */ @SkylarkCallable(name = "bin_dir", structField = true, doc = "The root corresponding to bin directory.") public Root getBinDirectory() { return getConfiguration().getBinDirectory(rule.getRepository()); } /** * Returns the genfiles directory for this build configuration. */ @SkylarkCallable(name = "genfiles_dir", structField = true, doc = "The root corresponding to genfiles directory.") public Root getGenfilesDirectory() { return getConfiguration().getGenfilesDirectory(rule.getRepository()); } /** * Creates an artifact in a directory that is unique to the package that contains the rule, * thus guaranteeing that it never clashes with artifacts created by rules in other packages. */ public Artifact getPackageRelativeArtifact(String relative, Root root) { return getPackageRelativeArtifact(new PathFragment(relative), root); } /** * Creates an artifact in a directory that is unique to the package that contains the rule, thus * guaranteeing that it never clashes with artifacts created by rules in other packages. */ public Artifact getBinArtifact(String relative) { return getBinArtifact(new PathFragment(relative)); } public Artifact getBinArtifact(PathFragment relative) { return getPackageRelativeArtifact( relative, getConfiguration().getBinDirectory(rule.getRepository())); } /** * Creates an artifact in a directory that is unique to the package that contains the rule, thus * guaranteeing that it never clashes with artifacts created by rules in other packages. */ public Artifact getGenfilesArtifact(String relative) { return getGenfilesArtifact(new PathFragment(relative)); } public Artifact getGenfilesArtifact(PathFragment relative) { return getPackageRelativeArtifact( relative, getConfiguration().getGenfilesDirectory(rule.getRepository())); } /** * Returns an artifact that can be an output of shared actions. Only use when there is no other * option. * *

This artifact can be created anywhere in the output tree, which, in addition to making * sharing possible, opens up the possibility of action conflicts and makes it impossible to * infer the label of the rule creating the artifact from the path of the artifact. */ public Artifact getShareableArtifact(PathFragment rootRelativePath, Root root) { return getAnalysisEnvironment().getDerivedArtifact(rootRelativePath, root); } /** * Creates an artifact in a directory that is unique to the package that contains the rule, * thus guaranteeing that it never clashes with artifacts created by rules in other packages. */ public Artifact getPackageRelativeArtifact(PathFragment relative, Root root) { return getDerivedArtifact(getPackageDirectory().getRelative(relative), root); } /** * Returns the root-relative path fragment under which output artifacts of this rule should go. * *

Note that: *

* * Ideally, user-visible artifacts should all have corresponding output file targets, all others * should go into a rule-specific directory. * {@link #getUniqueDirectoryArtifact(String, PathFragment, Root)}) ensures that this is the case. */ public PathFragment getPackageDirectory() { return getLabel().getPackageIdentifier().getSourceRoot(); } /** * Creates an artifact under a given root with the given root-relative path. * *

Verifies that it is in the root-relative directory corresponding to the package of the rule, * thus ensuring that it doesn't clash with other artifacts generated by other rules using this * method. */ @Override public Artifact getDerivedArtifact(PathFragment rootRelativePath, Root root) { Preconditions.checkState(rootRelativePath.startsWith(getPackageDirectory()), "Output artifact '%s' not under package directory '%s' for target '%s'", rootRelativePath, getPackageDirectory(), getLabel()); return getAnalysisEnvironment().getDerivedArtifact(rootRelativePath, root); } /** * Creates a TreeArtifact under a given root with the given root-relative path. * *

Verifies that it is in the root-relative directory corresponding to the package of the rule, * thus ensuring that it doesn't clash with other artifacts generated by other rules using this * method. */ public Artifact getTreeArtifact(PathFragment rootRelativePath, Root root) { Preconditions.checkState(rootRelativePath.startsWith(getPackageDirectory()), "Output artifact '%s' not under package directory '%s' for target '%s'", rootRelativePath, getPackageDirectory(), getLabel()); return getAnalysisEnvironment().getTreeArtifact(rootRelativePath, root); } /** * Creates an artifact in a directory that is unique to the rule, thus guaranteeing that it never * clashes with artifacts created by other rules. */ public Artifact getUniqueDirectoryArtifact( String uniqueDirectory, String relative, Root root) { return getUniqueDirectoryArtifact(uniqueDirectory, new PathFragment(relative), root); } /** * Creates an artifact in a directory that is unique to the rule, thus guaranteeing that it never * clashes with artifacts created by other rules. */ public Artifact getUniqueDirectoryArtifact( String uniqueDirectory, PathFragment relative, Root root) { return getDerivedArtifact(getUniqueDirectory(uniqueDirectory).getRelative(relative), root); } /** * Returns the Attribute associated with this name, if it's a valid attribute for this rule, * or is associated with an attached aspect. Otherwise returns null. */ @Nullable public Attribute getAttribute(String attributeName) { Attribute result = getRule().getAttributeDefinition(attributeName); if (result != null) { return result; } return aspectAttributes.get(attributeName); } /** * Returns the dependencies through a {@code LABEL_DICT_UNARY} attribute as a map from * a string to a {@link TransitiveInfoCollection}. */ public Map getPrerequisiteMap(String attributeName) { Attribute attributeDefinition = getAttribute(attributeName); Preconditions.checkState(attributeDefinition.getType() == BuildType.LABEL_DICT_UNARY); ImmutableMap.Builder result = ImmutableMap.builder(); Map dict = attributes().get(attributeName, BuildType.LABEL_DICT_UNARY); Map labelToDep = new HashMap<>(); for (ConfiguredTarget dep : targetMap.get(attributeName)) { labelToDep.put(dep.getLabel(), dep); } for (Map.Entry entry : dict.entrySet()) { result.put(entry.getKey(), Preconditions.checkNotNull(labelToDep.get(entry.getValue()))); } return result.build(); } /** * Returns the list of transitive info collections that feed into this target through the * specified attribute. Note that you need to specify the correct mode for the attribute, * otherwise an assertion will be raised. */ public List getPrerequisites(String attributeName, Mode mode) { Attribute attributeDefinition = getAttribute(attributeName); if ((mode == Mode.TARGET) && (attributeDefinition.hasSplitConfigurationTransition())) { // TODO(bazel-team): If you request a split-configured attribute in the target configuration, // we return only the list of configured targets for the first architecture; this is for // backwards compatibility with existing code in cases where the call to getPrerequisites is // deeply nested and we can't easily inject the behavior we want. However, we should fix all // such call sites. checkAttribute(attributeName, Mode.SPLIT); Map, ? extends List> map = getSplitPrerequisites(attributeName); return map.isEmpty() ? ImmutableList.of() : map.entrySet().iterator().next().getValue(); } checkAttribute(attributeName, mode); return targetMap.get(attributeName); } /** * Returns the a prerequisites keyed by the CPU of their configurations. * If the split transition is not active (e.g. split() returned an empty * list), the key is an empty Optional. */ public Map, ? extends List> getSplitPrerequisites(String attributeName) { checkAttribute(attributeName, Mode.SPLIT); Attribute attributeDefinition = getAttribute(attributeName); @SuppressWarnings("unchecked") // Attribute.java doesn't have the BuildOptions symbol. SplitTransition transition = (SplitTransition) attributeDefinition.getSplitTransition(rule); List deps = targetMap.get(attributeName); List splitOptions = transition.split(getConfiguration().getOptions()); if (splitOptions.isEmpty()) { // The split transition is not active. Defer the decision on which CPU to use. return ImmutableMap.of(Optional.absent(), deps); } Set cpus = new HashSet<>(); for (BuildOptions options : splitOptions) { // This method should only be called when the split config is enabled on the command line, in // which case this cpu can't be null. cpus.add(options.get(BuildConfiguration.Options.class).cpu); } // Use an ImmutableListMultimap.Builder here to preserve ordering. ImmutableListMultimap.Builder, TransitiveInfoCollection> result = ImmutableListMultimap.builder(); for (TransitiveInfoCollection t : deps) { if (t.getConfiguration() != null) { result.put(Optional.of(t.getConfiguration().getCpu()), t); } else { // Source files don't have a configuration, so we add them to all architecture entries. for (String cpu : cpus) { result.put(Optional.of(cpu), t); } } } return Multimaps.asMap(result.build()); } /** * Returns the specified provider of the prerequisite referenced by the attribute in the * argument. Note that you need to specify the correct mode for the attribute, otherwise an * assertion will be raised. If the attribute is empty of it does not support the specified * provider, returns null. */ public C getPrerequisite( String attributeName, Mode mode, Class provider) { TransitiveInfoCollection prerequisite = getPrerequisite(attributeName, mode); return prerequisite == null ? null : prerequisite.getProvider(provider); } /** * Returns the transitive info collection that feeds into this target through the specified * attribute. Note that you need to specify the correct mode for the attribute, otherwise an * assertion will be raised. Returns null if the attribute is empty. */ public TransitiveInfoCollection getPrerequisite(String attributeName, Mode mode) { checkAttribute(attributeName, mode); List elements = targetMap.get(attributeName); if (elements.size() > 1) { throw new IllegalStateException(getRuleClassNameForLogging() + " attribute " + attributeName + " produces more than one prerequisite"); } return elements.isEmpty() ? null : elements.get(0); } /** * For a given attribute, returns all {@link TransitiveInfoProvider}s provided by targets * of that attribute. Each {@link TransitiveInfoProvider} is keyed by the * {@link BuildConfiguration} under which the provider was created. */ public ImmutableListMultimap getPrerequisitesByConfiguration(String attributeName, Mode mode, final Class classType) { AnalysisUtils.checkProvider(classType); List transitiveInfoCollections = getPrerequisites(attributeName, mode); ImmutableListMultimap.Builder result = ImmutableListMultimap.builder(); for (TransitiveInfoCollection prerequisite : transitiveInfoCollections) { C prerequisiteProvider = prerequisite.getProvider(classType); if (prerequisiteProvider != null) { result.put(prerequisite.getConfiguration(), prerequisiteProvider); } } return result.build(); } /** * For a given attribute, returns all {@link TransitiveInfoCollection}s provided by targets * of that attribute. Each {@link TransitiveInfoCollection} is keyed by the * {@link BuildConfiguration} under which the collection was created. */ public ImmutableListMultimap getPrerequisitesByConfiguration(String attributeName, Mode mode) { List transitiveInfoCollections = getPrerequisites(attributeName, mode); ImmutableListMultimap.Builder result = ImmutableListMultimap.builder(); for (TransitiveInfoCollection prerequisite : transitiveInfoCollections) { result.put(prerequisite.getConfiguration(), prerequisite); } return result.build(); } /** * Returns all the providers of the specified type that are listed under the specified attribute * of this target in the BUILD file. */ public Iterable getPrerequisites(String attributeName, Mode mode, final Class classType) { AnalysisUtils.checkProvider(classType); return AnalysisUtils.getProviders(getPrerequisites(attributeName, mode), classType); } /** * Returns all the providers of the specified type that are listed under the specified attribute * of this target in the BUILD file, and that contain the specified provider. */ public Iterable getPrerequisitesIf(String attributeName, Mode mode, final Class classType) { AnalysisUtils.checkProvider(classType); return AnalysisUtils.filterByProvider(getPrerequisites(attributeName, mode), classType); } /** * Returns the prerequisite referred to by the specified attribute. Also checks whether * the attribute is marked as executable and that the target referred to can actually be * executed. * *

The {@code mode} argument must match the configuration transition specified in the * definition of the attribute. * * @param attributeName the name of the attribute * @param mode the configuration transition of the attribute * * @return the {@link FilesToRunProvider} interface of the prerequisite. */ @Nullable public FilesToRunProvider getExecutablePrerequisite(String attributeName, Mode mode) { Attribute ruleDefinition = getAttribute(attributeName); if (ruleDefinition == null) { throw new IllegalStateException(getRuleClassNameForLogging() + " attribute " + attributeName + " is not defined"); } if (!ruleDefinition.isExecutable()) { throw new IllegalStateException(getRuleClassNameForLogging() + " attribute " + attributeName + " is not configured to be executable"); } TransitiveInfoCollection prerequisite = getPrerequisite(attributeName, mode); if (prerequisite == null) { return null; } FilesToRunProvider result = prerequisite.getProvider(FilesToRunProvider.class); if (result == null || result.getExecutable() == null) { attributeError( attributeName, prerequisite.getLabel() + " does not refer to a valid executable target"); } return result; } /** Indicates whether a string list attribute should be tokenized. */ public enum Tokenize { YES, NO } /** * Gets an attribute of type STRING_LIST expanding Make variables, $(location) tags into the * dependency location (see {@link LocationExpander} for details) and tokenizes the result. * * @param attributeName the name of the attribute to process * @return a list of strings containing the expanded and tokenized values for the attribute */ public ImmutableList getTokenizedStringListAttr(String attributeName) { return getExpandedStringListAttr(attributeName, Tokenize.YES); } /** * Gets an attribute of type STRING_LIST expanding Make variables and $(location) tags, * and optionally tokenizes the result. * * @param attributeName the name of the attribute to process * @return a list of strings containing the processed values for the attribute */ public ImmutableList getExpandedStringListAttr(String attributeName, Tokenize tokenize) { if (!getRule().isAttrDefined(attributeName, Type.STRING_LIST)) { // TODO(bazel-team): This should be an error. return ImmutableList.of(); } List original = attributes().get(attributeName, Type.STRING_LIST); if (original.isEmpty()) { return ImmutableList.of(); } List tokens = new ArrayList<>(); LocationExpander locationExpander = new LocationExpander(this, LocationExpander.Options.ALLOW_DATA); for (String token : original) { expandValue(tokens, attributeName, token, locationExpander, tokenize); } return ImmutableList.copyOf(tokens); } /** * Expands make variables in value and tokenizes the result into tokens. * *

This methods should be called only during initialization. */ public void tokenizeAndExpandMakeVars(List tokens, String attributeName, String value) { tokenizeAndExpandMakeVars(tokens, attributeName, value, null); } /** * Expands make variables and $(location) tags in value and tokenizes the result into tokens. * *

This methods should be called only during initialization. */ public void tokenizeAndExpandMakeVars( List tokens, String attributeName, String value, @Nullable LocationExpander locationExpander) { expandValue(tokens, attributeName, value, locationExpander, Tokenize.YES); } /** * Expands make variables and $(location) tags in value, and optionally tokenizes the result. * *

This methods should be called only during initialization. */ public void expandValue( List tokens, String attributeName, String value, @Nullable LocationExpander locationExpander, Tokenize tokenize) { if (locationExpander != null) { value = locationExpander.expandAttribute(attributeName, value); } value = expandMakeVariables(attributeName, value); if (tokenize == Tokenize.YES) { try { ShellUtils.tokenize(tokens, value); } catch (ShellUtils.TokenizationException e) { attributeError(attributeName, e.getMessage()); } } else { tokens.add(value); } } /** * Return a context that maps Make variable names (string) to values (string). * * @return a ConfigurationMakeVariableContext. **/ public ConfigurationMakeVariableContext getConfigurationMakeVariableContext() { if (configurationMakeVariableContext == null) { configurationMakeVariableContext = new ConfigurationMakeVariableContext( getRule().getPackage(), getConfiguration()); } return configurationMakeVariableContext; } /** * Returns the string "expression" after expanding all embedded references to * "Make" variables. If any errors are encountered, they are reported, and * "expression" is returned unchanged. * * @param attributeName the name of the attribute from which "expression" comes; * used for error reporting. * @param expression the string to expand. * @return the expansion of "expression". */ public String expandMakeVariables(String attributeName, String expression) { return expandMakeVariables(attributeName, expression, getConfigurationMakeVariableContext()); } /** * Returns the string "expression" after expanding all embedded references to * "Make" variables. If any errors are encountered, they are reported, and * "expression" is returned unchanged. * * @param attributeName the name of the attribute from which "expression" comes; * used for error reporting. * @param expression the string to expand. * @param context the ConfigurationMakeVariableContext which can have a customized * lookupMakeVariable(String) method. * @return the expansion of "expression". */ public String expandMakeVariables(String attributeName, String expression, ConfigurationMakeVariableContext context) { try { return MakeVariableExpander.expand(expression, context); } catch (MakeVariableExpander.ExpansionException e) { attributeError(attributeName, e.getMessage()); return expression; } } /** * Gets the value of the STRING_LIST attribute expanding all make variables. */ public List expandedMakeVariablesList(String attrName) { List variables = new ArrayList<>(); for (String variable : attributes().get(attrName, Type.STRING_LIST)) { variables.add(expandMakeVariables(attrName, variable)); } return variables; } /** * If the string consists of a single variable, returns the expansion of * that variable. Otherwise, returns null. Syntax errors are reported. * * @param attrName the name of the attribute from which "expression" comes; * used for error reporting. * @param expression the string to expand. * @return the expansion of "expression", or null. */ public String expandSingleMakeVariable(String attrName, String expression) { try { return MakeVariableExpander.expandSingleVariable(expression, new ConfigurationMakeVariableContext(getRule().getPackage(), getConfiguration())); } catch (MakeVariableExpander.ExpansionException e) { attributeError(attrName, e.getMessage()); return expression; } } private void checkAttribute(String attributeName, Mode mode) { Attribute attributeDefinition = getAttribute(attributeName); if (attributeDefinition == null) { throw new IllegalStateException(getRule().getLocation() + ": " + getRuleClassNameForLogging() + " attribute " + attributeName + " is not defined"); } if (!(attributeDefinition.getType() == BuildType.LABEL || attributeDefinition.getType() == BuildType.LABEL_LIST)) { throw new IllegalStateException(getRuleClassNameForLogging() + " attribute " + attributeName + " is not a label type attribute"); } if (mode == Mode.HOST) { if (attributeDefinition.getConfigurationTransition() != ConfigurationTransition.HOST) { throw new IllegalStateException(getRule().getLocation() + ": " + getRuleClassNameForLogging() + " attribute " + attributeName + " is not configured for the host configuration"); } } else if (mode == Mode.TARGET) { if (attributeDefinition.getConfigurationTransition() != ConfigurationTransition.NONE) { throw new IllegalStateException(getRule().getLocation() + ": " + getRuleClassNameForLogging() + " attribute " + attributeName + " is not configured for the target configuration"); } } else if (mode == Mode.DATA) { if (attributeDefinition.getConfigurationTransition() != ConfigurationTransition.DATA) { throw new IllegalStateException(getRule().getLocation() + ": " + getRuleClassNameForLogging() + " attribute " + attributeName + " is not configured for the data configuration"); } } else if (mode == Mode.SPLIT) { if (!(attributeDefinition.hasSplitConfigurationTransition())) { throw new IllegalStateException(getRule().getLocation() + ": " + getRuleClassNameForLogging() + " attribute " + attributeName + " is not configured for a split transition"); } } } /** * Returns the Mode for which the attribute is configured. * This is intended for Skylark, where the Mode is implicitly chosen. */ public Mode getAttributeMode(String attributeName) { Attribute attributeDefinition = getAttribute(attributeName); if (attributeDefinition == null) { throw new IllegalStateException(getRule().getLocation() + ": " + getRuleClassNameForLogging() + " attribute " + attributeName + " is not defined"); } if (!(attributeDefinition.getType() == BuildType.LABEL || attributeDefinition.getType() == BuildType.LABEL_LIST)) { throw new IllegalStateException(getRuleClassNameForLogging() + " attribute " + attributeName + " is not a label type attribute"); } if (attributeDefinition.getConfigurationTransition() == ConfigurationTransition.HOST) { return Mode.HOST; } else if (attributeDefinition.getConfigurationTransition() == ConfigurationTransition.NONE) { return Mode.TARGET; } else if (attributeDefinition.getConfigurationTransition() == ConfigurationTransition.DATA) { return Mode.DATA; } else if (attributeDefinition.hasSplitConfigurationTransition()) { return Mode.SPLIT; } throw new IllegalStateException(getRule().getLocation() + ": " + getRuleClassNameForLogging() + " attribute " + attributeName + " is not configured"); } /** * For the specified attribute "attributeName" (which must be of type * list(label)), resolve all the labels into ConfiguredTargets (for the * configuration appropriate to the attribute) and return their build * artifacts as a {@link PrerequisiteArtifacts} instance. * * @param attributeName the name of the attribute to traverse */ public PrerequisiteArtifacts getPrerequisiteArtifacts(String attributeName, Mode mode) { return PrerequisiteArtifacts.get(this, attributeName, mode); } /** * For the specified attribute "attributeName" (which must be of type label), * resolves the ConfiguredTarget and returns its single build artifact. * *

If the attribute is optional, has no default and was not specified, then * null will be returned. Note also that null is returned (and an attribute * error is raised) if there wasn't exactly one build artifact for the target. */ public Artifact getPrerequisiteArtifact(String attributeName, Mode mode) { TransitiveInfoCollection target = getPrerequisite(attributeName, mode); return transitiveInfoCollectionToArtifact(attributeName, target); } /** * Equivalent to getPrerequisiteArtifact(), but also asserts that * host-configuration is appropriate for the specified attribute. */ public Artifact getHostPrerequisiteArtifact(String attributeName) { TransitiveInfoCollection target = getPrerequisite(attributeName, Mode.HOST); return transitiveInfoCollectionToArtifact(attributeName, target); } private Artifact transitiveInfoCollectionToArtifact( String attributeName, TransitiveInfoCollection target) { if (target != null) { Iterable artifacts = target.getProvider(FileProvider.class).getFilesToBuild(); if (Iterables.size(artifacts) == 1) { return Iterables.getOnlyElement(artifacts); } else { attributeError(attributeName, target.getLabel() + " expected a single artifact"); } } return null; } /** * Returns the sole file in the "srcs" attribute. Reports an error and * (possibly) returns null if "srcs" does not identify a single file of the * expected type. */ public Artifact getSingleSource(String fileTypeName) { List srcs = PrerequisiteArtifacts.get(this, "srcs", Mode.TARGET).list(); switch (srcs.size()) { case 0 : // error already issued by getSrc() return null; case 1 : // ok return Iterables.getOnlyElement(srcs); default : attributeError("srcs", "only a single " + fileTypeName + " is allowed here"); return srcs.get(0); } } public Artifact getSingleSource() { return getSingleSource(getRuleClassNameForLogging() + " source file"); } /** * Returns a path fragment qualified by the rule name and unique fragment to * disambiguate artifacts produced from the source file appearing in * multiple rules. * *

For example "pkg/dir/name" -> "pkg/<fragment>/rule/dir/name. */ public final PathFragment getUniqueDirectory(String fragment) { return AnalysisUtils.getUniqueDirectory(getLabel(), new PathFragment(fragment)); } /** * Check that all targets that were specified as sources are from the same * package as this rule. Output a warning or an error for every target that is * imported from a different package. */ public void checkSrcsSamePackage(boolean onlyWarn) { PathFragment packageName = getLabel().getPackageFragment(); for (Artifact srcItem : PrerequisiteArtifacts.get(this, "srcs", Mode.TARGET).list()) { if (!srcItem.isSourceArtifact()) { // In theory, we should not do this check. However, in practice, we // have a couple of rules that do not obey the "srcs must contain // files and only files" rule. Thus, we are stuck with this hack here :( continue; } Label associatedLabel = srcItem.getOwner(); PathFragment itemPackageName = associatedLabel.getPackageFragment(); if (!itemPackageName.equals(packageName)) { String message = "please do not import '" + associatedLabel + "' directly. " + "You should either move the file to this package or depend on " + "an appropriate rule there"; if (onlyWarn) { attributeWarning("srcs", message); } else { attributeError("srcs", message); } } } } /** * Returns the label to which the {@code NODEP_LABEL} attribute * {@code attrName} refers, checking that it is a valid label, and that it is * referring to a local target. Reports a warning otherwise. */ public Label getLocalNodepLabelAttribute(String attrName) { Label label = attributes().get(attrName, BuildType.NODEP_LABEL); if (label == null) { return null; } if (!getTarget().getLabel().getPackageFragment().equals(label.getPackageFragment())) { attributeWarning(attrName, "does not reference a local rule"); } return label; } /** * Returns the implicit output artifact for a given template function. If multiple or no artifacts * can be found as a result of the template, an exception is thrown. */ public Artifact getImplicitOutputArtifact(ImplicitOutputsFunction function) throws InterruptedException { Iterable result; try { result = function.getImplicitOutputs(RawAttributeMapper.of(rule)); } catch (EvalException e) { // It's ok as long as we don't use this method from Skylark. throw new IllegalStateException(e); } return getImplicitOutputArtifact(Iterables.getOnlyElement(result)); } /** * Only use from Skylark. Returns the implicit output artifact for a given output path. */ public Artifact getImplicitOutputArtifact(String path) { return getPackageRelativeArtifact(path, getBinOrGenfilesDirectory()); } /** * Convenience method to return a host configured target for the "compiler" * attribute. Allows caller to decide whether a warning should be printed if * the "compiler" attribute is not set to the default value. * * @param warnIfNotDefault if true, print a warning if the value for the * "compiler" attribute is set to something other than the default * @return a ConfiguredTarget using the host configuration for the "compiler" * attribute */ public final FilesToRunProvider getCompiler(boolean warnIfNotDefault) { Label label = attributes().get("compiler", BuildType.LABEL); if (warnIfNotDefault && !label.equals(getRule().getAttrDefaultValue("compiler"))) { attributeWarning("compiler", "setting the compiler is strongly discouraged"); } return getExecutablePrerequisite("compiler", Mode.HOST); } /** * Returns the (unmodifiable, ordered) list of artifacts which are the outputs * of this target. * *

Each element in this list is associated with a single output, either * declared implicitly (via setImplicitOutputsFunction()) or explicitly * (listed in the 'outs' attribute of our rule). */ public final ImmutableList getOutputArtifacts() { ImmutableList.Builder artifacts = ImmutableList.builder(); for (OutputFile out : getRule().getOutputFiles()) { artifacts.add(createOutputArtifact(out)); } return artifacts.build(); } /** * Like {@link #getOutputArtifacts()} but for a singular output item. * Reports an error if the "out" attribute is not a singleton. * * @return null if the output list is empty, the artifact for the first item * of the output list otherwise */ public Artifact getOutputArtifact() { List outs = getOutputArtifacts(); if (outs.size() != 1) { attributeError("out", "exactly one output file required"); if (outs.isEmpty()) { return null; } } return outs.get(0); } /** * Returns an artifact with a given file extension. All other path components * are the same as in {@code pathFragment}. */ public final Artifact getRelatedArtifact(PathFragment pathFragment, String extension) { PathFragment file = FileSystemUtils.replaceExtension(pathFragment, extension); return getDerivedArtifact(file, getConfiguration().getBinDirectory(rule.getRepository())); } /** * Returns true if the target for this context is a test target. */ public boolean isTestTarget() { return TargetUtils.isTestRule(getTarget()); } /** * Returns true if runfiles support should create the runfiles tree, or * false if it should just create the manifest. */ public boolean shouldCreateRunfilesSymlinks() { // TODO(bazel-team): Ideally we wouldn't need such logic, and we'd // always use the BuildConfiguration#buildRunfiles() to determine // whether to build the runfiles. The problem is that certain build // steps actually consume their runfiles. These include: // a. par files consumes the runfiles directory // We should modify autopar to take a list of files instead. // of the runfiles directory. // b. host tools could potentially use data files, but currently don't // (they're run from the execution root, not a runfiles tree). // Currently hostConfiguration.buildRunfiles() returns true. if (isTestTarget()) { // Tests are only executed during testing (duh), // and their runfiles are generated lazily on local // execution (see LocalTestStrategy). Therefore, it // is safe not to build their runfiles. return getConfiguration().buildRunfiles(); } else { return true; } } /** * @return true if {@code rule} is visible from {@code prerequisite}. * *

This only computes the logic as implemented by the visibility system. The final decision * whether a dependency is allowed is made by * {@link ConfiguredRuleClassProvider.PrerequisiteValidator}. */ public static boolean isVisible(Rule rule, TransitiveInfoCollection prerequisite) { // Check visibility attribute for (PackageSpecification specification : prerequisite.getProvider(VisibilityProvider.class).getVisibility()) { if (specification.containsPackage(rule.getLabel().getPackageIdentifier())) { return true; } } return false; } /** * @return the set of features applicable for the current rule's package. */ public ImmutableSet getFeatures() { return features; } @Override public String toString() { return "RuleContext(" + getLabel() + ", " + getConfiguration() + ")"; } /** * Builder class for a RuleContext. */ public static final class Builder implements RuleErrorConsumer { private final AnalysisEnvironment env; private final Rule rule; private final ConfigurationFragmentPolicy configurationFragmentPolicy; private Class universalFragment; private final BuildConfiguration configuration; private final BuildConfiguration hostConfiguration; private final PrerequisiteValidator prerequisiteValidator; @Nullable private final String aspectName; private final ErrorReporter reporter; private OrderedSetMultimap prerequisiteMap; private ImmutableMap configConditions; private NestedSet visibility; private ImmutableMap aspectAttributes; private ImmutableBiMap> skylarkProviderRegistry; Builder( AnalysisEnvironment env, Rule rule, @Nullable String aspectName, BuildConfiguration configuration, BuildConfiguration hostConfiguration, PrerequisiteValidator prerequisiteValidator, ConfigurationFragmentPolicy configurationFragmentPolicy) { this.env = Preconditions.checkNotNull(env); this.rule = Preconditions.checkNotNull(rule); this.aspectName = aspectName; this.configurationFragmentPolicy = Preconditions.checkNotNull(configurationFragmentPolicy); this.configuration = Preconditions.checkNotNull(configuration); this.hostConfiguration = Preconditions.checkNotNull(hostConfiguration); this.prerequisiteValidator = prerequisiteValidator; reporter = new ErrorReporter(env, rule, getRuleClassNameForLogging()); } RuleContext build() { Preconditions.checkNotNull(prerequisiteMap); Preconditions.checkNotNull(configConditions); Preconditions.checkNotNull(visibility); AttributeMap attributes = ConfiguredAttributeMapper.of(rule, configConditions); validateAttributes(attributes); ListMultimap targetMap = createTargetMap(); ListMultimap filesetEntryMap = createFilesetEntryMap(rule, configConditions); return new RuleContext( this, attributes, targetMap, filesetEntryMap, configConditions, universalFragment, getRuleClassNameForLogging(), aspectAttributes != null ? aspectAttributes : ImmutableMap.of()); } private void validateAttributes(AttributeMap attributes) { rule.getRuleClassObject().checkAttributesNonEmpty(rule, reporter, attributes); } Builder setVisibility(NestedSet visibility) { this.visibility = visibility; return this; } /** * Sets the prerequisites and checks their visibility. It also generates appropriate error or * warning messages and sets the error flag as appropriate. */ Builder setPrerequisites(OrderedSetMultimap prerequisiteMap) { this.prerequisiteMap = Preconditions.checkNotNull(prerequisiteMap); return this; } /** * Adds attributes which are defined by an Aspect (and not by RuleClass). */ Builder setAspectAttributes(Map aspectAttributes) { this.aspectAttributes = ImmutableMap.copyOf(aspectAttributes); return this; } /** * Sets the configuration conditions needed to determine which paths to follow for this * rule's configurable attributes. */ Builder setConfigConditions(ImmutableMap configConditions) { this.configConditions = Preconditions.checkNotNull(configConditions); return this; } /** * Sets the fragment that can be legally accessed even when not explicitly declared. */ Builder setUniversalFragment(Class fragment) { // TODO(bazel-team): Add this directly to ConfigurationFragmentPolicy, so we // don't need separate logic specifically for checking this fragment. The challenge is // that we need RuleClassProvider to figure out what this fragment is, and not every // call state that creates ConfigurationFragmentPolicy has access to that. this.universalFragment = fragment; return this; } /** * Sets a map that indicates which providers should be exported to skylark under the key * (map key). These provider types will also be exportable by skylark rules under (map key). */ Builder setSkylarkProvidersRegistry( ImmutableBiMap> skylarkProviderRegistry) { this.skylarkProviderRegistry = skylarkProviderRegistry; return this; } private boolean validateFilesetEntry(FilesetEntry filesetEntry, ConfiguredTarget src) { if (src.getProvider(FilesetProvider.class) != null) { return true; } if (filesetEntry.isSourceFileset()) { return true; } Target srcTarget = src.getTarget(); if (!(srcTarget instanceof FileTarget)) { attributeError("entries", String.format( "Invalid 'srcdir' target '%s'. Must be another Fileset or package", srcTarget.getLabel())); return false; } if (srcTarget instanceof OutputFile) { attributeWarning("entries", String.format("'srcdir' target '%s' is not an input file. " + "This forces the Fileset to be executed unconditionally", srcTarget.getLabel())); } return true; } /** * Determines and returns a map from attribute name to list of configured fileset entries, based * on a PrerequisiteMap instance. */ private ListMultimap createFilesetEntryMap( final Rule rule, ImmutableMap configConditions) { final ImmutableSortedKeyListMultimap.Builder mapBuilder = ImmutableSortedKeyListMultimap.builder(); for (Attribute attr : rule.getAttributes()) { if (attr.getType() != BuildType.FILESET_ENTRY_LIST) { continue; } String attributeName = attr.getName(); Map ctMap = new HashMap<>(); for (ConfiguredTarget prerequisite : prerequisiteMap.get(attr)) { ctMap.put(AliasProvider.getDependencyLabel(prerequisite), prerequisite); } List entries = ConfiguredAttributeMapper.of(rule, configConditions) .get(attributeName, BuildType.FILESET_ENTRY_LIST); for (FilesetEntry entry : entries) { if (entry.getFiles() == null) { Label label = entry.getSrcLabel(); ConfiguredTarget src = ctMap.get(label); if (!validateFilesetEntry(entry, src)) { continue; } mapBuilder.put(attributeName, new ConfiguredFilesetEntry(entry, src)); } else { ImmutableList.Builder files = ImmutableList.builder(); for (Label file : entry.getFiles()) { files.add(ctMap.get(file)); } mapBuilder.put(attributeName, new ConfiguredFilesetEntry(entry, files.build())); } } } return mapBuilder.build(); } /** * Determines and returns a map from attribute name to list of configured targets. */ private ImmutableSortedKeyListMultimap createTargetMap() { ImmutableSortedKeyListMultimap.Builder mapBuilder = ImmutableSortedKeyListMultimap.builder(); for (Map.Entry> entry : prerequisiteMap.asMap().entrySet()) { Attribute attribute = entry.getKey(); if (attribute == null) { continue; } if (attribute.isSilentRuleClassFilter()) { Predicate filter = attribute.getAllowedRuleClassesPredicate(); for (ConfiguredTarget configuredTarget : entry.getValue()) { Target prerequisiteTarget = configuredTarget.getTarget(); if ((prerequisiteTarget instanceof Rule) && filter.apply(((Rule) prerequisiteTarget).getRuleClassObject())) { validateDirectPrerequisite(attribute, configuredTarget); mapBuilder.put(attribute.getName(), configuredTarget); } } } else { for (ConfiguredTarget configuredTarget : entry.getValue()) { validateDirectPrerequisite(attribute, configuredTarget); mapBuilder.put(attribute.getName(), configuredTarget); } } } return mapBuilder.build(); } public void reportError(Location location, String message) { reporter.reportError(location, message); } @Override public void ruleError(String message) { reporter.ruleError(message); } @Override public void attributeError(String attrName, String message) { reporter.attributeError(attrName, message); } public void reportWarning(Location location, String message) { reporter.reportWarning(location, message); } @Override public void ruleWarning(String message) { reporter.ruleWarning(message); } @Override public void attributeWarning(String attrName, String message) { reporter.attributeWarning(attrName, message); } private String badPrerequisiteMessage(String targetKind, ConfiguredTarget prerequisite, String reason, boolean isWarning) { String msgPrefix = targetKind != null ? targetKind + " " : ""; String msgReason = reason != null ? " (" + reason + ")" : ""; if (isWarning) { return String.format( "%s'%s'%s is unexpected here%s; continuing anyway", msgPrefix, prerequisite.getLabel(), AliasProvider.printVisibilityChain(prerequisite), msgReason); } return String.format( "%s'%s'%s is misplaced here%s", msgPrefix, prerequisite.getLabel(), AliasProvider.printVisibilityChain(prerequisite), msgReason); } private void reportBadPrerequisite(Attribute attribute, String targetKind, ConfiguredTarget prerequisite, String reason, boolean isWarning) { String message = badPrerequisiteMessage(targetKind, prerequisite, reason, isWarning); if (isWarning) { attributeWarning(attribute.getName(), message); } else { attributeError(attribute.getName(), message); } } private void validateDirectPrerequisiteType(ConfiguredTarget prerequisite, Attribute attribute) { Target prerequisiteTarget = prerequisite.getTarget(); Label prerequisiteLabel = prerequisiteTarget.getLabel(); if (prerequisiteTarget instanceof Rule) { Rule prerequisiteRule = (Rule) prerequisiteTarget; String reason = attribute.getValidityPredicate().checkValid(rule, prerequisiteRule); if (reason != null) { reportBadPrerequisite(attribute, prerequisiteTarget.getTargetKind(), prerequisite, reason, false); } } if (prerequisiteTarget instanceof Rule) { validateRuleDependency(prerequisite, attribute); } else if (prerequisiteTarget instanceof FileTarget) { if (attribute.isStrictLabelCheckingEnabled()) { if (!attribute.getAllowedFileTypesPredicate() .apply(((FileTarget) prerequisiteTarget).getFilename())) { if (prerequisiteTarget instanceof InputFile && !((InputFile) prerequisiteTarget).getPath().exists()) { // Misplaced labels, no corresponding target exists if (attribute.getAllowedFileTypesPredicate().isNone() && !((InputFile) prerequisiteTarget).getFilename().contains(".")) { // There are no allowed files in the attribute but it's not a valid rule, // and the filename doesn't contain a dot --> probably a misspelled rule attributeError(attribute.getName(), "rule '" + prerequisiteLabel + "' does not exist"); } else { attributeError(attribute.getName(), "target '" + prerequisiteLabel + "' does not exist"); } } else { // The file exists but has a bad extension reportBadPrerequisite(attribute, "file", prerequisite, "expected " + attribute.getAllowedFileTypesPredicate(), false); } } } } } public Rule getRule() { return rule; } /** * Returns a rule class name suitable for log messages, including an aspect name if applicable. */ public String getRuleClassNameForLogging() { return aspectName != null ? aspectName + " aspect on " + rule.getRuleClass() : rule.getRuleClass(); } public BuildConfiguration getConfiguration() { return configuration; } /** * @return true if {@code rule} is visible from {@code prerequisite}. * *

This only computes the logic as implemented by the visibility system. The final decision * whether a dependency is allowed is made by * {@link ConfiguredRuleClassProvider.PrerequisiteValidator}, who is supposed to call this * method to determine whether a dependency is allowed as per visibility rules. */ public boolean isVisible(TransitiveInfoCollection prerequisite) { return RuleContext.isVisible(rule, prerequisite); } private void validateDirectPrerequisiteFileTypes(ConfiguredTarget prerequisite, Attribute attribute) { if (attribute.isSkipAnalysisTimeFileTypeCheck()) { return; } FileTypeSet allowedFileTypes = attribute.getAllowedFileTypesPredicate(); if (allowedFileTypes == null) { // It's not a label or label_list attribute. return; } if (allowedFileTypes == FileTypeSet.ANY_FILE && !attribute.isNonEmpty() && !attribute.isSingleArtifact()) { return; } // If we allow any file we still need to check if there are actually files generated // Note that this check only runs for ANY_FILE predicates if the attribute is NON_EMPTY // or SINGLE_ARTIFACT // If we performed this check when allowedFileTypes == NO_FILE this would // always throw an error in those cases if (allowedFileTypes != FileTypeSet.NO_FILE) { Iterable artifacts = prerequisite.getProvider(FileProvider.class) .getFilesToBuild(); if (attribute.isSingleArtifact() && Iterables.size(artifacts) != 1) { attributeError(attribute.getName(), "'" + prerequisite.getLabel() + "' must produce a single file"); return; } for (Artifact sourceArtifact : artifacts) { if (allowedFileTypes.apply(sourceArtifact.getFilename())) { return; } } attributeError(attribute.getName(), "'" + prerequisite.getLabel() + "' does not produce any " + getRuleClassNameForLogging() + " " + attribute.getName() + " files (expected " + allowedFileTypes + ")"); } } private String getMissingMandatoryProviders(ConfiguredTarget prerequisite, Attribute attribute){ List> mandatoryProvidersList = attribute.getMandatoryProvidersList(); if (mandatoryProvidersList.isEmpty()) { return null; } List> missingProvidersList = new ArrayList<>(); for (ImmutableSet providers : mandatoryProvidersList) { List missing = new ArrayList<>(); for (String provider : providers) { if (prerequisite.get(provider) == null) { missing.add(provider); } } if (missing.isEmpty()) { return null; } else { missingProvidersList.add(missing); } } StringBuilder missingProviders = new StringBuilder(); Joiner joinProvider = Joiner.on("', '"); for (List providers : missingProvidersList) { if (missingProviders.length() > 0) { missingProviders.append(" or "); } missingProviders.append((providers.size() > 1) ? "[" : "") .append("'"); joinProvider.appendTo(missingProviders, providers); missingProviders.append("'") .append((providers.size() > 1) ? "]" : ""); } return missingProviders.toString(); } private String getMissingMandatoryNativeProviders( ConfiguredTarget prerequisite, Attribute attribute) { List>> mandatoryProvidersList = attribute.getMandatoryNativeProvidersList(); if (mandatoryProvidersList.isEmpty()) { return null; } List> missingProvidersList = new ArrayList<>(); for (ImmutableList> providers : mandatoryProvidersList) { List missing = new ArrayList<>(); for (Class provider : providers) { if (prerequisite.getProvider(provider) == null) { missing.add(provider.getSimpleName()); } } if (missing.isEmpty()) { return null; } else { missingProvidersList.add(missing); } } StringBuilder missingProviders = new StringBuilder(); Joiner joinProvider = Joiner.on(", "); for (List providers : missingProvidersList) { if (missingProviders.length() > 0) { missingProviders.append(" or "); } missingProviders.append((providers.size() > 1) ? "[" : ""); joinProvider.appendTo(missingProviders, providers); missingProviders.append((providers.size() > 1) ? "]" : ""); } return missingProviders.toString(); } /** * Because some rules still have to use allowedRuleClasses to do rule dependency validation. * A dependency is valid if it is from a rule in allowedRuledClasses, OR if all of the providers * in mandatoryNativeProviders AND mandatoryProvidersList are provided by the target. */ private void validateRuleDependency(ConfiguredTarget prerequisite, Attribute attribute) { Target prerequisiteTarget = prerequisite.getTarget(); RuleClass ruleClass = ((Rule) prerequisiteTarget).getRuleClassObject(); HashSet unfulfilledRequirements = new HashSet<>(); if (attribute.getAllowedRuleClassesPredicate() != Predicates.alwaysTrue()) { if (attribute.getAllowedRuleClassesPredicate().apply(ruleClass)) { return; } unfulfilledRequirements.add(badPrerequisiteMessage(prerequisiteTarget.getTargetKind(), prerequisite, "expected " + attribute.getAllowedRuleClassesPredicate(), false)); } if (attribute.getAllowedRuleClassesWarningPredicate() != Predicates.alwaysTrue()) { Predicate warningPredicate = attribute.getAllowedRuleClassesWarningPredicate(); if (warningPredicate.apply(ruleClass)) { reportBadPrerequisite(attribute, prerequisiteTarget.getTargetKind(), prerequisite, "expected " + attribute.getAllowedRuleClassesPredicate(), true); return; } } // This condition is required; getMissingMandatory[Native]Providers() would be vacuously true // if no providers were mandatory (thus, none are missing), which would cause an early return // below without emitting the error message about the not-allowed rule class if that // requirement was unfulfilled. if (!attribute.getMandatoryNativeProvidersList().isEmpty() || !attribute.getMandatoryProvidersList().isEmpty()) { boolean hadAllMandatoryProviders = true; String missingNativeProviders = getMissingMandatoryNativeProviders(prerequisite, attribute); if (missingNativeProviders != null) { unfulfilledRequirements.add( "'" + prerequisite.getLabel() + "' does not have mandatory providers: " + missingNativeProviders); hadAllMandatoryProviders = false; } String missingProviders = getMissingMandatoryProviders(prerequisite, attribute); if (missingProviders != null) { unfulfilledRequirements.add( "'" + prerequisite.getLabel() + "' does not have mandatory provider " + missingProviders); hadAllMandatoryProviders = false; } if (hadAllMandatoryProviders) { return; } } if (!unfulfilledRequirements.isEmpty()) { attributeError( attribute.getName(), StringUtil.joinEnglishList(unfulfilledRequirements, "and")); } } private void validateDirectPrerequisite(Attribute attribute, ConfiguredTarget prerequisite) { validateDirectPrerequisiteType(prerequisite, attribute); validateDirectPrerequisiteFileTypes(prerequisite, attribute); if (attribute.performPrereqValidatorCheck()) { prerequisiteValidator.validate(this, prerequisite, attribute); } } } /** * Helper class for reporting errors and warnings. */ public static final class ErrorReporter implements RuleErrorConsumer { private final AnalysisEnvironment env; private final Rule rule; private final String ruleClassNameForLogging; ErrorReporter(AnalysisEnvironment env, Rule rule, String ruleClassNameForLogging) { this.env = env; this.rule = rule; this.ruleClassNameForLogging = ruleClassNameForLogging; } public void reportError(Location location, String message) { env.getEventHandler().handle(Event.error(location, message)); } @Override public void ruleError(String message) { reportError(rule.getLocation(), prefixRuleMessage(message)); } @Override public void attributeError(String attrName, String message) { reportError(rule.getAttributeLocation(attrName), completeAttributeMessage(attrName, message)); } public void reportWarning(Location location, String message) { env.getEventHandler().handle(Event.warn(location, message)); } @Override public void ruleWarning(String message) { env.getEventHandler().handle(Event.warn(rule.getLocation(), prefixRuleMessage(message))); } @Override public void attributeWarning(String attrName, String message) { reportWarning( rule.getAttributeLocation(attrName), completeAttributeMessage(attrName, message)); } private String prefixRuleMessage(String message) { return String.format( "in %s rule %s: %s", getRuleClassNameForLogging(), rule.getLabel(), message); } private String maskInternalAttributeNames(String name) { return Attribute.isImplicit(name) ? "(an implicit dependency)" : name; } /** * Prefixes the given message with details about the rule and appends details about the macro * that created this rule, if applicable. */ private String completeAttributeMessage(String attrName, String message) { // Appends a note to the given message if the offending rule was created by a macro. String macroMessageAppendix = rule.wasCreatedByMacro() ? String.format( ". Since this rule was created by the macro '%s', the error might have been " + "caused by the macro implementation in %s", getGeneratorFunction(), rule.getAttributeLocationWithoutMacro(attrName)) : ""; return String.format("in %s attribute of %s rule %s: %s%s", maskInternalAttributeNames(attrName), getRuleClassNameForLogging(), rule.getLabel(), message, macroMessageAppendix); } /** * Returns a rule class name suitable for log messages, including an aspect name if applicable. */ private String getRuleClassNameForLogging() { return ruleClassNameForLogging; } private String getGeneratorFunction() { return (String) rule.getAttributeContainer().getAttr("generator_function"); } } }