// Copyright 2014 The Bazel Authors. 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.actions; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Joiner; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Ordering; import com.google.devtools.build.lib.actions.ActionAnalysisMetadata.MiddlemanType; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; import com.google.devtools.build.lib.shell.ShellUtils; import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable; import com.google.devtools.build.lib.skylarkinterface.SkylarkModule; import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory; import com.google.devtools.build.lib.skylarkinterface.SkylarkValue; import com.google.devtools.build.lib.syntax.EvalUtils; import com.google.devtools.build.lib.syntax.EvalUtils.ComparisonException; import com.google.devtools.build.lib.syntax.Printer; import com.google.devtools.build.lib.util.FileType; import com.google.devtools.build.lib.util.Preconditions; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.Objects; import javax.annotation.Nullable; /** * An Artifact represents a file used by the build system, whether it's a source * file or a derived (output) file. Not all Artifacts have a corresponding * FileTarget object in the build.lib.packages API: for example, * low-level intermediaries internal to a given rule, such as a Java class files * or C++ object files. However all FileTargets have a corresponding Artifact. * *

In any given call to SkyframeExecutor#buildArtifacts(), no two Artifacts in the * action graph may refer to the same path. * *

Artifacts generally fall into two classifications, source and derived, but * there exist a few other cases that are fuzzy and difficult to classify. The * following cases exist: *

* * In the usual case, an Artifact represents a single file. However, an Artifact may * also represent the following: * *

