blob: acf40c8806eea7187b7575167c01c5d6cd53919f [file] [log] [blame]
// 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.skyframe;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.flogger.GoogleLogger;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.skyframe.DiffAwareness.View;
import com.google.devtools.build.lib.vfs.ModifiedFileSet;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.common.options.OptionsProvider;
import java.util.Map;
import javax.annotation.Nullable;
/**
* Helper class to make it easier to correctly use the {@link DiffAwareness} interface in a
* sequential manner.
*/
public final class DiffAwarenessManager {
private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
// The manager attempts to instantiate these in the order in which they are passed to the
// constructor; this is critical in the case where a factory always succeeds.
private final ImmutableList<? extends DiffAwareness.Factory> diffAwarenessFactories;
/** The unique key to retrieve a DiffAwarenessState. */
@AutoValue
public abstract static class StateKey {
private static StateKey create(Root root, ImmutableSet<Path> ignoredPaths) {
return new AutoValue_DiffAwarenessManager_StateKey(root, ignoredPaths);
}
abstract Root root();
abstract ImmutableSet<Path> ignoredPaths();
}
private final Map<StateKey, DiffAwarenessState> currentDiffAwarenessStates = Maps.newHashMap();
public DiffAwarenessManager(Iterable<? extends DiffAwareness.Factory> diffAwarenessFactories) {
this.diffAwarenessFactories = ImmutableList.copyOf(diffAwarenessFactories);
}
private static class DiffAwarenessState {
private final DiffAwareness diffAwareness;
/**
* The {@link View} that should be the baseline for the next {@link #getDiff} call, or
* {@code null} if the next {@link #getDiff} will be the first incremental one.
*/
@Nullable
private View baselineView;
private DiffAwarenessState(DiffAwareness diffAwareness, @Nullable View baselineView) {
this.diffAwareness = diffAwareness;
this.baselineView = baselineView;
}
}
/** Reset internal {@link DiffAwareness} state. */
public void reset() {
for (DiffAwarenessState diffAwarenessState : currentDiffAwarenessStates.values()) {
diffAwarenessState.diffAwareness.close();
}
currentDiffAwarenessStates.clear();
}
/** A set of modified files that should be marked as processed. */
public interface ProcessableModifiedFileSet {
ModifiedFileSet getModifiedFileSet();
@Nullable
WorkspaceInfoFromDiff getWorkspaceInfo();
/**
* This should be called when the changes have been noted. Otherwise, the result from the next
* call to {@link #getDiff} will be from the baseline of the old, unprocessed, diff.
*/
void markProcessed();
}
/**
* Gets the set of changed files since the last call with this path entry, or {@code
* ModifiedFileSet.EVERYTHING_MODIFIED} if this is the first such call.
*/
public ProcessableModifiedFileSet getDiff(
EventHandler eventHandler,
Root pathEntry,
ImmutableSet<Path> ignoredPaths,
OptionsProvider options)
throws InterruptedException {
DiffAwarenessState diffAwarenessState = maybeGetDiffAwarenessState(pathEntry, ignoredPaths);
if (diffAwarenessState == null) {
return BrokenProcessableModifiedFileSet.INSTANCE;
}
DiffAwareness diffAwareness = diffAwarenessState.diffAwareness;
View newView;
try {
newView = diffAwareness.getCurrentView(options);
} catch (BrokenDiffAwarenessException e) {
handleBrokenDiffAwareness(eventHandler, pathEntry, ignoredPaths, e);
return BrokenProcessableModifiedFileSet.INSTANCE;
}
View baselineView = diffAwarenessState.baselineView;
if (baselineView == null) {
logger.atInfo().log("Initial baseline view for %s is %s", pathEntry, newView);
diffAwarenessState.baselineView = newView;
return new InitialModifiedFileSet(newView.getWorkspaceInfo());
}
ModifiedFileSet diff;
logger.atInfo().log(
"About to compute diff between %s and %s for %s", baselineView, newView, pathEntry);
try {
diff = diffAwareness.getDiff(baselineView, newView);
} catch (BrokenDiffAwarenessException e) {
handleBrokenDiffAwareness(eventHandler, pathEntry, ignoredPaths, e);
return BrokenProcessableModifiedFileSet.INSTANCE;
} catch (IncompatibleViewException e) {
throw new IllegalStateException(pathEntry + " " + baselineView + " " + newView, e);
}
return new ProcessableModifiedFileSetImpl(diff, pathEntry, ignoredPaths, newView);
}
private void handleBrokenDiffAwareness(
EventHandler eventHandler,
Root pathEntry,
ImmutableSet<Path> ignoredPaths,
BrokenDiffAwarenessException e) {
StateKey stateKey = StateKey.create(pathEntry, ignoredPaths);
currentDiffAwarenessStates.remove(stateKey);
logger.atInfo().withCause(e).log("Broken diff awareness for %s", pathEntry);
eventHandler.handle(Event.warn(e.getMessage() + "... temporarily falling back to manually "
+ "checking files for changes"));
}
/**
* Returns the current diff awareness for the given path entry, or a fresh one if there is no
* current one, or otherwise {@code null} if no factory could make a fresh one.
*/
@Nullable
private DiffAwarenessState maybeGetDiffAwarenessState(
Root pathEntry, ImmutableSet<Path> ignoredPaths) {
StateKey stateKey = StateKey.create(pathEntry, ignoredPaths);
DiffAwarenessState diffAwarenessState = currentDiffAwarenessStates.get(stateKey);
if (diffAwarenessState != null) {
return diffAwarenessState;
}
for (DiffAwareness.Factory factory : diffAwarenessFactories) {
DiffAwareness newDiffAwareness = factory.maybeCreate(pathEntry, ignoredPaths);
if (newDiffAwareness != null) {
logger.atInfo().log(
"Using %s DiffAwareness strategy for %s", newDiffAwareness.name(), pathEntry);
diffAwarenessState = new DiffAwarenessState(newDiffAwareness, /*baselineView=*/null);
currentDiffAwarenessStates.put(stateKey, diffAwarenessState);
return diffAwarenessState;
}
}
return null;
}
private class ProcessableModifiedFileSetImpl implements ProcessableModifiedFileSet {
private final ModifiedFileSet modifiedFileSet;
private final Root pathEntry;
/**
* The {@link View} that should be the baseline on the next {@link #getDiff} call after
* {@link #markProcessed} is called.
*/
private final View nextView;
private final ImmutableSet<Path> ignoredPaths;
private ProcessableModifiedFileSetImpl(
ModifiedFileSet modifiedFileSet,
Root pathEntry,
ImmutableSet<Path> ignoredPaths,
View nextView) {
this.modifiedFileSet = modifiedFileSet;
this.pathEntry = pathEntry;
this.ignoredPaths = ignoredPaths;
this.nextView = nextView;
}
@Override
public ModifiedFileSet getModifiedFileSet() {
return modifiedFileSet;
}
@Nullable
@Override
public WorkspaceInfoFromDiff getWorkspaceInfo() {
return nextView.getWorkspaceInfo();
}
@Override
public void markProcessed() {
StateKey stateKey = StateKey.create(pathEntry, ignoredPaths);
DiffAwarenessState diffAwarenessState = currentDiffAwarenessStates.get(stateKey);
if (diffAwarenessState != null) {
diffAwarenessState.baselineView = nextView;
}
}
}
private static class BrokenProcessableModifiedFileSet implements ProcessableModifiedFileSet {
private static final BrokenProcessableModifiedFileSet INSTANCE =
new BrokenProcessableModifiedFileSet();
@Override
public ModifiedFileSet getModifiedFileSet() {
return ModifiedFileSet.EVERYTHING_MODIFIED;
}
@Nullable
@Override
public WorkspaceInfoFromDiff getWorkspaceInfo() {
return null;
}
@Override
public void markProcessed() {}
}
/** Modified file set for a clean build. */
private static class InitialModifiedFileSet implements ProcessableModifiedFileSet {
@Nullable private final WorkspaceInfoFromDiff workspaceInfo;
InitialModifiedFileSet(@Nullable WorkspaceInfoFromDiff workspaceInfo) {
this.workspaceInfo = workspaceInfo;
}
@Override
public ModifiedFileSet getModifiedFileSet() {
return ModifiedFileSet.EVERYTHING_MODIFIED;
}
@Nullable
@Override
public WorkspaceInfoFromDiff getWorkspaceInfo() {
return workspaceInfo;
}
@Override
public void markProcessed() {
}
}
}