| // 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.pkgcache; |
| |
| import com.google.common.collect.Lists; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.cmdline.ResolvedTargets; |
| import com.google.devtools.build.lib.cmdline.TargetParsingException; |
| import com.google.devtools.build.lib.events.Event; |
| import com.google.devtools.build.lib.events.EventHandler; |
| import com.google.devtools.build.lib.packages.Attribute; |
| import com.google.devtools.build.lib.packages.BuildType; |
| import com.google.devtools.build.lib.packages.DependencyFilter; |
| import com.google.devtools.build.lib.packages.FileTarget; |
| import com.google.devtools.build.lib.packages.NoSuchThingException; |
| import com.google.devtools.build.lib.packages.Package; |
| 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.Target; |
| import com.google.devtools.build.lib.util.BinaryPredicate; |
| |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.List; |
| |
| /** |
| * Implementation of --compile_one_dependency. |
| */ |
| public final class CompileOneDependencyTransformer { |
| private final TargetProvider targetProvider; |
| |
| public CompileOneDependencyTransformer(TargetProvider targetProvider) { |
| this.targetProvider = targetProvider; |
| } |
| |
| /** |
| * For each input file in the original result, returns a rule in the same package which has the |
| * input file as a source. |
| */ |
| public ResolvedTargets<Target> transformCompileOneDependency(EventHandler eventHandler, |
| ResolvedTargets<Target> original) throws TargetParsingException { |
| if (original.hasError()) { |
| return original; |
| } |
| ResolvedTargets.Builder<Target> builder = ResolvedTargets.builder(); |
| for (Target target : original.getTargets()) { |
| builder.add(transformCompileOneDependency(eventHandler, target)); |
| } |
| return builder.build(); |
| } |
| |
| /** |
| * Returns a list of rules in the given package sorted by BUILD file order. When |
| * multiple rules depend on a target, we choose the first match in this list (after |
| * filtering for preferred dependencies - see below). |
| */ |
| private Iterable<Rule> getOrderedRuleList(Package pkg) { |
| List<Rule> orderedList = Lists.newArrayList(); |
| for (Rule rule : pkg.getTargets(Rule.class)) { |
| orderedList.add(rule); |
| } |
| |
| Collections.sort(orderedList, new Comparator<Rule>() { |
| @Override |
| public int compare(Rule o1, Rule o2) { |
| return Integer.compare( |
| o1.getLocation().getStartOffset(), |
| o2.getLocation().getStartOffset()); |
| } |
| }); |
| return orderedList; |
| } |
| |
| private Target transformCompileOneDependency(EventHandler eventHandler, Target target) |
| throws TargetParsingException { |
| if (!(target instanceof FileTarget)) { |
| throw new TargetParsingException("--compile_one_dependency target '" + |
| target.getLabel() + "' must be a file"); |
| } |
| |
| Package pkg = target.getPackage(); |
| |
| Iterable<Rule> orderedRuleList = getOrderedRuleList(pkg); |
| // Consuming rule to return if no "preferred" rules have been found. |
| Rule fallbackRule = null; |
| |
| for (Rule rule : orderedRuleList) { |
| try { |
| // The call to getSrcTargets here can be removed in favor of the |
| // rule.getLabels() call below once we update "srcs" for all rules. |
| if (SrcTargetUtil.getSrcTargets(eventHandler, rule, targetProvider).contains(target)) { |
| if (rule.getRuleClassObject().isPreferredDependency(target.getName())) { |
| return rule; |
| } else if (fallbackRule == null) { |
| fallbackRule = rule; |
| } |
| } |
| } catch (NoSuchThingException e) { |
| // Nothing to see here. Move along. |
| } catch (InterruptedException e) { |
| throw new TargetParsingException("interrupted"); |
| } |
| } |
| |
| Rule result = null; |
| |
| // For each rule, see if it has directCompileTimeInputAttribute, |
| // and if so check the targets listed in that attribute match the label. |
| BinaryPredicate<Rule, Attribute> directCompileTimeInput = |
| new BinaryPredicate<Rule, Attribute>() { |
| @Override |
| public boolean apply(Rule rule, Attribute attribute) { |
| return DependencyFilter.DIRECT_COMPILE_TIME_INPUT.apply(rule, attribute) |
| // We don't know which path to follow for configurable attributes, so skip them. |
| && !rule.isConfigurableAttribute(attribute.getName()); |
| } |
| }; |
| for (Rule rule : orderedRuleList) { |
| if (rule.getLabels(directCompileTimeInput).contains(target.getLabel())) { |
| if (rule.getRuleClassObject().isPreferredDependency(target.getName())) { |
| result = rule; |
| } else if (fallbackRule == null) { |
| fallbackRule = rule; |
| } |
| } |
| } |
| |
| if (result == null) { |
| result = fallbackRule; |
| } |
| |
| if (result == null) { |
| throw new TargetParsingException( |
| "Couldn't find dependency on target '" + target.getLabel() + "'"); |
| } |
| |
| try { |
| // If the rule has source targets, return it. |
| if (!SrcTargetUtil.getSrcTargets(eventHandler, result, targetProvider).isEmpty()) { |
| return result; |
| } |
| } catch (NoSuchThingException e) { |
| eventHandler.handle(Event.error(e.getMessage())); |
| throw new TargetParsingException( |
| "Couldn't find dependency on target '" + target.getLabel() + "'"); |
| } catch (InterruptedException e) { |
| throw new TargetParsingException("interrupted"); |
| } |
| |
| for (Rule rule : orderedRuleList) { |
| RawAttributeMapper attributes = RawAttributeMapper.of(rule); |
| // We don't know which path to follow for configurable attributes, so skip them. |
| if (attributes.isConfigurable("deps", BuildType.LABEL_LIST) |
| || attributes.isConfigurable("srcs", BuildType.LABEL_LIST)) { |
| continue; |
| } |
| RuleClass ruleClass = rule.getRuleClassObject(); |
| if (ruleClass.hasAttr("deps", BuildType.LABEL_LIST) && |
| ruleClass.hasAttr("srcs", BuildType.LABEL_LIST)) { |
| for (Label dep : attributes.get("deps", BuildType.LABEL_LIST)) { |
| if (dep.equals(result.getLabel())) { |
| if (!attributes.get("srcs", BuildType.LABEL_LIST).isEmpty()) { |
| return rule; |
| } |
| } |
| } |
| } |
| } |
| |
| throw new TargetParsingException( |
| "Couldn't find dependency on target '" + target.getLabel() + "'"); |
| } |
| } |