blob: 1775144cfd400a80881fdfc82440158f402c41cb [file] [log] [blame]
// 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.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Streams;
import com.google.devtools.build.lib.actions.ArtifactRoot.RootType;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.LabelConstants;
import com.google.devtools.build.lib.collect.nestedset.Depset;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.concurrent.ThreadSafety;
import com.google.devtools.build.lib.skyframe.SkyFunctions;
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.VisibleForSerialization;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.SerializationConstant;
import com.google.devtools.build.lib.starlarkbuildapi.FileApi;
import com.google.devtools.build.lib.util.FileType;
import com.google.devtools.build.lib.util.FileTypeSet;
import com.google.devtools.build.lib.util.HashCodes;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.build.skyframe.ExecutionPhaseSkyKey;
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.Map;
import java.util.function.UnaryOperator;
import javax.annotation.Nullable;
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.Printer;
import net.starlark.java.eval.Starlark;
/**
* 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.lib.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 SkyframeExecutor#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>See {@link ArtifactRoot} for a detailed example on root, execRoot, and related paths.
*
* <p>In the usual case, an Artifact represents a single file. However, an Artifact may also
* represent the following:
*
* <ul>
* <li>A TreeArtifact, which is a directory containing a tree of unknown {@link Artifact}s. In the
* future, Actions will be able to examine these files as inputs and declare them as outputs
* at execution time, but this is not yet implemented. This is used for Actions where the
* inputs and/or outputs might not be discoverable except during Action execution.
* <li>A directory of unknown contents, but not a TreeArtifact. This is a legacy facility and
* should not be used by any new rule implementations. In particular, the file system cache
* integrity checks fail for directories.
* <li>A middleman special Artifact, which may be expanded using a {@link ArtifactExpander} at
* Action execution time. This is used by a handful of rules to save memory.
* <li>A 'constant metadata' special Artifact. These represent real files, changes to which are
* ignored by the build system. They are useful for files which change frequently but do not
* affect the result of a build, such as timestamp files.
* <li>A 'Fileset' special Artifact. This is a legacy type of Artifact and should not be used by
* new rule implementations.
* <li>A 'symlink' special Artifact. While a symlink can also be represented by a regular
* Artifact, using a symlink special Artifact would result in deriving the Artifact's SkyValue
* from the symlinks themselves (lstat, not stat), and not following the symlinks like in
* regular Artifacts. The underlying symlink can be unresolved, otherwise known as a dangling
* symlink.
* </ul>
*
* <p>While Artifact implements {@link SkyKey} for memory-saving purposes, Skyframe requests
* involving artifacts should always go through {@link Artifact#key} since ordinary derived
* artifacts should not be requested directly from Skyframe.
*/
public abstract class Artifact
implements FileType.HasFileType,
ActionInput,
FileApi,
Comparable<Artifact>,
CommandLineItem,
ExecutionPhaseSkyKey {
public static final Depset.ElementType TYPE = Depset.ElementType.of(Artifact.class);
/** Compares artifact according to their exec paths. Sorts null values first. */
@SerializationConstant
@SuppressWarnings("ReferenceEquality") // "a == b" is an optimization
public static final Comparator<Artifact> 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);
}
};
/** Compares artifact according to their root relative paths. Sorts null values first. */
@SuppressWarnings("ReferenceEquality") // "a == b" is an optimization
public static final Comparator<Artifact> ROOT_RELATIVE_PATH_COMPARATOR =
(a, b) -> {
if (a == b) {
return 0;
} else if (a == null) {
return -1;
} else if (b == null) {
return 1;
} else {
int result = a.getRootRelativePath().compareTo(b.getRootRelativePath());
if (result == 0) {
// Use the full exec path as a fallback if the root-relative paths are the same, thus
// avoiding problems when ImmutableSortedMaps are switched from EXEC_PATH_COMPARATOR.
return a.execPath.compareTo(b.execPath);
} else {
return result;
}
}
};
/**
* {@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.
*
* <p>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");
/**
* Returns a {@link SkyKey} that, when built, will produce this artifact. For source artifacts and
* generated artifacts that may aggregate other artifacts, returns the artifact itself. For normal
* generated artifacts, returns the key of the generating action.
*
* <p>Callers should use this method (or the related ones below) in preference to directly
* requesting an {@link Artifact} to be built by Skyframe, since ordinary derived artifacts should
* never be directly built by Skyframe.
*/
@ThreadSafety.ThreadSafe
public static SkyKey key(Artifact artifact) {
if (artifact.isTreeArtifact()
|| artifact.isMiddlemanArtifact()
|| !artifact.hasKnownGeneratingAction()) {
return artifact;
}
return ((DerivedArtifact) artifact).getGeneratingActionKey();
}
public static Collection<SkyKey> keys(Collection<Artifact> artifacts) {
return artifacts instanceof List
? keys((List<Artifact>) artifacts)
// Use Collections2 instead of Iterables#transform to ensure O(1) size().
: Collections2.transform(artifacts, Artifact::key);
}
public static List<SkyKey> keys(List<Artifact> artifacts) {
return Lists.transform(artifacts, Artifact::key);
}
@Override
public int compareTo(Artifact o) {
return EXEC_PATH_COMPARATOR.compare(this, o);
}
/** An object that can expand middleman and tree artifacts. */
public interface ArtifactExpander {
/**
* Expands the given artifact, and populates "output" with the result.
*
* <p>{@code artifact.isMiddlemanArtifact() || artifact.isTreeArtifact()} must be true. Only
* middlemen and tree artifacts are expanded.
*/
void expand(Artifact artifact, Collection<? super Artifact> output);
/**
* Returns the expansion of Fileset for the given artifact.
*
* @param artifact {@code artifact.isFileset()} must be true.
* @throws MissingExpansionException if the expander is missing data needed to expand provided
* fileset.
*/
default ImmutableList<FilesetOutputSymlink> getFileset(Artifact artifact)
throws MissingExpansionException {
throw new MissingExpansionException("Cannot expand fileset " + artifact);
}
/**
* Return an {@link ArchivedTreeArtifact} for a provided {@linkplain SpecialArtifact tree
* artifact} if one is available.
*
* <p>The {@linkplain ArchivedTreeArtifact archived tree artifact} can be used instead of the
* tree artifact expansion.
*/
@Nullable
default ArchivedTreeArtifact getArchivedTreeArtifact(SpecialArtifact treeArtifact) {
return null;
}
}
/**
* Exception thrown when attempting to {@linkplain ArtifactExpander expand} an artifact for which
* we do not have the necessary data.
*/
public static final class MissingExpansionException extends Exception {
public MissingExpansionException(String message) {
super(message);
}
}
/** Implementation of {@link ArtifactExpander} */
public static class ArtifactExpanderImpl implements ArtifactExpander {
private final Map<Artifact, ImmutableCollection<? extends Artifact>> expandedInputs;
private final Map<SpecialArtifact, ArchivedTreeArtifact> archivedTreeArtifacts;
private final Map<Artifact, ImmutableList<FilesetOutputSymlink>> expandedFilesets;
public ArtifactExpanderImpl(
Map<Artifact, ImmutableCollection<? extends Artifact>> expandedInputs,
Map<SpecialArtifact, ArchivedTreeArtifact> archivedTreeArtifacts,
Map<Artifact, ImmutableList<FilesetOutputSymlink>> expandedFilesets) {
this.expandedInputs = expandedInputs;
this.archivedTreeArtifacts = archivedTreeArtifacts;
this.expandedFilesets = expandedFilesets;
}
@Override
public void expand(Artifact artifact, Collection<? super Artifact> output) {
Preconditions.checkState(
artifact.isMiddlemanArtifact() || artifact.isTreeArtifact(), artifact);
ImmutableCollection<? extends Artifact> result = expandedInputs.get(artifact);
if (result != null) {
output.addAll(result);
}
}
@Override
public ImmutableList<FilesetOutputSymlink> getFileset(Artifact artifact)
throws MissingExpansionException {
Preconditions.checkState(artifact.isFileset());
ImmutableList<FilesetOutputSymlink> filesetLinks = expandedFilesets.get(artifact);
if (filesetLinks == null) {
throw new MissingExpansionException("Missing expansion for fileset: " + artifact);
}
return filesetLinks;
}
@Override
public ArchivedTreeArtifact getArchivedTreeArtifact(SpecialArtifact treeArtifact) {
return archivedTreeArtifacts.get(treeArtifact);
}
}
/** A Predicate that evaluates to true if the Artifact is not a middleman artifact. */
public static final Predicate<Artifact> MIDDLEMAN_FILTER = input -> !input.isMiddlemanArtifact();
private final ArtifactRoot root;
private final int hashCode;
private final PathFragment execPath;
private Artifact(ArtifactRoot root, PathFragment execPath, int hashCodeWithOwner) {
Preconditions.checkNotNull(root);
// Use a precomputed hashcode since there tends to be massive hash-based collections of
// artifacts. Importantly, the hashcode ought to incorporate the artifact's owner to prevent a
// hash collision on the same exec path but a different owner (this is the common case for
// multiple aspects that produce the same output file).
this.hashCode = hashCodeWithOwner;
this.root = root;
this.execPath = execPath;
}
/** An artifact corresponding to a file in the output tree, generated by an {@link Action}. */
public static class DerivedArtifact extends Artifact implements PathStrippable {
/**
* An {@link ActionLookupKey} until {@link #setGeneratingActionKey} is set, at which point it is
* an {@link ActionLookupData}, whose {@link ActionLookupData#getActionLookupKey} will be the
* same as the original value of owner.
*
* <p>We overload this field in order to save memory.
*/
private Object owner;
/**
* Content-based output paths are experimental. Only derived artifacts that are explicitly opted
* in by their creating rules should use them and only when {@link
* com.google.devtools.build.lib.analysis.config.BuildConfigurationValue#useContentBasedOutputPaths}
* is on.
*/
private final boolean contentBasedPath;
/** Standard factory method for derived artifacts. */
public static DerivedArtifact create(
ArtifactRoot root, PathFragment execPath, ActionLookupKey owner) {
return create(root, execPath, owner, /*contentBasedPath=*/ false);
}
/**
* Same as {@link #create(ArtifactRoot, PathFragment, ActionLookupKeyOrOwner)} but includes the
* option to use a content-based path for this artifact (see {@link
* com.google.devtools.build.lib.analysis.config.BuildConfigurationValue#useContentBasedOutputPaths}).
*/
public static DerivedArtifact create(
ArtifactRoot root, PathFragment execPath, ActionLookupKey owner, boolean contentBasedPath) {
return new DerivedArtifact(root, execPath, owner, contentBasedPath);
}
private DerivedArtifact(ArtifactRoot root, PathFragment execPath, Object owner) {
this(root, execPath, owner, /*contentBasedPath=*/ false);
}
private DerivedArtifact(
ArtifactRoot root, PathFragment execPath, Object owner, boolean contentBasedPath) {
super(root, execPath, HashCodes.hashObjects(execPath, getOwnerToUseForHashCode(owner)));
Preconditions.checkState(
!root.getExecPath().isEmpty(), "Derived root has no exec path: %s, %s", root, execPath);
this.owner = Preconditions.checkNotNull(owner);
this.contentBasedPath = contentBasedPath;
}
/**
* Called when a configured target's actions are being collected. {@code generatingActionKey}
* must have the same owner as this artifact's current {@link #getArtifactOwner}.
*/
@VisibleForTesting
public final void setGeneratingActionKey(ActionLookupData generatingActionKey) {
Preconditions.checkState(
this.owner != OMITTED_FOR_SERIALIZATION, "Owner was omitted for serialization: %s", this);
Preconditions.checkState(
this.owner instanceof ActionLookupKey,
"Already set generating action key: %s (%s %s)",
this,
this.owner,
generatingActionKey);
Preconditions.checkState(
Preconditions.checkNotNull(generatingActionKey, this).getActionLookupKey().equals(owner),
"Owner of generating action key not same as artifact's owner: %s (%s %s)",
this,
this.owner,
generatingActionKey);
this.owner = generatingActionKey;
}
@VisibleForTesting
public final boolean hasGeneratingActionKey() {
return this.owner instanceof ActionLookupData;
}
/** Can only be called once {@link #setGeneratingActionKey} is called. */
public final ActionLookupData getGeneratingActionKey() {
Preconditions.checkState(owner instanceof ActionLookupData, "Bad owner: %s %s", this, owner);
return (ActionLookupData) owner;
}
@Override
public final ActionLookupKey getArtifactOwner() {
Preconditions.checkState(
this.owner != OMITTED_FOR_SERIALIZATION, "Owner was omitted for serialization: %s", this);
return owner instanceof ActionLookupData
? getGeneratingActionKey().getActionLookupKey()
: (ActionLookupKey) owner;
}
/**
* Returns the object to use for the hash code for this artifact's owner, with the goal of being
* consistent across calls to {@link #setGeneratingActionKey} and also serialization.
*/
private static Object getOwnerToUseForHashCode(Object owner) {
return owner instanceof ActionLookupData
? ((ActionLookupData) owner).getActionLookupKey()
: owner;
}
@Override
public final Label getOwnerLabel() {
return getArtifactOwner().getLabel();
}
@Override
public final String toDebugString() {
if (hasGeneratingActionKey() || owner == OMITTED_FOR_SERIALIZATION) {
return super.toDetailString() + " (" + owner + ")";
}
return super.toDebugString();
}
@Override
public final PathFragment getRootRelativePath() {
return getExecPath().relativeTo(getRoot().getExecPath());
}
@Override
final boolean ownersEqual(Artifact other) {
DerivedArtifact that = (DerivedArtifact) other;
if (!(this.owner instanceof ActionLookupData) || !(that.owner instanceof ActionLookupData)) {
// Happens when at least one of these artifacts hasn't had its generating action key set
// yet, so its configured target is still being analyzed. Tolerate.
return this.getArtifactOwner().equals(that.getArtifactOwner());
}
return this.owner.equals(that.owner);
}
@Override
public boolean contentBasedPath() {
return contentBasedPath;
}
@Override
public String expand(UnaryOperator<PathFragment> stripPaths) {
return stripPaths.apply(getExecPath()).getPathString();
}
}
/** Supplies {@link SourceArtifact} instances and allows for interning of derived artifacts. */
public interface ArtifactSerializationContext {
SourceArtifact getSourceArtifact(PathFragment execPath, Root root, ArtifactOwner owner);
/**
* Whether to include the generating action key when serializing the given derived artifact.
*
* <p>If {@code false} is returned, upon deserialization the generating action key is replaced
* with the marker {@link #OMITTED_FOR_SERIALIZATION}. The artifact is then only intended for
* use with {@link #equalsWithoutOwner} or {@link OwnerlessArtifactWrapper} - any operation
* accessing the generating action key will fail.
*/
default boolean includeGeneratingActionKey(DerivedArtifact artifact) {
return true;
}
default DerivedArtifact intern(DerivedArtifact original) {
return original;
}
}
/**
* Marker stored in place of the generating action key for deserialized artifacts when {@link
* ArtifactSerializationContext#includeGeneratingActionKey} is {@code false}.
*/
@SerializationConstant @VisibleForSerialization
static final Object OMITTED_FOR_SERIALIZATION =
new Object() {
@Override
public String toString() {
return "OMITTED_FOR_SERIALIZATION";
}
};
@SuppressWarnings("unused") // Codec used by reflection.
private static final class DerivedArtifactCodec implements ObjectCodec<DerivedArtifact> {
@Override
public Class<DerivedArtifact> getEncodedClass() {
return DerivedArtifact.class;
}
@Override
public void serialize(
SerializationContext context, DerivedArtifact obj, CodedOutputStream codedOut)
throws SerializationException, IOException {
context.serialize(obj.getRoot(), codedOut);
context.serialize(obj.getRootRelativePath(), codedOut);
context.serialize(getGeneratingActionKeyForSerialization(obj, context), codedOut);
}
@Override
public DerivedArtifact deserialize(DeserializationContext context, CodedInputStream codedIn)
throws SerializationException, IOException {
ArtifactRoot root = context.deserialize(codedIn);
PathFragment rootRelativePath = context.deserialize(codedIn);
Object generatingActionKey = context.deserialize(codedIn);
DerivedArtifact artifact =
new DerivedArtifact(
root,
getExecPathForDeserialization(root, rootRelativePath, generatingActionKey),
generatingActionKey);
return context.getDependency(ArtifactSerializationContext.class).intern(artifact);
}
}
private static Object getGeneratingActionKeyForSerialization(
DerivedArtifact artifact, SerializationContext context) {
return context
.getDependency(ArtifactSerializationContext.class)
.includeGeneratingActionKey(artifact)
? artifact.getGeneratingActionKey()
: OMITTED_FOR_SERIALIZATION;
}
private static PathFragment getExecPathForDeserialization(
ArtifactRoot root, PathFragment rootRelativePath, Object generatingActionKey) {
Preconditions.checkArgument(
!root.isSourceRoot(),
"Root not derived: %s (rootRelativePath=%s, generatingActionKey=%s)",
root,
rootRelativePath,
generatingActionKey);
Preconditions.checkArgument(
root.getRoot().isAbsolute() == rootRelativePath.isAbsolute(),
"Illegal root relative path: %s (root=%s, generatingActionKey=%s)",
rootRelativePath,
root,
generatingActionKey);
return root.getExecPath().getRelative(rootRelativePath);
}
public final Path getPath() {
return root.getRoot().getRelative(getRootRelativePath());
}
public boolean hasParent() {
return getParent() != null;
}
/**
* Returns the parent Artifact containing this Artifact. Artifacts without parents shall return
* null.
*/
@Nullable
public SpecialArtifact getParent() {
return null;
}
/**
* Returns the directory name of this artifact, similar to dirname(1).
*
* <p> The directory name is always a relative path to the execution directory.
*/
@Override
public final String getDirname() {
PathFragment parent = execPath.getParentDirectory();
return (parent == null) ? "/" : parent.getSafePathString();
}
/**
* Returns the base file name of this artifact, similar to basename(1).
*/
@Override
public final String getFilename() {
return execPath.getBaseName();
}
@Override
public final String getExtension() {
return execPath.getFileExtension();
}
/**
* Checks whether this artifact is of the supplied file type.
*
* <p>Prefer this method to pulling out strings from the Artifact and passing to {@link
* FileType#apply(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 of one of the types in the supplied set. */
public boolean isFileType(FileTypeSet fileTypeSet) {
return fileTypeSet.matches(filePathForFileTypeMatcher());
}
@Override
public final String filePathForFileTypeMatcher() {
return execPath.filePathForFileTypeMatcher();
}
@Override
public final String expandToCommandLine() {
return getExecPathString();
}
/** Returns the artifact's owning label. May be null. */
@Nullable
public final Label getOwner() {
return getOwnerLabel();
}
/**
* 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#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 abstract ArtifactOwner getArtifactOwner();
/**
* 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 relative path to this artifact relative to its root. It makes no guarantees as to
* the semantic meaning or the completeness of the returned path value. In other words, no
* assumptions should be made in terms of where the root portion of the path ends, and the
* returned value almost always needs to be used in conjunction with its root.
*
* <p>{#link Artifact#getOutputDirRelativePath()} is more versatile for general use cases.
*/
public abstract PathFragment getRootRelativePath();
/**
* Returns the fully-qualified package path to this artifact. By "fully-qualified", it means the
* returned path is prefixed with "external/<repository name>" if this artifact is in an external
* repository.
*
* <p>Do not call this method just because you need a path prefixed with the "external/<repository
* name>" fragment for external repository artifacts. {@link * Artifact#getOutputDirRelativePath}
* is the right one to use in almost all cases.
*
* @deprecated This method is only to be used for $(location) and getOutputDirRelativePath
* implementations.
*/
@Deprecated
public PathFragment getPathForLocationExpansion() {
return getRootRelativePath();
}
/**
* Returns the path to this artifact relative to an output directory, e.g. the bin directory. Note
* that this is available on every Artifact type, including source artifacts. As a matter of fact,
* one of its most common use cases is to construct a derived artifact's output path out of a
* sibling source artifact's by replacing the basename in its output-dir-relative path.
*/
public PathFragment getOutputDirRelativePath(boolean siblingRepositoryLayout) {
return getRootRelativePath();
}
/**
* Returns the path to this artifact relative to its repository root. As a result, the returned
* path always starts with a corresponding package name, if exists.
*/
public PathFragment getRepositoryRelativePath() {
PathFragment relativePath = getRootRelativePath();
// External artifacts under legacy roots are still prefixed with "external/<repo name>".
if (root.isLegacy() && relativePath.startsWith(LabelConstants.EXTERNAL_PATH_PREFIX)) {
relativePath = relativePath.subFragment(2);
}
return relativePath;
}
/** Returns this.getExecPath().getPathString(). */
@Override
public final String getExecPathString() {
return execPath.getPathString();
}
public final String getRootRelativePathString() {
return getRootRelativePath().getPathString();
}
public final String getRepositoryRelativePathString() {
return getRepositoryRelativePath().getPathString();
}
@Override
public boolean isSymlink() {
return false;
}
/**
* 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, returns {@code null}.
*/
public PathFragment getParentRelativePath() {
return PathFragment.EMPTY_FRAGMENT;
}
@Override
public String getTreeRelativePathString() throws EvalException {
throw Starlark.errorf(
"tree_relative_path not allowed for files that are not tree artifact files.");
}
/**
* 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.
*
* <p>An {@link Artifact} is a {@link SourceArtifact} iff this returns true, and a {@link
* DerivedArtifact} otherwise.
*/
@Override
public final boolean isSourceArtifact() {
return root.isSourceRoot();
}
/**
* Returns true iff this artifact has a generating action, and that generating action is known.
*/
public boolean hasKnownGeneratingAction() {
return !isSourceArtifact();
}
/**
* Returns true iff this is a middleman Artifact as determined by its root.
*
* <p>If true, this artifact is necessarily a {@link DerivedArtifact}.
*/
public final boolean isMiddlemanArtifact() {
return root.isMiddlemanRoot();
}
/**
* Returns true iff this is a TreeArtifact representing a directory tree containing Artifacts.
*
* <p>if true, this artifact is necessarily a {@link SpecialArtifact} with type {@link
* SpecialArtifactType#TREE}.
*/
public boolean isTreeArtifact() {
return false;
}
/**
* Returns {@code true} if this is a {@link TreeFileArtifact} that was created by an action which
* declared an output directory, as opposed to an action that was generated by an action template
* expansion.
*
* <p>Such artifacts should always be stored within a {@link
* com.google.devtools.build.lib.skyframe.TreeArtifactValue} representing the declared directory
* and all children, not individually like other derived artifacts.
*/
public boolean isChildOfDeclaredDirectory() {
return false;
}
/**
* Returns whether the artifact represents a Fileset.
*
* <p>if true, this artifact is necessarily a {@link SpecialArtifact} with type {@link
* SpecialArtifactType#FILESET}.
*/
public boolean isFileset() {
return false;
}
/** The disjunction of {@link #isTreeArtifact} and {@link #isFileset}. */
@Override
public boolean isDirectory() {
return isTreeArtifact() || isFileset();
}
/**
* Returns true iff metadata cache must return constant metadata for the given artifact.
*
* <p>If true, this artifact is necessarily a {@link SpecialArtifact} with type {@link
* SpecialArtifactType#CONSTANT_METADATA}.
*/
public boolean isConstantMetadata() {
return false;
}
/**
* 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 = getRootRelativePath();
// Runfile paths for external artifacts should be prefixed with "../<repo name>".
if (root.isLegacy()) {
// Root-relative paths of external artifacts under legacy roots are already prefixed with
// "external/<repo name>". Just replace "external" with "..".
if (relativePath.startsWith(LabelConstants.EXTERNAL_PATH_PREFIX)) {
relativePath = relativePath.relativeTo(LabelConstants.EXTERNAL_PATH_PREFIX);
relativePath = LabelConstants.EXTERNAL_RUNFILES_PATH_PREFIX.getRelative(relativePath);
}
} else {
if (root.isExternal()) {
// Both external source artifacts and external derived artifacts have their repo name as
// their 2nd level directory name in their exec paths.
// i.e. external/<repo name>/... and bazel-out/<repo name>/...
// This is a pure coincidence, and the below line needs to be updated if any of the
// directory structures change.
String repoName = execPath.getSegment(1);
relativePath =
LabelConstants.EXTERNAL_RUNFILES_PATH_PREFIX
.getRelative(repoName)
.getRelative(relativePath);
}
}
// We can't use root.isExternalSource() here since it needs to handle derived artifacts too.
return relativePath;
}
@Override
public final String getRunfilesPathString() {
return getRunfilesPath().getPathString();
}
public final String prettyPrint() {
// toDetailString would probably be more useful to users, but lots of tests rely on the
// current values.
return getRootRelativePath().toString();
}
@SuppressWarnings("EqualsGetClass") // Distinct classes of Artifact are never equal.
@Override
public final boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof Artifact)) {
return false;
}
if (!getClass().equals(other.getClass())) {
return false;
}
Artifact that = (Artifact) other;
return equalsWithoutOwner(that) && ownersEqual(that);
}
final int hashCodeWithoutOwner() {
return HashCodes.hashObjects(execPath, root);
}
final boolean equalsWithoutOwner(Artifact other) {
return execPath.equals(other.execPath) && root.equals(other.root);
}
abstract boolean ownersEqual(Artifact other);
@Override
public final int hashCode() {
return hashCode;
}
@Override
public final String toString() {
return "File:" + toDetailString();
}
/** 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 + "]" + getRootRelativePathString();
} else {
// Derived Artifact: path and root are under execRoot
//
// TODO(blaze-team): this is misleading because execution_root isn't unique. Dig the
// workspace name out and print that also.
return "[[<execution_root>]" + root.getExecPath() + "]" + getRootRelativePathString();
}
}
public String toDebugString() {
if (getOwner() == null || getOwner().toPathFragment().equals(execPath)) {
return toDetailString();
}
return toDetailString() + " (" + getArtifactOwner() + ")";
}
@Override
public final SkyFunctionName functionName() {
return ARTIFACT;
}
/** {@link Artifact#isSourceArtifact() is true.
*
* <p>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 {
private final ArtifactOwner owner;
@VisibleForTesting
public SourceArtifact(ArtifactRoot root, PathFragment execPath, ArtifactOwner owner) {
super(root, execPath, execPath.hashCode());
this.owner = owner;
}
/**
* Source artifacts do not consider their owners in equality checks, since their owners are
* purely cosmetic.
*/
@Override
boolean ownersEqual(Artifact other) {
return true;
}
@Override
public PathFragment getRootRelativePath() {
return getRoot().isExternal() ? getExecPath().subFragment(2) : getExecPath();
}
@Override
public PathFragment getPathForLocationExpansion() {
return getExecPath();
}
@Override
public PathFragment getOutputDirRelativePath(boolean siblingRepositoryLayout) {
return siblingRepositoryLayout ? getRepositoryRelativePath() : getExecPath();
}
@Override
public PathFragment getRepositoryRelativePath() {
return getRootRelativePath();
}
@Override
public ArtifactOwner getArtifactOwner() {
return owner;
}
@Override
public Label getOwnerLabel() {
return owner.getLabel();
}
boolean differentOwnerOrRoot(ArtifactOwner owner, ArtifactRoot root) {
return !this.owner.equals(owner) || !this.getRoot().equals(root);
}
}
/** {@link ObjectCodec} for {@link SourceArtifact} */
@SuppressWarnings("unused") // Used by reflection.
private static final class SourceArtifactCodec implements ObjectCodec<SourceArtifact> {
@Override
public Class<SourceArtifact> 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 context
.getDependency(ArtifactSerializationContext.class)
.getSourceArtifact(execPath, artifactRoot.getRoot(), owner);
}
}
/**
* Special artifact types.
*
* @see SpecialArtifact
*/
@VisibleForTesting
public enum SpecialArtifactType {
/** Google-specific legacy type. */
FILESET,
/**
* A symlink. Not chased, can be dangling. All we care about is the return value of {@code
* readlink()}.
*/
UNRESOLVED_SYMLINK,
/** A subtree containing multiple files and directories. */
TREE,
/** Special artifact type for workspace status information. */
CONSTANT_METADATA,
}
/**
* A special kind of artifact that either is a fileset or needs special metadata caching behavior.
*
* <p>We subclass {@link DerivedArtifact} 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.
*/
public static final class SpecialArtifact extends DerivedArtifact {
private final SpecialArtifactType type;
@VisibleForTesting
public static SpecialArtifact create(
ArtifactRoot root, PathFragment execPath, ActionLookupKey owner, SpecialArtifactType type) {
return new SpecialArtifact(root, execPath, owner, type);
}
private SpecialArtifact(
ArtifactRoot root, PathFragment execPath, Object owner, SpecialArtifactType type) {
super(root, execPath, owner);
this.type = type;
}
@Override
public 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 isSymlink() {
return type == SpecialArtifactType.UNRESOLVED_SYMLINK;
}
@Override
public boolean hasParent() {
return false;
}
@Override
@Nullable
public PathFragment getParentRelativePath() {
return null;
}
@Override
public boolean valueIsShareable() {
return !isConstantMetadata();
}
}
// Keep in sync with DerivedArtifactCodec.
@SuppressWarnings("unused") // Used by reflection.
private static final class SpecialArtifactCodec implements ObjectCodec<SpecialArtifact> {
@Override
public Class<SpecialArtifact> getEncodedClass() {
return SpecialArtifact.class;
}
@Override
public void serialize(
SerializationContext context, SpecialArtifact obj, CodedOutputStream codedOut)
throws SerializationException, IOException {
context.serialize(obj.getRoot(), codedOut);
context.serialize(obj.getRootRelativePath(), codedOut);
context.serialize(getGeneratingActionKeyForSerialization(obj, context), codedOut);
context.serialize(obj.type, codedOut);
}
@Override
public SpecialArtifact deserialize(DeserializationContext context, CodedInputStream codedIn)
throws SerializationException, IOException {
ArtifactRoot root = context.deserialize(codedIn);
PathFragment rootRelativePath = context.deserialize(codedIn);
Object generatingActionKey = context.deserialize(codedIn);
SpecialArtifactType type = context.deserialize(codedIn);
SpecialArtifact artifact =
new SpecialArtifact(
root,
getExecPathForDeserialization(root, rootRelativePath, generatingActionKey),
generatingActionKey,
type);
return (SpecialArtifact)
context.getDependency(ArtifactSerializationContext.class).intern(artifact);
}
}
/**
* Artifact representing a single-file archive with the filesystem tree belonging to a {@linkplain
* SpecialArtifact tree artifact}.
*
* <p>The archive is equivalent to the entire tree artifact -- it contains all of the {@linkplain
* TreeFileArtifact children} (and nothing else) of the tree artifact with their filesystem
* structure, relative to the {@linkplain SpecialArtifact#getExecPath() tree artifact directory}.
*/
public static final class ArchivedTreeArtifact extends DerivedArtifact {
private static final PathFragment DEFAULT_DERIVED_TREE_ROOT =
PathFragment.create(":archived_tree_artifacts");
private final SpecialArtifact treeArtifact;
private ArchivedTreeArtifact(
SpecialArtifact treeArtifact,
ArtifactRoot root,
PathFragment execPath,
Object generatingActionKey) {
super(root, execPath, generatingActionKey);
Preconditions.checkArgument(
treeArtifact.isTreeArtifact(), "Not a tree artifact: %s", treeArtifact);
this.treeArtifact = treeArtifact;
}
@Override
public SpecialArtifact getParent() {
return treeArtifact;
}
/**
* Creates an {@link ArchivedTreeArtifact} for a given tree artifact at the path inferred from
* the provided tree.
*
* <p>Returned artifact is stored in a permanent location, therefore can be shared across
* actions and builds.
*
* <p>Example: for a tree artifact of {@code bazel-out/k8-fastbuild/bin/directory} returns an
* {@linkplain ArchivedTreeArtifact artifact} of: {@code
* bazel-out/:archived_tree_artifacts/k8-fastbuild/bin/directory.zip}.
*/
public static ArchivedTreeArtifact createForTree(SpecialArtifact treeArtifact) {
return createInternal(
treeArtifact,
DEFAULT_DERIVED_TREE_ROOT,
treeArtifact.getRootRelativePath().replaceName(treeArtifact.getFilename() + ".zip"),
treeArtifact.getGeneratingActionKey());
}
/**
* Creates an {@link ArchivedTreeArtifact} for a given tree artifact within provided derived
* tree directory.
*
* <p>Example: for a tree artifact with root of {@code bazel-out/k8-fastbuild/bin} returns an
* {@linkplain ArchivedTreeArtifact artifact} of: {@code
* bazel-out/{derivedTreeRoot}/k8-fastbuild/bin/{rootRelativePath}} with root of: {@code
* bazel-out/{derivedTreeRoot}/k8-fastbuild/bin}.
*
* <p>Such artifacts should only be used as outputs of intermediate spawns. Action execution
* results must come from {@link #createForTree}.
*/
public static ArchivedTreeArtifact createWithCustomDerivedTreeRoot(
SpecialArtifact treeArtifact, PathFragment derivedTreeRoot, PathFragment rootRelativePath) {
return createInternal(
treeArtifact, derivedTreeRoot, rootRelativePath, treeArtifact.getGeneratingActionKey());
}
private static ArchivedTreeArtifact createInternal(
SpecialArtifact treeArtifact,
PathFragment derivedTreeRoot,
PathFragment rootRelativePath,
Object generatingActionKey) {
ArtifactRoot treeRoot = treeArtifact.getRoot();
PathFragment archiveRoot = embedDerivedTreeRoot(treeRoot.getExecPath(), derivedTreeRoot);
return new ArchivedTreeArtifact(
treeArtifact,
ArtifactRoot.asDerivedRoot(getExecRoot(treeRoot), RootType.Output, archiveRoot),
archiveRoot.getRelative(rootRelativePath),
generatingActionKey);
}
/**
* Returns an exec path within the archived artifacts directory tree corresponding to the
* provided one.
*
* <p>Example: {@code bazel-out/k8-fastbuild/bin ->
* bazel-out/{customDerivedTreeRoot}/k8-fastbuild/bin}.
*/
public static PathFragment getExecPathWithinArchivedArtifactsTree(PathFragment execPath) {
return embedDerivedTreeRoot(execPath, DEFAULT_DERIVED_TREE_ROOT);
}
/**
* Translates provided output {@code execPath} to one under provided derived tree root.
*
* <p>Example: {@code bazel-out/k8-fastbuild/bin ->
* bazel-out/{derivedTreeRoot}/k8-fastbuild/bin}.
*/
private static PathFragment embedDerivedTreeRoot(
PathFragment execPath, PathFragment derivedTreeRoot) {
return execPath
.subFragment(0, 1)
.getRelative(derivedTreeRoot)
.getRelative(execPath.subFragment(1));
}
private static Path getExecRoot(ArtifactRoot artifactRoot) {
// /output_base/execroot/bazel-out/k8-fastbuild/bin
Path rootPath = artifactRoot.getRoot().asPath();
PathFragment rootPathFragment = rootPath.asFragment();
// /output_base/execroot
PathFragment execRootPath =
rootPathFragment.subFragment(
0, rootPathFragment.segmentCount() - artifactRoot.getExecPath().segmentCount());
return rootPath.getFileSystem().getPath(execRootPath);
}
}
@SuppressWarnings("unused") // Codec used by reflection.
private static final class ArchivedTreeArtifactCodec
implements ObjectCodec<ArchivedTreeArtifact> {
@Override
public Class<ArchivedTreeArtifact> getEncodedClass() {
return ArchivedTreeArtifact.class;
}
@Override
public void serialize(
SerializationContext context, ArchivedTreeArtifact obj, CodedOutputStream codedOut)
throws SerializationException, IOException {
PathFragment derivedTreeRoot = obj.getRoot().getExecPath().subFragment(1, 2);
context.serialize(obj.getParent(), codedOut);
context.serialize(derivedTreeRoot, codedOut);
context.serialize(obj.getRootRelativePath(), codedOut);
}
@Override
public ArchivedTreeArtifact deserialize(
DeserializationContext context, CodedInputStream codedIn)
throws SerializationException, IOException {
SpecialArtifact treeArtifact = context.deserialize(codedIn);
PathFragment derivedTreeRoot = context.deserialize(codedIn);
PathFragment rootRelativePath = context.deserialize(codedIn);
Object generatingActionKey =
treeArtifact.hasGeneratingActionKey()
? treeArtifact.getGeneratingActionKey()
: OMITTED_FOR_SERIALIZATION;
return ArchivedTreeArtifact.createInternal(
treeArtifact, derivedTreeRoot, rootRelativePath, generatingActionKey);
}
}
/**
* A special kind of artifact that represents a concrete file created at execution time under its
* associated parent TreeArtifact.
*
* <p>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.
*
* <p>There are two types of TreeFileArtifacts:
*
* <ol>
* <li>Outputs under a directory created by an action using {@code declare_directory}. In this
* case, a single action creates both the parent and all of the children. Instances should
* be created by calling {@link #createTreeOutput}. {@link #isChildOfDeclaredDirectory} will
* return {@code true}.
* <li>Outputs of an action template expansion. In this case, the parent directory is not
* actually produced by any action, but rather serves as a placeholder for dependant actions
* to declare a dep on during analysis, before the children are known. The children are
* created by various actions (from the template expansion). Instances should be created by
* calling {@link #createTemplateExpansionOutput}. {@link #isChildOfDeclaredDirectory} will
* return {@code false}.
* </ol>
*/
public static final class TreeFileArtifact extends DerivedArtifact {
private final SpecialArtifact parent;
private final PathFragment parentRelativePath;
/**
* Creates a {@link TreeFileArtifact} representing a child of the given parent tree artifact.
*
* <p>The child should already have been created by the parent's generating action. For this
* reason, {@link DerivedArtifact#hasGeneratingActionKey} on the parent must be {@code true}
* when this is called. The child is set with the same generating action.
*/
public static TreeFileArtifact createTreeOutput(
SpecialArtifact parent, PathFragment parentRelativePath) {
Preconditions.checkArgument(
parent.hasGeneratingActionKey(),
"%s has no generating action key (parent owner: %s, parent relative path: %s)",
parent,
parent.getArtifactOwner(),
parentRelativePath);
ActionLookupData generatingActionKey = parent.getGeneratingActionKey();
Preconditions.checkArgument(
!isActionTemplateExpansionKey(generatingActionKey.getActionLookupKey()),
"%s owned by action template expansion %s (parent relative path: %s)",
parent,
generatingActionKey.getActionLookupKey(),
parentRelativePath);
return new TreeFileArtifact(parent, parentRelativePath, generatingActionKey);
}
/**
* Convenience method for {@link #createTreeOutput(SpecialArtifact, PathFragment)} with a string
* relative path.
*/
public static TreeFileArtifact createTreeOutput(
SpecialArtifact parent, String parentRelativePath) {
return createTreeOutput(parent, PathFragment.create(parentRelativePath));
}
/**
* Creates a {@link TreeFileArtifact} representing the output of an action generated dynamically
* by an {@link ActionTemplate} during the execution phase.
*
* <p>The returned artifact does not yet have a generating action set.
*/
public static TreeFileArtifact createTemplateExpansionOutput(
SpecialArtifact parent, PathFragment parentRelativePath, ActionLookupKey owner) {
Preconditions.checkArgument(
isActionTemplateExpansionKey(owner),
"Template expansion outputs must be owned by an action template expansion key, but %s is"
+ " owned by %s (parent relative path: %s)",
parent,
owner,
parentRelativePath);
return new TreeFileArtifact(parent, parentRelativePath, owner);
}
/**
* Convenience method for {@link #createTemplateExpansionOutput(SpecialArtifact, PathFragment,
* ActionLookupKey)} with a string relative path.
*/
public static TreeFileArtifact createTemplateExpansionOutput(
SpecialArtifact parent, String parentRelativePath, ActionLookupKey owner) {
return createTemplateExpansionOutput(parent, PathFragment.create(parentRelativePath), owner);
}
private TreeFileArtifact(
SpecialArtifact parent, PathFragment parentRelativePath, Object owner) {
super(parent.getRoot(), parent.getExecPath().getRelative(parentRelativePath), owner);
Preconditions.checkArgument(
parent.isTreeArtifact(),
"The parent of TreeFileArtifact (parent-relative path: %s) is not a TreeArtifact: %s",
parentRelativePath,
parent);
Preconditions.checkArgument(
!parentRelativePath.containsUplevelReferences() && !parentRelativePath.isAbsolute(),
"%s is not a proper normalized relative path",
parentRelativePath);
this.parent = parent;
this.parentRelativePath = parentRelativePath;
}
@Override
public SpecialArtifact getParent() {
return parent;
}
@Override
public PathFragment getParentRelativePath() {
return parentRelativePath;
}
@Override
public String getTreeRelativePathString() {
return parentRelativePath.getPathString();
}
@Override
public boolean isChildOfDeclaredDirectory() {
return !isActionTemplateExpansionKey(getArtifactOwner());
}
private static boolean isActionTemplateExpansionKey(ActionLookupKey key) {
return SkyFunctions.ACTION_TEMPLATE_EXPANSION.equals(key.functionName());
}
}
@SuppressWarnings("unused") // Used by reflection.
private static final class TreeFileArtifactCodec implements ObjectCodec<TreeFileArtifact> {
@Override
public Class<TreeFileArtifact> getEncodedClass() {
return TreeFileArtifact.class;
}
@Override
public void serialize(
SerializationContext context, TreeFileArtifact obj, CodedOutputStream codedOut)
throws SerializationException, IOException {
context.serialize(obj.parent, codedOut);
context.serialize(obj.parentRelativePath, codedOut);
context.serialize(getGeneratingActionKeyForSerialization(obj, context), codedOut);
}
@Override
public TreeFileArtifact deserialize(DeserializationContext context, CodedInputStream codedIn)
throws SerializationException, IOException {
SpecialArtifact parent = context.deserialize(codedIn);
PathFragment parentRelativePath = context.deserialize(codedIn);
Object generatingActionKey = context.deserialize(codedIn);
return new TreeFileArtifact(parent, parentRelativePath, generatingActionKey);
}
}
// ---------------------------------------------------------------------------
// Static methods to assist in working with Artifacts
/** Formatter for execPath PathFragment output. */
public static final Function<Artifact, String> ROOT_RELATIVE_PATH_STRING =
artifact -> artifact.getRootRelativePath().getPathString();
public static final Function<Artifact, String> RUNFILES_PATH_STRING =
artifact -> artifact.getRunfilesPath().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, ActionInput::getExecPathString);
}
/**
* Converts a collection of artifacts into the outputs computed by outputFormatter and adds them
* to a given collection. Middleman artifacts are ignored.
*/
public 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(NestedSet<Artifact> artifacts) {
return toRootRelativePaths(artifacts.toList());
}
/**
* 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),
artifact -> artifact.getRootRelativePath().getPathString());
}
/**
* Lazily converts artifacts into execution-time path strings. Middleman artifacts are ignored by
* this method.
*/
public static Iterable<String> toExecPaths(Iterable<Artifact> artifacts) {
return Iterables.transform(
Iterables.filter(artifacts, MIDDLEMAN_FILTER), ActionInput::getExecPathString);
}
/**
* Converts a collection of artifacts into execution-time path strings, and returns those as an
* immutable list. Middleman artifacts are ignored by this method.
*
* <p>Avoid this method in production code - it flattens the given nested set unconditionally.
*/
@VisibleForTesting
public static List<String> asExecPaths(NestedSet<Artifact> artifacts) {
return asExecPaths(artifacts.toList());
}
/**
* 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 an artifact to a collection, expanding it once if it's a middleman or tree artifact.
*
* <p>A middleman artifact is never added to the collection. If {@code keepEmptyTreeArtifacts} is
* true, a tree artifact will be added to the collection when it expands into zero file artifacts.
* Otherwise, only the file artifacts the tree artifact expands into will be added.
*/
static void addExpandedArtifact(
Artifact artifact,
Collection<? super Artifact> output,
ArtifactExpander artifactExpander,
boolean keepEmptyTreeArtifacts) {
if (artifact.isMiddlemanArtifact() || artifact.isTreeArtifact()) {
List<Artifact> expandedArtifacts = new ArrayList<>();
artifactExpander.expand(artifact, expandedArtifacts);
output.addAll(expandedArtifacts);
if (keepEmptyTreeArtifacts && artifact.isTreeArtifact() && expandedArtifacts.isEmpty()) {
output.add(artifact);
}
} else {
output.add(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.apply(artifact.getFilename())) {
filesToBuild.add(artifact);
}
}
return filesToBuild;
}
/**
* Converts artifacts into their exec paths. Returns an immutable list.
*/
public static List<PathFragment> asPathFragments(Iterable<? extends Artifact> artifacts) {
return Streams.stream(artifacts).map(Artifact::getExecPath).collect(toImmutableList());
}
/**
* Returns the exec paths of the input artifacts in alphabetical order.
*/
public static ImmutableList<PathFragment> asSortedPathFragments(Iterable<Artifact> input) {
return Streams.stream(input).map(Artifact::getExecPath).sorted().collect(toImmutableList());
}
@Override
public boolean isImmutable() {
return true;
}
@Override
public void repr(Printer printer) {
if (isSourceArtifact()) {
printer.append("<source file " + getRootRelativePathString() + ">");
} else {
printer.append("<generated file " + getRootRelativePathString() + ">");
}
}
/**
* 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 final class OwnerlessArtifactWrapper {
private final Artifact artifact;
private final int hashCode;
public OwnerlessArtifactWrapper(Artifact artifact) {
this.artifact = artifact;
this.hashCode = artifact.hashCodeWithoutOwner();
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(Object obj) {
return obj instanceof OwnerlessArtifactWrapper
&& this.artifact.equalsWithoutOwner(((OwnerlessArtifactWrapper) obj).artifact);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("artifact", artifact.toDebugString()).toString();
}
}
}