diff options
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/actions/Artifact.java')
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/actions/Artifact.java | 654 |
1 files changed, 654 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/actions/Artifact.java b/src/main/java/com/google/devtools/build/lib/actions/Artifact.java new file mode 100644 index 0000000000..2f2272b378 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/actions/Artifact.java @@ -0,0 +1,654 @@ +// 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.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.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.actions.Action.MiddlemanType; +import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; +import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.syntax.SkylarkCallable; +import com.google.devtools.build.lib.syntax.SkylarkModule; +import com.google.devtools.build.lib.util.FileType; +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.List; + +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 <code>build.packages</code> 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. + * + * <p>In any given call to Builder#buildArtifacts(), no two Artifacts in the + * action graph may refer to the same path. + * + * <p>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: + * <ul> + * <li>Well-formed source Artifacts will have null generating Actions and a root + * that is orthogonal to execRoot. (With the root coming from the package path.) + * <li>Well-formed derived Artifacts will have non-null generating Actions, and + * a root that is below execRoot. + * <li>Symlinked include source Artifacts under the output/include tree will + * appear to be derived artifacts with null generating Actions. + * <li>Some derived Artifacts, mostly in the genfiles tree and mostly discovered + * during include validation, will also have null generating Actions. + * </ul> + * + * <p>This class is "theoretically" final; it should not be subclassed except by + * {@link SpecialArtifact}. + */ +@Immutable +@SkylarkModule(name = "File", + 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.") +public class Artifact implements FileType.HasFilename, Comparable<Artifact>, ActionInput { + + /** An object that can expand middleman artifacts. */ + public interface MiddlemanExpander { + + /** + * Expands the middleman artifact "mm", and populates "output" with the result. + * + * <p>{@code mm.isMiddlemanArtifact()} must be true. Only aggregating middlemen are expanded. + */ + void expand(Artifact mm, Collection<? super Artifact> output); + } + + public static final ImmutableList<Artifact> NO_ARTIFACTS = ImmutableList.of(); + + /** + * A Predicate that evaluates to true if the Artifact is not a middleman artifact. + */ + public static final Predicate<Artifact> MIDDLEMAN_FILTER = new Predicate<Artifact>() { + @Override + public boolean apply(Artifact input) { + return !input.isMiddlemanArtifact(); + } + }; + + private final Path path; + private final Root root; + private final PathFragment execPath; + private final PathFragment rootRelativePath; + // Non-final only for use when dealing with deserialized artifacts. + private 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. + * + * <p>In a source Artifact, the path tail after the root will be identical to the execPath, but + * the root will be orthogonal to execRoot. + * <pre> + * [path] == [/root/][execPath] + * </pre> + * + * <p>In a derived Artifact, the execPath will overlap with part of the root, which in turn will + * be below of the execRoot. + * <pre> + * [path] == [/root][pathTail] == [/execRoot][execPath] == [/execRoot][rootPrefix][pathTail] + * <pre> + */ + @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); + } + if (execPath == null || execPath.isAbsolute() || !path.asFragment().endsWith(execPath)) { + throw new IllegalArgumentException(execPath + ": illegal execPath for " + path); + } + 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. + * + * <p>In a source Artifact, the path tail after the root will be identical to the execPath, but + * the root will be orthogonal to execRoot. + * <pre> + * [path] == [/root/][execPath] + * </pre> + * + * <p>In a derived Artifact, the execPath will overlap with part of the root, which in turn will + * be below of the execRoot. + * <pre> + * [path] == [/root][pathTail] == [/execRoot][execPath] == [/execRoot][rootPrefix][pathTail] + * <pre> + */ + @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); + } + + /** + * Returns the location of this Artifact on the filesystem. + */ + public final Path getPath() { + return path; + } + + /** + * Returns the base file name of this artifact. + */ + @Override + public final String getFilename() { + return getExecPath().getBaseName(); + } + + /** + * 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. + * + * When deserializing artifacts we end up with a dummy owner. In that case, it must be set using + * {@link #setArtifactOwner} before this method is called. + */ + public final ArtifactOwner getArtifactOwner() { + Preconditions.checkState(owner != DESERIALIZED_MARKER_OWNER, this); + return owner; + } + + /** + * Sets the artifact owner of this artifact. Should only be called for artifacts that were created + * through deserialization, and so their owner was unknown at the time of creation. + */ + public final void setArtifactOwner(ArtifactOwner owner) { + if (this.owner == DESERIALIZED_MARKER_OWNER) { + // We tolerate multiple calls of this method to accommodate shared actions. + this.owner = Preconditions.checkNotNull(owner, this); + } + } + + /** + * 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(). + */ + public final Root getRoot() { + return root; + } + + /** + * Returns the exec path of this Artifact. The exec path is a relative path + * that is suitable for accessing this artifact relative to the execution + * directory for this build. + */ + public final PathFragment getExecPath() { + return execPath; + } + + /** + * 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. + */ + 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 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 + */ + static enum SpecialArtifactType { + FILESET, + CONSTANT_METADATA, + } + + /** + * A special kind of artifact that either is a fileset or needs special metadata caching behavior. + * + * <p>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; + + 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; + } + } + + /** + * 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; + } + + /** + * Returns this.getExecPath().getPathString(). + */ + @Override + @SkylarkCallable(name = "path", structField = true, + doc = "The execution path of this file, relative to the execution directory.") + public final String getExecPathString() { + return getExecPath().getPathString(); + } + + @SkylarkCallable(name = "short_path", structField = true, + doc = "The path of this file relative to its root.") + public final String getRootRelativePathString() { + return getRootRelativePath().getPathString(); + } + + /** + * Returns a pretty string representation of the path denoted by this artifact, suitable for use + * in user error messages. Artifacts beneath a root will be printed relative to that root; other + * artifacts will be printed as an absolute path. + * + * <p>(The toString method is intended for developer messages since its more informative.) + */ + 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 'root' is an ancestor of 'path', and that all possible roots + // are disjoint, so unless things are really screwed up, it's ok. + Artifact that = (Artifact) other; + return this.path.equals(that.path); + } + + @Override + public final int compareTo(Artifact o) { + // The artifact factory ensures that there is a unique artifact for a given path. + return this.path.compareTo(o.path); + } + + @Override + public final int hashCode() { + return path.hashCode(); + } + + @Override + public final String toString() { + return "Artifact:" + 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<Artifact, PathFragment> EXEC_PATH_FORMATTER = + new Function<Artifact, PathFragment>() { + @Override + public PathFragment apply(Artifact input) { + return input.getExecPath(); + } + }; + + private static final Function<Artifact, String> ROOT_RELATIVE_PATH_STRING = + new Function<Artifact, String>() { + @Override + public String apply(Artifact artifact) { + return 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<Artifact> artifacts, Collection<String> 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 <E> void addNonMiddlemanArtifacts(Iterable<Artifact> artifacts, + Collection<? super E> output, Function<? super Artifact, E> outputFormatter) { + for (Artifact artifact : artifacts) { + if (MIDDLEMAN_FILTER.apply(artifact)) { + output.add(outputFormatter.apply(artifact)); + } + } + } + + /** + * Lazily converts artifacts into root-relative path strings. Middleman artifacts are ignored by + * this method. + */ + public static Iterable<String> toRootRelativePaths(Iterable<Artifact> 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<String> toExecPaths(Iterable<Artifact> 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<String> asExecPaths(Iterable<Artifact> 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<Artifact> 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<Artifact> 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<Artifact> artifacts, + Collection<? super Artifact> output, MiddlemanExpander middlemanExpander) { + addExpandedArtifacts(artifacts, output, Functions.<Artifact>identity(), middlemanExpander); + } + + /** + * 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<Artifact> artifacts, + Collection<String> output, + MiddlemanExpander middlemanExpander) { + addExpandedArtifacts(artifacts, output, ActionInputHelper.EXEC_PATH_STRING_FORMATTER, + middlemanExpander); + } + + /** + * 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<Artifact> artifacts, + Collection<PathFragment> output, MiddlemanExpander middlemanExpander) { + addExpandedArtifacts(artifacts, output, EXEC_PATH_FORMATTER, middlemanExpander); + } + + /** + * 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 <E> void addExpandedArtifacts(Iterable<Artifact> artifacts, + Collection<? super E> output, + Function<? super Artifact, E> outputFormatter, + MiddlemanExpander middlemanExpander) { + for (Artifact artifact : artifacts) { + if (artifact.isMiddlemanArtifact()) { + expandMiddlemanArtifact(artifact, output, outputFormatter, middlemanExpander); + } else { + output.add(outputFormatter.apply(artifact)); + } + } + } + + private static <E> void expandMiddlemanArtifact(Artifact middleman, + Collection<? super E> output, + Function<? super Artifact, E> outputFormatter, + MiddlemanExpander middlemanExpander) { + Preconditions.checkArgument(middleman.isMiddlemanArtifact()); + List<Artifact> artifacts = new ArrayList<>(); + middlemanExpander.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<String> asExpandedExecPathStrings(Iterable<Artifact> artifacts, + MiddlemanExpander middlemanExpander) { + List<String> result = new ArrayList<>(); + addExpandedExecPathStrings(artifacts, result, middlemanExpander); + 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<PathFragment> asExpandedExecPaths(Iterable<Artifact> artifacts, + MiddlemanExpander middlemanExpander) { + List<PathFragment> result = new ArrayList<>(); + addExpandedExecPaths(artifacts, result, middlemanExpander); + 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. + * <pre> + * Source: sourceRoot/rootRelative => :rootRelative + * Derived: execRoot/rootPrefix/rootRelative => rootPrefix:rootRelative + * </pre> + */ + public static void addRootPrefixedExecPaths(Iterable<Artifact> artifacts, + List<String> 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<Artifact> filterFiles(Iterable<Artifact> artifacts, FileType allowedType) { + List<Artifact> 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<PathFragment> asPathFragments(Iterable<Artifact> artifacts) { + return ImmutableList.copyOf(Iterables.transform(artifacts, EXEC_PATH_FORMATTER)); + } + + static final ArtifactOwner DESERIALIZED_MARKER_OWNER = new ArtifactOwner() { + @Override + public Label getLabel() { + return null; + }}; +} |