// 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 static com.google.common.collect.ImmutableList.toImmutableList; 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.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.Interner; import com.google.common.collect.Iterables; import com.google.common.collect.Streams; import com.google.devtools.build.lib.actions.ActionAnalysisMetadata.MiddlemanType; import com.google.devtools.build.lib.actions.ArtifactResolver.ArtifactResolverSupplier; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.concurrent.BlazeInterners; import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; import com.google.devtools.build.lib.shell.ShellUtils; import com.google.devtools.build.lib.skyframe.serialization.DeserializationContext; import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec; import com.google.devtools.build.lib.skyframe.serialization.SerializationContext; import com.google.devtools.build.lib.skyframe.serialization.SerializationException; import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization; import com.google.devtools.build.lib.skylarkbuildapi.FileApi; import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter; import com.google.devtools.build.lib.syntax.EvalUtils; import com.google.devtools.build.lib.syntax.EvalUtils.ComparisonException; import com.google.devtools.build.lib.util.FileType; import com.google.devtools.build.lib.util.FileTypeSet; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.PathFragment; import com.google.devtools.build.skyframe.SkyFunctionName; import com.google.devtools.build.skyframe.SkyKey; import com.google.protobuf.CodedInputStream; import com.google.protobuf.CodedOutputStream; import java.io.IOException; 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: * * */ @Immutable @AutoCodec public class Artifact implements FileType.HasFileType, ActionInput, FileApi, Comparable, CommandLineItem, SkyKey { /** Compares artifact according to their exec paths. Sorts null values first. */ @SuppressWarnings("ReferenceEquality") // "a == b" is an optimization public static final Comparator EXEC_PATH_COMPARATOR = (a, 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); } }; /** * {@link com.google.devtools.build.lib.skyframe.ArtifactFunction} does direct filesystem access * without declaring Skyframe dependencies if the artifact is a source directory. However, that * filesystem access is not invalidated on incremental builds, and we have no plans to fix it, * since general consumption of source directories in this way is unsound. Therefore no new bugs * are created by declaring {@link com.google.devtools.build.lib.skyframe.ArtifactFunction} to be * hermetic. * *

