/*
* Copyright 2016-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.distributed;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.BuildRuleResolver;
import com.google.common.base.Preconditions;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
public class BuildTargetsQueue {
private List<EnqueuedTarget> zeroDependencyTargets;
private final Map<String, EnqueuedTarget> allEnqueuedTargets;
private BuildTargetsQueue(
List<EnqueuedTarget> zeroDependencyTargets, Map<String, EnqueuedTarget> allEnqueuedTargets) {
this.zeroDependencyTargets = zeroDependencyTargets;
this.allEnqueuedTargets = allEnqueuedTargets;
}
public static BuildTargetsQueue newEmptyQueue() {
return new BuildTargetsQueue(new ArrayList<>(), new HashMap<>());
}
public static BuildTargetsQueue newQueue(
BuildRuleResolver resolver, Iterable<BuildTarget> targetsToBuild) {
// Build the reverse dependency graph by traversing the action graph Top-Down.
Map<String, Set<String>> allReverseDeps = new HashMap<>();
Map<String, Integer> numberOfDependencies = new HashMap<>();
Set<String> visitedTargets = Sets.newHashSet();
Queue<BuildRule> buildRulesToProcess =
Lists.newLinkedList(
FluentIterable.from(targetsToBuild)
.transform(
x -> {
BuildRule rule = resolver.getRule(x);
visitedTargets.add(ruleToTarget(rule));
return rule;
}));
while (!buildRulesToProcess.isEmpty()) {
BuildRule rule = buildRulesToProcess.remove();
String target = ruleToTarget(rule);
numberOfDependencies.put(target, rule.getBuildDeps().size());
for (BuildRule dependencyRule : rule.getBuildDeps()) {
String dependencyTarget = ruleToTarget(dependencyRule);
if (!allReverseDeps.containsKey(dependencyTarget)) {
allReverseDeps.put(dependencyTarget, Sets.newHashSet());
}
allReverseDeps.get(dependencyTarget).add(target);
if (!visitedTargets.contains(dependencyTarget)) {
visitedTargets.add(dependencyTarget);
buildRulesToProcess.add(dependencyRule);
}
}
}
// Do the reference counting and create the EnqueuedTargets.
List<EnqueuedTarget> zeroDependencyTargets = new ArrayList<>();
Map<String, EnqueuedTarget> allEnqueuedTargets = new HashMap<>();
for (String target : visitedTargets) {
Iterable<String> currentRevDeps = null;
if (allReverseDeps.containsKey(target)) {
currentRevDeps = allReverseDeps.get(target);
} else {
currentRevDeps = new ArrayList<>();
}
EnqueuedTarget enqueuedTarget =
new EnqueuedTarget(
target,
ImmutableList.copyOf(currentRevDeps),
Preconditions.checkNotNull(numberOfDependencies.get(target)));
allEnqueuedTargets.put(target, enqueuedTarget);
if (enqueuedTarget.areAllDependenciesResolved()) {
zeroDependencyTargets.add(enqueuedTarget);
}
}
return new BuildTargetsQueue(zeroDependencyTargets, allEnqueuedTargets);
}
public ImmutableList<String> dequeueZeroDependencyNodes(ImmutableList<String> finishedNodes) {
// Decrement reference counting.
for (String node : finishedNodes) {
EnqueuedTarget target = Preconditions.checkNotNull(allEnqueuedTargets.get(node));
for (String dependent : target.getDependentTargets()) {
EnqueuedTarget dep = Preconditions.checkNotNull(allEnqueuedTargets.get(dependent));
dep.decrementUnsatisfiedDeps();
if (dep.areAllDependenciesResolved()) {
zeroDependencyTargets.add(dep);
}
}
}
// Return all the Targets that have all dependencies resolved.
ImmutableList<String> targetsReadyToBuild =
ImmutableList.copyOf(
FluentIterable.from(zeroDependencyTargets).transform(x -> x.getBuildTarget()));
zeroDependencyTargets.clear();
return targetsReadyToBuild;
}
private static String ruleToTarget(BuildRule rule) {
return rule.getFullyQualifiedName();
}
private static class EnqueuedTarget {
private final String buildTarget;
private final ImmutableList<String> dependentTargets;
private int unsatisfiedDependencies;
private EnqueuedTarget(
String buildTarget, ImmutableList<String> dependentTargets, int numberOfDependencies) {
this.buildTarget = buildTarget;
this.dependentTargets = dependentTargets;
this.unsatisfiedDependencies = numberOfDependencies;
}
public boolean areAllDependenciesResolved() {
return 0 == unsatisfiedDependencies;
}
public String getBuildTarget() {
return buildTarget;
}
public ImmutableList<String> getDependentTargets() {
return dependentTargets;
}
public void decrementUnsatisfiedDeps() {
--unsatisfiedDependencies;
Preconditions.checkArgument(
unsatisfiedDependencies >= 0,
"The number of unsatisfied dependencies can never be negative.");
}
@Override
public String toString() {
return "EnqueuedTarget{"
+ "buildTarget='"
+ buildTarget
+ '\''
+ ", unsatisfiedDependencies="
+ unsatisfiedDependencies
+ ", dependentTargets="
+ dependentTargets
+ '}';
}
}
}