aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/analysis/Runfiles.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/analysis/Runfiles.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/Runfiles.java756
1 files changed, 756 insertions, 0 deletions
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 0000000000..a4da4b4b25
--- /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();
+ }
+ }
+ }
+}