Introduce a PackageBoundaryMode enum to FilesetTraversalParams.

This enables the introduction of a third behavior mode when
RecursiveFilesystemTraversalFunction encounters a package boundary: on top of
the existing cross/no-cross behavior, it can now bail out with an error.

This is preparatory work to support Skyframe-native Filesets.

The CL also contains a couple of prettifying cleanups (in ActionCacheChecker
and in FilesetManifestAction).

--
MOS_MIGRATED_REVID=86185105
diff --git a/src/main/java/com/google/devtools/build/lib/actions/ActionCacheChecker.java b/src/main/java/com/google/devtools/build/lib/actions/ActionCacheChecker.java
index 4b970e3..07b9977 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/ActionCacheChecker.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/ActionCacheChecker.java
@@ -107,9 +107,9 @@
     if (handler != null) {
       if (verboseExplanations) {
         String keyDescription = action.describeKey();
-        reportRebuild(handler, action,
-            keyDescription == null ? "action command has changed" :
-            "action command has changed.\nNew action: " + keyDescription);
+        reportRebuild(handler, action, keyDescription == null
+            ? "action command has changed"
+            : "action command has changed.\nNew action: " + keyDescription);
       } else {
         reportRebuild(handler, action,
             "action command has changed (try --verbose_explanations for more info)");
diff --git a/src/main/java/com/google/devtools/build/lib/actions/FilesetTraversalParams.java b/src/main/java/com/google/devtools/build/lib/actions/FilesetTraversalParams.java
index 1464f73..51f131e 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/FilesetTraversalParams.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/FilesetTraversalParams.java
@@ -1,4 +1,4 @@
-// Copyright 2014 Google Inc. All rights reserved.
+// Copyright 2015 Google Inc. 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.
@@ -31,6 +31,29 @@
  */
 public interface FilesetTraversalParams {
 
+  /** Desired behavior if the traversal hits a directory with a BUILD file, i.e. a subpackage. */
+  public enum PackageBoundaryMode {
+    /** The traversal should recurse into the directory, optionally reporting a warning. */
+    CROSS,
+
+    // TODO(bazel-team): deprecate CROSS and REPORT_ERROR in favor of DONT_CROSS. Clean up the depot
+    // and lock down the semantics of FilesetEntry.srcdir to only accept other Filesets or BUILD
+    // files of a package, in which case also require an explicit list of files.
+    /** The traversal should not recurse into the directory but silently skip it. */
+    DONT_CROSS,
+
+    /** The traversal should not recurse into the directory and report an error. */
+    REPORT_ERROR;
+
+    public static PackageBoundaryMode forStrictFilesetFlag(boolean flagEnabled) {
+      return flagEnabled ? REPORT_ERROR : CROSS;
+    }
+
+    public void fingerprint(Fingerprint fp) {
+      fp.addInt(ordinal());
+    }
+  }
+
   /**
    * Abstraction of the root directory of a {@link DirectTraversal}.
    *
@@ -128,7 +151,7 @@
     boolean isFollowingSymlinks();
 
     /** Returns the desired behavior when the traversal hits a subpackage. */
-    boolean getCrossPackageBoundary();
+    PackageBoundaryMode getPackageBoundaryMode();
   }
 
   /** Label of the Fileset rule that owns this traversal. */
diff --git a/src/main/java/com/google/devtools/build/lib/actions/FilesetTraversalParamsFactory.java b/src/main/java/com/google/devtools/build/lib/actions/FilesetTraversalParamsFactory.java
index acc0e86..c26c603 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/FilesetTraversalParamsFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/FilesetTraversalParamsFactory.java
@@ -1,4 +1,4 @@
-// Copyright 2014 Google Inc. All rights reserved.
+// Copyright 2015 Google Inc. 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.
@@ -19,6 +19,7 @@
 import com.google.common.collect.Ordering;
 import com.google.devtools.build.lib.actions.FilesetTraversalParams.DirectTraversal;
 import com.google.devtools.build.lib.actions.FilesetTraversalParams.DirectTraversalRoot;
+import com.google.devtools.build.lib.actions.FilesetTraversalParams.PackageBoundaryMode;
 import com.google.devtools.build.lib.syntax.FilesetEntry.SymlinkBehavior;
 import com.google.devtools.build.lib.syntax.Label;
 import com.google.devtools.build.lib.util.Fingerprint;
@@ -46,15 +47,15 @@
    * @param excludes optional; set of files directly under this package's directory to exclude;
    *     files in subdirectories cannot be excluded
    * @param symlinkBehaviorMode what to do with symlinks
-   * @param crossPkgBoundary whether to traverse a subdirectory if it's also a subpackage (contains
-   *     a BUILD file)
+   * @param pkgBoundaryMode what to do when the traversal hits a subdirectory that is also a
+   *     subpackage (contains a BUILD file)
    */
   public static FilesetTraversalParams recursiveTraversalOfPackage(Label ownerLabel,
       Artifact buildFile, PathFragment destPath, @Nullable Set<String> excludes,
-      SymlinkBehavior symlinkBehaviorMode, boolean crossPkgBoundary) {
+      SymlinkBehavior symlinkBehaviorMode, PackageBoundaryMode pkgBoundaryMode) {
     Preconditions.checkState(buildFile.isSourceArtifact(), "%s", buildFile);
     return new DirectoryTraversalParams(ownerLabel, DirectTraversalRootImpl.forPackage(buildFile),
-        true, destPath, excludes, symlinkBehaviorMode, crossPkgBoundary, true, false);
+        true, destPath, excludes, symlinkBehaviorMode, pkgBoundaryMode, true, false);
   }
 
   /**
@@ -70,15 +71,15 @@
    * @param excludes optional; set of files directly below this directory to exclude; files in
    *     subdirectories cannot be excluded
    * @param symlinkBehaviorMode what to do with symlinks
-   * @param crossPkgBoundary whether to traverse a subdirectory if it's also a subpackage (contains
-   *     a BUILD file)
+   * @param pkgBoundaryMode what to do when the traversal hits a subdirectory that is also a
+   *     subpackage (contains a BUILD file)
    */
   public static FilesetTraversalParams recursiveTraversalOfDirectory(Label ownerLabel,
       Artifact directoryToTraverse, PathFragment destPath, @Nullable Set<String> excludes,
-      SymlinkBehavior symlinkBehaviorMode, boolean crossPkgBoundary) {
+      SymlinkBehavior symlinkBehaviorMode, PackageBoundaryMode pkgBoundaryMode) {
     return new DirectoryTraversalParams(ownerLabel,
         DirectTraversalRootImpl.forFileOrDirectory(directoryToTraverse), false, destPath,
-        excludes, symlinkBehaviorMode, crossPkgBoundary, true,
+        excludes, symlinkBehaviorMode, pkgBoundaryMode, true,
         !directoryToTraverse.isSourceArtifact());
   }
 
