/*
* Copyright 2014-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.graph.AbstractBreadthFirstTraversal;
import com.facebook.buck.graph.DirectedAcyclicGraph;
import com.facebook.buck.graph.MutableDirectedGraph;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.util.ExceptionWithHumanReadableMessage;
import com.facebook.buck.util.MoreMaps;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;
/**
* Represents the graph of {@link com.facebook.buck.rules.TargetNode}s constructed by parsing the
* build files.
*/
public class TargetGraph extends DirectedAcyclicGraph<TargetNode<?, ?>> {
public static final TargetGraph EMPTY =
new TargetGraph(new MutableDirectedGraph<>(), ImmutableMap.of());
private final ImmutableMap<BuildTarget, TargetNode<?, ?>> targetsToNodes;
public TargetGraph(
MutableDirectedGraph<TargetNode<?, ?>> graph,
ImmutableMap<BuildTarget, TargetNode<?, ?>> index) {
super(graph);
this.targetsToNodes = index;
verifyVisibilityIntegrity();
}
private void verifyVisibilityIntegrity() {
for (TargetNode<?, ?> node : getNodes()) {
for (TargetNode<?, ?> dep : getOutgoingNodesFor(node)) {
dep.isVisibleToOrThrow(node);
}
}
}
@Nullable
protected TargetNode<?, ?> getInternal(BuildTarget target) {
TargetNode<?, ?> node = targetsToNodes.get(target);
if (node == null) {
node = targetsToNodes.get(BuildTarget.of(target.getUnflavoredBuildTarget()));
if (node == null) {
return null;
}
return node.copyWithFlavors(target.getFlavors());
}
return node;
}
public Optional<TargetNode<?, ?>> getOptional(BuildTarget target) {
return Optional.ofNullable(getInternal(target));
}
public TargetNode<?, ?> get(BuildTarget target) {
TargetNode<?, ?> node = getInternal(target);
if (node == null) {
throw new NoSuchNodeException(target);
}
return node;
}
public Iterable<TargetNode<?, ?>> getAll(Iterable<BuildTarget> targets) {
return Iterables.transform(targets, this::get);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof TargetGraph)) {
return false;
}
return targetsToNodes.equals(((TargetGraph) obj).targetsToNodes);
}
@Override
public int hashCode() {
return targetsToNodes.hashCode();
}
/**
* Get the subgraph of the the current graph containing the passed in roots and all of their
* transitive dependencies as nodes. Edges between the included nodes are preserved.
*
* @param roots An iterable containing the roots of the new subgraph.
* @return A subgraph of the current graph.
*/
public <T> TargetGraph getSubgraph(Iterable<? extends TargetNode<? extends T, ?>> roots) {
final MutableDirectedGraph<TargetNode<?, ?>> subgraph = new MutableDirectedGraph<>();
final Map<BuildTarget, TargetNode<?, ?>> index = new HashMap<>();
new AbstractBreadthFirstTraversal<TargetNode<?, ?>>(roots) {
@Override
public ImmutableSet<TargetNode<?, ?>> visit(TargetNode<?, ?> node) {
subgraph.addNode(node);
MoreMaps.putCheckEquals(index, node.getBuildTarget(), node);
if (node.getBuildTarget().isFlavored()) {
BuildTarget unflavoredBuildTarget =
BuildTarget.of(node.getBuildTarget().getUnflavoredBuildTarget());
MoreMaps.putCheckEquals(
index, unflavoredBuildTarget, targetsToNodes.get(unflavoredBuildTarget));
}
ImmutableSet<TargetNode<?, ?>> dependencies =
ImmutableSet.copyOf(getAll(node.getParseDeps()));
for (TargetNode<?, ?> dependency : dependencies) {
subgraph.addEdge(node, dependency);
}
return dependencies;
}
}.start();
return new TargetGraph(subgraph, ImmutableMap.copyOf(index));
}
@SuppressWarnings("serial")
public static class NoSuchNodeException extends RuntimeException
implements ExceptionWithHumanReadableMessage {
public NoSuchNodeException(BuildTarget buildTarget) {
super(
String.format(
"Required target for rule '%s' was not found in the target graph.",
buildTarget.getFullyQualifiedName()));
}
@Override
public String getHumanReadableErrorMessage() {
return getMessage();
}
}
}