Add a warning when shared actions detection can be producing a false positive.

Shared actions detection is based on the action key -- if 2 actions have the
same key, we will consider them equal. The problem is that we cannot reliably
construct the action key for actions which have tree artifact inputs.

Example:

```
args = ctx.actions.args()
args.add_all([tree_artifact], map_each=function, expand_directories=True)
action = ctx.actions.run(arguments=[args], ...)
```

For such action, we will constuct the key by running `function` on the
directory itself (rather than expanded contents of it). This means that for 2
actions with equal inputs and different functions, as long as their result is
the same when called for the directory, we will consider them equal. Please
note that it does not imply that their result for files within the directory
are actually the same.

As a result of that problem, we may falsly share an action and proceed with a
build which should otherwise fail. In order to aid the users in debugging
issues (like non-determinism) of such builds, add a warning about the potential
problem.

PiperOrigin-RevId: 319805921
diff --git a/src/main/java/com/google/devtools/build/lib/actions/Actions.java b/src/main/java/com/google/devtools/build/lib/actions/Actions.java
index 359df27..52eb3c4 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/Actions.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/Actions.java
@@ -23,6 +23,8 @@
 import com.google.devtools.build.lib.actions.MutableActionGraph.ActionConflictException;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
 import com.google.devtools.build.lib.packages.OutputFile;
 import com.google.devtools.build.lib.vfs.OsPathPolicy;
 import com.google.devtools.build.lib.vfs.PathFragment;
@@ -32,6 +34,7 @@
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.Optional;
 import java.util.SortedMap;
 import javax.annotation.Nullable;
 
@@ -88,6 +91,38 @@
     return true;
   }
 
