| // Copyright 2018 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.query2.output; |
| |
| import com.google.common.collect.HashMultimap; |
| import com.google.common.collect.SetMultimap; |
| import com.google.devtools.build.lib.cmdline.Label; |
| 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.BuildType; |
| import com.google.devtools.build.lib.packages.RawAttributeMapper; |
| import com.google.devtools.build.lib.packages.Rule; |
| import com.google.devtools.build.lib.packages.Target; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.Set; |
| |
| /** |
| * Utility class to hold all conditional edges in a graph. Allows easy look-up of all conditions |
| * between two nodes. |
| */ |
| public class ConditionalEdges { |
| // Map containing all the conditions for all the conditional edges in a graph. |
| private HashMap< |
| Label /* src node */, SetMultimap<Label /* dest node */, Label /* condition labels */>> |
| map; |
| |
| public ConditionalEdges() {} |
| |
| /** Builds ConditionalEdges from given graph. */ |
| public ConditionalEdges(Digraph<Target> graph) { |
| this.map = new HashMap<>(); |
| |
| for (Node<Target> node : graph.getNodes()) { |
| Rule rule = node.getLabel().getAssociatedRule(); |
| if (rule == null) { |
| // rule is null for source files and package groups. Skip them. |
| continue; |
| } |
| |
| SetMultimap<Label, Label> conditions = getAllConditions(rule, RawAttributeMapper.of(rule)); |
| if (conditions.isEmpty()) { |
| // bail early for most common case of no conditions in the rule. |
| continue; |
| } |
| |
| Label nodeLabel = node.getLabel().getLabel(); |
| for (Node<Target> succ : node.getSuccessors()) { |
| Label successorLabel = succ.getLabel().getLabel(); |
| if (conditions.containsKey(successorLabel)) { |
| insert(nodeLabel, successorLabel, conditions.get(successorLabel)); |
| } |
| } |
| } |
| } |
| |
| /** Inserts `conditions` for edge src --> dest. */ |
| public void insert(Label src, Label dest, Set<Label> conditions) { |
| map.computeIfAbsent(src, (k) -> HashMultimap.create()); |
| map.get(src).putAll(dest, conditions); |
| } |
| |
| /** |
| * Returns all conditions for edge src --> dest, if they exist. Does not return default |
| * conditions. |
| */ |
| public Optional<Set<Label>> get(Label src, Label dest) { |
| if (!map.containsKey(src) || !map.get(src).containsKey(dest)) { |
| return Optional.empty(); |
| } |
| |
| return Optional.of(map.get(src).get(dest)); |
| } |
| |
| /** |
| * Returns map of dependency to list of condition-labels. |
| * |
| * <p>Example: For a rule like below, |
| * |
| * <pre> |
| * some_rule( |
| * ... |
| * deps = [ |
| * ... default dependencies ... |
| * ] + select ({ |
| * "//some:config1": [ "//some:a", "//some:common" ], |
| * "//some:config2": [ "//other:a", "//some:common" ], |
| * "//conditions:default": [ "//some:default" ], |
| * }) |
| * ) |
| * </pre> |
| * |
| * it returns following map: |
| * |
| * <pre> |
| * { |
| * "//some:a": ["//some:config1" ] |
| * "//other:a": ["//some:config2" ] |
| * "//some:common": ["//some:config1", "//some:config2" ] |
| * "//some:default": [ "//conditions:default" ] |
| * } |
| * </pre> |
| */ |
| private SetMultimap<Label, Label> getAllConditions(Rule rule, RawAttributeMapper attributeMap) { |
| SetMultimap<Label, Label> conditions = HashMultimap.create(); |
| for (Attribute attr : rule.getAttributes()) { |
| // TODO(bazel-team): Handle the case where dependency exists through both configurable as well |
| // as non-configurable attributes. Currently this prints such an edge as a conditional one. |
| if (!attributeMap.isConfigurable(attr.getName())) { |
| // skip non configurable attributes |
| continue; |
| } |
| |
| for (BuildType.Selector<?> selector : |
| ((BuildType.SelectorList<?>) attributeMap.getRawAttributeValue(rule, attr)) |
| .getSelectors()) { |
| if (selector.isUnconditional()) { |
| // skip unconditional selectors |
| continue; |
| } |
| for (Map.Entry<Label, ?> entry : selector.getEntries().entrySet()) { |
| if (entry.getValue() instanceof List<?>) { |
| List<?> deps = (List<?>) entry.getValue(); |
| for (Object dep : deps) { |
| if (dep instanceof Label) { |
| conditions.put((Label) dep, entry.getKey()); |
| } |
| } |
| } else if (entry.getValue() instanceof Label) { |
| conditions.put((Label) entry.getValue(), entry.getKey()); |
| } |
| } |
| } |
| } |
| return conditions; |
| } |
| }; |