Optionally expand Fileset in BEP. When referenced in top-level targets, we now show the expanded Fileset in BEP when enabled.

RELNOTES: None
PiperOrigin-RevId: 258422325
diff --git a/src/main/java/com/google/devtools/build/lib/actions/CompletionContext.java b/src/main/java/com/google/devtools/build/lib/actions/CompletionContext.java
index a4f9848..e372a26 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/CompletionContext.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/CompletionContext.java
@@ -17,15 +17,23 @@
 import com.google.common.collect.ImmutableList;
 import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
 import com.google.devtools.build.lib.actions.Artifact.ArtifactExpanderImpl;
+import com.google.devtools.build.lib.actions.FilesetManifest.RelativeSymlinkBehavior;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import javax.annotation.Nullable;
 
 /**
  * {@link CompletionContext} contains an {@link ArtifactExpander} and {@link ArtifactPathResolver}
  * used to resolve output files during a {@link
  * com.google.devtools.build.lib.skyframe.CompletionFunction} evaluation.
+ *
+ * <p>Note that output Artifacts may in fact refer to aggregations, namely tree artifacts and
+ * Filesets. We expand these aggregations when visiting artifacts.
  */
 @AutoValue
 public abstract class CompletionContext {
@@ -36,31 +44,59 @@
 
   public abstract ArtifactPathResolver pathResolver();
 
+  public abstract boolean expandFilesets();
+
+  @Nullable
+  public abstract Path execRoot();
+
   public static CompletionContext create(
       Map<Artifact, Collection<Artifact>> expandedArtifacts,
       Map<Artifact, ImmutableList<FilesetOutputSymlink>> expandedFilesets,
+      boolean expandFilesets,
       ActionInputMap inputMap,
       PathResolverFactory pathResolverFactory,
-      String workspaceName) {
+      Path execRoot,
+      String workspaceName)
+      throws IOException {
     ArtifactExpander expander = new ArtifactExpanderImpl(expandedArtifacts, expandedFilesets);
     ArtifactPathResolver pathResolver =
         pathResolverFactory.shouldCreatePathResolverForArtifactValues()
             ? pathResolverFactory.createPathResolverForArtifactValues(
-                inputMap, expandedArtifacts, expandedFilesets.keySet(), workspaceName)
+                inputMap, expandedArtifacts, expandedFilesets, workspaceName)
             : ArtifactPathResolver.IDENTITY;
-    return new AutoValue_CompletionContext(expander, pathResolver);
+    return new AutoValue_CompletionContext(expander, pathResolver, expandFilesets, execRoot);
   }
 
   private static CompletionContext createNull() {
-    return new AutoValue_CompletionContext((artifact, output) -> {}, ArtifactPathResolver.IDENTITY);
+    return new AutoValue_CompletionContext(
+        (artifact, output) -> {}, ArtifactPathResolver.IDENTITY, false, null);
   }
 
   public void visitArtifacts(Iterable<Artifact> artifacts, ArtifactReceiver receiver) {
     for (Artifact artifact : artifacts) {
-      if (artifact.isMiddlemanArtifact() || artifact.isFileset()) {
-        // We never want to report middleman artifacts. They are for internal use only.
-        // Filesets are not currently supported, but should be in the future.
+      if (artifact.isMiddlemanArtifact()) {
         continue;
+      } else if (artifact.isFileset()) {
+        if (!expandFilesets()) {
+          continue;
+        }
+        ImmutableList<FilesetOutputSymlink> links = expander().getFileset(artifact);
+        FilesetManifest filesetManifest;
+        try {
+          filesetManifest =
+              FilesetManifest.constructFilesetManifest(
+                  links, PathFragment.EMPTY_FRAGMENT, RelativeSymlinkBehavior.RESOLVE);
+        } catch (IOException e) {
+          // Unexpected: RelativeSymlinkBehavior.RESOLVE should not throw.
+          throw new IllegalStateException(e);
+        }
+
+        for (Map.Entry<PathFragment, String> mapping : filesetManifest.getEntries().entrySet()) {
+          String targetFile = mapping.getValue();
+          PathFragment locationInFileset = mapping.getKey();
+          receiver.acceptFilesetMapping(
+              artifact, locationInFileset, execRoot().getRelative(targetFile));
+        }
       } else if (artifact.isTreeArtifact()) {
         List<Artifact> expandedArtifacts = new ArrayList<>();
         expander().expand(artifact, expandedArtifacts);
@@ -74,9 +110,10 @@
   }
 
   /** A function that accepts an {@link Artifact}. */
-  @FunctionalInterface
   public interface ArtifactReceiver {
-    void accept(Artifact a);
+    void accept(Artifact artifact);
+
+    void acceptFilesetMapping(Artifact fileset, PathFragment relName, Path targetFile);
   }
 
   /** A factory for {@link ArtifactPathResolver}. */
@@ -84,8 +121,9 @@
     ArtifactPathResolver createPathResolverForArtifactValues(
         ActionInputMap actionInputMap,
         Map<Artifact, Collection<Artifact>> expandedArtifacts,
-        Iterable<Artifact> filesets,
-        String workspaceName);
+        Map<Artifact, ImmutableList<FilesetOutputSymlink>> filesets,
+        String workspaceName)
+        throws IOException;
 
     boolean shouldCreatePathResolverForArtifactValues();
   }
diff --git a/src/main/java/com/google/devtools/build/lib/exec/FilesetManifest.java b/src/main/java/com/google/devtools/build/lib/actions/FilesetManifest.java
similarity index 97%
rename from src/main/java/com/google/devtools/build/lib/exec/FilesetManifest.java
rename to src/main/java/com/google/devtools/build/lib/actions/FilesetManifest.java
index 7dd1a98..3d457db 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/FilesetManifest.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/FilesetManifest.java
@@ -11,12 +11,10 @@
 // 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.exec;
+package com.google.devtools.build.lib.actions;
 
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
-import com.google.devtools.build.lib.actions.FileArtifactValue;
-import com.google.devtools.build.lib.actions.FilesetOutputSymlink;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import java.io.IOException;
 import java.util.Collections;
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TargetCompleteEvent.java b/src/main/java/com/google/devtools/build/lib/analysis/TargetCompleteEvent.java
index aab8910..53472c6 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/TargetCompleteEvent.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/TargetCompleteEvent.java
@@ -22,6 +22,7 @@
 import com.google.common.collect.Iterables;
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.actions.CompletionContext;
+import com.google.devtools.build.lib.actions.CompletionContext.ArtifactReceiver;
 import com.google.devtools.build.lib.actions.EventReportingArtifacts;
 import com.google.devtools.build.lib.analysis.TopLevelArtifactHelper.ArtifactsInOutputGroup;
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
@@ -298,20 +299,47 @@
       Iterable<Artifact> artifacts) {
     completionContext.visitArtifacts(
         artifacts,
-        artifact -> {
-          String name = artifactNameFunction.apply(artifact);
-          String uri =
-              converters.pathConverter().apply(completionContext.pathResolver().toPath(artifact));
-          if (uri != null) {
-            builder.addImportantOutput(newFileFromArtifact(name, artifact).setUri(uri).build());
+        new ArtifactReceiver() {
+          @Override
+          public void accept(Artifact artifact) {
+            String name = artifactNameFunction.apply(artifact);
+            String uri =
+                converters.pathConverter().apply(completionContext.pathResolver().toPath(artifact));
+            if (uri != null) {
+              builder.addImportantOutput(newFileFromArtifact(name, artifact).setUri(uri).build());
+            }
+          }
+
+          @Override
+          public void acceptFilesetMapping(
+              Artifact fileset, PathFragment relativePath, Path targetFile) {
+            String name = artifactNameFunction.apply(fileset);
+            name = PathFragment.create(name).getRelative(relativePath).getPathString();
+            String uri =
+                converters
+                    .pathConverter()
+                    .apply(completionContext.pathResolver().convertPath(targetFile));
+            if (uri != null) {
+              builder.addImportantOutput(
+                  newFileFromArtifact(name, fileset, relativePath).setUri(uri).build());
+            }
           }
         });
   }
 
   public static BuildEventStreamProtos.File.Builder newFileFromArtifact(
       String name, Artifact artifact) {
+    return newFileFromArtifact(name, artifact, PathFragment.EMPTY_FRAGMENT);
+  }
+
+  public static BuildEventStreamProtos.File.Builder newFileFromArtifact(
+      String name, Artifact artifact, PathFragment relPath) {
     File.Builder builder =
-        File.newBuilder().setName(name == null ? artifact.getRootRelativePathString() : name);
+        File.newBuilder()
+            .setName(
+                name == null
+                    ? artifact.getRootRelativePath().getRelative(relPath).getPathString()
+                    : name);
     if (artifact.getRoot().getComponents() != null) {
       builder.addAllPathPrefix(
           Iterables.transform(artifact.getRoot().getComponents(), PathFragment::getPathString));
@@ -330,10 +358,22 @@
       if (group.areImportant()) {
         completionContext.visitArtifacts(
             group.getArtifacts(),
-            artifact -> {
-              builder.add(
-                  new LocalFile(
-                      completionContext.pathResolver().toPath(artifact), LocalFileType.OUTPUT));
+            new ArtifactReceiver() {
+              @Override
+              public void accept(Artifact artifact) {
+                builder.add(
+                    new LocalFile(
+                        completionContext.pathResolver().toPath(artifact), LocalFileType.OUTPUT));
+              }
+
+              @Override
+              public void acceptFilesetMapping(
+                  Artifact fileset, PathFragment name, Path targetFile) {
+                builder.add(
+                    new LocalFile(
+                        completionContext.pathResolver().convertPath(targetFile),
+                        LocalFileType.OUTPUT));
+              }
             });
       }
     }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactContext.java b/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactContext.java
index f6192f0..f3fa2d4 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactContext.java
@@ -25,11 +25,15 @@
 @AutoCodec
 public final class TopLevelArtifactContext {
   private final boolean runTestsExclusively;
+  private final boolean expandFilesets;
   private final ImmutableSortedSet<String> outputGroups;
 
-  public TopLevelArtifactContext(boolean runTestsExclusively,
+  public TopLevelArtifactContext(
+      boolean runTestsExclusively,
+      boolean expandFilesets,
       ImmutableSortedSet<String> outputGroups) {
     this.runTestsExclusively = runTestsExclusively;
+    this.expandFilesets = expandFilesets;
     this.outputGroups = outputGroups;
   }
 
@@ -38,11 +42,16 @@
     return runTestsExclusively;
   }
 
+  public boolean expandFilesets() {
+    return expandFilesets;
+  }
+
   /** Returns the value of the --output_groups flag. */
   public Set<String> outputGroups() {
     return outputGroups;
   }
 
+
   // TopLevelArtifactContexts are stored in maps in BuildView,
   // so equals() and hashCode() need to work.
   @Override
@@ -50,6 +59,7 @@
     if (other instanceof TopLevelArtifactContext) {
       TopLevelArtifactContext otherContext = (TopLevelArtifactContext) other;
       return runTestsExclusively == otherContext.runTestsExclusively
+          && expandFilesets == otherContext.expandFilesets
           && outputGroups.equals(otherContext.outputGroups);
     } else {
       return false;
@@ -58,6 +68,6 @@
 
   @Override
   public int hashCode() {
-    return Objects.hash(runTestsExclusively, outputGroups);
+    return Objects.hash(runTestsExclusively, expandFilesets, outputGroups);
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkCustomCommandLine.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkCustomCommandLine.java
index 237bb51..9994782 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkCustomCommandLine.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkCustomCommandLine.java
@@ -25,14 +25,14 @@
 import com.google.devtools.build.lib.actions.CommandLine;
 import com.google.devtools.build.lib.actions.CommandLineExpansionException;
 import com.google.devtools.build.lib.actions.CommandLineItem;
+import com.google.devtools.build.lib.actions.FilesetManifest;
+import com.google.devtools.build.lib.actions.FilesetManifest.RelativeSymlinkBehavior;
 import com.google.devtools.build.lib.actions.SingleStringArgFormatter;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.concurrent.BlazeInterners;
 import com.google.devtools.build.lib.events.Location;
 import com.google.devtools.build.lib.events.NullEventHandler;
-import com.google.devtools.build.lib.exec.FilesetManifest;
-import com.google.devtools.build.lib.exec.FilesetManifest.RelativeSymlinkBehavior;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import com.google.devtools.build.lib.skylarkbuildapi.FileApi;
 import com.google.devtools.build.lib.skylarkbuildapi.FileRootApi;
diff --git a/src/main/java/com/google/devtools/build/lib/buildeventstream/BuildEventProtocolOptions.java b/src/main/java/com/google/devtools/build/lib/buildeventstream/BuildEventProtocolOptions.java
index 4f31f75..fb8fc2e 100644
--- a/src/main/java/com/google/devtools/build/lib/buildeventstream/BuildEventProtocolOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/buildeventstream/BuildEventProtocolOptions.java
@@ -40,4 +40,12 @@
     help = "Selects how to upload artifacts referenced in the build event protocol."
   )
   public String buildEventUploadStrategy;
+
+  @Option(
+      name = "experimental_build_event_expand_filesets",
+      defaultValue = "false",
+      documentationCategory = OptionDocumentationCategory.LOGGING,
+      effectTags = {OptionEffectTag.AFFECTS_OUTPUTS},
+      help = "If true, expand Filesets in the BEP when presenting output files.")
+  public boolean expandFilesets;
 }
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/BuildRequest.java b/src/main/java/com/google/devtools/build/lib/buildtool/BuildRequest.java
index e6213e4..c0a4aea 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/BuildRequest.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/BuildRequest.java
@@ -25,6 +25,7 @@
 import com.google.devtools.build.lib.analysis.TopLevelArtifactContext;
 import com.google.devtools.build.lib.analysis.config.BuildOptions;
 import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
+import com.google.devtools.build.lib.buildeventstream.BuildEventProtocolOptions;
 import com.google.devtools.build.lib.exec.ExecutionOptions;
 import com.google.devtools.build.lib.packages.StarlarkSemanticsOptions;
 import com.google.devtools.build.lib.pkgcache.LoadingOptions;
@@ -293,6 +294,7 @@
   public TopLevelArtifactContext getTopLevelArtifactContext() {
     return new TopLevelArtifactContext(
         getOptions(ExecutionOptions.class).testStrategy.equals("exclusive"),
+        getOptions(BuildEventProtocolOptions.class).expandFilesets,
         OutputGroupInfo.determineOutputGroups(getBuildOptions().outputGroups));
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/exec/SpawnInputExpander.java b/src/main/java/com/google/devtools/build/lib/exec/SpawnInputExpander.java
index 6285f31..63cd3e0 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/SpawnInputExpander.java
+++ b/src/main/java/com/google/devtools/build/lib/exec/SpawnInputExpander.java
@@ -23,12 +23,13 @@
 import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
 import com.google.devtools.build.lib.actions.ArtifactPathResolver;
 import com.google.devtools.build.lib.actions.FileArtifactValue;
+import com.google.devtools.build.lib.actions.FilesetManifest;
+import com.google.devtools.build.lib.actions.FilesetManifest.RelativeSymlinkBehavior;
 import com.google.devtools.build.lib.actions.FilesetOutputSymlink;
 import com.google.devtools.build.lib.actions.MetadataProvider;
 import com.google.devtools.build.lib.actions.RunfilesSupplier;
 import com.google.devtools.build.lib.actions.Spawn;
 import com.google.devtools.build.lib.actions.cache.VirtualActionInput.EmptyActionInput;
-import com.google.devtools.build.lib.exec.FilesetManifest.RelativeSymlinkBehavior;
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import java.io.IOException;
diff --git a/src/main/java/com/google/devtools/build/lib/remote/RemoteOutputService.java b/src/main/java/com/google/devtools/build/lib/remote/RemoteOutputService.java
index bb607e1..c1157a0 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/RemoteOutputService.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/RemoteOutputService.java
@@ -21,6 +21,7 @@
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.actions.Artifact.SourceArtifact;
 import com.google.devtools.build.lib.actions.ArtifactPathResolver;
+import com.google.devtools.build.lib.actions.FilesetOutputSymlink;
 import com.google.devtools.build.lib.actions.cache.MetadataHandler;
 import com.google.devtools.build.lib.events.EventHandler;
 import com.google.devtools.build.lib.vfs.BatchStat;
@@ -135,7 +136,7 @@
       ImmutableList<Root> pathEntries,
       ActionInputMap actionInputMap,
       Map<Artifact, Collection<Artifact>> expandedArtifacts,
-      Iterable<Artifact> filesets) {
+      Map<Artifact, ImmutableList<FilesetOutputSymlink>> filesets) {
     FileSystem remoteFileSystem =
         new RemoteActionFileSystem(
             fileSystem, execRoot, relativeOutputPath, actionInputMap, actionInputFetcher);
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/NamedArtifactGroup.java b/src/main/java/com/google/devtools/build/lib/runtime/NamedArtifactGroup.java
index a324b29..57c1b96 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/NamedArtifactGroup.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/NamedArtifactGroup.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.actions.CompletionContext;
+import com.google.devtools.build.lib.actions.CompletionContext.ArtifactReceiver;
 import com.google.devtools.build.lib.actions.EventReportingArtifacts;
 import com.google.devtools.build.lib.buildeventstream.ArtifactGroupNamer;
 import com.google.devtools.build.lib.buildeventstream.BuildEvent;
@@ -30,6 +31,8 @@
 import com.google.devtools.build.lib.buildeventstream.GenericBuildEvent;
 import com.google.devtools.build.lib.buildeventstream.PathConverter;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetView;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
 import java.util.Collection;
 
 /**
@@ -65,10 +68,22 @@
     ImmutableList.Builder<LocalFile> artifacts = ImmutableList.builder();
     completionContext.visitArtifacts(
         view.directs(),
-        artifact -> {
-          artifacts.add(
-              new LocalFile(
-                  completionContext.pathResolver().toPath(artifact), LocalFileType.OUTPUT));
+        new ArtifactReceiver() {
+          @Override
+          public void accept(Artifact artifact) {
+            artifacts.add(
+                new LocalFile(
+                    completionContext.pathResolver().toPath(artifact), LocalFileType.OUTPUT));
+          }
+
+          @Override
+          public void acceptFilesetMapping(
+              Artifact fileset, PathFragment relName, Path targetFile) {
+            artifacts.add(
+                new LocalFile(
+                    completionContext.pathResolver().convertPath(targetFile),
+                    LocalFileType.OUTPUT));
+          }
         });
     return artifacts.build();
   }
@@ -82,10 +97,25 @@
         BuildEventStreamProtos.NamedSetOfFiles.newBuilder();
     completionContext.visitArtifacts(
         view.directs(),
-        artifact -> {
-          String uri = pathConverter.apply(completionContext.pathResolver().toPath(artifact));
-          if (uri != null) {
-            builder.addFiles(newFileFromArtifact(artifact).setUri(uri));
+        new ArtifactReceiver() {
+          @Override
+          public void accept(Artifact artifact) {
+            String uri = pathConverter.apply(completionContext.pathResolver().toPath(artifact));
+            if (uri != null) {
+              builder.addFiles(newFileFromArtifact(artifact).setUri(uri));
+            }
+          }
+
+          @Override
+          public void acceptFilesetMapping(
+              Artifact fileset, PathFragment relName, Path targetFile) {
+            String uri =
+                converters
+                    .pathConverter()
+                    .apply(completionContext.pathResolver().convertPath(targetFile));
+            if (uri != null) {
+              builder.addFiles(newFileFromArtifact(null, fileset, relName).setUri(uri).build());
+            }
           }
         });
     for (NestedSetView<Artifact> child : view.transitives()) {
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/BuildCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/BuildCommand.java
index 78d3eb2..0f0f9b5 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/BuildCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/BuildCommand.java
@@ -14,6 +14,7 @@
 package com.google.devtools.build.lib.runtime.commands;
 
 import com.google.devtools.build.lib.analysis.AnalysisOptions;
+import com.google.devtools.build.lib.buildeventstream.BuildEventProtocolOptions;
 import com.google.devtools.build.lib.buildtool.BuildRequest;
 import com.google.devtools.build.lib.buildtool.BuildRequestOptions;
 import com.google.devtools.build.lib.buildtool.BuildTool;
@@ -41,24 +42,24 @@
  * passed to Blaze.
  */
 @Command(
-  name = "build",
-  builds = true,
-  options = {
-    BuildRequestOptions.class,
-    ExecutionOptions.class,
-    LocalExecutionOptions.class,
-    PackageCacheOptions.class,
-    AnalysisOptions.class,
-    LoadingOptions.class,
-    KeepGoingOption.class,
-    LoadingPhaseThreadsOption.class
-  },
-  usesConfigurationOptions = true,
-  shortDescription = "Builds the specified targets.",
-  allowResidue = true,
-  completion = "label",
-  help = "resource:build.txt"
-)
+    name = "build",
+    builds = true,
+    options = {
+      BuildRequestOptions.class,
+      ExecutionOptions.class,
+      LocalExecutionOptions.class,
+      PackageCacheOptions.class,
+      AnalysisOptions.class,
+      LoadingOptions.class,
+      KeepGoingOption.class,
+      LoadingPhaseThreadsOption.class,
+      BuildEventProtocolOptions.class
+    },
+    usesConfigurationOptions = true,
+    shortDescription = "Builds the specified targets.",
+    allowResidue = true,
+    completion = "label",
+    help = "resource:build.txt")
 public final class BuildCommand implements BlazeCommand {
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/CompletionFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/CompletionFunction.java
index 9cc54f9..04df6fd 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/CompletionFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/CompletionFunction.java
@@ -39,14 +39,17 @@
 import com.google.devtools.build.lib.skyframe.AspectCompletionValue.AspectCompletionKey;
 import com.google.devtools.build.lib.skyframe.AspectValue.AspectKey;
 import com.google.devtools.build.lib.skyframe.TargetCompletionValue.TargetCompletionKey;
+import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.skyframe.SkyFunction;
 import com.google.devtools.build.skyframe.SkyFunctionException;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
 import com.google.devtools.build.skyframe.ValueOrException;
+import java.io.IOException;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.function.Supplier;
 import javax.annotation.Nullable;
 
 /**
@@ -322,21 +325,27 @@
     }
   }
 
-  public static SkyFunction targetCompletionFunction(PathResolverFactory pathResolverFactory) {
-    return new CompletionFunction<>(pathResolverFactory, new TargetCompletor());
+  public static SkyFunction targetCompletionFunction(
+      PathResolverFactory pathResolverFactory, Supplier<Path> execRootSupplier) {
+    return new CompletionFunction<>(pathResolverFactory, new TargetCompletor(), execRootSupplier);
   }
 
-  public static SkyFunction aspectCompletionFunction(PathResolverFactory pathResolverFactory) {
-    return new CompletionFunction<>(pathResolverFactory, new AspectCompletor());
+  public static SkyFunction aspectCompletionFunction(
+      PathResolverFactory pathResolverFactory, Supplier<Path> execRootSupplier) {
+    return new CompletionFunction<>(pathResolverFactory, new AspectCompletor(), execRootSupplier);
   }
 
   private final PathResolverFactory pathResolverFactory;
   private final Completor<TValue, TResult> completor;
+  private final Supplier<Path> execRootSupplier;
 
   private CompletionFunction(
-      PathResolverFactory pathResolverFactory, Completor<TValue, TResult> completor) {
+      PathResolverFactory pathResolverFactory,
+      Completor<TValue, TResult> completor,
+      Supplier<Path> execRootSupplier) {
     this.pathResolverFactory = pathResolverFactory;
     this.completor = completor;
+    this.execRootSupplier = execRootSupplier;
   }
 
   @Nullable
@@ -431,13 +440,20 @@
       return null;
     }
 
-    CompletionContext ctx =
-        CompletionContext.create(
-            expandedArtifacts,
-            expandedFilesets,
-            inputMap,
-            pathResolverFactory,
-            workspaceNameValue.getName());
+    final CompletionContext ctx;
+    try {
+      ctx =
+          CompletionContext.create(
+              expandedArtifacts,
+              expandedFilesets,
+              topLevelContext.expandFilesets(),
+              inputMap,
+              pathResolverFactory,
+              execRootSupplier.get(),
+              workspaceNameValue.getName());
+    } catch (IOException e) {
+      throw new CompletionFunctionException(e);
+    }
 
     ExtendedEventHandler.Postable postable =
         completor.createSucceeded(skyKey, value, ctx, topLevelContext, env);
@@ -467,6 +483,11 @@
       this.actionException = null;
     }
 
+    public CompletionFunctionException(IOException e) {
+      super(e, Transience.TRANSIENT);
+      this.actionException = null;
+    }
+
     @Override
     public boolean isCatastrophic() {
       return actionException != null && actionException.isCatastrophe();
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 972e530..540fdd9 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
@@ -60,6 +60,7 @@
 import com.google.devtools.build.lib.actions.FileStateType;
 import com.google.devtools.build.lib.actions.FileStateValue;
 import com.google.devtools.build.lib.actions.FileValue;
+import com.google.devtools.build.lib.actions.FilesetOutputSymlink;
 import com.google.devtools.build.lib.actions.MetadataProvider;
 import com.google.devtools.build.lib.actions.ResourceManager;
 import com.google.devtools.build.lib.analysis.AnalysisProtos.ActionGraphContainer;
@@ -186,6 +187,7 @@
 import com.google.devtools.common.options.OptionsParsingException;
 import com.google.devtools.common.options.OptionsProvider;
 import com.google.errorprone.annotations.ForOverride;
+import java.io.IOException;
 import java.io.PrintStream;
 import java.math.BigInteger;
 import java.util.ArrayList;
@@ -366,8 +368,9 @@
     public ArtifactPathResolver createPathResolverForArtifactValues(
         ActionInputMap actionInputMap,
         Map<Artifact, Collection<Artifact>> expandedArtifacts,
-        Iterable<Artifact> filesets,
-        String workspaceName) {
+        Map<Artifact, ImmutableList<FilesetOutputSymlink>> filesets,
+        String workspaceName)
+        throws IOException {
       Preconditions.checkState(shouldCreatePathResolverForArtifactValues());
       return outputService.createPathResolverForArtifactValues(
           directories.getExecRoot(workspaceName).asFragment(),
@@ -583,10 +586,12 @@
     map.put(SkyFunctions.EXTERNAL_PACKAGE, new ExternalPackageFunction());
     map.put(
         SkyFunctions.TARGET_COMPLETION,
-        CompletionFunction.targetCompletionFunction(pathResolverFactory));
+        CompletionFunction.targetCompletionFunction(
+            pathResolverFactory, skyframeActionExecutor::getExecRoot));
     map.put(
         SkyFunctions.ASPECT_COMPLETION,
-        CompletionFunction.aspectCompletionFunction(pathResolverFactory));
+        CompletionFunction.aspectCompletionFunction(
+            pathResolverFactory, skyframeActionExecutor::getExecRoot));
     map.put(SkyFunctions.TEST_COMPLETION, new TestCompletionFunction());
     map.put(
         Artifact.ARTIFACT,
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/OutputService.java b/src/main/java/com/google/devtools/build/lib/vfs/OutputService.java
index 29bf057..f70be6f 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/OutputService.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/OutputService.java
@@ -192,7 +192,8 @@
       ImmutableList<Root> pathEntries,
       ActionInputMap actionInputMap,
       Map<Artifact, Collection<Artifact>> expandedArtifacts,
-      Iterable<Artifact> filesets) {
+      Map<Artifact, ImmutableList<FilesetOutputSymlink>> filesets)
+      throws IOException {
     throw new IllegalStateException("Path resolver not supported by this class");
   }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/TargetCompleteEventTest.java b/src/test/java/com/google/devtools/build/lib/analysis/TargetCompleteEventTest.java
index dfbc33b..a2b39f1 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/TargetCompleteEventTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/TargetCompleteEventTest.java
@@ -43,7 +43,7 @@
     ConfiguredTargetAndData ctAndData =
         new ConfiguredTargetAndData(ct, tac.getTarget(), tac.getConfiguration());
     TopLevelArtifactContext context =
-        new TopLevelArtifactContext(false, OutputGroupInfo.DEFAULT_GROUPS);
+        new TopLevelArtifactContext(false, false, OutputGroupInfo.DEFAULT_GROUPS);
     ArtifactsToBuild artifactsToBuild =
         TopLevelArtifactHelper.getAllArtifactsToBuild(ct, context);
     TargetCompleteEvent event =
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelperTest.java b/src/test/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelperTest.java
index 68cd008..832d049 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelperTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelperTest.java
@@ -65,7 +65,7 @@
       mapBuilder.put(
           groupArtifact.getFirst(), newArtifacts(checkNotNull(groupArtifact.getSecond())));
     }
-    ctx = new TopLevelArtifactContext(false, setBuilder.build());
+    ctx = new TopLevelArtifactContext(false, false, setBuilder.build());
     groupProvider = new OutputGroupInfo(mapBuilder.build());
   }
 
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestUtil.java b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestUtil.java
index 7b5eb43..d513af8 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestUtil.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestUtil.java
@@ -69,13 +69,12 @@
  */
 public final class AnalysisTestUtil {
 
-  /**
-   * TopLevelArtifactContext that should be sufficient for testing.
-   */
+  /** TopLevelArtifactContext that should be sufficient for testing. */
   public static final TopLevelArtifactContext TOP_LEVEL_ARTIFACT_CONTEXT =
       new TopLevelArtifactContext(
-          /*runTestsExclusively=*/false,
-          /*outputGroups=*/ImmutableSortedSet.copyOf(OutputGroupInfo.DEFAULT_GROUPS));
+          /*runTestsExclusively=*/ false,
+          /*expandFilesets=*/ false,
+          /*outputGroups=*/ ImmutableSortedSet.copyOf(OutputGroupInfo.DEFAULT_GROUPS));
 
   /**
    * An {@link AnalysisEnvironment} implementation that collects the actions registered.
diff --git a/src/test/java/com/google/devtools/build/lib/buildtool/BUILD b/src/test/java/com/google/devtools/build/lib/buildtool/BUILD
index e99248e..54b626f 100644
--- a/src/test/java/com/google/devtools/build/lib/buildtool/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/buildtool/BUILD
@@ -29,6 +29,7 @@
         "//src/main/java/com/google/devtools/build/lib:runtime",
         "//src/main/java/com/google/devtools/build/lib:util",
         "//src/main/java/com/google/devtools/build/lib/actions",
+        "//src/main/java/com/google/devtools/build/lib/buildeventstream",
         "//src/main/java/com/google/devtools/build/lib/cmdline",
         "//src/main/java/com/google/devtools/build/lib/collect/nestedset",
         "//src/main/java/com/google/devtools/build/lib/exec/local:options",
diff --git a/src/test/java/com/google/devtools/build/lib/buildtool/util/BlazeRuntimeWrapper.java b/src/test/java/com/google/devtools/build/lib/buildtool/util/BlazeRuntimeWrapper.java
index fb363d7..8762d59 100644
--- a/src/test/java/com/google/devtools/build/lib/buildtool/util/BlazeRuntimeWrapper.java
+++ b/src/test/java/com/google/devtools/build/lib/buildtool/util/BlazeRuntimeWrapper.java
@@ -25,6 +25,7 @@
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.ServerDirectories;
 import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection;
+import com.google.devtools.build.lib.buildeventstream.BuildEventProtocolOptions;
 import com.google.devtools.build.lib.buildtool.BuildRequest;
 import com.google.devtools.build.lib.buildtool.BuildRequestOptions;
 import com.google.devtools.build.lib.buildtool.BuildResult;
@@ -138,6 +139,7 @@
         new HashSet<>(
             ImmutableList.of(
                 BuildRequestOptions.class,
+                BuildEventProtocolOptions.class,
                 ExecutionOptions.class,
                 LocalExecutionOptions.class,
                 CommonCommandOptions.class,
diff --git a/src/test/java/com/google/devtools/build/lib/exec/FilesetManifestTest.java b/src/test/java/com/google/devtools/build/lib/exec/FilesetManifestTest.java
index 5b4fdb2..d9ca4ec 100644
--- a/src/test/java/com/google/devtools/build/lib/exec/FilesetManifestTest.java
+++ b/src/test/java/com/google/devtools/build/lib/exec/FilesetManifestTest.java
@@ -14,12 +14,13 @@
 package com.google.devtools.build.lib.exec;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.devtools.build.lib.exec.FilesetManifest.RelativeSymlinkBehavior.ERROR;
-import static com.google.devtools.build.lib.exec.FilesetManifest.RelativeSymlinkBehavior.IGNORE;
-import static com.google.devtools.build.lib.exec.FilesetManifest.RelativeSymlinkBehavior.RESOLVE;
+import static com.google.devtools.build.lib.actions.FilesetManifest.RelativeSymlinkBehavior.ERROR;
+import static com.google.devtools.build.lib.actions.FilesetManifest.RelativeSymlinkBehavior.IGNORE;
+import static com.google.devtools.build.lib.actions.FilesetManifest.RelativeSymlinkBehavior.RESOLVE;
 import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows;
 
 import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.FilesetManifest;
 import com.google.devtools.build.lib.actions.FilesetOutputSymlink;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import java.io.IOException;
diff --git a/src/test/java/com/google/devtools/build/lib/exec/SpawnInputExpanderTest.java b/src/test/java/com/google/devtools/build/lib/exec/SpawnInputExpanderTest.java
index 153f44e..d2bfeea 100644
--- a/src/test/java/com/google/devtools/build/lib/exec/SpawnInputExpanderTest.java
+++ b/src/test/java/com/google/devtools/build/lib/exec/SpawnInputExpanderTest.java
@@ -14,9 +14,9 @@
 package com.google.devtools.build.lib.exec;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.devtools.build.lib.exec.FilesetManifest.RelativeSymlinkBehavior.ERROR;
-import static com.google.devtools.build.lib.exec.FilesetManifest.RelativeSymlinkBehavior.IGNORE;
-import static com.google.devtools.build.lib.exec.FilesetManifest.RelativeSymlinkBehavior.RESOLVE;
+import static com.google.devtools.build.lib.actions.FilesetManifest.RelativeSymlinkBehavior.ERROR;
+import static com.google.devtools.build.lib.actions.FilesetManifest.RelativeSymlinkBehavior.IGNORE;
+import static com.google.devtools.build.lib.actions.FilesetManifest.RelativeSymlinkBehavior.RESOLVE;
 import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows;
 import static org.junit.Assert.fail;