TODO(janakr): Avoid this issue entirely by giving {@link SourceArtifact} its own {@code * SkyFunction}. Then we can just declare that function to be non-hermetic. That will also save * memory since we can make mandatory source artifacts their own SkyKeys! */ public static final SkyFunctionName ARTIFACT = SkyFunctionName.createHermetic("ARTIFACT"); @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); /** * Retrieve the expansion of Filesets for the given artifact. * * @param artifact {@code artifact.isFileset()} must be true. */ default ImmutableList getFileset(Artifact artifact) { throw new UnsupportedOperationException(); } } 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 = input -> !input.isMiddlemanArtifact(); private static final Interner ARTIFACT_INTERNER = BlazeInterners.newWeakInterner(); private final int hashCode; private final ArtifactRoot root; private final PathFragment execPath; private final PathFragment rootRelativePath; private final ArtifactOwner owner; /** * The {@code rootRelativePath is a few characters shorter than the {@code execPath} for derived * artifacts, so we save a few bytes by serializing it rather than the {@code execPath}, * especially when the {@code root} is common to many artifacts and therefore memoized. */ @AutoCodec.VisibleForSerialization @AutoCodec.Instantiator static Artifact createForSerialization( ArtifactRoot root, ArtifactOwner owner, PathFragment rootRelativePath) { if (rootRelativePath == null || rootRelativePath.isAbsolute() != root.getRoot().isAbsolute()) { throw new IllegalArgumentException( rootRelativePath + ": illegal rootRelativePath for " + rootRelativePath + " (root: " + root + ")"); } PathFragment rootExecPath = root.getExecPath(); Artifact artifact = new Artifact( root, rootExecPath.isEmpty() ? rootRelativePath : rootExecPath.getRelative(rootRelativePath), rootRelativePath, owner); if (artifact.isSourceArtifact()) { return artifact; } else { return ARTIFACT_INTERNER.intern(artifact); } } /** * 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(ArtifactRoot root, PathFragment execPath, ArtifactOwner owner) { this( Preconditions.checkNotNull(root), Preconditions.checkNotNull(execPath, "Null execPath not allowed (root %s", root), root.getExecPath().isEmpty() ? execPath : execPath.relativeTo(root.getExecPath()), Preconditions.checkNotNull(owner)); if (execPath.isAbsolute() != root.getRoot().isAbsolute()) { throw new IllegalArgumentException( execPath + ": illegal execPath for " + execPath + " (root: " + root + ")"); } } private Artifact( ArtifactRoot root, PathFragment execPath, PathFragment rootRelativePath, ArtifactOwner owner) { Preconditions.checkNotNull(root); if (execPath.isEmpty()) { throw new IllegalArgumentException( "it is illegal to create an artifact with an empty execPath"); } // The ArtifactOwner is not part of this computation because it is very rare that two Artifacts // have the same execPath and different owners, so a collision is fine there. If this is // changed, OwnerlessArtifactWrapper must also be changed. this.hashCode = execPath.hashCode() + this.getClass().hashCode() * 13; this.root = root; this.execPath = execPath; this.rootRelativePath = rootRelativePath; this.owner = Preconditions.checkNotNull(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. 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(ArtifactRoot root, PathFragment execPath) {
    this(root, execPath, ArtifactOwner.NullArtifactOwner.INSTANCE);
  }

  /**
   * 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, ArtifactRoot root) {
    this(
        root,
        root.getExecPath().getRelative(root.getRoot().relativize(path)),
        ArtifactOwner.NullArtifactOwner.INSTANCE);
  }

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

  public final Path getPath() {
    return root.getRoot().getRelative(rootRelativePath);
  }

  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. */ @Override 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 public final String getFilename() { return getExecPath().getBaseName(); } @Override public final String getExtension() { return getExecPath().getFileExtension(); } /** * Checks whether this artifact is of the supplied file type. * *

Prefer this method to pulling out strings from the Artifact and passing to {@link * FileType#matches(String)} manually. This method has been optimized to generate a minimum of * garbage. */ public boolean isFileType(FileType fileType) { return fileType.matches(this); } /** Checks whether this artifact is any of the supplied file types. */ public boolean isAnyFileType(FileTypeSet fileTypeSet) { return fileTypeSet.matches(filePathForFileTypeMatcher()); } @Override public String filePathForFileTypeMatcher() { return getExecPath().filePathForFileTypeMatcher(); } @Override public String expandToCommandLine() { return getExecPathString(); } /** * Returns the artifact owner. May be null. */ @Nullable public final Label getOwner() { return owner.getLabel(); } /** * Gets the {@code ActionLookupKey} of the {@code ConfiguredTarget} that owns this artifact, if it * was set. Otherwise, this should be a dummy value -- either {@link * ArtifactOwner.NullArtifactOwner#INSTANCE} 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; } @Override 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(). */ @Override public final ArtifactRoot 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. */ @Override public final boolean isSourceArtifact() { return root.isSourceRoot(); } /** * 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. @Override 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; } /** Only callable if isSourceArtifact() is true. */ public SourceArtifact asSourceArtifact() { throw new IllegalStateException("Not a source artifact!"); } /** {@link Artifact#isSourceArtifact() is true. * *

Source artifacts have the property that unlike for output artifacts, direct file system * access for their contents should be safe, even in a distributed context. * * TODO(shahan): move {@link Artifact#getPath} to this subclass. * */ public static final class SourceArtifact extends Artifact { @VisibleForTesting public SourceArtifact(ArtifactRoot root, PathFragment execPath, ArtifactOwner owner) { super(root, execPath, owner); } @Override public SourceArtifact asSourceArtifact() { return this; } /** * SourceArtifacts are compared without their owners, since owners do not affect behavior, * unlike with derived artifacts, whose owners determine their generating actions. */ @Override public boolean equals(Object other) { return other instanceof SourceArtifact && equalsWithoutOwner((SourceArtifact) other); } } /** * 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 @AutoCodec public static final class SpecialArtifact extends Artifact { private final SpecialArtifactType type; @VisibleForSerialization public SpecialArtifact( ArtifactRoot root, PathFragment execPath, ArtifactOwner owner, SpecialArtifactType type) { super(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 @AutoCodec public static final class TreeFileArtifact extends Artifact { private final SpecialArtifact 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(SpecialArtifact 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}. */ @AutoCodec.Instantiator TreeFileArtifact( SpecialArtifact parentTreeArtifact, PathFragment parentRelativePath, ArtifactOwner owner) { super( parentTreeArtifact.getRoot(), parentTreeArtifact.getExecPath().getRelative(parentRelativePath), owner); Preconditions.checkArgument( parentTreeArtifact.isTreeArtifact(), "The parent of TreeFileArtifact (parent-relative path: %s) is not a TreeArtifact: %s", parentRelativePath, parentTreeArtifact); Preconditions.checkArgument( !parentRelativePath.containsUplevelReferences() && !parentRelativePath.isAbsolute(), "%s is not a proper normalized relative path", parentRelativePath); this.parentTreeArtifact = parentTreeArtifact; 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.startsWith(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; } @Override public final String getRunfilesPathString() { return getRunfilesPath().getPathString(); } /** * Returns this.getExecPath().getPathString(). */ @Override 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(); } @SuppressWarnings("EqualsGetClass") // Distinct classes of Artifact are never equal. @Override public boolean equals(Object other) { if (!(other instanceof Artifact)) { return false; } if (!getClass().equals(other.getClass())) { return false; } Artifact that = (Artifact) other; return equalsWithoutOwner(that) && owner.equals(that.getArtifactOwner()); } public boolean equalsWithoutOwner(Artifact other) { return Objects.equals(this.execPath, other.execPath) && Objects.equals(this.root, other.root); } @Override public final int hashCode() { // This is just execPath.hashCode() (along with the class). 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 // // TODO(blaze-team): this is misleading beacuse execution_root isn't unique. Dig the // workspace name out and print that also. return "[[]" + root.getExecPath() + "]" + rootRelativePath; } } /** {@link ObjectCodec} for {@link SourceArtifact} */ private static class SourceArtifactCodec implements ObjectCodec { @Override public Class getEncodedClass() { return SourceArtifact.class; } @Override public void serialize( SerializationContext context, SourceArtifact obj, CodedOutputStream codedOut) throws SerializationException, IOException { context.serialize(obj.getExecPath(), codedOut); context.serialize(obj.getRoot(), codedOut); context.serialize(obj.getArtifactOwner(), codedOut); } @Override public SourceArtifact deserialize(DeserializationContext context, CodedInputStream codedIn) throws SerializationException, IOException { PathFragment execPath = context.deserialize(codedIn); ArtifactRoot artifactRoot = context.deserialize(codedIn); ArtifactOwner owner = context.deserialize(codedIn); return (SourceArtifact) context .getDependency(ArtifactResolverSupplier.class) .get() .getSourceArtifact(execPath, artifactRoot.getRoot(), owner); } } // --------------------------------------------------------------------------- // Static methods to assist in working with Artifacts /** Formatter for execPath PathFragment output. */ public static final Function ROOT_RELATIVE_PATH_STRING = artifact -> artifact.getRootRelativePath().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), artifact -> artifact.getPath().getPathString()); } /** * 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), artifact -> artifact.getRootRelativePath().getPathString()); } /** * 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, Artifact::getExecPath, 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 Streams.stream(artifacts).map(Artifact::getExecPath).collect(toImmutableList()); } /** * Returns the exec paths of the input artifacts in alphabetical order. */ public static ImmutableList asSortedPathFragments(Iterable input) { return Streams.stream(input).map(Artifact::getExecPath).sorted().collect(toImmutableList()); } @Override public boolean isImmutable() { return true; } @Override public void repr(SkylarkPrinter printer) { if (isSourceArtifact()) { printer.append(""); } else { printer.append(""); } } /** * A utility class that compares {@link Artifact}s without taking their owners into account. * Should only be used for detecting action conflicts and merging shared action data. */ public static class OwnerlessArtifactWrapper { private final Artifact artifact; public OwnerlessArtifactWrapper(Artifact artifact) { this.artifact = artifact; } @Override public int hashCode() { // Depends on the fact that Artifact#hashCode does not use ArtifactOwner. return artifact.hashCode(); } @Override public boolean equals(Object obj) { return obj instanceof OwnerlessArtifactWrapper && this.artifact.equalsWithoutOwner(((OwnerlessArtifactWrapper) obj).artifact); } } @Override public SkyFunctionName functionName() { return ARTIFACT; } }