Artifact conflict check: remove unnecessary check

Don't check for artifact conflicts if it's a null
build and the only change since the last build is
that some SkyKeys were deleted.

Deleting ConfiguredTargets can only ever resolve
and not create conflicts:
- if the last build had a conflict, then we'd
  re-check anyway, because that is the same case
  as when building (x y) is in conflict but then
  null-building just (x) is not, and we re-check
  solely on the basis of the last build having had
  conflicts.
- if the last build had no conflict, then deleting
  a CT won't change that.

This CL also adds a comment that explains why we
don't check in this case.

This is a follow-up to commit 044264ee7290292195d0980657469ac69ba280e0.

RELNOTES: none
PiperOrigin-RevId: 291111167
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 fd173c2..00b68b6 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
@@ -122,7 +122,6 @@
   // We keep the set of invalidated configuration target keys so that we can know if something
   // has been invalidated after graph pruning has been executed.
   private Set<SkyKey> dirtiedConfiguredTargetKeys = Sets.newConcurrentHashSet();
-  private volatile boolean anyConfiguredTargetDeleted = false;
 
   private final ConfiguredRuleClassProvider ruleClassProvider;
 
@@ -526,14 +525,8 @@
       return true;
     }
 
-    if (anyConfiguredTargetDeleted) {
-      // No target was (re)computed, but some were deleted, which may resolve a conflict.
-      // TODO(laszlocsomor): get to understand this condition and give an example for it.
-      return true;
-    }
-
     if (!dirtiedConfiguredTargetKeys.isEmpty()) {
-      // No target was (re)computed and none was deleted, but at least one was dirtied.
+      // No target was (re)computed but at least one was dirtied.
       // Example: (//:x //foo:y) are built, and in conflict (//:x creates foo/C and //foo:y
       // creates C). Then y is removed from foo/BUILD and only //:x is built, so //foo:y is
       // dirtied but not recomputed, and no other nodes are recomputed (and none are deleted).
@@ -546,9 +539,9 @@
       // Example sequence:
       // 1.  Build (x y z), and there is a conflict. We store (x y z) as the largest checked key
       //     set, and record the fact that there were bad actions.
-      // 2.  Null-build (x z), so we don't evaluate or dirty or delete anything, but because we know
-      //     there was some conflict last time but don't know exactly which targets conflicted, it
-      //     could have been (x z), so we now check again.
+      // 2.  Null-build (x z), so we don't evaluate or dirty anything, but because we know there was
+      //     some conflict last time but don't know exactly which targets conflicted, it could have
+      //     been (x z), so we now check again.
       return true;
     }
 
@@ -556,26 +549,39 @@
       // Example sequence:
       // 1.  Build (x y z), and there is a conflict. We store (x y z) as the largest checked key
       //     set, and record the fact that there were bad actions.
-      // 2.  Null-build (x z), so we don't evaluate or dirty or delete anything, but because we know
-      //     there was some conflict last time but don't know exactly which targets conflicted, it
-      //     could have been (x z), so we now check again, and store (x z) as the largest checked
-      //     key set.
-      // 3.  Null-build (y z), so again we don't evaluate or dirty or delete anything, and the
-      //     previous build had no conflicts, so no other condition is true. But because (y z) is no
-      //     subset of (x z) and we only keep the most recent largest checked key set, we don't know
-      //     if (y z) are conflict free, so we check.
+      // 2.  Null-build (x z), so we don't evaluate or dirty anything, but because we know there was
+      //     some conflict last time but don't know exactly which targets conflicted, it could have
+      //     been (x z), so we now check again, and store (x z) as the largest checked key set.
+      // 3.  Null-build (y z), so again we don't evaluate or dirty anything, and the previous build
+      //     had no conflicts, so no other condition is true. But because (y z) is not a subset of
+      //     (x z) and we only keep the most recent largest checked key set, we don't know if (y z)
+      //     are conflict free, so we check.
       return true;
     }
 
     // We believe the conditions above are correct in the sense that we always check for conflicts
     // when we have to. But they are incomplete, so we sometimes check for conflicts even if we
     // wouldn't have to. For example:
-    // - if no target was evaluated (nor dirtied, nor deleted), and build sequence is (x y)
-    //   [no conflict], (z), where z is in the transitive closure of (x y), then we shouldn't check.
-    // - if no target was evaluated (nor dirtied, nor deleted), and build sequence is (x y)
-    //   [no conflict], (z), (x), then the last build shouldn't conflict-check because (x y) was
-    //   checked earlier. But it does, since after the second build we store (z) as the largest
-    //   checked set of which (x) is no subset.
+    // - if no target was evaluated nor dirtied and build sequence is (x y) [no conflict], (z),
+    //   where z is in the transitive closure of (x y), then we shouldn't check.
+    // - if no target was evaluated nor dirtied and build sequence is (x y) [no conflict], (w), (x),
+    //   then the last build shouldn't conflict-check because (x y) was checked earlier. But it
+    //   does, because after the second build we store (w) as the largest checked set, and (x) is
+    //   not a subset of that.
+
+    // Case when we DON'T need to re-check:
+    // - a configured target is deleted. Deletion can only resolve conflicts, not introduce any, and
+    //   if the previuos build had a conflict then skyframeActionExecutor.badActions() would not be
+    //   empty, and if the previous build had no conflict then deleting a CT won't change that.
+    //   Example that triggers this scenario:
+    //   1.  genrule(name='x', srcs=['A'], ...)
+    //       genrule(name='y', outs=['A'], ...)
+    //   2.  Build (x y)
+    //   3.  Rename 'x' to 'y', and 'y' to 'z'
+    //   4.  Build (y z)
+    //   5.  Null-build (y z) again
+    // We only delete the old 'x' value in (5), and we don't evaluate nor dirty anything, nor was
+    // (4) bad. So there's no reason to re-check just because we deleted something.
     return false;
   }
 
@@ -912,7 +918,6 @@
   /** Clear the invalidated configured targets detected during loading and analysis phases. */
   public void clearInvalidatedConfiguredTargets() {
     dirtiedConfiguredTargetKeys = Sets.newConcurrentHashSet();
-    anyConfiguredTargetDeleted = false;
   }
 
   /**
@@ -936,16 +941,13 @@
 
     @Override
     public void invalidated(SkyKey skyKey, InvalidationState state) {
-      if (skyKey.functionName().equals(SkyFunctions.CONFIGURED_TARGET)) {
-        if (state == InvalidationState.DELETED) {
-          anyConfiguredTargetDeleted = true;
-        } else {
-          // If the value was just dirtied and not deleted, then it may not be truly invalid, since
-          // it may later get re-validated. Therefore adding the key to dirtiedConfiguredTargetKeys
-          // is provisional--if the key is later evaluated and the value found to be clean, then we
-          // remove it from the set.
-          dirtiedConfiguredTargetKeys.add(skyKey);
-        }
+      if (skyKey.functionName().equals(SkyFunctions.CONFIGURED_TARGET)
+          && state != InvalidationState.DELETED) {
+        // If the value was just dirtied and not deleted, then it may not be truly invalid, since
+        // it may later get re-validated. Therefore adding the key to dirtiedConfiguredTargetKeys
+        // is provisional--if the key is later evaluated and the value found to be clean, then we
+        // remove it from the set.
+        dirtiedConfiguredTargetKeys.add(skyKey);
       }
     }