Allow `DiffAwareness` to share precomputed information about the workspace and
propagate it to the `WorkspaceStatusAction`.
Each successful Bazel build issue 2 `BuildInfo` events -- one based on the
result of running the command indicated by `workspace_status_command` and a
dummy one based on a subset of available information. Receivers of those
discard all but the first one. If a build fails before execution phase, the
dummy event will be the only even issued, hence it will be used.
`DiffAwareness` operates on the workspace to figure out the diffs and in the
process probes properties of it. Allow `DiffAwareness` to share such
information with the intention to make it available for the rest of the build.
Propagate unanimous precomputed workspace information when it is available from
`DiffAwareness` through the `DiffAwarenessManager` and `SkyframeExecutor` to
`CommandEnvironment` and store it there to make it available for the rest of
the build.
Make precomputed workspace information available to the dummy `BuildInfo`
event.
PiperOrigin-RevId: 355072552
diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD
index 32a3459..e4aa445 100644
--- a/src/main/java/com/google/devtools/build/lib/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/BUILD
@@ -366,6 +366,7 @@
"//src/main/java/com/google/devtools/build/lib/skyframe:skyframe_cluster",
"//src/main/java/com/google/devtools/build/lib/skyframe:target_pattern_phase_value",
"//src/main/java/com/google/devtools/build/lib/skyframe:top_down_action_cache",
+ "//src/main/java/com/google/devtools/build/lib/skyframe:workspace_info",
"//src/main/java/com/google/devtools/build/lib/unix",
"//src/main/java/com/google/devtools/build/lib/util",
"//src/main/java/com/google/devtools/build/lib/util:TestType",
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BUILD b/src/main/java/com/google/devtools/build/lib/analysis/BUILD
index 220d4d5..52ef94a 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BUILD
@@ -1206,6 +1206,7 @@
"//src/main/java/com/google/devtools/build/lib/actions:artifacts",
"//src/main/java/com/google/devtools/build/lib/collect/nestedset",
"//src/main/java/com/google/devtools/build/lib/shell",
+ "//src/main/java/com/google/devtools/build/lib/skyframe:workspace_info",
"//src/main/java/com/google/devtools/build/lib/util",
"//src/main/java/com/google/devtools/build/lib/util:detailed_exit_code",
"//src/main/java/com/google/devtools/build/lib/vfs",
@@ -1213,6 +1214,7 @@
"//src/main/java/com/google/devtools/common/options",
"//src/main/protobuf:failure_details_java_proto",
"//third_party:guava",
+ "//third_party:jsr305",
],
)
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/WorkspaceStatusAction.java b/src/main/java/com/google/devtools/build/lib/analysis/WorkspaceStatusAction.java
index a4cd247..3e1205f 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/WorkspaceStatusAction.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/WorkspaceStatusAction.java
@@ -26,6 +26,7 @@
import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
import com.google.devtools.build.lib.server.FailureDetails.WorkspaceStatus;
import com.google.devtools.build.lib.server.FailureDetails.WorkspaceStatus.Code;
+import com.google.devtools.build.lib.skyframe.WorkspaceInfoFromDiff;
import com.google.devtools.build.lib.util.DetailedExitCode;
import com.google.devtools.build.lib.util.OptionsUtils;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
@@ -42,6 +43,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import javax.annotation.Nullable;
/**
* An action writing the workspace status files.
@@ -176,6 +178,10 @@
public interface DummyEnvironment {
Path getWorkspace();
+ /** Returns optional precomputed workspace info to include in the build info event. */
+ @Nullable
+ WorkspaceInfoFromDiff getWorkspaceInfoFromDiff();
+
String getBuildRequestId();
OptionsProvider getOptions();
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java b/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
index 09ddea4..7cc89e5 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
@@ -55,6 +55,7 @@
import com.google.devtools.build.lib.server.FailureDetails.BuildConfiguration;
import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
import com.google.devtools.build.lib.skyframe.SequencedSkyframeExecutor;
+import com.google.devtools.build.lib.skyframe.WorkspaceInfoFromDiff;
import com.google.devtools.build.lib.skyframe.actiongraph.v2.ActionGraphDump;
import com.google.devtools.build.lib.skyframe.actiongraph.v2.AqueryOutputHandler;
import com.google.devtools.build.lib.skyframe.actiongraph.v2.AqueryOutputHandler.OutputType;
@@ -274,6 +275,12 @@
public OptionsProvider getOptions() {
return env.getOptions();
}
+
+ @Nullable
+ @Override
+ public WorkspaceInfoFromDiff getWorkspaceInfoFromDiff() {
+ return env.getWorkspaceInfoFromDiff();
+ }
})));
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java b/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java
index 7639bcc..39ddcc9 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java
@@ -14,6 +14,8 @@
package com.google.devtools.build.lib.runtime;
+import static com.google.common.base.Preconditions.checkState;
+
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
@@ -41,6 +43,7 @@
import com.google.devtools.build.lib.skyframe.SkyframeBuildView;
import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
import com.google.devtools.build.lib.skyframe.TopDownActionCache;
+import com.google.devtools.build.lib.skyframe.WorkspaceInfoFromDiff;
import com.google.devtools.build.lib.util.AbruptExitException;
import com.google.devtools.build.lib.util.DetailedExitCode;
import com.google.devtools.build.lib.util.io.OutErr;
@@ -103,6 +106,7 @@
private TopDownActionCache topDownActionCache;
private String workspaceName;
private boolean hasSyncedPackageLoading = false;
+ @Nullable private WorkspaceInfoFromDiff workspaceInfoFromDiff;
// This AtomicReference is set to:
// - null, if neither BlazeModuleEnvironment#exit nor #precompleteCommand have been called
@@ -513,7 +517,7 @@
}
public void setWorkspaceName(String workspaceName) {
- Preconditions.checkState(this.workspaceName == null, "workspace name can only be set once");
+ checkState(this.workspaceName == null, "workspace name can only be set once");
this.workspaceName = workspaceName;
eventBus.post(new ExecRootEvent(getExecRoot()));
}
@@ -577,6 +581,18 @@
this.outputService = outputService;
}
+ /**
+ * Returns precomputed workspace information or null.
+ *
+ * <p>Precomputed workspace info is an optimization allowing to share information about the
+ * workspace if it was derived at the time of synchronizing the workspace. This way we can make it
+ * available earlier during the build and avoid retrieving it again.
+ */
+ @Nullable
+ public WorkspaceInfoFromDiff getWorkspaceInfoFromDiff() {
+ return workspaceInfoFromDiff;
+ }
+
public ActionCache getPersistentActionCache() throws IOException {
return workspace.getPersistentActionCache(reporter);
}
@@ -675,16 +691,17 @@
"We should never call this method more than once over the course of a single command");
}
hasSyncedPackageLoading = true;
- getSkyframeExecutor()
- .sync(
- reporter,
- options.getOptions(PackageOptions.class),
- packageLocator,
- options.getOptions(BuildLanguageOptions.class),
- getCommandId(),
- clientEnv,
- timestampGranularityMonitor,
- options);
+ workspaceInfoFromDiff =
+ getSkyframeExecutor()
+ .sync(
+ reporter,
+ options.getOptions(PackageOptions.class),
+ packageLocator,
+ options.getOptions(BuildLanguageOptions.class),
+ getCommandId(),
+ clientEnv,
+ timestampGranularityMonitor,
+ options);
}
public void recordLastExecutionTime() {
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/BUILD b/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
index a021adf..8b07bc2 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
@@ -214,6 +214,7 @@
":tree_artifact_value",
":unloaded_toolchain_context",
":unloaded_toolchain_context_impl",
+ ":workspace_info",
":workspace_name_function",
":workspace_name_value",
":workspace_status_function",
@@ -1286,6 +1287,7 @@
deps = [
":broken_diff_awareness_exception",
":incompatible_view_exception",
+ ":workspace_info",
"//src/main/java/com/google/devtools/build/lib/vfs",
"//src/main/java/com/google/devtools/common/options",
"//third_party:jsr305",
@@ -1299,6 +1301,7 @@
":broken_diff_awareness_exception",
":diff_awareness",
":incompatible_view_exception",
+ ":workspace_info",
"//src/main/java/com/google/devtools/build/lib/events",
"//src/main/java/com/google/devtools/build/lib/vfs",
"//src/main/java/com/google/devtools/common/options",
@@ -2799,6 +2802,11 @@
)
java_library(
+ name = "workspace_info",
+ srcs = ["WorkspaceInfoFromDiff.java"],
+)
+
+java_library(
name = "workspace_name_function",
srcs = ["WorkspaceNameFunction.java"],
deps = [
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/BrokenDiffAwarenessException.java b/src/main/java/com/google/devtools/build/lib/skyframe/BrokenDiffAwarenessException.java
index da0ee7d..a1e3ad7 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/BrokenDiffAwarenessException.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BrokenDiffAwarenessException.java
@@ -13,7 +13,7 @@
// limitations under the License.
package com.google.devtools.build.lib.skyframe;
-import com.google.common.base.Preconditions;
+import static com.google.common.base.Preconditions.checkNotNull;
/**
* Thrown on {@link DiffAwareness#getDiff} to indicate that something is wrong with the
@@ -22,6 +22,10 @@
public class BrokenDiffAwarenessException extends Exception {
public BrokenDiffAwarenessException(String msg) {
- super(Preconditions.checkNotNull(msg));
+ super(checkNotNull(msg));
+ }
+
+ public BrokenDiffAwarenessException(String msg, Throwable cause) {
+ super(checkNotNull(msg), cause);
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/DiffAwareness.java b/src/main/java/com/google/devtools/build/lib/skyframe/DiffAwareness.java
index 0579d9e..b05b070 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/DiffAwareness.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/DiffAwareness.java
@@ -45,6 +45,11 @@
/** Opaque view of the filesystem under a package path entry at a specific point in time. */
interface View {
+ /** Returns workspace info unanimously associated with the package path or null. */
+ @Nullable
+ default WorkspaceInfoFromDiff getWorkspaceInfo() {
+ return null;
+ }
}
/**
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/DiffAwarenessManager.java b/src/main/java/com/google/devtools/build/lib/skyframe/DiffAwarenessManager.java
index ed56b41..1958441 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/DiffAwarenessManager.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/DiffAwarenessManager.java
@@ -69,6 +69,9 @@
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.
@@ -99,7 +102,7 @@
if (baselineView == null) {
logger.atInfo().log("Initial baseline view for %s is %s", pathEntry, newView);
diffAwarenessState.baselineView = newView;
- return BrokenProcessableModifiedFileSet.INSTANCE;
+ return new InitialModifiedFileSet(newView.getWorkspaceInfo());
}
ModifiedFileSet diff;
@@ -172,6 +175,12 @@
return modifiedFileSet;
}
+ @Nullable
+ @Override
+ public WorkspaceInfoFromDiff getWorkspaceInfo() {
+ return nextView.getWorkspaceInfo();
+ }
+
@Override
public void markProcessed() {
DiffAwarenessState diffAwarenessState = currentDiffAwarenessStates.get(pathEntry);
@@ -191,6 +200,36 @@
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() {
}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutor.java
index 35fdfe2..5f704bf 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutor.java
@@ -226,8 +226,9 @@
return recordingDiffer;
}
+ @Nullable
@Override
- public void sync(
+ public WorkspaceInfoFromDiff sync(
ExtendedEventHandler eventHandler,
PackageOptions packageOptions,
PathPackageLocator packageLocator,
@@ -262,11 +263,13 @@
tsgm,
options);
long startTime = System.nanoTime();
- handleDiffs(eventHandler, packageOptions.checkOutputFiles, options);
+ WorkspaceInfoFromDiff workspaceInfo =
+ handleDiffs(eventHandler, packageOptions.checkOutputFiles, options);
long stopTime = System.nanoTime();
Profiler.instance().logSimpleTask(startTime, stopTime, ProfilerTask.INFO, "handleDiffs");
long duration = stopTime - startTime;
sourceDiffCheckingDuration = duration > 0 ? Duration.ofNanos(duration) : Duration.ZERO;
+ return workspaceInfo;
}
/**
@@ -341,7 +344,8 @@
handleDiffs(eventHandler, /*checkOutputFiles=*/false, OptionsProvider.EMPTY);
}
- private void handleDiffs(
+ @Nullable
+ private WorkspaceInfoFromDiff handleDiffs(
ExtendedEventHandler eventHandler, boolean checkOutputFiles, OptionsProvider options)
throws InterruptedException, AbruptExitException {
TimestampGranularityMonitor tsgm = this.tsgm.get();
@@ -354,13 +358,18 @@
invalidateCachedWorkspacePathsStates();
}
+ WorkspaceInfoFromDiff workspaceInfo = null;
Map<Root, DiffAwarenessManager.ProcessableModifiedFileSet> modifiedFilesByPathEntry =
Maps.newHashMap();
Set<Pair<Root, DiffAwarenessManager.ProcessableModifiedFileSet>>
pathEntriesWithoutDiffInformation = Sets.newHashSet();
- for (Root pathEntry : pkgLocator.get().getPathEntries()) {
+ ImmutableList<Root> pkgRoots = pkgLocator.get().getPathEntries();
+ for (Root pathEntry : pkgRoots) {
DiffAwarenessManager.ProcessableModifiedFileSet modifiedFileSet =
diffAwarenessManager.getDiff(eventHandler, pathEntry, options);
+ if (pkgRoots.size() == 1) {
+ workspaceInfo = modifiedFileSet.getWorkspaceInfo();
+ }
if (modifiedFileSet.getModifiedFileSet().treatEverythingAsModified()) {
pathEntriesWithoutDiffInformation.add(Pair.of(pathEntry, modifiedFileSet));
} else {
@@ -381,6 +390,7 @@
managedDirectoriesChanged,
fsvcThreads);
handleClientEnvironmentChanges();
+ return workspaceInfo;
}
/**
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
index 07713cf..52f8e24 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
@@ -2668,8 +2668,13 @@
/**
* Initializes and syncs the graph with the given options, readying it for the next evaluation.
+ *
+ * <p>Returns precomputed information about the workspace if it is available at this stage. This
+ * is an optimization allowing implementations which have such information to make it available
+ * early in the build.
*/
- public void sync(
+ @Nullable
+ public WorkspaceInfoFromDiff sync(
ExtendedEventHandler eventHandler,
PackageOptions packageOptions,
PathPackageLocator pathPackageLocator,
@@ -2696,6 +2701,7 @@
dropConfiguredTargetsNow(eventHandler);
lastAnalysisDiscarded = false;
}
+ return null;
}
protected void syncPackageLoading(
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceInfoFromDiff.java b/src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceInfoFromDiff.java
new file mode 100644
index 0000000..c8520d3
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceInfoFromDiff.java
@@ -0,0 +1,17 @@
+// Copyright 2021 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;
+
+/** Information for a workspace computed at the time of collecting diff. */
+public interface WorkspaceInfoFromDiff {}
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/BUILD b/src/test/java/com/google/devtools/build/lib/skyframe/BUILD
index 6b2a59e..bc02a34 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/BUILD
@@ -87,7 +87,6 @@
],
deps = select({
"//src/conditions:darwin": [
- "//src/main/java/com/google/devtools/build/lib/skyframe:incompatible_view_exception",
"//src/main/java/com/google/devtools/build/lib/skyframe:local_diff_awareness",
"//src/main/java/com/google/devtools/build/lib/testing/common:fake-options",
],
@@ -201,6 +200,7 @@
"//src/main/java/com/google/devtools/build/lib/skyframe:glob_descriptor",
"//src/main/java/com/google/devtools/build/lib/skyframe:glob_function",
"//src/main/java/com/google/devtools/build/lib/skyframe:glob_value",
+ "//src/main/java/com/google/devtools/build/lib/skyframe:incompatible_view_exception",
"//src/main/java/com/google/devtools/build/lib/skyframe:local_repository_lookup_value",
"//src/main/java/com/google/devtools/build/lib/skyframe:managed_directories_knowledge",
"//src/main/java/com/google/devtools/build/lib/skyframe:metadata_consumer_for_metrics",
@@ -234,6 +234,7 @@
"//src/main/java/com/google/devtools/build/lib/skyframe:tree_artifact_value",
"//src/main/java/com/google/devtools/build/lib/skyframe:unloaded_toolchain_context",
"//src/main/java/com/google/devtools/build/lib/skyframe:toolchain_context_key",
+ "//src/main/java/com/google/devtools/build/lib/skyframe:workspace_info",
"//src/main/java/com/google/devtools/build/lib/skyframe:workspace_name_value",
"//src/main/java/com/google/devtools/build/lib/skyframe/serialization",
"//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec",
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/DiffAwarenessManagerTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/DiffAwarenessManagerTest.java
index 8cd2ecb..853ebe7 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/DiffAwarenessManagerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/DiffAwarenessManagerTest.java
@@ -15,11 +15,16 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.devtools.build.lib.events.util.EventCollectionApparatus;
+import com.google.devtools.build.lib.skyframe.DiffAwareness.View;
import com.google.devtools.build.lib.skyframe.DiffAwarenessManager.ProcessableModifiedFileSet;
import com.google.devtools.build.lib.vfs.DigestHashFunction;
import com.google.devtools.build.lib.vfs.FileSystem;
@@ -195,6 +200,135 @@
processableDiff.markProcessed();
}
+ @Test
+ public void getDiff_cleanBuild_propagatesWorkspaceInfo() throws Exception {
+ Root pathEntry = Root.fromPath(fs.getPath("/path"));
+ WorkspaceInfoFromDiff workspaceInfo = new WorkspaceInfoFromDiff() {};
+ DiffAwareness diffAwareness = mock(DiffAwareness.class);
+ when(diffAwareness.getCurrentView(any())).thenReturn(createView(workspaceInfo));
+ DiffAwareness.Factory factory = mock(DiffAwareness.Factory.class);
+ when(factory.maybeCreate(pathEntry)).thenReturn(diffAwareness);
+ DiffAwarenessManager manager = new DiffAwarenessManager(ImmutableList.of(factory));
+
+ ProcessableModifiedFileSet diff =
+ manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY);
+
+ assertThat(diff.getWorkspaceInfo()).isSameInstanceAs(workspaceInfo);
+ }
+
+ @Test
+ public void getDiff_incrementalBuild_propagatesLatestWorkspaceInfo() throws Exception {
+ Root pathEntry = Root.fromPath(fs.getPath("/path"));
+ WorkspaceInfoFromDiff workspaceInfo1 = new WorkspaceInfoFromDiff() {};
+ WorkspaceInfoFromDiff workspaceInfo2 = new WorkspaceInfoFromDiff() {};
+ DiffAwareness diffAwareness = mock(DiffAwareness.class);
+ View view1 = createView(workspaceInfo1);
+ View view2 = createView(workspaceInfo2);
+ when(diffAwareness.getCurrentView(any())).thenReturn(view1, view2);
+ when(diffAwareness.getDiff(view1, view2))
+ .thenReturn(ModifiedFileSet.builder().modify(PathFragment.create("file")).build());
+ DiffAwareness.Factory factory = mock(DiffAwareness.Factory.class);
+ when(factory.maybeCreate(pathEntry)).thenReturn(diffAwareness);
+ DiffAwarenessManager manager = new DiffAwarenessManager(ImmutableList.of(factory));
+ manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY);
+
+ ProcessableModifiedFileSet diff =
+ manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY);
+
+ assertThat(diff.getWorkspaceInfo()).isSameInstanceAs(workspaceInfo2);
+ }
+
+ @Test
+ public void getDiff_incrementalBuildNoChange_propagatesNewWorkspaceInfo() throws Exception {
+ Root pathEntry = Root.fromPath(fs.getPath("/path"));
+ WorkspaceInfoFromDiff workspaceInfo1 = new WorkspaceInfoFromDiff() {};
+ WorkspaceInfoFromDiff workspaceInfo2 = new WorkspaceInfoFromDiff() {};
+ DiffAwareness diffAwareness = mock(DiffAwareness.class);
+ View view1 = createView(workspaceInfo1);
+ View view2 = createView(workspaceInfo2);
+ when(diffAwareness.getCurrentView(any())).thenReturn(view1, view2);
+ when(diffAwareness.getDiff(view1, view2)).thenReturn(ModifiedFileSet.NOTHING_MODIFIED);
+ DiffAwareness.Factory factory = mock(DiffAwareness.Factory.class);
+ when(factory.maybeCreate(pathEntry)).thenReturn(diffAwareness);
+ DiffAwarenessManager manager = new DiffAwarenessManager(ImmutableList.of(factory));
+ manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY);
+
+ ProcessableModifiedFileSet diff =
+ manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY);
+
+ assertThat(diff.getWorkspaceInfo()).isSameInstanceAs(workspaceInfo2);
+ }
+
+ @Test
+ public void getDiff_incrementalBuildWithNoWorkspaceInfo_returnsDiffWithNullWorkspaceInfo()
+ throws Exception {
+ Root pathEntry = Root.fromPath(fs.getPath("/path"));
+ DiffAwareness diffAwareness = mock(DiffAwareness.class);
+ View view1 = createView(new WorkspaceInfoFromDiff() {});
+ View view2 = createView(/*workspaceInfo=*/ null);
+ when(diffAwareness.getCurrentView(any())).thenReturn(view1, view2);
+ when(diffAwareness.getDiff(view1, view2))
+ .thenReturn(ModifiedFileSet.builder().modify(PathFragment.create("file")).build());
+ DiffAwareness.Factory factory = mock(DiffAwareness.Factory.class);
+ when(factory.maybeCreate(pathEntry)).thenReturn(diffAwareness);
+ DiffAwarenessManager manager = new DiffAwarenessManager(ImmutableList.of(factory));
+ manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY);
+
+ ProcessableModifiedFileSet diff =
+ manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY);
+
+ assertThat(diff.getWorkspaceInfo()).isNull();
+ }
+
+ @Test
+ public void getDiff_brokenDiffAwareness_returnsDiffWithNullWorkspaceInfo() throws Exception {
+ Root pathEntry = Root.fromPath(fs.getPath("/path"));
+ WorkspaceInfoFromDiff workspaceInfo1 = new WorkspaceInfoFromDiff() {};
+ WorkspaceInfoFromDiff workspaceInfo2 = new WorkspaceInfoFromDiff() {};
+ DiffAwareness diffAwareness = mock(DiffAwareness.class);
+ View view1 = createView(workspaceInfo1);
+ View view2 = createView(workspaceInfo2);
+ when(diffAwareness.getCurrentView(any())).thenReturn(view1, view2);
+ when(diffAwareness.getDiff(view1, view2)).thenThrow(BrokenDiffAwarenessException.class);
+ DiffAwareness.Factory factory = mock(DiffAwareness.Factory.class);
+ when(factory.maybeCreate(pathEntry)).thenReturn(diffAwareness);
+ DiffAwarenessManager manager = new DiffAwarenessManager(ImmutableList.of(factory));
+ manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY);
+
+ ProcessableModifiedFileSet diff =
+ manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY);
+
+ assertThat(diff.getWorkspaceInfo()).isNull();
+ }
+
+ @Test
+ public void getDiff_incompatibleDiff_fails() throws Exception {
+ Root pathEntry = Root.fromPath(fs.getPath("/path"));
+ DiffAwareness diffAwareness = mock(DiffAwareness.class);
+ View view1 = createView(/*workspaceInfo=*/ null);
+ View view2 = createView(/*workspaceInfo=*/ null);
+ when(diffAwareness.getCurrentView(any())).thenReturn(view1, view2);
+ when(diffAwareness.getDiff(view1, view2)).thenThrow(IncompatibleViewException.class);
+ DiffAwareness.Factory factory = mock(DiffAwareness.Factory.class);
+ when(factory.maybeCreate(pathEntry)).thenReturn(diffAwareness);
+ DiffAwarenessManager manager = new DiffAwarenessManager(ImmutableList.of(factory));
+ manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY);
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY));
+ }
+
+ private static View createView(@Nullable WorkspaceInfoFromDiff workspaceInfo) {
+ return new View() {
+ @Nullable
+ @Override
+ public WorkspaceInfoFromDiff getWorkspaceInfo() {
+ return workspaceInfo;
+ }
+ };
+ }
+
private static class DiffAwarenessFactoryStub implements DiffAwareness.Factory {
private final Map<Root, DiffAwareness> diffAwarenesses = Maps.newHashMap();