This class is "theoretically" final; it should not be subclassed except by * {@link SpecialArtifact}. */ @Immutable @SkylarkModule(name = "File", category = SkylarkModuleCategory.BUILTIN, doc = "

This type represents a file used by the build system. It can be " + "either a source file or a derived file produced by a rule.

" + "

The File constructor is private, so you cannot call it directly to create new " + "Files. If you have a Skylark rule that needs to create a new File, you might need to " + "add the label to the attrs (if it's an input) or the outputs (if it's an output). Then " + "you can access the File through the rule's context. You can " + "also use ctx.new_file to create a new file in the rule " + "implementation.

") public class Artifact implements FileType.HasFilename, ActionInput, SkylarkValue, Comparable { /** * Compares artifact according to their exec paths. Sorts null values first. */ public static final Comparator EXEC_PATH_COMPARATOR = new Comparator() { @Override public int compare(Artifact a, Artifact b) { if (a == b) { return 0; } else if (a == null) { return -1; } else if (b == null) { return -1; } else { return a.execPath.compareTo(b.execPath); } } }; /** Compares artifacts according to their root relative paths. */ public static final Comparator ROOT_RELATIVE_PATH_COMPARATOR = new Comparator() { @Override public int compare(Artifact lhs, Artifact rhs) { return lhs.getRootRelativePath().compareTo(rhs.getRootRelativePath()); } }; @Override public int compareTo(Object o) { if (o instanceof Artifact) { return EXEC_PATH_COMPARATOR.compare(this, (Artifact) o); } throw new ComparisonException("Cannot compare artifact with " + EvalUtils.getDataTypeName(o)); } /** An object that can expand middleman artifacts. */ public interface ArtifactExpander { /** * Expands the given artifact, and populates "output" with the result. * *

{@code artifact.isMiddlemanArtifact() || artifact.isTreeArtifact()} must be true. * Only aggregating middlemen and tree artifacts are expanded. */ void expand(Artifact artifact, Collection output); } public static final ImmutableList NO_ARTIFACTS = ImmutableList.of(); /** * A Predicate that evaluates to true if the Artifact is not a middleman artifact. */ public static final Predicate MIDDLEMAN_FILTER = new Predicate() { @Override public boolean apply(Artifact input) { return !input.isMiddlemanArtifact(); } }; /** * A Predicate that evaluates to true if the Artifact is a tree artifact. */ public static final Predicate IS_TREE_ARTIFACT = new Predicate() { @Override public boolean apply(Artifact input) { return input.isTreeArtifact(); } }; private final int hashCode; private final Path path; private final Root root; private final PathFragment execPath; private final PathFragment rootRelativePath; private final ArtifactOwner owner; /** * Constructs an artifact for the specified path, root and execPath. The root must be an ancestor * of path, and execPath must be a non-absolute tail of path. Outside of testing, this method * should only be called by ArtifactFactory. The ArtifactOwner may be null. * *

In a source Artifact, the path tail after the root will be identical to the execPath, but * the root will be orthogonal to execRoot. *

   *  [path] == [/root/][execPath]
   * 
* *

In a derived Artifact, the execPath will overlap with part of the root, which in turn will * be below the execRoot. *

   *  [path] == [/root][pathTail] == [/execRoot][execPath] == [/execRoot][rootPrefix][pathTail]
   * 
*/ @VisibleForTesting public Artifact(Path path, Root root, PathFragment execPath, ArtifactOwner owner) { if (root == null || !path.startsWith(root.getPath())) { throw new IllegalArgumentException(root + ": illegal root for " + path + " (execPath: " + execPath + ")"); } if (execPath == null || execPath.isAbsolute() || !path.asFragment().endsWith(execPath)) { throw new IllegalArgumentException(execPath + ": illegal execPath for " + path + " (root: " + root + ")"); } this.hashCode = path.hashCode(); this.path = path; this.root = root; this.execPath = execPath; // These two lines establish the invariant that // execPath == rootRelativePath <=> execPath.equals(rootRelativePath) // This is important for isSourceArtifact. PathFragment rootRel = path.relativeTo(root.getPath()); if (!execPath.endsWith(rootRel)) { throw new IllegalArgumentException(execPath + ": illegal execPath doesn't end with " + rootRel + " at " + path + " with root " + root); } this.rootRelativePath = rootRel.equals(execPath) ? execPath : rootRel; this.owner = Preconditions.checkNotNull(owner, path); } /** * Constructs an artifact for the specified path, root and execPath. The root must be an ancestor * of path, and execPath must be a non-absolute tail of path. Should only be called for testing. * *

In a source Artifact, the path tail after the root will be identical to the execPath, but * the root will be orthogonal to execRoot. *

   *  [path] == [/root/][execPath]
   * 
* *

In a derived Artifact, the execPath will overlap with part of the root, which in turn will * be below of the execRoot. *

   *  [path] == [/root][pathTail] == [/execRoot][execPath] == [/execRoot][rootPrefix][pathTail]
   * 
   */
  @VisibleForTesting
  public Artifact(Path path, Root root, PathFragment execPath) {
    this(path, root, execPath, ArtifactOwner.NULL_OWNER);
  }

  /**
   * Constructs a source or derived Artifact for the specified path and specified root. The root
   * must be an ancestor of the path.
   */
  @VisibleForTesting  // Only exists for testing.
  public Artifact(Path path, Root root) {
    this(path, root, root.getExecPath().getRelative(path.relativeTo(root.getPath())),
        ArtifactOwner.NULL_OWNER);
  }

  /**
   * Constructs a source or derived Artifact for the specified root-relative path and root.
   */
  @VisibleForTesting  // Only exists for testing.
  public Artifact(PathFragment rootRelativePath, Root root) {
    this(root.getPath().getRelative(rootRelativePath), root,
        root.getExecPath().getRelative(rootRelativePath), ArtifactOwner.NULL_OWNER);
  }

  public final Path getPath() {
    return path;
  }

  public boolean hasParent() {
    return getParent() != null;
  }

  /**
   * Returns the parent Artifact containing this Artifact. Artifacts without parents shall
   * return null.
   */
  @Nullable public Artifact getParent() {
    return null;
  }

  /**
   * Returns the directory name of this artifact, similar to dirname(1).
   *
   * 

The directory name is always a relative path to the execution directory. */ @SkylarkCallable(name = "dirname", structField = true, doc = "The name of the directory containing this file.") public final String getDirname() { PathFragment parent = getExecPath().getParentDirectory(); return (parent == null) ? "/" : parent.getSafePathString(); } /** * Returns the base file name of this artifact, similar to basename(1). */ @Override @SkylarkCallable(name = "basename", structField = true, doc = "The base file name of this file.") public final String getFilename() { return getExecPath().getBaseName(); } @SkylarkCallable(name = "extension", structField = true, doc = "The file extension of this file.") public final String getExtension() { return getExecPath().getFileExtension(); } /** * Returns the artifact owner. May be null. */ @Nullable public final Label getOwner() { return owner.getLabel(); } /** * Get the {@code LabelAndConfiguration} of the {@code ConfiguredTarget} that owns this artifact, * if it was set. Otherwise, this should be a dummy value -- either {@link * ArtifactOwner#NULL_OWNER} or a dummy owner set in tests. Such a dummy value should only occur * for source artifacts if created without specifying the owner, or for special derived artifacts, * such as target completion middleman artifacts, build info artifacts, and the like. */ public final ArtifactOwner getArtifactOwner() { return owner; } @SkylarkCallable(name = "owner", structField = true, allowReturnNones = true, doc = "A label of a target that produces this File." ) public Label getOwnerLabel() { return owner.getLabel(); } /** * Returns the root beneath which this Artifact resides, if any. This may be one of the * package-path entries (for source Artifacts), or one of the bin, genfiles or includes dirs * (for derived Artifacts). It will always be an ancestor of getPath(). */ @SkylarkCallable(name = "root", structField = true, doc = "The root beneath which this file resides." ) public final Root getRoot() { return root; } @Override public final PathFragment getExecPath() { return execPath; } /** * Returns the path of this Artifact relative to this containing Artifact. Since * ordinary Artifacts correspond to only one Artifact -- itself -- for ordinary Artifacts, * this just returns the empty path. For special Artifacts, throws * {@link UnsupportedOperationException}. See also {@link Artifact#getParentRelativePath()}. */ public PathFragment getParentRelativePath() { return PathFragment.EMPTY_FRAGMENT; } /** * Returns true iff this is a source Artifact as determined by its path and root relationships. * Note that this will report all Artifacts in the output tree, including in the include symlink * tree, as non-source. */ @SkylarkCallable( name = "is_source", structField = true, doc = "Returns true if this is a source file, i.e. it is not generated." ) public final boolean isSourceArtifact() { return execPath == rootRelativePath; } /** * Returns true iff this is a middleman Artifact as determined by its root. */ public final boolean isMiddlemanArtifact() { return getRoot().isMiddlemanRoot(); } /** * Returns true iff this is a TreeArtifact representing a directory tree containing Artifacts. */ // TODO(rduan): Document this Skylark method once TreeArtifact is no longer experimental. @SkylarkCallable(name = "is_directory", structField = true, documented = false) public boolean isTreeArtifact() { return false; } /** * Returns whether the artifact represents a Fileset. */ public boolean isFileset() { return false; } /** * Returns true iff metadata cache must return constant metadata for the * given artifact. */ public boolean isConstantMetadata() { return false; } /** * Special artifact types. * * @see SpecialArtifact */ @VisibleForTesting public enum SpecialArtifactType { FILESET, TREE, CONSTANT_METADATA, } /** * A special kind of artifact that either is a fileset or needs special metadata caching behavior. * *

We subclass {@link Artifact} instead of storing the special attributes inside in order * to save memory. The proportion of artifacts that are special is very small, and by not having * to keep around the attribute for the rest we save some memory. */ @Immutable @VisibleForTesting public static final class SpecialArtifact extends Artifact { private final SpecialArtifactType type; @VisibleForTesting public SpecialArtifact(Path path, Root root, PathFragment execPath, ArtifactOwner owner, SpecialArtifactType type) { super(path, root, execPath, owner); this.type = type; } @Override public final boolean isFileset() { return type == SpecialArtifactType.FILESET; } @Override public boolean isConstantMetadata() { return type == SpecialArtifactType.CONSTANT_METADATA; } @Override public boolean isTreeArtifact() { return type == SpecialArtifactType.TREE; } @Override public boolean hasParent() { return false; } @Override @Nullable public Artifact getParent() { return null; } @Override @Nullable public PathFragment getParentRelativePath() { return null; } } /** * A special kind of artifact that represents a concrete file created at execution time under * its associated TreeArtifact. * *

TreeFileArtifacts should be only created during execution time inside some special actions * to support action inputs and outputs that are unpredictable at analysis time. * TreeFileArtifacts should not be created directly by any rules at analysis time. * *

We subclass {@link Artifact} instead of storing the extra fields directly inside in order * to save memory. The proportion of TreeFileArtifacts is very small, and by not having to keep * around the extra fields for the rest we save some memory. */ @Immutable public static final class TreeFileArtifact extends Artifact { private final Artifact parentTreeArtifact; private final PathFragment parentRelativePath; /** * Constructs a TreeFileArtifact with the given parent-relative path under the given parent * TreeArtifact. The {@link ArtifactOwner} of the TreeFileArtifact is the {@link ArtifactOwner} * of the parent TreeArtifact. */ TreeFileArtifact(Artifact parent, PathFragment parentRelativePath) { this(parent, parentRelativePath, parent.getArtifactOwner()); } /** * Constructs a TreeFileArtifact with the given parent-relative path under the given parent * TreeArtifact, owned by the given {@code artifactOwner}. */ TreeFileArtifact(Artifact parent, PathFragment parentRelativePath, ArtifactOwner artifactOwner) { super( parent.getPath().getRelative(parentRelativePath), parent.getRoot(), parent.getExecPath().getRelative(parentRelativePath), artifactOwner); Preconditions.checkState( parent.isTreeArtifact(), "The parent of TreeFileArtifact (parent-relative path: %s) is not a TreeArtifact: %s", parentRelativePath, parent); Preconditions.checkState( parentRelativePath.isNormalized() && !parentRelativePath.isAbsolute(), "%s is not a proper normalized relative path", parentRelativePath); this.parentTreeArtifact = parent; this.parentRelativePath = parentRelativePath; } @Override public Artifact getParent() { return parentTreeArtifact; } @Override public PathFragment getParentRelativePath() { return parentRelativePath; } } /** * Returns the relative path to this artifact relative to its root. (Useful * when deriving output filenames from input files, etc.) */ public final PathFragment getRootRelativePath() { return rootRelativePath; } /** * For targets in external repositories, this returns the path the artifact live at in the * runfiles tree. For local targets, it returns the rootRelativePath. */ public final PathFragment getRunfilesPath() { PathFragment relativePath = rootRelativePath; if (relativePath.segmentCount() > 1 && relativePath.getSegment(0).equals(Label.EXTERNAL_PATH_PREFIX)) { // Turn external/repo/foo into ../repo/foo. relativePath = relativePath.relativeTo(Label.EXTERNAL_PATH_PREFIX); relativePath = PathFragment.create("..").getRelative(relativePath); } return relativePath; } @SkylarkCallable( name = "short_path", structField = true, doc = "The path of this file relative to its root. This excludes the aforementioned " + "root, i.e. configuration-specific fragments of the path. This is also the " + "path under which the file is mapped if it's in the runfiles of a binary." ) public final String getRunfilesPathString() { return getRunfilesPath().getPathString(); } /** * Returns this.getExecPath().getPathString(). */ @Override @SkylarkCallable( name = "path", structField = true, doc = "The execution path of this file, relative to the workspace's execution directory. It " + "consists of two parts, an optional first part called the root (see also the " + "root module), and the second part which is the " + "short_path. The root may be empty, which it usually is for " + "non-generated files. For generated files it usually contains a " + "configuration-specific path fragment that encodes things like the target CPU " + "architecture that was used while building said file. Use the " + "short_path for the path under which the file is mapped if it's in the " + "runfiles of a binary." ) public final String getExecPathString() { return getExecPath().getPathString(); } /* * Returns getExecPathString escaped for potential use in a shell command. */ public final String getShellEscapedExecPathString() { return ShellUtils.shellEscape(getExecPathString()); } public final String getRootRelativePathString() { return getRootRelativePath().getPathString(); } public final String prettyPrint() { // toDetailString would probably be more useful to users, but lots of tests rely on the // current values. return rootRelativePath.toString(); } @Override public final boolean equals(Object other) { if (!(other instanceof Artifact)) { return false; } // We don't bother to check root in the equivalence relation, because we // assume that no root is an ancestor of another one. Artifact that = (Artifact) other; return Objects.equals(this.path, that.path); } @Override public final int hashCode() { // This is just path.hashCode(). We cache a copy in the Artifact object to reduce LLC misses // during operations which build a HashSet out of many Artifacts. This is a slight loss for // memory but saves ~1% overall CPU in some real builds. return hashCode; } @Override public final String toString() { return "File:" + toDetailString(); } /** * Returns the root-part of a given path by trimming off the end specified by * a given tail. Assumes that the tail is known to match, and simply relies on * the segment lengths. */ private static PathFragment trimTail(PathFragment path, PathFragment tail) { return path.subFragment(0, path.segmentCount() - tail.segmentCount()); } /** * Returns a string representing the complete artifact path information. */ public final String toDetailString() { if (isSourceArtifact()) { // Source Artifact: relPath == execPath, & real path is not under execRoot return "[" + root + "]" + rootRelativePath; } else { // Derived Artifact: path and root are under execRoot PathFragment execRoot = trimTail(path.asFragment(), execPath); return "[[" + execRoot + "]" + root.getPath().asFragment().relativeTo(execRoot) + "]" + rootRelativePath; } } /** * Serializes this artifact to a string that has enough data to reconstruct the artifact. */ public final String serializeToString() { // In theory, it should be enough to serialize execPath and rootRelativePath (which is a suffix // of execPath). However, in practice there is code around that uses other attributes which // needs cleaning up. String result = execPath + " /" + rootRelativePath.toString().length(); if (getOwner() != null) { result += " " + getOwner(); } return result; } //--------------------------------------------------------------------------- // Static methods to assist in working with Artifacts /** * Formatter for execPath PathFragment output. */ private static final Function EXEC_PATH_FORMATTER = new Function() { @Override public PathFragment apply(Artifact input) { return input.getExecPath(); } }; public static final Function ROOT_RELATIVE_PATH_STRING = new Function() { @Override public String apply(Artifact artifact) { return artifact.getRootRelativePath().getPathString(); } }; public static final Function ABSOLUTE_PATH_STRING = new Function() { @Override public String apply(Artifact artifact) { return artifact.getPath().getPathString(); } }; /** * Converts a collection of artifacts into execution-time path strings, and * adds those to a given collection. Middleman artifacts are ignored by this * method. */ public static void addExecPaths(Iterable artifacts, Collection output) { addNonMiddlemanArtifacts(artifacts, output, ActionInputHelper.EXEC_PATH_STRING_FORMATTER); } /** * Converts a collection of artifacts into the outputs computed by * outputFormatter and adds them to a given collection. Middleman artifacts * are ignored. */ static void addNonMiddlemanArtifacts(Iterable artifacts, Collection output, Function outputFormatter) { for (Artifact artifact : artifacts) { if (MIDDLEMAN_FILTER.apply(artifact)) { output.add(outputFormatter.apply(artifact)); } } } /** * Lazily converts artifacts into absolute path strings. Middleman artifacts are ignored by * this method. */ public static Iterable toAbsolutePaths(Iterable artifacts) { return Iterables.transform( Iterables.filter(artifacts, MIDDLEMAN_FILTER), ABSOLUTE_PATH_STRING); } /** * Lazily converts artifacts into root-relative path strings. Middleman artifacts are ignored by * this method. */ public static Iterable toRootRelativePaths(Iterable artifacts) { return Iterables.transform( Iterables.filter(artifacts, MIDDLEMAN_FILTER), ROOT_RELATIVE_PATH_STRING); } /** * Lazily converts artifacts into execution-time path strings. Middleman artifacts are ignored by * this method. */ public static Iterable toExecPaths(Iterable artifacts) { return ActionInputHelper.toExecPaths(Iterables.filter(artifacts, MIDDLEMAN_FILTER)); } /** * Converts a collection of artifacts into execution-time path strings, and * returns those as an immutable list. Middleman artifacts are ignored by this method. */ public static List asExecPaths(Iterable artifacts) { return ImmutableList.copyOf(toExecPaths(artifacts)); } /** * Renders a collection of artifacts as execution-time paths and joins * them into a single string. Middleman artifacts are ignored by this method. */ public static String joinExecPaths(String delimiter, Iterable artifacts) { return Joiner.on(delimiter).join(toExecPaths(artifacts)); } /** * Renders a collection of artifacts as root-relative paths and joins * them into a single string. Middleman artifacts are ignored by this method. */ public static String joinRootRelativePaths(String delimiter, Iterable artifacts) { return Joiner.on(delimiter).join(toRootRelativePaths(artifacts)); } /** * Adds a collection of artifacts to a given collection, with * {@link MiddlemanType#AGGREGATING_MIDDLEMAN} middleman actions expanded once. */ public static void addExpandedArtifacts(Iterable artifacts, Collection output, ArtifactExpander artifactExpander) { addExpandedArtifacts(artifacts, output, Functions.identity(), artifactExpander); } /** * Converts a collection of artifacts into execution-time path strings, and * adds those to a given collection. Middleman artifacts for * {@link MiddlemanType#AGGREGATING_MIDDLEMAN} middleman actions are expanded * once. */ @VisibleForTesting public static void addExpandedExecPathStrings(Iterable artifacts, Collection output, ArtifactExpander artifactExpander) { addExpandedArtifacts(artifacts, output, ActionInputHelper.EXEC_PATH_STRING_FORMATTER, artifactExpander); } /** * Converts a collection of artifacts into execution-time path fragments, and * adds those to a given collection. Middleman artifacts for * {@link MiddlemanType#AGGREGATING_MIDDLEMAN} middleman actions are expanded * once. */ public static void addExpandedExecPaths(Iterable artifacts, Collection output, ArtifactExpander artifactExpander) { addExpandedArtifacts(artifacts, output, EXEC_PATH_FORMATTER, artifactExpander); } /** * Converts a collection of artifacts into the outputs computed by * outputFormatter and adds them to a given collection. Middleman artifacts * are expanded once. */ private static void addExpandedArtifacts(Iterable artifacts, Collection output, Function outputFormatter, ArtifactExpander artifactExpander) { for (Artifact artifact : artifacts) { if (artifact.isMiddlemanArtifact() || artifact.isTreeArtifact()) { expandArtifact(artifact, output, outputFormatter, artifactExpander); } else { output.add(outputFormatter.apply(artifact)); } } } private static void expandArtifact(Artifact middleman, Collection output, Function outputFormatter, ArtifactExpander artifactExpander) { Preconditions.checkArgument(middleman.isMiddlemanArtifact() || middleman.isTreeArtifact()); List artifacts = new ArrayList<>(); artifactExpander.expand(middleman, artifacts); for (Artifact artifact : artifacts) { output.add(outputFormatter.apply(artifact)); } } /** * Converts a collection of artifacts into execution-time path strings, and * returns those as a list. Middleman artifacts are expanded once. The * returned list is mutable. */ public static List asExpandedExecPathStrings(Iterable artifacts, ArtifactExpander artifactExpander) { List result = new ArrayList<>(); addExpandedExecPathStrings(artifacts, result, artifactExpander); return result; } /** * Converts a collection of artifacts into execution-time path fragments, and * returns those as a list. Middleman artifacts are expanded once. The * returned list is mutable. */ public static List asExpandedExecPaths(Iterable artifacts, ArtifactExpander artifactExpander) { List result = new ArrayList<>(); addExpandedExecPaths(artifacts, result, artifactExpander); return result; } /** * Converts a collection of artifacts into execution-time path strings with * the root-break delimited with a colon ':', and adds those to a given list. *

   * Source: sourceRoot/rootRelative => :rootRelative
   * Derived: execRoot/rootPrefix/rootRelative => rootPrefix:rootRelative
   * 
*/ public static void addRootPrefixedExecPaths(Iterable artifacts, List output) { for (Artifact artifact : artifacts) { output.add(asRootPrefixedExecPath(artifact)); } } /** * Convenience method to filter the files to build for a certain filetype. * * @param artifacts the files to filter * @param allowedType the allowed filetype * @return all members of filesToBuild that are of one of the * allowed filetypes */ public static List filterFiles(Iterable artifacts, FileType allowedType) { List filesToBuild = new ArrayList<>(); for (Artifact artifact : artifacts) { if (allowedType.matches(artifact.getFilename())) { filesToBuild.add(artifact); } } return filesToBuild; } @VisibleForTesting static String asRootPrefixedExecPath(Artifact artifact) { PathFragment execPath = artifact.getExecPath(); PathFragment rootRel = artifact.getRootRelativePath(); if (execPath.equals(rootRel)) { return ":" + rootRel.getPathString(); } else { //if (execPath.endsWith(rootRel)) { PathFragment rootPrefix = trimTail(execPath, rootRel); return rootPrefix.getPathString() + ":" + rootRel.getPathString(); } } /** * Converts artifacts into their exec paths. Returns an immutable list. */ public static List asPathFragments(Iterable artifacts) { return ImmutableList.copyOf(Iterables.transform(artifacts, EXEC_PATH_FORMATTER)); } /** * Returns the exec paths of the input artifacts in alphabetical order. */ public static ImmutableList asSortedPathFragments(Iterable input) { return Ordering.natural().immutableSortedCopy(Iterables.transform( input, EXEC_PATH_FORMATTER)); } @Override public boolean isImmutable() { return true; } @Override public void write(Appendable buffer, char quotationMark) { Printer.append(buffer, toString()); // TODO(bazel-team): implement a readable representation } }