/*
* 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.rules;
import com.facebook.buck.event.BuckEventBus;
import com.facebook.buck.model.BuildTarget;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Keeps track of the number of build rules that either were already executed or might be executed
* in the future. This class assumes that a rule that was not yet executed might be executed in the
* future if either there is a dependency path from a top level rule to this rule such that no rule
* on the path was yet executed or the rule is a runtime dependency of a rule fulfilling the
* previous condition. This model is consistent with {@link CachingBuildEngine}'s shallow build
* mode. The class uses reference counting to efficiently keep track of those rules.
*/
public class UnskippedRulesTracker {
private static final CacheLoader<BuildTarget, AtomicInteger> DEFAULT_REFERENCE_COUNT_LOADER =
new CacheLoader<BuildTarget, AtomicInteger>() {
@Override
public AtomicInteger load(BuildTarget ignored) {
return new AtomicInteger(0);
}
};
private final RuleDepsCache ruleDepsCache;
private final BuildRuleResolver ruleResolver;
private final AtomicInteger unskippedRules = new AtomicInteger(0);
private final AtomicBoolean stateChanged = new AtomicBoolean(false);
private final LoadingCache<BuildTarget, AtomicInteger> ruleReferenceCounts =
CacheBuilder.newBuilder().build(DEFAULT_REFERENCE_COUNT_LOADER);
public UnskippedRulesTracker(RuleDepsCache ruleDepsCache, BuildRuleResolver ruleResolver) {
this.ruleDepsCache = ruleDepsCache;
this.ruleResolver = ruleResolver;
}
public void registerTopLevelRule(BuildRule rule, final BuckEventBus eventBus) {
// Add a reference to the top-level rule so that it is never marked as skipped.
acquireReference(rule);
sendEventIfStateChanged(eventBus);
}
public void markRuleAsUsed(final BuildRule rule, final BuckEventBus eventBus) {
// Add a reference to the used rule so that it is never marked as skipped.
acquireReference(rule);
if (rule instanceof HasRuntimeDeps) {
// Add references to rule's runtime deps since they cannot be skipped now.
ruleResolver
.getAllRules(((HasRuntimeDeps) rule).getRuntimeDeps()::iterator)
.forEach(this::acquireReference);
}
// Release references from rule's dependencies since this rule will not need them anymore.
ruleDepsCache.get(rule).forEach(this::releaseReference);
sendEventIfStateChanged(eventBus);
}
private void sendEventIfStateChanged(BuckEventBus eventBus) {
if (stateChanged.getAndSet(false)) {
eventBus.post(BuildEvent.unskippedRuleCountUpdated(unskippedRules.get()));
}
}
// TODO(cjhopman): convert these to be non-recursive.
private void acquireReference(BuildRule rule) {
AtomicInteger referenceCount = ruleReferenceCounts.getUnchecked(rule.getBuildTarget());
int newValue = referenceCount.incrementAndGet();
if (newValue == 1) {
// 0 -> 1 transition means that the rule might be used in the future (not skipped for now).
unskippedRules.incrementAndGet();
stateChanged.set(true);
// Add references to all dependencies of the rule.
ruleDepsCache.get(rule).forEach(this::acquireReference);
}
}
private void releaseReference(BuildRule rule) {
AtomicInteger referenceCount = ruleReferenceCounts.getUnchecked(rule.getBuildTarget());
int newValue = referenceCount.decrementAndGet();
if (newValue == 0) {
// 1 -> 0 transition means that the rule can be marked as skipped.
unskippedRules.decrementAndGet();
stateChanged.set(true);
// Remove references from all dependencies of the rule.
ruleDepsCache.get(rule).forEach(this::releaseReference);
}
}
}