Update from Google.

--
MOE_MIGRATED_REVID=85702957
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/Runfiles.java b/src/main/java/com/google/devtools/build/lib/analysis/Runfiles.java
new file mode 100644
index 0000000..a4da4b4
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/Runfiles.java
@@ -0,0 +1,756 @@
+// Copyright 2014 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.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.analysis;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+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;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * An object that encapsulates runfiles. Conceptually, the runfiles are a map of paths to files,
+ * forming a symlink tree.
+ *
+ * <p>In order to reduce memory consumption, this map is not explicitly stored here, but instead as
+ * a combination of four parts: artifacts placed at their root-relative paths, source tree symlinks,
+ * root symlinks (outside of the source tree), and artifacts included as parts of "pruning
+ * manifests" (see {@link PruningManifest}).
+ */
+@Immutable
+public final class Runfiles {
+  private static final Function<Map.Entry<PathFragment, Artifact>, Artifact> TO_ARTIFACT =
+      new Function<Map.Entry<PathFragment, Artifact>, Artifact>() {
+        @Override
+        public Artifact apply(Map.Entry<PathFragment, Artifact> input) {
+          return input.getValue();
+        }
+      };
+
+  private static final Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>>
+      DUMMY_SYMLINK_EXPANDER =
+      new Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>>() {
+        @Override
+        public Map<PathFragment, Artifact> apply(Map<PathFragment, Artifact> input) {
+          return ImmutableMap.of();
+        }
+      };
+
+  // It is important to declare this *after* the DUMMY_SYMLINK_EXPANDER to avoid NPEs
+  public static final Runfiles EMPTY = new Builder().build();
+
+
+  /**
+   * The artifacts that should *always* be present in the runfiles directory. These are
+   * differentiated from the artifacts that may or may not be included by a pruning manifest
+   * (see {@link PruningManifest} below).
+   *
+   * <p>This collection may not include any middlemen. These artifacts will be placed at a location
+   * that corresponds to the root-relative path of each artifact. It's possible for several
+   * artifacts to have the same root-relative path, in which case the last one will win.
+   */
+  private final NestedSet<Artifact> unconditionalArtifacts;
+
+  /**
+   * A map of symlinks that should be present in the runfiles directory. In general, the symlink can
+   * be determined from the artifact by using the root-relative path, so this should only be used
+   * for cases where that isn't possible.
+   *
+   * <p>This may include runfiles symlinks from the root of the runfiles tree.
+   */
+  private final NestedSet<Map.Entry<PathFragment, Artifact>> symlinks;
+
+  /**
+   * A map of symlinks that should be present above the runfiles directory. These are useful for
+   * certain rule types like AppEngine apps which have root level config files outside of the
+   * regular source tree.
+   */
+  private final NestedSet<Map.Entry<PathFragment, Artifact>> rootSymlinks;
+
+  /**
+   * A function to generate extra manifest entries.
+   */
+  private final Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>>
+      manifestExpander;
+
+  /**
+   * Defines a set of artifacts that may or may not be included in the runfiles directory and
+   * a manifest file that makes that determination. These are applied on top of any artifacts
+   * specified in {@link #unconditionalArtifacts}.
+   *
+   * <p>The incentive behind this is to enable execution-phase "pruning" of runfiles. Anything
+   * set in unconditionalArtifacts is hard-set in Blaze's analysis phase, and thus unchangeable in
+   * response to execution phase results. This isn't always convenient. For example, say we have an
+   * action that consumes a set of "possible" runtime dependencies for a source file, parses that
+   * file for "import a.b.c" statements, and outputs a manifest of the actual dependencies that are
+   * referenced and thus really needed. This can reduce the size of the runfiles set, but we can't
+   * use this information until the manifest output is available.
+   *
+   * <p>Only artifacts present in the candidate set AND the manifest output make it into the
+   * runfiles tree. The candidate set requirement guarantees that analysis-time dependencies are a
+   * superset of the pruned dependencies, so undeclared inclusions (which can break build
+   * correctness) aren't possible.
+   */
+  public static class PruningManifest {
+    private final NestedSet<Artifact> candidateRunfiles;
+    private final Artifact manifestFile;
+
+    /**
+     * Creates a new pruning manifest.
+     *
+     * @param candidateRunfiles set of possible artifacts that the manifest file may reference
+     * @param manifestFile the manifest file, expected to be a newline-separated list of
+     *     source tree root-relative paths (i.e. "my/package/myfile.txt"). Anything that can't be
+     *     resolved back to an entry in candidateRunfiles is ignored and will *not* make it into
+     *     the runfiles tree.
+     */
+    public PruningManifest(NestedSet<Artifact> candidateRunfiles, Artifact manifestFile) {
+      this.candidateRunfiles = candidateRunfiles;
+      this.manifestFile = manifestFile;
+    }
+
+    public NestedSet<Artifact> getCandidateRunfiles() {
+      return candidateRunfiles;
+    }
+
+    public Artifact getManifestFile() {
+      return manifestFile;
+    }
+  }
+
+  /**
+   * The pruning manifests that should be applied to these runfiles.
+   */
+  private final NestedSet<PruningManifest> pruningManifests;
+
+  private Runfiles(NestedSet<Artifact> artifacts,
+      NestedSet<Map.Entry<PathFragment, Artifact>> symlinks,
+      NestedSet<Map.Entry<PathFragment, Artifact>> rootSymlinks,
+      NestedSet<PruningManifest> pruningManifests,
+      Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> expander) {
+    this.unconditionalArtifacts = Preconditions.checkNotNull(artifacts);
+    this.symlinks = Preconditions.checkNotNull(symlinks);
+    this.rootSymlinks = Preconditions.checkNotNull(rootSymlinks);
+    this.pruningManifests = Preconditions.checkNotNull(pruningManifests);
+    this.manifestExpander = Preconditions.checkNotNull(expander);
+  }
+
+  /**
+   * Returns the artifacts that are unconditionally included in the runfiles (as opposed to
+   * pruning manifest candidates, which may or may not be included).
+   */
+  @VisibleForTesting
+  public NestedSet<Artifact> getUnconditionalArtifacts() {
+    return unconditionalArtifacts;
+  }
+
+  /**
+   * Returns the artifacts that are unconditionally included in the runfiles (as opposed to
+   * pruning manifest candidates, which may or may not be included). Middleman artifacts are
+   * excluded.
+   */
+  public Iterable<Artifact> getUnconditionalArtifactsWithoutMiddlemen() {
+    return Iterables.filter(unconditionalArtifacts, Artifact.MIDDLEMAN_FILTER);
+  }
+
+  /**
+   * Returns the collection of runfiles as artifacts, including both unconditional artifacts
+   * and pruning manifest candidates.
+   */
+  @VisibleForTesting
+  public NestedSet<Artifact> getArtifacts() {
+    NestedSetBuilder<Artifact> allArtifacts = NestedSetBuilder.stableOrder();
+    allArtifacts.addAll(unconditionalArtifacts.toCollection());
+    for (PruningManifest manifest : getPruningManifests()) {
+      allArtifacts.addTransitive(manifest.getCandidateRunfiles());
+    }
+    return allArtifacts.build();
+  }
+
+  /**
+   * Returns the collection of runfiles as artifacts, including both unconditional artifacts
+   * and pruning manifest candidates. Middleman artifacts are excluded.
+   */
+  public Iterable<Artifact> getArtifactsWithoutMiddlemen() {
+    return Iterables.filter(getArtifacts(), Artifact.MIDDLEMAN_FILTER);
+  }
+
+  /**
+   * Returns the symlinks.
+   */
+  public NestedSet<Map.Entry<PathFragment, Artifact>> getSymlinks() {
+    return symlinks;
+  }
+
+  /**
+   * Returns the symlinks as a map from path fragment to artifact.
+   */
+  public Map<PathFragment, Artifact> getSymlinksAsMap() {
+    return entriesToMap(symlinks);
+  }
+
+  /**
+   * @param eventHandler Used for throwing an error if we have an obscuring runlink.
+   *                 May be null, in which case obscuring symlinks are silently discarded.
+   * @param location Location for reporter. Ignored if reporter is null.
+   * @param workingManifest Manifest to be checked for obscuring symlinks.
+   * @return map of source file names mapped to their location on disk.
+   */
+  public static Map<PathFragment, Artifact> filterListForObscuringSymlinks(
+      EventHandler eventHandler, Location location, Map<PathFragment, Artifact> workingManifest) {
+    Map<PathFragment, Artifact> newManifest = new HashMap<>();
+
+    outer:
+    for (Iterator<Entry<PathFragment, Artifact>> i = workingManifest.entrySet().iterator();
+         i.hasNext(); ) {
+      Entry<PathFragment, Artifact> entry = i.next();
+      PathFragment source = entry.getKey();
+      Artifact symlink = entry.getValue();
+      // drop nested entries; warn if this changes anything
+      int n = source.segmentCount();
+      for (int j = 1; j < n; ++j) {
+        PathFragment prefix = source.subFragment(0, n - j);
+        Artifact ancestor = workingManifest.get(prefix);
+        if (ancestor != null) {
+          // This is an obscuring symlink, so just drop it and move on if there's no reporter.
+          if (eventHandler == null) {
+            continue outer;
+          }
+          PathFragment suffix = source.subFragment(n - j, n);
+          Path viaAncestor = ancestor.getPath().getRelative(suffix);
+          Path expected = symlink.getPath();
+          if (!viaAncestor.equals(expected)) {
+            eventHandler.handle(Event.warn(location, "runfiles symlink " + source + " -> "
+                + expected + " obscured by " + prefix + " -> " + ancestor.getPath()));
+          }
+          continue outer;
+        }
+      }
+      newManifest.put(entry.getKey(), entry.getValue());
+    }
+    return newManifest;
+  }
+
+  /**
+   * Returns the symlinks as a map from PathFragment to Artifact, with PathFragments relativized
+   * and rooted at the specified points.
+   * @param root The root the PathFragment is computed relative to (before it is
+   *             rooted again). May be null.
+   * @param eventHandler Used for throwing an error if we have an obscuring runlink.
+   *                 May be null, in which case obscuring symlinks are silently discarded.
+   * @param location Location for eventHandler warnings. Ignored if eventHandler is null.
+   * @return Pair of Maps from remote path fragment to artifact, the first of normal source tree
+   *         entries, the second of any elements that live outside the source tree.
+   */
+  public Pair<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> getRunfilesInputs(
+      PathFragment root, String workspaceSuffix, EventHandler eventHandler, Location location)
+          throws IOException {
+    Map<PathFragment, Artifact> manifest = getSymlinksAsMap();
+    // Add unconditional artifacts (committed to inclusion on construction of runfiles).
+    for (Artifact artifact : getUnconditionalArtifactsWithoutMiddlemen()) {
+      addToManifest(manifest, artifact, root);
+    }
+
+    // Add conditional artifacts (only included if they appear in a pruning manifest).
+    for (Runfiles.PruningManifest pruningManifest : getPruningManifests()) {
+      // This map helps us convert from source tree root-relative paths back to artifacts.
+      Map<String, Artifact> allowedRunfiles = new HashMap<>();
+      for (Artifact artifact : pruningManifest.getCandidateRunfiles()) {
+        allowedRunfiles.put(artifact.getRootRelativePath().getPathString(), artifact);
+      }
+      BufferedReader reader = new BufferedReader(
+          new InputStreamReader(pruningManifest.getManifestFile().getPath().getInputStream()));
+      String line;
+      while ((line = reader.readLine()) != null) {
+        Artifact artifact = allowedRunfiles.get(line);
+        if (artifact != null) {
+          addToManifest(manifest, artifact, root);
+        }
+      }
+    }
+
+    manifest = filterListForObscuringSymlinks(eventHandler, location, manifest);
+    manifest.putAll(manifestExpander.apply(manifest));
+    PathFragment path = new PathFragment(workspaceSuffix);
+    Map<PathFragment, Artifact> result = new HashMap<>();
+    for (Map.Entry<PathFragment, Artifact> entry : manifest.entrySet()) {
+      result.put(path.getRelative(entry.getKey()), entry.getValue());
+    }
+    return Pair.of(result, (Map<PathFragment, Artifact>) new HashMap<>(getRootSymlinksAsMap()));
+  }
+
+  @VisibleForTesting
+  protected static void addToManifest(Map<PathFragment, Artifact> manifest, Artifact artifact,
+      PathFragment root) {
+    PathFragment rootRelativePath = root != null
+        ? artifact.getRootRelativePath().relativeTo(root)
+        : artifact.getRootRelativePath();
+    manifest.put(rootRelativePath, artifact);
+  }
+
+  /**
+   * Returns the root symlinks.
+   */
+  public NestedSet<Map.Entry<PathFragment, Artifact>> getRootSymlinks() {
+    return rootSymlinks;
+  }
+
+  /**
+   * Returns the root symlinks.
+   */
+  public Map<PathFragment, Artifact> getRootSymlinksAsMap() {
+    return entriesToMap(rootSymlinks);
+  }
+
+  /**
+   * Returns the unified map of path fragments to artifacts, taking both artifacts and symlinks into
+   * account.
+   */
+  public Map<PathFragment, Artifact> asMapWithoutRootSymlinks() {
+    Map<PathFragment, Artifact> result = entriesToMap(symlinks);
+    // If multiple artifacts have the same root-relative path, the last one in the list will win.
+    // That is because the runfiles tree cannot contain the same artifact for different
+    // configurations, because it only uses root-relative paths.
+    for (Artifact artifact : Iterables.filter(unconditionalArtifacts, Artifact.MIDDLEMAN_FILTER)) {
+      result.put(artifact.getRootRelativePath(), artifact);
+    }
+    return result;
+  }
+
+  /**
+   * Returns the pruning manifests specified for this runfiles tree.
+   */
+  public NestedSet<PruningManifest> getPruningManifests() {
+    return pruningManifests;
+  }
+
+  /**
+   * Returns the symlinks expander specified for this runfiles tree.
+   */
+  public Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> getSymlinkExpander() {
+    return manifestExpander;
+  }
+
+  /**
+   * Returns the unified map of path fragments to artifacts, taking into account artifacts,
+   * symlinks, and pruning manifest candidates. The returned set is guaranteed to be a (not
+   * necessarily strict) superset of the actual runfiles tree created at execution time.
+   */
+  public NestedSet<Artifact> getAllArtifacts() {
+    if (isEmpty()) {
+      return NestedSetBuilder.emptySet(Order.STABLE_ORDER);
+    }
+    NestedSetBuilder<Artifact> allArtifacts = NestedSetBuilder.stableOrder();
+    allArtifacts
+        .addTransitive(unconditionalArtifacts)
+        .addAll(Iterables.transform(symlinks, TO_ARTIFACT))
+        .addAll(Iterables.transform(rootSymlinks, TO_ARTIFACT));
+    for (PruningManifest manifest : getPruningManifests()) {
+      allArtifacts.addTransitive(manifest.getCandidateRunfiles());
+    }
+    return allArtifacts.build();
+  }
+
+  /**
+   * Returns if there are no runfiles.
+   */
+  public boolean isEmpty() {
+    return unconditionalArtifacts.isEmpty() && symlinks.isEmpty() && rootSymlinks.isEmpty() &&
+        pruningManifests.isEmpty();
+  }
+
+  private static <K, V> Map<K, V> entriesToMap(Iterable<Map.Entry<K, V>> entrySet) {
+    Map<K, V> map = new LinkedHashMap<>();
+    for (Map.Entry<K, V> entry : entrySet) {
+      map.put(entry.getKey(), entry.getValue());
+    }
+    return map;
+  }
+
+  /**
+   * Builder for Runfiles objects.
+   */
+  public static final class Builder {
+    /**
+     * This must be COMPILE_ORDER because {@link #asMapWithoutRootSymlinks} overwrites earlier
+     * entries with later ones, so we want a post-order iteration.
+     */
+    private NestedSetBuilder<Artifact> artifactsBuilder =
+        NestedSetBuilder.compileOrder();
+    private NestedSetBuilder<Map.Entry<PathFragment, Artifact>> symlinksBuilder =
+        NestedSetBuilder.stableOrder();
+    private NestedSetBuilder<Map.Entry<PathFragment, Artifact>> rootSymlinksBuilder =
+        NestedSetBuilder.stableOrder();
+    private NestedSetBuilder<PruningManifest> pruningManifestsBuilder =
+        NestedSetBuilder.stableOrder();
+    private Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>>
+        manifestExpander = DUMMY_SYMLINK_EXPANDER;
+
+    /**
+     * Builds a new Runfiles object.
+     */
+    public Runfiles build() {
+      return new Runfiles(artifactsBuilder.build(), symlinksBuilder.build(),
+          rootSymlinksBuilder.build(), pruningManifestsBuilder.build(),
+          manifestExpander);
+    }
+
+    /**
+     * Adds an artifact to the internal collection of artifacts.
+     */
+    public Builder addArtifact(Artifact artifact) {
+      Preconditions.checkNotNull(artifact);
+      artifactsBuilder.add(artifact);
+      return this;
+    }
+
+    /**
+     * Adds several artifacts to the internal collection.
+     */
+    public Builder addArtifacts(Iterable<Artifact> artifacts) {
+      for (Artifact artifact : artifacts) {
+        addArtifact(artifact);
+      }
+      return this;
+    }
+
+
+    /**
+     * Use {@link #addTransitiveArtifacts} instead, to prevent increased memory use.
+     */
+    @Deprecated
+    public Builder addArtifacts(NestedSet<Artifact> artifacts) {
+      // Do not delete this method, or else addArtifacts(Iterable) calls with a NestedSet argument
+      // will not be flagged.
+      Iterable<Artifact> it = artifacts;
+      addArtifacts(it);
+      return this;
+    }
+    /**
+     * Adds a nested set to the internal collection.
+     */
+    public Builder addTransitiveArtifacts(NestedSet<Artifact> artifacts) {
+      artifactsBuilder.addTransitive(artifacts);
+      return this;
+    }
+
+    /**
+     * Adds a symlink.
+     */
+    public Builder addSymlink(PathFragment link, Artifact target) {
+      Preconditions.checkNotNull(link);
+      Preconditions.checkNotNull(target);
+      symlinksBuilder.add(Maps.immutableEntry(link, target));
+      return this;
+    }
+
+    /**
+     * Adds several symlinks.
+     */
+    public Builder addSymlinks(Map<PathFragment, Artifact> symlinks) {
+      symlinksBuilder.addAll(symlinks.entrySet());
+      return this;
+    }
+
+    /**
+     * Adds several symlinks as a NestedSet.
+     */
+    public Builder addSymlinks(NestedSet<Map.Entry<PathFragment, Artifact>> symlinks) {
+      symlinksBuilder.addTransitive(symlinks);
+      return this;
+    }
+
+    /**
+     * Adds several root symlinks.
+     */
+    public Builder addRootSymlinks(Map<PathFragment, Artifact> symlinks) {
+      rootSymlinksBuilder.addAll(symlinks.entrySet());
+      return this;
+    }
+
+    /**
+     * Adds several root symlinks as a NestedSet.
+     */
+    public Builder addRootSymlinks(NestedSet<Map.Entry<PathFragment, Artifact>> symlinks) {
+      rootSymlinksBuilder.addTransitive(symlinks);
+      return this;
+    }
+
+    /**
+     * Adds a pruning manifest. See {@link PruningManifest} for an explanation.
+     */
+    public Builder addPruningManifest(PruningManifest manifest) {
+      pruningManifestsBuilder.add(manifest);
+      return this;
+    }
+
+    /**
+     * Adds several pruning manifests as a NestedSet. See {@link PruningManifest} for an
+     * explanation.
+     */
+    public Builder addPruningManifests(NestedSet<PruningManifest> manifests) {
+      pruningManifestsBuilder.addTransitive(manifests);
+      return this;
+    }
+
+    /**
+     * Specify a function that can create additional manifest entries based on the input entries.
+     */
+    public Builder setManifestExpander(
+        Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> expander) {
+      manifestExpander = Preconditions.checkNotNull(expander);
+      return this;
+    }
+
+    /**
+     * Merges runfiles from a given runfiles support.
+     *
+     * @param runfilesSupport the runfiles support to be merged in
+     */
+    public Builder merge(@Nullable RunfilesSupport runfilesSupport) {
+      return merge(runfilesSupport, null);
+    }
+
+    /**
+     * Merges runfiles from a given runfiles support.
+     *
+     * <p>Sometimes a particular symlink from the runfiles support must not be included in runfiles.
+     * In such cases the path fragment denoting the symlink should be passed in as {@code
+     * ommittedAdditionalSymlink}. The symlink will then be filtered away from the set of additional
+     * symlinks of the target.
+     *
+     * @param runfilesSupport the runfiles support to be merged in
+     * @param omittedAdditionalSymlink the symlink to be omitted, or null if no filtering is needed
+     */
+    public Builder merge(@Nullable RunfilesSupport runfilesSupport,
+        @Nullable final PathFragment omittedAdditionalSymlink) {
+      if (runfilesSupport == null) {
+        return this;
+      }
+      // TODO(bazel-team): We may be able to remove this now.
+      addArtifact(runfilesSupport.getRunfilesMiddleman());
+      Runfiles runfiles = runfilesSupport.getRunfiles();
+      if (omittedAdditionalSymlink == null) {
+        merge(runfiles);
+      } else {
+        artifactsBuilder.addTransitive(runfiles.getUnconditionalArtifacts());
+        symlinksBuilder.addAll(Maps.filterKeys(entriesToMap(runfiles.getSymlinks()),
+            Predicates.not(Predicates.equalTo(omittedAdditionalSymlink))).entrySet());
+        rootSymlinksBuilder.addTransitive(runfiles.getRootSymlinks());
+        pruningManifestsBuilder.addTransitive(runfiles.getPruningManifests());
+        if (manifestExpander == DUMMY_SYMLINK_EXPANDER) {
+          manifestExpander = runfiles.getSymlinkExpander();
+        } else {
+          Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> otherExpander =
+              runfiles.getSymlinkExpander();
+          Preconditions.checkState((otherExpander == DUMMY_SYMLINK_EXPANDER)
+            || manifestExpander.equals(otherExpander));
+        }
+      }
+      return this;
+    }
+
+    /**
+     * Adds the runfiles for a particular target and visits the transitive closure of "srcs",
+     * "deps" and "data", collecting all of their respective runfiles.
+     */
+    public Builder addRunfiles(RuleContext ruleContext,
+        Function<TransitiveInfoCollection, Runfiles> mapping) {
+      Preconditions.checkNotNull(mapping);
+      Preconditions.checkNotNull(ruleContext);
+      addDataDeps(ruleContext);
+      addNonDataDeps(ruleContext, mapping);
+      return this;
+    }
+
+    /**
+     * Adds the files specified by a mapping from the transitive info collection to the runfiles.
+     *
+     * <p>Dependencies in {@code srcs} and {@code deps} are considered.
+     */
+    public Builder add(RuleContext ruleContext,
+        Function<TransitiveInfoCollection, Runfiles> mapping) {
+      Preconditions.checkNotNull(ruleContext);
+      Preconditions.checkNotNull(mapping);
+      for (TransitiveInfoCollection dep : getNonDataDeps(ruleContext)) {
+        Runfiles runfiles = mapping.apply(dep);
+        if (runfiles != null) {
+          merge(runfiles);
+        }
+      }
+
+      return this;
+    }
+
+    /**
+     * Collects runfiles from data dependencies of a target.
+     */
+    public Builder addDataDeps(RuleContext ruleContext) {
+      addTargets(getPrerequisites(ruleContext, "data", Mode.DATA),
+          RunfilesProvider.DATA_RUNFILES);
+      return this;
+    }
+
+    /**
+     * Collects runfiles from "srcs" and "deps" of a target.
+     */
+    public Builder addNonDataDeps(RuleContext ruleContext,
+        Function<TransitiveInfoCollection, Runfiles> mapping) {
+      for (TransitiveInfoCollection target : getNonDataDeps(ruleContext)) {
+        addTargetExceptFileTargets(target, mapping);
+      }
+      return this;
+    }
+
+    public Builder addTargets(Iterable<? extends TransitiveInfoCollection> targets,
+        Function<TransitiveInfoCollection, Runfiles> mapping) {
+      for (TransitiveInfoCollection target : targets) {
+        addTarget(target, mapping);
+      }
+      return this;
+    }
+
+    public Builder addTarget(TransitiveInfoCollection target,
+        Function<TransitiveInfoCollection, Runfiles> mapping) {
+      return addTargetIncludingFileTargets(target, mapping);
+    }
+
+    private Builder addTargetExceptFileTargets(TransitiveInfoCollection target,
+        Function<TransitiveInfoCollection, Runfiles> mapping) {
+      Runfiles runfiles = mapping.apply(target);
+      if (runfiles != null) {
+        merge(runfiles);
+      }
+
+      return this;
+    }
+
+    private Builder addTargetIncludingFileTargets(TransitiveInfoCollection target,
+        Function<TransitiveInfoCollection, Runfiles> mapping) {
+      if (target.getProvider(RunfilesProvider.class) == null
+          && mapping == RunfilesProvider.DATA_RUNFILES) {
+        // RuleConfiguredTarget implements RunfilesProvider, so this will only be called on
+        // FileConfiguredTarget instances.
+        // TODO(bazel-team): This is a terrible hack. We should be able to make this go away
+        // by implementing RunfilesProvider on FileConfiguredTarget. We'd need to be mindful
+        // of the memory use, though, since we have a whole lot of FileConfiguredTarget instances.
+        addTransitiveArtifacts(target.getProvider(FileProvider.class).getFilesToBuild());
+        return this;
+      }
+
+      return addTargetExceptFileTargets(target, mapping);
+    }
+
+    /**
+     * Adds symlinks to given artifacts at their exec paths.
+     */
+    public Builder addSymlinksToArtifacts(Iterable<Artifact> artifacts) {
+      for (Artifact artifact : artifacts) {
+        addSymlink(artifact.getExecPath(), artifact);
+      }
+      return this;
+    }
+
+    /**
+     * Add the other {@link Runfiles} object transitively.
+     */
+    public Builder merge(Runfiles runfiles) {
+      return merge(runfiles, true);
+    }
+
+    /**
+     * Add the other {@link Runfiles} object transitively, but don't merge
+     * pruning manifests.
+     */
+    public Builder mergeExceptPruningManifests(Runfiles runfiles) {
+      return merge(runfiles, false);
+    }
+
+    /**
+     * Add the other {@link Runfiles} object transitively, with the option to include or exclude
+     * pruning manifests in the merge.
+     */
+    private Builder merge(Runfiles runfiles, boolean includePruningManifests) {
+      artifactsBuilder.addTransitive(runfiles.getUnconditionalArtifacts());
+      symlinksBuilder.addTransitive(runfiles.getSymlinks());
+      rootSymlinksBuilder.addTransitive(runfiles.getRootSymlinks());
+      if (includePruningManifests) {
+        pruningManifestsBuilder.addTransitive(runfiles.getPruningManifests());
+      }
+      if (manifestExpander == DUMMY_SYMLINK_EXPANDER) {
+        manifestExpander = runfiles.getSymlinkExpander();
+      } else {
+        Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> otherExpander =
+            runfiles.getSymlinkExpander();
+        Preconditions.checkState((otherExpander == DUMMY_SYMLINK_EXPANDER)
+          || manifestExpander.equals(otherExpander));
+      }
+      return this;
+    }
+
+    private static Iterable<TransitiveInfoCollection> getNonDataDeps(RuleContext ruleContext) {
+      return Iterables.concat(
+          // TODO(bazel-team): This line shouldn't be here. Removing it requires that no rules have
+          // dependent rules in srcs (except for filegroups and such), but always in deps.
+          // TODO(bazel-team): DONT_CHECK is not optimal here. Rules that use split configs need to
+          // be changed not to call into here.
+          getPrerequisites(ruleContext, "srcs", Mode.DONT_CHECK),
+          getPrerequisites(ruleContext, "deps", Mode.DONT_CHECK));
+    }
+
+    /**
+     * For the specified attribute "attributeName" (which must be of type list(label)), resolves all
+     * the labels into ConfiguredTargets (for the same configuration as this one) and returns them
+     * as a list.
+     *
+     * <p>If the rule does not have the specified attribute, returns the empty list.
+     */
+    private static Iterable<? extends TransitiveInfoCollection> getPrerequisites(
+        RuleContext ruleContext, String attributeName, Mode mode) {
+      if (ruleContext.getRule().isAttrDefined(attributeName, Type.LABEL_LIST)) {
+        return ruleContext.getPrerequisites(attributeName, mode);
+      } else {
+        return Collections.emptyList();
+      }
+    }
+  }
+}