|  | // 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.base.Preconditions.checkArgument; | 
|  | import static com.google.common.base.Preconditions.checkNotNull; | 
|  | import static com.google.common.base.Preconditions.checkState; | 
|  |  | 
|  | import com.google.common.collect.ImmutableList; | 
|  | import com.google.common.collect.ImmutableMap; | 
|  | import com.google.common.collect.ImmutableSet; | 
|  | import com.google.common.collect.Iterables; | 
|  | import com.google.common.collect.Iterators; | 
|  | import com.google.common.collect.Maps; | 
|  | import com.google.common.flogger.GoogleLogger; | 
|  | import com.google.devtools.build.lib.actions.Artifact.ArchivedTreeArtifact; | 
|  | import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact; | 
|  | import com.google.devtools.build.lib.actions.cache.OutputMetadataStore; | 
|  | import com.google.devtools.build.lib.actions.extra.ExtraActionInfo; | 
|  | import com.google.devtools.build.lib.analysis.platform.PlatformInfo; | 
|  | import com.google.devtools.build.lib.cmdline.Label; | 
|  | import com.google.devtools.build.lib.cmdline.RepositoryMapping; | 
|  | import com.google.devtools.build.lib.collect.nestedset.Depset; | 
|  | import com.google.devtools.build.lib.collect.nestedset.NestedSet; | 
|  | import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; | 
|  | import com.google.devtools.build.lib.collect.nestedset.Order; | 
|  | import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; | 
|  | import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; | 
|  | import com.google.devtools.build.lib.events.Event; | 
|  | import com.google.devtools.build.lib.events.EventHandler; | 
|  | import com.google.devtools.build.lib.packages.AspectDescriptor; | 
|  | import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions; | 
|  | import com.google.devtools.build.lib.server.FailureDetails.Execution.Code; | 
|  | import com.google.devtools.build.lib.starlarkbuildapi.ActionApi; | 
|  | import com.google.devtools.build.lib.starlarkbuildapi.CommandLineArgsApi; | 
|  | import com.google.devtools.build.lib.vfs.BulkDeleter; | 
|  | 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.lib.vfs.Symlinks; | 
|  | import com.google.errorprone.annotations.ForOverride; | 
|  | import java.io.IOException; | 
|  | import java.util.AbstractSet; | 
|  | import java.util.Collection; | 
|  | import java.util.Iterator; | 
|  | import java.util.Map; | 
|  | import java.util.Set; | 
|  | import javax.annotation.Nullable; | 
|  | import javax.annotation.concurrent.GuardedBy; | 
|  | import net.starlark.java.eval.Dict; | 
|  | import net.starlark.java.eval.EvalException; | 
|  | import net.starlark.java.eval.Printer; | 
|  | import net.starlark.java.eval.Sequence; | 
|  | import net.starlark.java.eval.StarlarkSemantics; | 
|  |  | 
|  | /** | 
|  | * Abstract implementation of Action which implements basic functionality: the inputs, outputs, and | 
|  | * toString method. Both input and output sets are immutable. Subclasses must be generally immutable | 
|  | * - see the documentation on {@link Action}. | 
|  | */ | 
|  | @Immutable | 
|  | @ThreadSafe | 
|  | public abstract class AbstractAction extends ActionKeyCacher implements Action, ActionApi { | 
|  | private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); | 
|  |  | 
|  | /** | 
|  | * An arbitrary default resource set. We assume that a typical subprocess is single-threaded | 
|  | * (i.e., uses one CPU core) and CPU-bound, and uses a small-ish amount of memory. In the past, | 
|  | * we've seen that assuming less than one core can lead to local overload. Unless you have data | 
|  | * indicating otherwise (for example, we've observed in the past that C++ linking can use large | 
|  | * amounts of memory), we suggest to use this default set. | 
|  | */ | 
|  | // TODO(ulfjack): Collect actual data to confirm that this is an acceptable approximation. | 
|  | public static final ResourceSet DEFAULT_RESOURCE_SET = ResourceSet.createWithRamCpu(250, 1); | 
|  |  | 
|  | private final ActionOwner owner; | 
|  |  | 
|  | // The variable inputs is non-final only so that actions that discover their inputs can modify it. | 
|  | // Access through getInputs() in case it's overridden. | 
|  | @GuardedBy("this") | 
|  | private NestedSet<Artifact> inputs; | 
|  |  | 
|  | /** | 
|  | * To save memory, this is either an {@link Artifact} for actions with a single output, or a | 
|  | * duplicate-free {@code Artifact[]} for actions with multiple outputs. | 
|  | */ | 
|  | private final Object outputs; | 
|  |  | 
|  | protected AbstractAction( | 
|  | ActionOwner owner, NestedSet<Artifact> inputs, Iterable<? extends Artifact> outputs) { | 
|  | this.owner = checkNotNull(owner); | 
|  | this.inputs = checkNotNull(inputs); | 
|  | this.outputs = singletonOrArray(outputs); | 
|  | } | 
|  |  | 
|  | private static Object singletonOrArray(Iterable<? extends Artifact> outputs) { | 
|  | ImmutableSet<Artifact> set = ImmutableSet.copyOf(outputs); | 
|  | checkArgument(!set.isEmpty(), "Action outputs may not be empty"); | 
|  | return set.size() == 1 ? Iterables.getOnlyElement(set) : set.toArray(Artifact[]::new); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public final boolean isImmutable() { | 
|  | return true; // immutable and Starlark-hashable | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public final ActionOwner getOwner() { | 
|  | return owner; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public final boolean inputsKnown() { | 
|  | if (!discoversInputs()) { | 
|  | return true; | 
|  | } | 
|  | synchronized (this) { | 
|  | return inputsDiscovered(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * {@inheritDoc} | 
|  | * | 
|  | * <p>Should be overridden along with {@link #discoverInputs}, {@link #inputsDiscovered}, and | 
|  | * {@link #setInputsDiscovered} by actions that do input discovery. | 
|  | */ | 
|  | @Override | 
|  | public boolean discoversInputs() { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | @Nullable | 
|  | public NestedSet<Artifact> discoverInputs(ActionExecutionContext actionExecutionContext) | 
|  | throws ActionExecutionException, InterruptedException { | 
|  | throw new IllegalStateException("discoverInputs cannot be called for " + this.prettyPrint() | 
|  | + " since it does not discover inputs"); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public final void resetDiscoveredInputs() { | 
|  | checkState(discoversInputs(), "Not an input-discovering action: %s", this); | 
|  | if (!inputsKnown()) { | 
|  | return; | 
|  | } | 
|  | NestedSet<Artifact> originalInputs = getOriginalInputs(); | 
|  | if (originalInputs != null) { | 
|  | synchronized (this) { | 
|  | inputs = originalInputs; | 
|  | setInputsDiscovered(false); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns true if inputs have been discovered. | 
|  | * | 
|  | * <p>The value returned reflects the most recent call to {@link #setInputsDiscovered}. If {@link | 
|  | * #setInputsDiscovered} has never been called, returns false. | 
|  | * | 
|  | * <p>This method is used instead of a {@code boolean} field in this class in order to save memory | 
|  | * for actions which do not discover inputs. | 
|  | */ | 
|  | @ForOverride | 
|  | @GuardedBy("this") | 
|  | protected boolean inputsDiscovered() { | 
|  | throw new IllegalStateException("Must be overridden by input-discovering actions: " + this); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Informs input-discovering actions about their discovery state so that they can correctly | 
|  | * implement {@link #inputsDiscovered}. | 
|  | */ | 
|  | @ForOverride | 
|  | @GuardedBy("this") | 
|  | protected void setInputsDiscovered(boolean inputsDiscovered) { | 
|  | throw new IllegalStateException("Must be overridden by input-discovering actions: " + this); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns this action's <em>original</em> inputs, prior to {@linkplain #discoverInputs input | 
|  | * discovery}. | 
|  | * | 
|  | * <p>Input-discovering actions which are able to reconstitute their original inputs may override | 
|  | * this, allowing for memory savings. | 
|  | */ | 
|  | @Nullable | 
|  | @ForOverride | 
|  | protected NestedSet<Artifact> getOriginalInputs() { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public NestedSet<Artifact> getAllowedDerivedInputs() { | 
|  | throw new IllegalStateException( | 
|  | "Method must be overridden for actions that may have unknown inputs."); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public NestedSet<Artifact> getSchedulingDependencies() { | 
|  | return NestedSetBuilder.emptySet(Order.STABLE_ORDER); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Should be called when the inputs of the action become known, that is, either during {@link | 
|  | * #discoverInputs(ActionExecutionContext)} or during {@link #execute(ActionExecutionContext)}. | 
|  | * | 
|  | * <p>When an action discovers inputs, it must have been called by the time {@code #execute()} | 
|  | * returns. It can be called both during {@code discoverInputs} and during {@code execute()}. | 
|  | * | 
|  | * <p>In addition to being called from action implementations, it will also be called by Bazel | 
|  | * itself when an action is loaded from the on-disk action cache. | 
|  | */ | 
|  | @Override | 
|  | public synchronized void updateInputs(NestedSet<Artifact> inputs) { | 
|  | checkState(discoversInputs(), "Can't update inputs unless discovering: %s %s", this, inputs); | 
|  | this.inputs = inputs; | 
|  | setInputsDiscovered(true); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public NestedSet<Artifact> getTools() { | 
|  | return NestedSetBuilder.emptySet(Order.STABLE_ORDER); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public synchronized NestedSet<Artifact> getInputs() { | 
|  | return inputs; | 
|  | } | 
|  |  | 
|  | public ActionEnvironment getEnvironment() { | 
|  | return ActionEnvironment.EMPTY; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public ImmutableMap<String, String> getEffectiveEnvironment(Map<String, String> clientEnv) | 
|  | throws CommandLineExpansionException { | 
|  | ActionEnvironment env = getEnvironment(); | 
|  | Map<String, String> effectiveEnvironment = | 
|  | Maps.newLinkedHashMapWithExpectedSize(env.estimatedSize()); | 
|  | env.resolve(effectiveEnvironment, clientEnv); | 
|  | return ImmutableMap.copyOf(effectiveEnvironment); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Collection<String> getClientEnvironmentVariables() { | 
|  | return getEnvironment().getInheritedEnv(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public RunfilesSupplier getRunfilesSupplier() { | 
|  | return EmptyRunfilesSupplier.INSTANCE; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Collection<Artifact> getOutputs() { | 
|  | return outputs instanceof Artifact | 
|  | ? ImmutableSet.of((Artifact) outputs) | 
|  | : new OutputSet((Artifact[]) outputs); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Simple {@link Set} wrapper around an array for actions with multiple outputs. | 
|  | * | 
|  | * <p>Implements {@link Set} so that passing an instance to {@link ImmutableSet#copyOf} results in | 
|  | * precise pre-sizing (since it is known to be duplicate-free). Note that the return type of | 
|  | * {@link ActionAnalysisMetadata#getOutputs} is {@link Collection}, so callers are unlikely to | 
|  | * expect a fast {@link #contains} implementation. | 
|  | */ | 
|  | private static final class OutputSet extends AbstractSet<Artifact> { | 
|  | private final Artifact[] array; | 
|  |  | 
|  | OutputSet(Artifact[] array) { | 
|  | this.array = array; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Iterator<Artifact> iterator() { | 
|  | return Iterators.forArray(array); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int size() { | 
|  | return array.length; | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Artifact getPrimaryInput() { | 
|  | // The default behavior is to return the first input artifact. | 
|  | // Call through the method, not the field, because it may be overridden. | 
|  | return Iterables.getFirst(getInputs().toList(), null); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public final Artifact getPrimaryOutput() { | 
|  | return outputs instanceof Artifact ? (Artifact) outputs : ((Artifact[]) outputs)[0]; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public NestedSet<Artifact> getMandatoryInputs() { | 
|  | return getInputs(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String toString() { | 
|  | return prettyPrint() | 
|  | + " (" | 
|  | + getMnemonic() | 
|  | + "[" | 
|  | + getInputs().toList() | 
|  | + (inputsKnown() ? " -> " : ", unknown inputs -> ") | 
|  | + getOutputs() | 
|  | + "]" | 
|  | + ")"; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public abstract String getMnemonic(); | 
|  |  | 
|  | @Override | 
|  | public String describeKey() { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean executeUnconditionally() { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isVolatile() { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isShareable() { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean showsOutputUnconditionally() { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Nullable | 
|  | @Override | 
|  | public final String getProgressMessage() { | 
|  | return getProgressMessageChecked(null); | 
|  | } | 
|  |  | 
|  | @Nullable | 
|  | @Override | 
|  | public final String getProgressMessage(RepositoryMapping mainRepositoryMapping) { | 
|  | checkNotNull(mainRepositoryMapping); | 
|  | return getProgressMessageChecked(mainRepositoryMapping); | 
|  | } | 
|  |  | 
|  | @Nullable | 
|  | private String getProgressMessageChecked(@Nullable RepositoryMapping mainRepositoryMapping) { | 
|  | String message = getRawProgressMessage(); | 
|  | if (message == null) { | 
|  | return null; | 
|  | } | 
|  | message = replaceProgressMessagePlaceholders(message, mainRepositoryMapping); | 
|  | return owner.isBuildConfigurationForTool() ? message + " [for tool]" : message; | 
|  | } | 
|  |  | 
|  | private String replaceProgressMessagePlaceholders( | 
|  | String progressMessage, @Nullable RepositoryMapping mainRepositoryMapping) { | 
|  | if (progressMessage.contains("%{label}") && owner.getLabel() != null) { | 
|  | String labelString; | 
|  | if (mainRepositoryMapping != null) { | 
|  | labelString = owner.getLabel().getDisplayForm(mainRepositoryMapping); | 
|  | } else { | 
|  | labelString = owner.getLabel().toString(); | 
|  | } | 
|  | progressMessage = progressMessage.replace("%{label}", labelString); | 
|  | } | 
|  | if (progressMessage.contains("%{output}") && getPrimaryOutput() != null) { | 
|  | progressMessage = | 
|  | progressMessage.replace("%{output}", getPrimaryOutput().getExecPathString()); | 
|  | } | 
|  | if (progressMessage.contains("%{input}") && getPrimaryInput() != null) { | 
|  | progressMessage = progressMessage.replace("%{input}", getPrimaryInput().getExecPathString()); | 
|  | } | 
|  | return progressMessage; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns a progress message string that is specific for this action. This is then annotated with | 
|  | * additional information, currently the string '[for tool]' for actions in the tool | 
|  | * configurations. | 
|  | * | 
|  | * <p>A return value of null indicates no message should be reported. | 
|  | */ | 
|  | protected String getRawProgressMessage() { | 
|  | // A cheesy default implementation.  Subclasses are invited to do something | 
|  | // more meaningful. | 
|  | return defaultProgressMessage(); | 
|  | } | 
|  |  | 
|  | private String defaultProgressMessage() { | 
|  | return getMnemonic() + " " + getPrimaryOutput().prettyPrint(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String prettyPrint() { | 
|  | return "action '" + describe() + "'"; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void repr(Printer printer) { | 
|  | printer.append(prettyPrint()); // TODO(bazel-team): implement a readable representation | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Deletes all of the action's output files, if they exist. If any of the Artifacts refers to a | 
|  | * directory recursively removes the contents of the directory. | 
|  | * | 
|  | * @param execRoot the exec root in which this action is executed | 
|  | * @param bulkDeleter a helper to bulk delete outputs to avoid delegating to the filesystem | 
|  | * @param cleanupArchivedArtifacts whether to clean up archived tree artifacts | 
|  | */ | 
|  | protected final void deleteOutputs( | 
|  | Path execRoot, | 
|  | ArtifactPathResolver pathResolver, | 
|  | @Nullable BulkDeleter bulkDeleter, | 
|  | boolean cleanupArchivedArtifacts) | 
|  | throws IOException, InterruptedException { | 
|  | Collection<Artifact> outputs = getOutputs(); | 
|  | Iterable<Artifact> artifactsToDelete = | 
|  | cleanupArchivedArtifacts | 
|  | ? Iterables.concat(outputs, archivedTreeArtifactOutputs(outputs)) | 
|  | : outputs; | 
|  | Iterable<PathFragment> additionalPathOutputsToDelete = getAdditionalPathOutputsToDelete(); | 
|  | Iterable<PathFragment> directoryOutputsToDelete = getDirectoryOutputsToDelete(); | 
|  | if (bulkDeleter != null) { | 
|  | bulkDeleter.bulkDelete( | 
|  | Iterables.concat( | 
|  | Artifact.asPathFragments(artifactsToDelete), | 
|  | additionalPathOutputsToDelete, | 
|  | directoryOutputsToDelete)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // TODO(b/185277726): Either we don't need a path resolver for actual deletion of output | 
|  | //  artifacts (likely) or we need to transform the fragments below (and then the resolver should | 
|  | //  be augmented to deal with exec-path PathFragments). | 
|  | for (Artifact output : artifactsToDelete) { | 
|  | deleteOutput(output, pathResolver); | 
|  | } | 
|  |  | 
|  | for (PathFragment path : additionalPathOutputsToDelete) { | 
|  | deleteOutput(execRoot.getRelative(path), /*root=*/ null); | 
|  | } | 
|  |  | 
|  | for (PathFragment path : directoryOutputsToDelete) { | 
|  | execRoot.getRelative(path).deleteTree(); | 
|  | } | 
|  | } | 
|  |  | 
|  | @ForOverride | 
|  | protected Iterable<PathFragment> getAdditionalPathOutputsToDelete() { | 
|  | return ImmutableList.of(); | 
|  | } | 
|  |  | 
|  | @ForOverride | 
|  | protected Iterable<PathFragment> getDirectoryOutputsToDelete() { | 
|  | return ImmutableList.of(); | 
|  | } | 
|  |  | 
|  | private static Iterable<Artifact> archivedTreeArtifactOutputs(Collection<Artifact> outputs) { | 
|  | return Iterables.transform( | 
|  | Iterables.filter(outputs, Artifact::isTreeArtifact), | 
|  | tree -> ArchivedTreeArtifact.createForTree((SpecialArtifact) tree)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Remove an output artifact. | 
|  | * | 
|  | * <p>If the path refers to a directory, recursively removes the contents of the directory. | 
|  | * | 
|  | * @param output artifact to remove | 
|  | */ | 
|  | protected static void deleteOutput(Artifact output, ArtifactPathResolver pathResolver) | 
|  | throws IOException { | 
|  | deleteOutput( | 
|  | pathResolver.toPath(output), pathResolver.transformRoot(output.getRoot().getRoot())); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Helper method to remove an output file. | 
|  | * | 
|  | * <p>If the path refers to a directory, recursively removes the contents of the directory. | 
|  | * | 
|  | * @param path the output to remove | 
|  | * @param root the root containing the output. This is used to check that we don't delete | 
|  | *     arbitrary files in the file system. | 
|  | */ | 
|  | public static void deleteOutput(Path path, @Nullable Root root) throws IOException { | 
|  | try { | 
|  | // Optimize for the common case: output artifacts are files. | 
|  | path.delete(); | 
|  | } catch (IOException e) { | 
|  | // Handle a couple of scenarios where the output can still be deleted, but make sure we're not | 
|  | // deleting random files on the filesystem. | 
|  | if (root == null) { | 
|  | throw new IOException("null root", e); | 
|  | } | 
|  | if (!root.contains(path)) { | 
|  | throw new IOException(String.format("%s not under %s", path, root), e); | 
|  | } | 
|  |  | 
|  | Path parentDir = path.getParentDirectory(); | 
|  | if (!parentDir.isWritable() && root.contains(parentDir)) { | 
|  | parentDir.setWritable(true); | 
|  | } | 
|  |  | 
|  | // Retry deleting after making the parent writable. | 
|  | if (path.isDirectory(Symlinks.NOFOLLOW)) { | 
|  | path.deleteTree(); | 
|  | } else { | 
|  | path.delete(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * If the action might read directories as inputs in a way that is unsound wrt dependency | 
|  | * checking, this method must be called. | 
|  | */ | 
|  | protected void checkInputsForDirectories( | 
|  | EventHandler eventHandler, InputMetadataProvider inputMetadataProvider) throws ExecException { | 
|  | // Report "directory dependency checking" warning only for non-generated directories (generated | 
|  | // ones will be reported earlier). | 
|  | for (Artifact input : getMandatoryInputs().toList()) { | 
|  | // Assume that if the file did not exist, we would not have gotten here. | 
|  | try { | 
|  | if (input.isSourceArtifact() | 
|  | && inputMetadataProvider.getInputMetadata(input).getType().isDirectory()) { | 
|  | // TODO(ulfjack): What about dependency checking of special files? | 
|  | eventHandler.handle( | 
|  | Event.warn( | 
|  | owner.getLocation(), | 
|  | String.format( | 
|  | "input '%s' to %s is a directory; " | 
|  | + "dependency checking of directories is unsound", | 
|  | input.prettyPrint(), owner.getLabel()))); | 
|  | } | 
|  | } catch (IOException e) { | 
|  | throw new EnvironmentalExecException(e, Code.INPUT_DIRECTORY_CHECK_IO_EXCEPTION); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public MiddlemanType getActionType() { | 
|  | return MiddlemanType.NORMAL; | 
|  | } | 
|  |  | 
|  | /** If the action might create directories as outputs this method must be called. */ | 
|  | protected void checkOutputsForDirectories(ActionExecutionContext actionExecutionContext) | 
|  | throws InterruptedException { | 
|  | FileArtifactValue metadata; | 
|  | for (Artifact output : getOutputs()) { | 
|  | OutputMetadataStore outputMetadataStore = actionExecutionContext.getOutputMetadataStore(); | 
|  | if (outputMetadataStore.artifactOmitted(output)) { | 
|  | continue; | 
|  | } | 
|  | try { | 
|  | metadata = outputMetadataStore.getOutputMetadata(output); | 
|  | } catch (IOException e) { | 
|  | logger.atWarning().withCause(e).log("Error getting metadata for %s", output); | 
|  | metadata = null; | 
|  | } | 
|  | if (metadata != null) { | 
|  | if (!metadata.getType().isDirectory()) { | 
|  | continue; | 
|  | } | 
|  | } else if (!actionExecutionContext.getInputPath(output).isDirectory()) { | 
|  | continue; | 
|  | } | 
|  | String ownerString = Label.print(owner.getLabel()); | 
|  | actionExecutionContext | 
|  | .getEventHandler() | 
|  | .handle( | 
|  | Event.warn( | 
|  | owner.getLocation(), | 
|  | "output '" | 
|  | + output.prettyPrint() | 
|  | + "' of " | 
|  | + ownerString | 
|  | + " is a directory; dependency checking of directories is unsound") | 
|  | .withTag(ownerString)); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void prepare( | 
|  | Path execRoot, | 
|  | ArtifactPathResolver pathResolver, | 
|  | @Nullable BulkDeleter bulkDeleter, | 
|  | boolean cleanupArchivedArtifacts) | 
|  | throws IOException, InterruptedException { | 
|  | deleteOutputs(execRoot, pathResolver, bulkDeleter, cleanupArchivedArtifacts); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public final String describe() { | 
|  | String progressMessage = getProgressMessage(); | 
|  | return progressMessage != null ? progressMessage : defaultProgressMessage(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean shouldReportPathPrefixConflict(ActionAnalysisMetadata action) { | 
|  | return this != action; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public ExtraActionInfo.Builder getExtraActionInfo(ActionKeyContext actionKeyContext) | 
|  | throws CommandLineExpansionException, InterruptedException { | 
|  | ExtraActionInfo.Builder result = | 
|  | ExtraActionInfo.newBuilder() | 
|  | .setOwner(owner.getLabel().toString()) | 
|  | .setId(getKey(actionKeyContext, /* artifactExpander= */ null)) | 
|  | .setMnemonic(getMnemonic()); | 
|  | ImmutableList<AspectDescriptor> aspectDescriptors = owner.getAspectDescriptors(); | 
|  | AspectDescriptor lastAspect = | 
|  | aspectDescriptors.isEmpty() ? null : Iterables.getLast(aspectDescriptors); | 
|  | if (lastAspect != null) { | 
|  | result.setAspectName(lastAspect.getAspectClass().getName()); | 
|  |  | 
|  | for (Map.Entry<String, Collection<String>> entry : | 
|  | lastAspect.getParameters().getAttributes().asMap().entrySet()) { | 
|  | result.putAspectParameters( | 
|  | entry.getKey(), | 
|  | ExtraActionInfo.StringList.newBuilder().addAllValue(entry.getValue()).build()); | 
|  | } | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public ImmutableSet<Artifact> getMandatoryOutputs() { | 
|  | return ImmutableSet.of(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns input files that need to be present to allow extra_action rules to shadow this action | 
|  | * correctly when run remotely. This is at least the normal inputs of the action, but may include | 
|  | * other files as well. For example C(++) compilation may perform include file header scanning. | 
|  | * This needs to be mirrored by the extra_action rule. Called by {@link | 
|  | * com.google.devtools.build.lib.analysis.extra.ExtraAction} at execution time for actions that | 
|  | * return true for {link #discoversInputs}. | 
|  | * | 
|  | * <p>Returns null when a required value is missing and a Skyframe restart is required. | 
|  | * | 
|  | * @param actionExecutionContext Services in the scope of the action, like the Out/Err streams. | 
|  | * @throws ActionExecutionException only when code called from this method throws that exception. | 
|  | * @throws InterruptedException if interrupted | 
|  | */ | 
|  | @Override | 
|  | public NestedSet<Artifact> getInputFilesForExtraAction( | 
|  | ActionExecutionContext actionExecutionContext) | 
|  | throws ActionExecutionException, InterruptedException { | 
|  | return NestedSetBuilder.emptySet(Order.STABLE_ORDER); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Depset getStarlarkInputs() { | 
|  | return Depset.of(Artifact.class, getInputs()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Depset getStarlarkOutputs() { | 
|  | return Depset.of(Artifact.class, NestedSetBuilder.wrap(Order.STABLE_ORDER, getOutputs())); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | @Nullable | 
|  | public Sequence<String> getStarlarkArgv() throws EvalException, InterruptedException { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | @Nullable | 
|  | public Sequence<CommandLineArgsApi> getStarlarkArgs() { | 
|  | // Not all action types support returning Args. | 
|  | return null; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | @Nullable | 
|  | public String getStarlarkContent() throws IOException, EvalException, InterruptedException { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | @Nullable | 
|  | public Dict<String, String> getStarlarkSubstitutions() throws EvalException { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Dict<String, String> getExecutionInfoDict() { | 
|  | ImmutableMap<String, String> executionInfo = getExecutionInfo(); | 
|  | return Dict.immutableCopyOf(executionInfo); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Dict<String, String> getEnv(StarlarkSemantics semantics) throws EvalException { | 
|  | if (semantics.getBool(BuildLanguageOptions.EXPERIMENTAL_GET_FIXED_CONFIGURED_ACTION_ENV)) { | 
|  | try { | 
|  | return Dict.immutableCopyOf(getEffectiveEnvironment(/*clientEnv=*/ ImmutableMap.of())); | 
|  | } catch (CommandLineExpansionException ex) { | 
|  | throw new EvalException(ex); | 
|  | } | 
|  | } else { | 
|  | return Dict.immutableCopyOf(getEnvironment().getFixedEnv()); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public ImmutableMap<String, String> getExecProperties() { | 
|  | return owner.getExecProperties(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | @Nullable | 
|  | public PlatformInfo getExecutionPlatform() { | 
|  | return owner.getExecutionPlatform(); | 
|  | } | 
|  | } |