() {
@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 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 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 super Artifact> 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 extends Artifact> artifacts,
Collection super E> output,
Function super Artifact, E> 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 super E> output,
Function super Artifact, E> 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 extends Artifact> 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
}
}