+  /**
+   * Checks whether provided actions are equivalent and issues a warning in case we may be overly
+   * permissive in the result. Returned result is the same as for {@link
+   * #canBeShared(ActionKeyContext, ActionAnalysisMetadata, ActionAnalysisMetadata)}.
+   *
+   * <p>TODO(b/160181927): Remove the warning once we move shared actions detection to execution
+   * phase.
+   */
+  static boolean canBeSharedWarnForPotentialFalsePositives(
+      EventHandler eventHandler,
+      ActionKeyContext actionKeyContext,
+      ActionAnalysisMetadata actionA,
+      ActionAnalysisMetadata actionB) {
+    boolean canBeShared = canBeShared(actionKeyContext, actionA, actionB);
+    if (canBeShared) {
+      Optional<Artifact> treeArtifactInput =
+          actionA.getMandatoryInputs().toList().stream()
+              .filter(Artifact::isTreeArtifact)
+              .findFirst();
+      treeArtifactInput.ifPresent(
+          treeArtifact ->
+              eventHandler.handle(
+                  Event.warn(
+                      String.format(
+                          "Shared action: %s has a tree artifact input: %s -- shared actions"
+                              + " detection is overly permissive in this case and may allow"
+                              + " sharing of different actions",
+                          actionA, treeArtifact))));
+    }
+    return canBeShared;
+  }
+
   private static boolean artifactsEqualWithoutOwner(
       Iterable<Artifact> iterable1, Iterable<Artifact> iterable2) {
     if (iterable1 instanceof Collection && iterable2 instanceof Collection) {
@@ -124,11 +159,13 @@
    * @throws ActionConflictException iff there are two actions generate the same output
    */
   public static GeneratingActions assignOwnersAndFindAndThrowActionConflict(
+      EventHandler eventHandler,
       ActionKeyContext actionKeyContext,
       ImmutableList<ActionAnalysisMetadata> actions,
       ActionLookupValue.ActionLookupKey actionLookupKey)
       throws ActionConflictException {
     return Actions.assignOwnersAndMaybeFilterSharedActionsAndThrowIfConflict(
+        eventHandler,
         actionKeyContext,
         actions,
         actionLookupKey,
@@ -150,16 +187,23 @@
    *     output
    */
   public static GeneratingActions assignOwnersAndFilterSharedActionsAndThrowActionConflict(
+      EventHandler eventHandler,
       ActionKeyContext actionKeyContext,
       ImmutableList<ActionAnalysisMetadata> actions,
       ActionLookupKey actionLookupKey,
       @Nullable Collection<OutputFile> outputFiles)
       throws ActionConflictException {
     return Actions.assignOwnersAndMaybeFilterSharedActionsAndThrowIfConflict(
-        actionKeyContext, actions, actionLookupKey, /*allowSharedAction=*/ true, outputFiles);
+        eventHandler,
+        actionKeyContext,
+        actions,
+        actionLookupKey,
+        /*allowSharedAction=*/ true,
+        outputFiles);
   }
 
   private static void verifyGeneratingActionKeys(
+      EventHandler eventHandler,
       Artifact.DerivedArtifact output,
       ActionLookupData otherKey,
       boolean allowSharedAction,
@@ -177,8 +221,11 @@
     int otherIndex = otherKey.getActionIndex();
     if (actionIndex != otherIndex
         && (!allowSharedAction
-            || !Actions.canBeShared(
-                actionKeyContext, actions.get(actionIndex), actions.get(otherIndex)))) {
+            || !Actions.canBeSharedWarnForPotentialFalsePositives(
+                eventHandler,
+                actionKeyContext,
+                actions.get(actionIndex),
+                actions.get(otherIndex)))) {
       throw new ActionConflictException(
           actionKeyContext, output, actions.get(actionIndex), actions.get(otherIndex));
     }
@@ -196,6 +243,7 @@
    * associated rule configured target.
    */
   private static GeneratingActions assignOwnersAndMaybeFilterSharedActionsAndThrowIfConflict(
+      EventHandler eventHandler,
       ActionKeyContext actionKeyContext,
       ImmutableList<ActionAnalysisMetadata> actions,
       ActionLookupKey actionLookupKey,
@@ -239,7 +287,12 @@
         if (equalOutput != null) {
           // Yes: assert that its generating action and this artifact's are compatible.
           verifyGeneratingActionKeys(
-              equalOutput, generatingActionKey, allowSharedAction, actionKeyContext, actions);
+              eventHandler,
+              equalOutput,
+              generatingActionKey,
+              allowSharedAction,
+              actionKeyContext,
+              actions);
         } else {
           // No: populate the output label map with this artifact if applicable: if this
           // artifact corresponds to a target that is an OutputFile with associated rule this label.
@@ -259,7 +312,12 @@
         } else {
           // Key is already set: verify that the generating action and this action are compatible.
           verifyGeneratingActionKeys(
-              output, generatingActionKey, allowSharedAction, actionKeyContext, actions);
+              eventHandler,
+              output,
+              generatingActionKey,
+              allowSharedAction,
+              actionKeyContext,
+              actions);
         }
       }
       actionIndex++;
diff --git a/src/main/java/com/google/devtools/build/lib/actions/MapBasedActionGraph.java b/src/main/java/com/google/devtools/build/lib/actions/MapBasedActionGraph.java
index dadad15..573948a 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/MapBasedActionGraph.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/MapBasedActionGraph.java
@@ -16,6 +16,7 @@
 
 import com.google.common.base.Preconditions;
 import com.google.devtools.build.lib.actions.Artifact.OwnerlessArtifactWrapper;
+import com.google.devtools.build.lib.events.EventHandler;
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.ThreadSafe;
 
@@ -24,11 +25,13 @@
  */
 @ThreadSafe
 public final class MapBasedActionGraph implements MutableActionGraph {
+  private final EventHandler eventHandler;
   private final ActionKeyContext actionKeyContext;
   private final ConcurrentMultimapWithHeadElement<OwnerlessArtifactWrapper, ActionAnalysisMetadata>
       generatingActionMap = new ConcurrentMultimapWithHeadElement<>();
 
-  public MapBasedActionGraph(ActionKeyContext actionKeyContext) {
+  public MapBasedActionGraph(EventHandler eventHandler, ActionKeyContext actionKeyContext) {
+    this.eventHandler = eventHandler;
     this.actionKeyContext = actionKeyContext;
   }
 
@@ -45,7 +48,8 @@
       ActionAnalysisMetadata previousAction = generatingActionMap.putAndGet(wrapper, action);
       if (previousAction != null
           && previousAction != action
-          && !Actions.canBeShared(actionKeyContext, action, previousAction)) {
+          && !Actions.canBeSharedWarnForPotentialFalsePositives(
+              eventHandler, actionKeyContext, action, previousAction)) {
         generatingActionMap.remove(wrapper, action);
         throw new ActionConflictException(actionKeyContext, artifact, previousAction, action);
       }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAspect.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAspect.java
index 4990310..b9d5bdd 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAspect.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAspect.java
@@ -241,6 +241,7 @@
       AnalysisEnvironment analysisEnvironment = ruleContext.getAnalysisEnvironment();
       GeneratingActions generatingActions =
           Actions.assignOwnersAndFilterSharedActionsAndThrowActionConflict(
+              analysisEnvironment.getEventHandler(),
               analysisEnvironment.getActionKeyContext(),
               analysisEnvironment.getRegisteredActions(),
               ruleContext.getOwner(),
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java
index bac52e5..5b188df 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java
@@ -248,6 +248,7 @@
     AnalysisEnvironment analysisEnvironment = ruleContext.getAnalysisEnvironment();
     GeneratingActions generatingActions =
         Actions.assignOwnersAndFilterSharedActionsAndThrowActionConflict(
+            analysisEnvironment.getEventHandler(),
             analysisEnvironment.getActionKeyContext(),
             analysisEnvironment.getRegisteredActions(),
             ruleContext.getOwner(),
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/CoverageReportActionFactory.java b/src/main/java/com/google/devtools/build/lib/analysis/test/CoverageReportActionFactory.java
index f07216e..620bc8b 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/test/CoverageReportActionFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/CoverageReportActionFactory.java
@@ -47,6 +47,7 @@
     private final Actions.GeneratingActions processedActions;
 
     public CoverageReportActionsWrapper(
+        EventHandler eventHandler,
         ActionAnalysisMetadata lcovWriteAction,
         ActionAnalysisMetadata coverageReportAction,
         ActionKeyContext actionKeyContext) {
@@ -54,6 +55,7 @@
       try {
         this.processedActions =
             Actions.assignOwnersAndFindAndThrowActionConflict(
+                eventHandler,
                 actionKeyContext,
                 ImmutableList.of(lcovWriteAction, coverageReportAction),
                 CoverageReportValue.COVERAGE_REPORT_KEY);
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/coverage/CoverageReportActionBuilder.java b/src/main/java/com/google/devtools/build/lib/bazel/coverage/CoverageReportActionBuilder.java
index cb6c676..4de0391 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/coverage/CoverageReportActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/coverage/CoverageReportActionBuilder.java
@@ -214,7 +214,7 @@
               reportGenerator, workspaceName, htmlReport),
           argsFunction, locationFunc);
       return new CoverageReportActionsWrapper(
-          lcovFileAction, coverageReportAction, actionKeyContext);
+          reporter, lcovFileAction, coverageReportAction, actionKeyContext);
     } else {
       reporter.handle(
           Event.error("Cannot generate coverage report - no coverage information was collected"));
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionTemplateExpansionFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionTemplateExpansionFunction.java
index 2cf032d..45d2bbc 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ActionTemplateExpansionFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionTemplateExpansionFunction.java
@@ -33,6 +33,7 @@
 import com.google.devtools.build.lib.actions.MutableActionGraph.ActionConflictException;
 import com.google.devtools.build.lib.bugreport.BugReport;
 import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
 import com.google.devtools.build.lib.skyframe.ActionTemplateExpansionValue.ActionTemplateExpansionKey;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.skyframe.SkyFunction;
@@ -89,7 +90,7 @@
       // of the ActionTemplate.
       ImmutableList<? extends Action> actions =
           generateAndValidateActionsFromTemplate(actionTemplate, inputTreeFileArtifacts, key);
-      generatingActions = checkActionAndArtifactConflicts(actions, key);
+      generatingActions = checkActionAndArtifactConflicts(env.getListener(), actions, key);
     } catch (ActionConflictException e) {
       e.reportTo(env.getListener());
       throw new ActionTemplateExpansionFunctionException(e);
@@ -157,11 +158,13 @@
   }
 
   private GeneratingActions checkActionAndArtifactConflicts(
-      ImmutableList<? extends Action> actions, ActionTemplateExpansionKey key)
+      EventHandler eventHandler,
+      ImmutableList<? extends Action> actions,
+      ActionTemplateExpansionKey key)
       throws ActionConflictException, ArtifactPrefixConflictException {
     GeneratingActions generatingActions =
         Actions.assignOwnersAndFindAndThrowActionConflict(
-            actionKeyContext, ImmutableList.copyOf(actions), key);
+            eventHandler, actionKeyContext, ImmutableList.copyOf(actions), key);
     Map<ActionAnalysisMetadata, ArtifactPrefixConflictException> artifactPrefixConflictMap =
         findArtifactPrefixConflicts(getMapForConsistencyCheck(generatingActions.getActions()));
 
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ArtifactConflictFinder.java b/src/main/java/com/google/devtools/build/lib/skyframe/ArtifactConflictFinder.java
index 6b6ce32..c7196f2 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ArtifactConflictFinder.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ArtifactConflictFinder.java
@@ -31,6 +31,7 @@
 import com.google.devtools.build.lib.concurrent.ExecutorUtil;
 import com.google.devtools.build.lib.concurrent.Sharder;
 import com.google.devtools.build.lib.concurrent.ThrowableRecordingRunnableWrapper;
+import com.google.devtools.build.lib.events.EventHandler;
 import com.google.devtools.build.lib.skyframe.PrecomputedValue.Precomputed;
 import com.google.devtools.build.lib.util.Pair;
 import com.google.devtools.build.lib.vfs.PathFragment;
@@ -63,6 +64,7 @@
    * build as of 2014), so it should only be called when necessary.
    */
   static ImmutableMap<ActionAnalysisMetadata, ConflictException> findAndStoreArtifactConflicts(
+      EventHandler eventHandler,
       Iterable<ActionLookupValue> actionLookupValues,
       boolean strictConflictChecks,
       ActionKeyContext actionKeyContext)
@@ -71,7 +73,8 @@
         new ConcurrentHashMap<>();
     Pair<ActionGraph, SortedMap<PathFragment, Artifact>> result;
     result =
-        constructActionGraphAndPathMap(actionKeyContext, actionLookupValues, temporaryBadActionMap);
+        constructActionGraphAndPathMap(
+            eventHandler, actionKeyContext, actionLookupValues, temporaryBadActionMap);
     ActionGraph actionGraph = result.first;
     SortedMap<PathFragment, Artifact> artifactPathMap = result.second;
 
@@ -92,11 +95,12 @@
    */
   private static Pair<ActionGraph, SortedMap<PathFragment, Artifact>>
       constructActionGraphAndPathMap(
+          EventHandler eventHandler,
           ActionKeyContext actionKeyContext,
           Iterable<ActionLookupValue> values,
           ConcurrentMap<ActionAnalysisMetadata, ConflictException> badActionMap)
           throws InterruptedException {
-    MutableActionGraph actionGraph = new MapBasedActionGraph(actionKeyContext);
+    MutableActionGraph actionGraph = new MapBasedActionGraph(eventHandler, actionKeyContext);
     ConcurrentNavigableMap<PathFragment, Artifact> artifactPathMap =
         new ConcurrentSkipListMap<>(Actions.comparatorForPrefixConflicts());
     // Action graph construction is CPU-bound.
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 72ec5c66..012bd2f 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
@@ -745,6 +745,7 @@
         ":precomputed_value",
         "//src/main/java/com/google/devtools/build/lib/actions",
         "//src/main/java/com/google/devtools/build/lib/concurrent",
+        "//src/main/java/com/google/devtools/build/lib/events",
         "//src/main/java/com/google/devtools/build/lib/util",
         "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
         "//third_party:guava",
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/BuildInfoCollectionFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/BuildInfoCollectionFunction.java
index 221e973..a0807da 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/BuildInfoCollectionFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BuildInfoCollectionFunction.java
@@ -91,7 +91,11 @@
     try {
       generatingActions =
           Actions.assignOwnersAndFilterSharedActionsAndThrowActionConflict(
-              actionKeyContext, collection.getActions(), keyAndConfig, /*outputFiles=*/ null);
+              env.getListener(),
+              actionKeyContext,
+              collection.getActions(),
+              keyAndConfig,
+              /*outputFiles=*/ null);
     } catch (ActionConflictException e) {
       throw new IllegalStateException("Action conflicts not expected in build info: " + skyKey, e);
     }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java
index ac310f4..0f4fc6a 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java
@@ -1029,6 +1029,7 @@
       try {
         generatingActions =
             Actions.assignOwnersAndFilterSharedActionsAndThrowActionConflict(
+                analysisEnvironment.getEventHandler(),
                 analysisEnvironment.getActionKeyContext(),
                 analysisEnvironment.getRegisteredActions(),
                 configuredTargetKey,
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/CoverageReportFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/CoverageReportFunction.java
index dc5dfd5..9ab1e70 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/CoverageReportFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/CoverageReportFunction.java
@@ -56,6 +56,7 @@
     try {
       generatingActions =
           Actions.assignOwnersAndFilterSharedActionsAndThrowActionConflict(
+              env.getListener(),
               actionKeyContext,
               actions,
               CoverageReportValue.COVERAGE_REPORT_KEY,
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java
index 0aa2808..5529455 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java
@@ -443,6 +443,7 @@
         // targets together that haven't been built before.
         actionConflicts =
             ArtifactConflictFinder.findAndStoreArtifactConflicts(
+                eventHandler,
                 skyframeExecutor.getActionLookupValuesInBuild(ctKeys, aspectKeys),
                 strictConflictChecks,
                 actionKeyContext);
diff --git a/src/test/java/com/google/devtools/build/lib/actions/ArtifactFactoryTest.java b/src/test/java/com/google/devtools/build/lib/actions/ArtifactFactoryTest.java
index c5f2e7f..f0a80e3 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/ArtifactFactoryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/ArtifactFactoryTest.java
@@ -228,7 +228,8 @@
         artifactFactory.getDerivedArtifact(barRelative, outRoot, NULL_ARTIFACT_OWNER);
     a.setGeneratingActionKey(ActionsTestUtil.NULL_ACTION_LOOKUP_DATA);
     b.setGeneratingActionKey(ActionsTestUtil.NULL_ACTION_LOOKUP_DATA);
-    MutableActionGraph actionGraph = new MapBasedActionGraph(actionKeyContext);
+    MutableActionGraph actionGraph =
+        new MapBasedActionGraph(/*eventHandler=*/ ignored -> {}, actionKeyContext);
     Action originalAction = new ActionsTestUtil.NullAction(NULL_ACTION_OWNER, a);
     actionGraph.registerAction(originalAction);
 
diff --git a/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java b/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java
index bdf925b..9a3ae83 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java
@@ -187,7 +187,8 @@
   @Test
   public void testAddExecPaths() throws Exception {
     List<String> paths = new ArrayList<>();
-    MutableActionGraph actionGraph = new MapBasedActionGraph(actionKeyContext);
+    MutableActionGraph actionGraph =
+        new MapBasedActionGraph(/*eventHandler=*/ ignored -> {}, actionKeyContext);
     Artifact.addExecPaths(getFooBarArtifacts(actionGraph, false), paths);
     assertThat(paths).containsExactly("bar1.h", "bar2.h");
   }
@@ -195,7 +196,8 @@
   @Test
   public void testAddExpandedArtifacts() throws Exception {
     List<Artifact> expanded = new ArrayList<>();
-    MutableActionGraph actionGraph = new MapBasedActionGraph(actionKeyContext);
+    MutableActionGraph actionGraph =
+        new MapBasedActionGraph(/*eventHandler=*/ ignored -> {}, actionKeyContext);
     List<Artifact> original = getFooBarArtifacts(actionGraph, true);
     Artifact.addExpandedArtifacts(original, expanded,
         ActionInputHelper.actionGraphArtifactExpander(actionGraph));
@@ -215,7 +217,8 @@
   @Test
   public void testAddExecPathsNewActionGraph() throws Exception {
     List<String> paths = new ArrayList<>();
-    MutableActionGraph actionGraph = new MapBasedActionGraph(actionKeyContext);
+    MutableActionGraph actionGraph =
+        new MapBasedActionGraph(/*eventHandler=*/ ignored -> {}, actionKeyContext);
     Artifact.addExecPaths(getFooBarArtifacts(actionGraph, false), paths);
     assertThat(paths).containsExactly("bar1.h", "bar2.h");
   }
diff --git a/src/test/java/com/google/devtools/build/lib/actions/FailActionTest.java b/src/test/java/com/google/devtools/build/lib/actions/FailActionTest.java
index 601a412..4eabbd3 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/FailActionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/FailActionTest.java
@@ -37,7 +37,8 @@
   private FailAction failAction;
   private final ActionKeyContext actionKeyContext = new ActionKeyContext();
 
-  protected MutableActionGraph actionGraph = new MapBasedActionGraph(actionKeyContext);
+  protected MutableActionGraph actionGraph =
+      new MapBasedActionGraph(/*eventHandler=*/ ignored -> {}, actionKeyContext);
 
   @Before
   public final void setUp() throws Exception  {
diff --git a/src/test/java/com/google/devtools/build/lib/actions/MapBasedActionGraphTest.java b/src/test/java/com/google/devtools/build/lib/actions/MapBasedActionGraphTest.java
index fac6aa9..324ee47 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/MapBasedActionGraphTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/MapBasedActionGraphTest.java
@@ -44,7 +44,8 @@
 
   @Test
   public void testSmoke() throws Exception {
-    MutableActionGraph actionGraph = new MapBasedActionGraph(actionKeyContext);
+    MutableActionGraph actionGraph =
+        new MapBasedActionGraph(/*eventHandler=*/ ignored -> {}, actionKeyContext);
     Path execRoot = fileSystem.getPath("/");
     String outSegment = "root";
     Path root = execRoot.getChild(outSegment);
@@ -72,7 +73,8 @@
 
   @Test
   public void testNoActionConflictWhenUnregisteringSharedAction() throws Exception {
-    MutableActionGraph actionGraph = new MapBasedActionGraph(actionKeyContext);
+    MutableActionGraph actionGraph =
+        new MapBasedActionGraph(/*eventHandler=*/ ignored -> {}, actionKeyContext);
     Path execRoot = fileSystem.getPath("/");
     Path root = fileSystem.getPath("/root");
     Path path = root.getRelative("foo");
@@ -95,7 +97,8 @@
   }
 
   private class ActionRegisterer extends AbstractQueueVisitor {
-    private final MutableActionGraph graph = new MapBasedActionGraph(new ActionKeyContext());
+    private final MutableActionGraph graph =
+        new MapBasedActionGraph(/*eventHandler=*/ ignored -> {}, new ActionKeyContext());
     private final Artifact output;
     // Just to occasionally add actions that were already present.
     private final Set<Action> allActions = Sets.newConcurrentHashSet();
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
index 9e20694..e962a0b 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
@@ -264,7 +264,7 @@
     packageOptions = parsePackageOptions();
     starlarkSemanticsOptions = parseStarlarkSemanticsOptions();
     workspaceStatusActionFactory = new AnalysisTestUtil.DummyWorkspaceStatusActionFactory();
-    mutableActionGraph = new MapBasedActionGraph(actionKeyContext);
+    mutableActionGraph = new MapBasedActionGraph(/*eventHandler=*/ ignored -> {}, actionKeyContext);
     ruleClassProvider = createRuleClassProvider();
     getOutputPath().createDirectoryAndParents();
     ImmutableList<PrecomputedValue.Injected> extraPrecomputedValues =
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java
index d542c52..9ff385e 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java
@@ -448,6 +448,7 @@
               ALL_OWNER,
               new BasicActionLookupValue(
                   Actions.assignOwnersAndFilterSharedActionsAndThrowActionConflict(
+                      /*eventHandler=*/ ignored -> {},
                       actionKeyContext,
                       ImmutableList.copyOf(actions),
                       ALL_OWNER,
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutorTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutorTest.java
index a6e7c5b..7806bed 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutorTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutorTest.java
@@ -1766,6 +1766,7 @@
         new NonRuleConfiguredTargetValue(
             new SerializableConfiguredTarget(),
             Actions.assignOwnersAndFilterSharedActionsAndThrowActionConflict(
+                /*eventHandler=*/ ignored -> {},
                 new ActionKeyContext(),
                 ImmutableList.<ActionAnalysisMetadata>builder()
                     .add(catastrophicAction)
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/TimestampBuilderTestCase.java b/src/test/java/com/google/devtools/build/lib/skyframe/TimestampBuilderTestCase.java
index e3f37ab..e03bd04 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/TimestampBuilderTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/TimestampBuilderTestCase.java
@@ -298,6 +298,7 @@
                   ACTION_LOOKUP_KEY,
                   new BasicActionLookupValue(
                       Actions.assignOwnersAndFilterSharedActionsAndThrowActionConflict(
+                          /*eventHandler=*/ ignored -> {},
                           actionKeyContext,
                           ImmutableList.copyOf(actions),
                           ACTION_LOOKUP_KEY,
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactBuildTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactBuildTest.java
index dc4a43d..3315e67 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactBuildTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactBuildTest.java
@@ -1013,7 +1013,11 @@
       try {
         return new ActionTemplateExpansionValue(
             Actions.assignOwnersAndFilterSharedActionsAndThrowActionConflict(
-                actionKeyContext, actions, (ActionLookupKey) skyKey, /*outputFiles=*/ null));
+                /*eventHandler=*/ ignored -> {},
+                actionKeyContext,
+                actions,
+                (ActionLookupKey) skyKey,
+                /*outputFiles=*/ null));
       } catch (ActionConflictException e) {
         throw new IllegalStateException(e);
       }
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactMetadataTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactMetadataTest.java
index c158fc9..8816189 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactMetadataTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactMetadataTest.java
@@ -252,6 +252,7 @@
               ALL_OWNER,
               new BasicActionLookupValue(
                   Actions.assignOwnersAndFilterSharedActionsAndThrowActionConflict(
+                      /*eventHandler=*/ ignored -> {},
                       actionKeyContext,
                       ImmutableList.copyOf(actions),
                       ALL_OWNER,