/* * Copyright 2012-present Facebook, Inc. * * 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.facebook.buck.rules; import com.facebook.buck.event.BuckEventBus; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.Pair; import com.facebook.buck.parser.NoSuchBuildTargetException; import com.facebook.buck.util.HumanReadableException; import com.facebook.buck.util.RichStream; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; import com.google.common.collect.Ordering; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import javax.annotation.Nullable; /** * Provides a mechanism for mapping between a {@link BuildTarget} and the {@link BuildRule} it * represents. Once parsing is complete, instances of this class can be considered immutable. */ public class BuildRuleResolver { private final TargetGraph targetGraph; private final TargetNodeToBuildRuleTransformer buildRuleGenerator; /** Event bus for reporting performance information. Will likely be null in unit tests. */ @Nullable private final BuckEventBus eventBus; private final ConcurrentHashMap<BuildTarget, BuildRule> buildRuleIndex; private final LoadingCache<Pair<BuildTarget, Class<?>>, Optional<?>> metadataCache; public BuildRuleResolver( TargetGraph targetGraph, TargetNodeToBuildRuleTransformer buildRuleGenerator) { this(targetGraph, buildRuleGenerator, null); } public BuildRuleResolver( TargetGraph targetGraph, TargetNodeToBuildRuleTransformer buildRuleGenerator, @Nullable BuckEventBus eventBus) { this.targetGraph = targetGraph; this.buildRuleGenerator = buildRuleGenerator; this.eventBus = eventBus; // We preallocate our maps to have this amount of slots to get rid of re-allocations final int initialCapacity = (int) (targetGraph.getNodes().size() * 5 * 1.1); this.buildRuleIndex = new ConcurrentHashMap<>(initialCapacity); this.metadataCache = CacheBuilder.newBuilder() .initialCapacity(initialCapacity) .build( new CacheLoader<Pair<BuildTarget, Class<?>>, Optional<?>>() { @Override public Optional<?> load(Pair<BuildTarget, Class<?>> key) throws Exception { TargetNode<?, ?> node = BuildRuleResolver.this.targetGraph.get(key.getFirst()); return load(node, key.getSecond()); } @SuppressWarnings("unchecked") private <T, U> Optional<U> load(TargetNode<T, ?> node, Class<U> metadataClass) throws NoSuchBuildTargetException { T arg = node.getConstructorArg(); if (metadataClass.isAssignableFrom(arg.getClass())) { return Optional.of(metadataClass.cast(arg)); } Description<?> description = node.getDescription(); if (!(description instanceof MetadataProvidingDescription)) { return Optional.empty(); } MetadataProvidingDescription<T> metadataProvidingDescription = (MetadataProvidingDescription<T>) description; return metadataProvidingDescription.createMetadata( node.getBuildTarget(), BuildRuleResolver.this, arg, node.getSelectedVersions(), metadataClass); } }); } /** @return an unmodifiable view of the rules in the index */ public Iterable<BuildRule> getBuildRules() { return Iterables.unmodifiableIterable(buildRuleIndex.values()); } private <T> T fromNullable(BuildTarget target, @Nullable T rule) { if (rule == null) { throw new HumanReadableException("Rule for target '%s' could not be resolved.", target); } return rule; } /** Returns the {@link BuildRule} with the {@code buildTarget}. */ public BuildRule getRule(BuildTarget buildTarget) { return fromNullable(buildTarget, buildRuleIndex.get(buildTarget)); } public Optional<BuildRule> getRuleOptional(BuildTarget buildTarget) { return Optional.ofNullable(buildRuleIndex.get(buildTarget)); } public BuildRule requireRule(BuildTarget target) throws NoSuchBuildTargetException { BuildRule rule = buildRuleIndex.get(target); if (rule != null) { return rule; } TargetNode<?, ?> node = targetGraph.get(target); rule = buildRuleGenerator.transform(targetGraph, this, node); Preconditions.checkState( // TODO(jakubzika): This should hold for flavored build targets as well. rule.getBuildTarget().getUnflavoredBuildTarget().equals(target.getUnflavoredBuildTarget()), "Description returned rule for '%s' instead of '%s'.", rule.getBuildTarget(), target); BuildRule oldRule = buildRuleIndex.put(target, rule); Preconditions.checkState( // TODO(jakubzika): Eventually we should be able to remove the oldRule == rule part. // For now we need it to handle cases where a description adds a rule to the index before // returning it. oldRule == null || oldRule == rule, "Multiple rules created for target '%s':\n" + "new rule '%s' does not match existing rule '%s'.", target, rule, oldRule); return rule; } public ImmutableSortedSet<BuildRule> requireAllRules(Iterable<BuildTarget> buildTargets) throws NoSuchBuildTargetException { ImmutableSortedSet.Builder<BuildRule> rules = ImmutableSortedSet.naturalOrder(); for (BuildTarget target : buildTargets) { rules.add(requireRule(target)); } return rules.build(); } @SuppressWarnings("unchecked") public <T> Optional<T> requireMetadata(BuildTarget target, Class<T> metadataClass) throws NoSuchBuildTargetException { try { return (Optional<T>) metadataCache.get(new Pair<BuildTarget, Class<?>>(target, metadataClass)); } catch (ExecutionException e) { Throwables.throwIfInstanceOf(e.getCause(), NoSuchBuildTargetException.class); throw new RuntimeException(e); } } @SuppressWarnings("unchecked") public <T> Optional<T> getRuleOptionalWithType(BuildTarget buildTarget, Class<T> cls) { BuildRule rule = buildRuleIndex.get(buildTarget); if (rule != null) { if (cls.isInstance(rule)) { return Optional.of((T) rule); } else { throw new HumanReadableException( "Rule for target '%s' is present but not of expected type %s (got %s)", buildTarget, cls, rule.getClass()); } } return Optional.empty(); } public <T> T getRuleWithType(BuildTarget buildTarget, Class<T> cls) { return fromNullable(buildTarget, getRuleOptionalWithType(buildTarget, cls).orElse(null)); } public ImmutableSortedSet<BuildRule> getAllRules(Iterable<BuildTarget> targets) { return getAllRulesStream(targets).toImmutableSortedSet(Ordering.natural()); } public RichStream<BuildRule> getAllRulesStream(Iterable<BuildTarget> targets) { return RichStream.from(targets).map(this::getRule); } /** * Adds to the index a mapping from {@code buildRule}'s target to itself and returns {@code * buildRule}. */ @VisibleForTesting public <T extends BuildRule> T addToIndex(T buildRule) { BuildRule oldValue = buildRuleIndex.put(buildRule.getBuildTarget(), buildRule); // Yuck! This is here to make it possible for a rule to depend on a flavor of itself but it // would be much much better if we just got rid of the BuildRuleResolver entirely. if (oldValue != null && oldValue != buildRule) { throw new IllegalStateException( "A build rule for this target has already been created: " + oldValue.getBuildTarget()); } return buildRule; } /** Adds an iterable of build rules to the index. */ public <T extends BuildRule, C extends Iterable<T>> C addAllToIndex(C buildRules) { for (T buildRule : buildRules) { addToIndex(buildRule); } return buildRules; } @Nullable public BuckEventBus getEventBus() { return eventBus; } }