@@ -93,14 +94,15 @@
    * @param destPath path in the Fileset's output directory that will be the name of this file's
    *     respective symlink there, or the root of files found (in case this is a directory)
    * @param symlinkBehaviorMode what to do with symlinks
-   * @param crossPkgBoundary whether to traverse a subdirectory if it's also a subpackage (contains
-   *     a BUILD file)
+   * @param pkgBoundaryMode what to do when the traversal hits a subdirectory that is also a
+   *     subpackage (contains a BUILD file)
    */
   public static FilesetTraversalParams fileTraversal(Label ownerLabel, Artifact fileToTraverse,
-      PathFragment destPath, SymlinkBehavior symlinkBehaviorMode, boolean crossPkgBoundary) {
+      PathFragment destPath, SymlinkBehavior symlinkBehaviorMode,
+      PackageBoundaryMode pkgBoundaryMode) {
     return new DirectoryTraversalParams(ownerLabel,
         DirectTraversalRootImpl.forFileOrDirectory(fileToTraverse), false, destPath, null,
-        symlinkBehaviorMode, crossPkgBoundary, false, !fileToTraverse.isSourceArtifact());
+        symlinkBehaviorMode, pkgBoundaryMode, false, !fileToTraverse.isSourceArtifact());
   }
 
   /**
@@ -162,16 +164,16 @@
     private final DirectTraversalRoot root;
     private final boolean isPackage;
     private final boolean followSymlinks;
-    private final boolean crossPkgBoundary;
+    private final PackageBoundaryMode pkgBoundaryMode;
     private final boolean isRecursive;
     private final boolean isGenerated;
 
     DirectTraversalImpl(DirectTraversalRoot root, boolean isPackage, boolean followSymlinks,
-        boolean crossPkgBoundary, boolean isRecursive, boolean isGenerated) {
+        PackageBoundaryMode pkgBoundaryMode, boolean isRecursive, boolean isGenerated) {
       this.root = root;
       this.isPackage = isPackage;
       this.followSymlinks = followSymlinks;
-      this.crossPkgBoundary = crossPkgBoundary;
+      this.pkgBoundaryMode = pkgBoundaryMode;
       this.isRecursive = isRecursive;
       this.isGenerated = isGenerated;
     }
@@ -202,8 +204,8 @@
     }
 
     @Override
-    public boolean getCrossPackageBoundary() {
-      return crossPkgBoundary;
+    public PackageBoundaryMode getPackageBoundaryMode() {
+      return pkgBoundaryMode;
     }
 
     void fingerprint(Fingerprint fp) {
@@ -212,7 +214,7 @@
       fp.addBoolean(followSymlinks);
       fp.addBoolean(isRecursive);
       fp.addBoolean(isGenerated);
-      fp.addBoolean(crossPkgBoundary);
+      pkgBoundaryMode.fingerprint(fp);
     }
   }
 
@@ -225,12 +227,12 @@
         PathFragment destPath,
         @Nullable Set<String> excludes,
         SymlinkBehavior symlinkBehaviorMode,
-        boolean crossPkgBoundary,
+        PackageBoundaryMode pkgBoundaryMode,
         boolean isRecursive,
         boolean isGenerated) {
       super(ownerLabel, destPath, excludes);
       traversal = new DirectTraversalImpl(root, isPackage,
-          symlinkBehaviorMode == SymlinkBehavior.DEREFERENCE, crossPkgBoundary, isRecursive,
+          symlinkBehaviorMode == SymlinkBehavior.DEREFERENCE, pkgBoundaryMode, isRecursive,
           isGenerated);
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunction.java
index a559206..67b3f40 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunction.java
@@ -1,4 +1,4 @@
-// Copyright 2014 Google Inc. All rights reserved.
+// Copyright 2015 Google Inc. 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.
@@ -222,7 +222,7 @@
       DirectTraversal traversal) throws MissingDepException {
     SkyKey depKey = RecursiveFilesystemTraversalValue.key(
         new RecursiveFilesystemTraversalValue.TraversalRequest(traversal.getRoot().asRootedPath(),
-            traversal.isGenerated(), traversal.getCrossPackageBoundary(), traversal.isPackage(),
+            traversal.isGenerated(), traversal.getPackageBoundaryMode(), traversal.isPackage(),
             errorInfo));
     RecursiveFilesystemTraversalValue v = (RecursiveFilesystemTraversalValue) env.getValue(depKey);
     if (env.valuesMissing()) {
@@ -234,7 +234,7 @@
   private static String createErrorInfo(FilesetTraversalParams t) {
     if (t.getDirectTraversal().isPresent()) {
       DirectTraversal direct = t.getDirectTraversal().get();
-      return String.format("Fileset '%s' traversing %s %s", t.getOwnerLabel(),
+      return String.format("Fileset '%s' traversing %s '%s'", t.getOwnerLabel(),
           direct.isPackage() ? "package" : "file (or directory)",
           direct.getRoot().getRelativePart().getPathString());
     } else {
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunction.java
index d54e98f..ab2faa2 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunction.java
@@ -63,7 +63,7 @@
 
   /**
    * Thrown when the traversal encounters a subdirectory with a BUILD file but is not allowed to
-   * recurse into it.
+   * recurse into it. See {@code PackageBoundaryMode#REPORT_ERROR}.
    */
   public static final class CannotCrossPackageBoundaryException extends
       RecursiveFilesystemTraversalException {
@@ -137,14 +137,22 @@
             new GeneratedPathConflictException(traversal));
       } else if (pkgLookupResult.isPackage() && !traversal.skipTestingForSubpackage) {
         // The traversal was requested for a directory that defines a package.
-        if (traversal.crossPkgBoundaries) {
-          // We are free to traverse the subpackage but we need to display a warning.
-          String msg = traversal.errorInfo + " crosses package boundary into package rooted at "
-              + traversal.path.getRelativePath().getPathString();
-          env.getListener().handle(new Event(EventKind.WARNING, null, msg));
-        } else {
-          // We cannot traverse the subpackage and should skip it silently. Return empty results.
-          return RecursiveFilesystemTraversalValue.EMPTY;
+        String msg = traversal.errorInfo + " crosses package boundary into package rooted at "
+            + traversal.path.getRelativePath().getPathString();
+        switch (traversal.crossPkgBoundaries) {
+          case CROSS:
+            // We are free to traverse the subpackage but we need to display a warning.
+            env.getListener().handle(new Event(EventKind.WARNING, null, msg));
+            break;
+          case DONT_CROSS:
+            // We cannot traverse the subpackage and should skip it silently. Return empty results.
+            return RecursiveFilesystemTraversalValue.EMPTY;
+          case REPORT_ERROR:
+            // We cannot traverse the subpackage and should complain loudly (display an error).
+            throw new RecursiveFilesystemTraversalFunctionException(
+                new CannotCrossPackageBoundaryException(msg));
+          default:
+            throw new IllegalStateException(traversal.toString());
         }
       }
 
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalValue.java
index 023b1cf..f72792f 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalValue.java
@@ -17,6 +17,7 @@
 import com.google.common.base.Objects;
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
+import com.google.devtools.build.lib.actions.FilesetTraversalParams.PackageBoundaryMode;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.collect.nestedset.Order;
@@ -120,7 +121,7 @@
     final boolean isGenerated;
 
     /** Whether traversal should descend into directories that are roots of subpackages. */
-    final boolean crossPkgBoundaries;
+    final PackageBoundaryMode crossPkgBoundaries;
 
     /**
      * Whether to skip checking if the root (if it's a directory) contains a BUILD file.
@@ -135,7 +136,7 @@
     @Nullable final String errorInfo;
 
     public TraversalRequest(RootedPath path, boolean isRootGenerated,
-        boolean crossPkgBoundaries, boolean skipTestingForSubpackage,
+        PackageBoundaryMode crossPkgBoundaries, boolean skipTestingForSubpackage,
         @Nullable String errorInfo) {
       this.path = path;
       this.isGenerated = isRootGenerated;
@@ -188,8 +189,8 @@
     public String toString() {
       return String.format(
           "TraversalParams(root=%s, is_generated=%d, skip_testing_for_subpkg=%d,"
-          + " pkg_boundaries=%d)", path, isGenerated ? 1 : 0, skipTestingForSubpackage ? 1 : 0,
-          crossPkgBoundaries ? 1 : 0);
+          + " pkg_boundaries=%s)", path, isGenerated ? 1 : 0, skipTestingForSubpackage ? 1 : 0,
+          crossPkgBoundaries);
     }
   }