| // Copyright 2023 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.collect.ImmutableList.toImmutableList; |
| |
| import com.google.common.base.Optional; |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableListMultimap; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Multimaps; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.collect.ImmutableSortedKeyListMultimap; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSet; |
| import com.google.devtools.build.lib.packages.Attribute; |
| import com.google.devtools.build.lib.packages.AttributeMap; |
| import com.google.devtools.build.lib.packages.BuiltinProvider; |
| import com.google.devtools.build.lib.packages.Info; |
| import com.google.devtools.build.lib.packages.Rule; |
| import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException; |
| import com.google.devtools.build.lib.packages.StarlarkProviderWrapper; |
| import com.google.devtools.build.lib.packages.Type.LabelClass; |
| import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData; |
| import java.util.List; |
| import java.util.Map; |
| import javax.annotation.Nullable; |
| |
| /** Collection of the attributes dependencies available in a {@link RuleContext}. */ |
| public final class PrerequisitesCollection { |
| |
| private interface AttributesMapper { |
| Attribute getAttribute(String attributeName); |
| } |
| |
| private final ImmutableSortedKeyListMultimap<String, ConfiguredTargetAndData> |
| attributeToPrerequisitesMap; |
| private final AttributesMapper attributesMapper; |
| |
| private final RuleErrorConsumer ruleErrorConsumer; |
| private final Rule rule; |
| private final String ruleClassNameForLogging; |
| |
| PrerequisitesCollection( |
| ImmutableSortedKeyListMultimap<String, ConfiguredTargetAndData> attributeToPrerequisitesMap, |
| ImmutableMap<String, Attribute> attributes, |
| RuleErrorConsumer ruleErrorConsumer, |
| Rule rule, |
| String ruleClassNameForLogging) { |
| this( |
| attributeToPrerequisitesMap, |
| /* attributesMapper= */ attributes::get, |
| ruleErrorConsumer, |
| rule, |
| ruleClassNameForLogging); |
| } |
| |
| PrerequisitesCollection( |
| ImmutableSortedKeyListMultimap<String, ConfiguredTargetAndData> attributeToPrerequisitesMap, |
| AttributeMap attributes, |
| RuleErrorConsumer ruleErrorConsumer, |
| Rule rule, |
| String ruleClassNameForLogging) { |
| this( |
| attributeToPrerequisitesMap, |
| /* attributesMapper= */ attributes::getAttributeDefinition, |
| ruleErrorConsumer, |
| rule, |
| ruleClassNameForLogging); |
| } |
| |
| private PrerequisitesCollection( |
| ImmutableSortedKeyListMultimap<String, ConfiguredTargetAndData> attributeToPrerequisitesMap, |
| AttributesMapper attributesMapper, |
| RuleErrorConsumer ruleErrorConsumer, |
| Rule rule, |
| String ruleClassNameForLogging) { |
| this.attributeToPrerequisitesMap = attributeToPrerequisitesMap; |
| this.attributesMapper = attributesMapper; |
| this.ruleErrorConsumer = ruleErrorConsumer; |
| this.rule = rule; |
| this.ruleClassNameForLogging = ruleClassNameForLogging; |
| } |
| |
| boolean has(String attributeName) { |
| return attributesMapper.getAttribute(attributeName) != null; |
| } |
| |
| /** Returns a list of all prerequisites as {@code ConfiguredTarget} objects. */ |
| public ImmutableList<? extends TransitiveInfoCollection> getAllPrerequisites() { |
| return attributeToPrerequisitesMap.values().stream() |
| .map(ConfiguredTargetAndData::getConfiguredTarget) |
| .collect(toImmutableList()); |
| } |
| |
| /** Returns the {@link ConfiguredTargetAndData} the given attribute. */ |
| public List<ConfiguredTargetAndData> getPrerequisiteConfiguredTargets(String attributeName) { |
| return attributeToPrerequisitesMap.get(attributeName); |
| } |
| |
| /** |
| * Returns the list of transitive info collections that feed into this target through the |
| * specified attribute. |
| */ |
| public List<? extends TransitiveInfoCollection> getPrerequisites(String attributeName) { |
| Attribute attribute = attributesMapper.getAttribute(attributeName); |
| if (attribute == null) { |
| return ImmutableList.of(); |
| } |
| |
| List<ConfiguredTargetAndData> prerequisiteConfiguredTargets; |
| // android_binary, android_test, and android_binary_internal override deps to use a split |
| // transition. |
| if ((rule.getRuleClass().equals("android_binary") |
| || rule.getRuleClass().equals("android_test") |
| || rule.getRuleClass().equals("android_binary_internal")) |
| && attributeName.equals("deps") |
| && attribute.getTransitionFactory().isSplit()) { |
| // TODO(b/168038145): Restore legacy behavior of returning the prerequisites from the first |
| // portion of the split transition. |
| // Callers should be identified, cleaned up, and this check removed. |
| Map<Optional<String>, List<ConfiguredTargetAndData>> map = |
| getSplitPrerequisites(attributeName); |
| prerequisiteConfiguredTargets = |
| map.isEmpty() ? ImmutableList.of() : map.entrySet().iterator().next().getValue(); |
| } else { |
| prerequisiteConfiguredTargets = getPrerequisiteConfiguredTargets(attributeName); |
| } |
| |
| return Lists.transform( |
| prerequisiteConfiguredTargets, ConfiguredTargetAndData::getConfiguredTarget); |
| } |
| |
| /** |
| * Returns all the providers of the specified type that are listed under the specified attribute |
| * of this target in the BUILD file. |
| */ |
| public <C extends TransitiveInfoProvider> List<C> getPrerequisites( |
| String attributeName, Class<C> classType) { |
| AnalysisUtils.checkProvider(classType); |
| return AnalysisUtils.getProviders(getPrerequisites(attributeName), classType); |
| } |
| |
| /** |
| * Returns all the declared Starlark wrapped providers for the specified constructor under the |
| * specified attribute of this target in the BUILD file. |
| */ |
| public <T> ImmutableList<T> getPrerequisites( |
| String attributeName, StarlarkProviderWrapper<T> starlarkKey) throws RuleErrorException { |
| return AnalysisUtils.getProviders(getPrerequisites(attributeName), starlarkKey); |
| } |
| |
| /** |
| * Returns all the declared providers (native and Starlark) for the specified constructor under |
| * the specified attribute of this target in the BUILD file. |
| */ |
| public <T extends Info> List<T> getPrerequisites( |
| String attributeName, BuiltinProvider<T> builtinProvider) { |
| return AnalysisUtils.getProviders(getPrerequisites(attributeName), builtinProvider); |
| } |
| |
| /** |
| * Returns the prerequisites keyed by their transition keys. If the split transition is not active |
| * (e.g. split() returned an empty list), the key is an empty Optional. |
| */ |
| public Map<Optional<String>, List<ConfiguredTargetAndData>> getSplitPrerequisites( |
| String attributeName) { |
| checkAttributeIsDependency(attributeName); |
| // Use an ImmutableListMultimap.Builder here to preserve ordering. |
| ImmutableListMultimap.Builder<Optional<String>, ConfiguredTargetAndData> result = |
| ImmutableListMultimap.builder(); |
| List<ConfiguredTargetAndData> deps = getPrerequisiteConfiguredTargets(attributeName); |
| for (ConfiguredTargetAndData t : deps) { |
| ImmutableList<String> transitionKeys = t.getTransitionKeys(); |
| if (transitionKeys.isEmpty()) { |
| // The split transition is not active, i.e. does not change build configurations. |
| // TODO(jungjw): Investigate if we need to do a check here. |
| return ImmutableMap.of(Optional.absent(), deps); |
| } |
| for (String key : transitionKeys) { |
| result.put(Optional.of(key), t); |
| } |
| } |
| return Multimaps.asMap(result.build()); |
| } |
| |
| /** |
| * Returns the transitive info collection that feeds into this target through the specified |
| * attribute. Returns null if the attribute is empty. |
| */ |
| @Nullable |
| public TransitiveInfoCollection getPrerequisite(String attributeName) { |
| checkAttributeIsDependency(attributeName); |
| List<ConfiguredTargetAndData> elements = getPrerequisiteConfiguredTargets(attributeName); |
| Preconditions.checkState( |
| elements.size() <= 1, |
| "%s attribute %s produces more than one prerequisite", |
| ruleClassNameForLogging, |
| attributeName); |
| return elements.isEmpty() ? null : elements.get(0).getConfiguredTarget(); |
| } |
| |
| /** |
| * Returns the declared provider (native and Starlark) for the specified constructor under the |
| * specified attribute of this target in the BUILD file. May return null if there is no |
| * TransitiveInfoCollection under the specified attribute. |
| */ |
| @Nullable |
| public <T extends Info> T getPrerequisite( |
| String attributeName, BuiltinProvider<T> builtinProvider) { |
| TransitiveInfoCollection prerequisite = getPrerequisite(attributeName); |
| return prerequisite == null ? null : prerequisite.get(builtinProvider); |
| } |
| |
| /** |
| * Returns the specified provider of the prerequisite referenced by the attribute in the argument. |
| * If the attribute is empty or it does not support the specified provider, returns null. |
| */ |
| @Nullable |
| public <C extends TransitiveInfoProvider> C getPrerequisite( |
| String attributeName, Class<C> provider) { |
| TransitiveInfoCollection prerequisite = getPrerequisite(attributeName); |
| return prerequisite == null ? null : prerequisite.getProvider(provider); |
| } |
| |
| @Nullable |
| public <T> T getPrerequisite(String attributeName, StarlarkProviderWrapper<T> key) |
| throws RuleErrorException { |
| TransitiveInfoCollection prerequisite = getPrerequisite(attributeName); |
| return prerequisite == null ? null : prerequisite.get(key); |
| } |
| |
| /** |
| * For the specified attribute "attributeName" (which must be of type label), resolves the |
| * ConfiguredTarget and returns its single build artifact. |
| * |
| * <p>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) { |
| TransitiveInfoCollection target = getPrerequisite(attributeName); |
| return transitiveInfoCollectionToArtifact(attributeName, target); |
| } |
| |
| @Nullable |
| private Artifact transitiveInfoCollectionToArtifact( |
| String attributeName, TransitiveInfoCollection target) { |
| if (target != null) { |
| NestedSet<Artifact> artifacts = target.getProvider(FileProvider.class).getFilesToBuild(); |
| if (artifacts.isSingleton()) { |
| return artifacts.getSingleton(); |
| } else { |
| ruleErrorConsumer.attributeError( |
| attributeName, target.getLabel() + " expected a single artifact"); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * 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. |
| * |
| * @param attributeName the name of the attribute |
| * @return the {@link FilesToRunProvider} interface of the prerequisite. |
| */ |
| @Nullable |
| public FilesToRunProvider getExecutablePrerequisite(String attributeName) { |
| Attribute ruleDefinition = attributesMapper.getAttribute(attributeName); |
| |
| Preconditions.checkNotNull( |
| ruleDefinition, "%s attribute %s is not defined", ruleClassNameForLogging, attributeName); |
| Preconditions.checkState( |
| ruleDefinition.isExecutable(), |
| "%s attribute %s is not configured to be executable", |
| ruleClassNameForLogging, |
| attributeName); |
| |
| TransitiveInfoCollection prerequisite = getPrerequisite(attributeName); |
| if (prerequisite == null) { |
| return null; |
| } |
| |
| FilesToRunProvider result = prerequisite.getProvider(FilesToRunProvider.class); |
| if (result == null || result.getExecutable() == null) { |
| ruleErrorConsumer.attributeError( |
| attributeName, prerequisite.getLabel() + " does not refer to a valid executable target"); |
| } |
| return result; |
| } |
| |
| /** |
| * 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 <C extends TransitiveInfoProvider> |
| Iterable<? extends TransitiveInfoCollection> getPrerequisitesIf( |
| String attributeName, Class<C> classType) { |
| AnalysisUtils.checkProvider(classType); |
| return AnalysisUtils.filterByProvider(getPrerequisites(attributeName), classType); |
| } |
| |
| private void checkAttributeIsDependency(String attributeName) { |
| Attribute attributeDefinition = attributesMapper.getAttribute(attributeName); |
| Preconditions.checkNotNull( |
| attributeDefinition, |
| "%s: %s attribute %s is not defined", |
| rule.getLocation(), |
| ruleClassNameForLogging, |
| attributeName); |
| Preconditions.checkState( |
| attributeDefinition.getType().getLabelClass() == LabelClass.DEPENDENCY, |
| "%s attribute %s is not a label type attribute", |
| ruleClassNameForLogging, |
| attributeName); |
| } |
| } |