// 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 com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; 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.collect.CollectionUtils; 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.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization; import com.google.devtools.build.lib.skylarkbuildapi.ActionApi; import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter; import com.google.devtools.build.lib.syntax.EvalException; import com.google.devtools.build.lib.syntax.SkylarkDict; import com.google.devtools.build.lib.syntax.SkylarkList; import com.google.devtools.build.lib.syntax.SkylarkNestedSet; import com.google.devtools.build.lib.util.Fingerprint; import com.google.devtools.build.lib.vfs.FileSystem; import com.google.devtools.build.lib.vfs.FileSystemUtils; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.Root; import com.google.devtools.build.lib.vfs.Symlinks; import java.io.IOException; import java.util.Collection; import java.util.Map; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; /** * 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 implements Action, ActionApi { /** * An arbitrary default resource set. Currently 250MB of memory, 50% CPU and 0% of total I/O. */ public static final ResourceSet DEFAULT_RESOURCE_SET = ResourceSet.createWithRamCpuIo(250, 0.5, 0); /** * The owner/inputs/outputs attributes below should never be directly accessed even within * AbstractAction itself. The appropriate getter methods should be used instead. This has to be * done due to the fact that the getter methods can be overridden in subclasses. */ @VisibleForSerialization protected final ActionOwner owner; /** * Tools are a subset of inputs and used by the WorkerSpawnStrategy to determine whether a * compiler has changed since the last time it was used. This should include all artifacts that * the tool does not dynamically reload / check on each unit of work - e.g. its own binary, the * JDK for Java binaries, shared libraries, ... but not a configuration file, if it reloads that * when it has changed. * *
If the "tools" set does not contain exactly the right set of artifacts, the following can
* happen: If an artifact that should be included is missing, the tool might not be restarted when
* it should, and builds can become incorrect (example: The compiler binary is not part of this
* set, then the compiler gets upgraded, but the worker strategy still reuses the old version).
* If an artifact that should *not* be included is accidentally part of this set, the worker
* process will be restarted more often that is necessary - e.g. if a file that is unique to each
* unit of work, e.g. the source code that a compiler should compile for a compile action, is
* part of this set, then the worker will never be reused and will be restarted for each unit of
* work.
*/
private final Iterable The value returned by each instance should be constant over the lifetime of that instance.
*
* If this returns true, {@link #discoverInputs(ActionExecutionContext)} must also be
* implemented.
*/
@Override
public boolean discoversInputs() {
return false;
}
/**
* Run input discovery on the action.
*
* Called by Blaze if {@link #discoversInputs()} returns true. It must return the set of
* input artifacts that were not known at analysis time. May also call
* {@link #updateInputs(Iterable Since keeping state within an action bad, don't do that unless there is a very good reason
* to do so.
*/
@Override
public Iterable 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()}.
*
* 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(Iterable 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(SkylarkPrinter 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
*/
protected void deleteOutputs(FileSystem fileSystem, Path execRoot) throws IOException {
for (Artifact output : getOutputs()) {
deleteOutput(fileSystem, output);
}
}
/**
* Helper method to remove an Artifact. If the Artifact refers to a directory recursively removes
* the contents of the directory.
*/
protected void deleteOutput(FileSystem fileSystem, Artifact output) throws IOException {
Path path = output.getPath();
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 (output.getRoot() == null) {
throw e;
}
Root outputRoot = output.getRoot().getRoot();
if (!outputRoot.contains(path)) {
throw e;
}
Path parentDir = path.getParentDirectory();
if (!parentDir.isWritable() && outputRoot.contains(parentDir)) {
// Retry deleting after making the parent writable.
parentDir.setWritable(true);
deleteOutput(fileSystem, output);
} else if (path.isDirectory(Symlinks.NOFOLLOW)) {
FileSystemUtils.deleteTree(path);
} else {
throw e;
}
}
}
/**
* 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, MetadataProvider metadataProvider) throws ExecException {
// Report "directory dependency checking" warning only for non-generated directories (generated
// ones will be reported earlier).
for (Artifact input : getMandatoryInputs()) {
// Assume that if the file did not exist, we would not have gotten here.
try {
if (input.isSourceArtifact()
&& metadataProvider.getMetadata(input).getType().isDirectory()) {
// TODO(ulfjack): What about dependency checking of special files?
eventHandler.handle(Event.warn(getOwner().getLocation(),
String.format(
"input '%s' to %s is a directory; dependency checking of directories is unsound",
input.prettyPrint(), getOwner().getLabel())));
}
} catch (IOException e) {
throw new UserExecException(e);
}
}
}
@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) {
for (Artifact output : getOutputs()) {
Path path = actionExecutionContext.getInputPath(output);
String ownerString = Label.print(getOwner().getLabel());
if (path.isDirectory()) {
actionExecutionContext
.getEventHandler()
.handle(
Event.warn(
getOwner().getLocation(),
"output '"
+ output.prettyPrint()
+ "' of "
+ ownerString
+ " is a directory; dependency checking of directories is unsound")
.withTag(ownerString));
}
}
}
@Override
public void prepare(FileSystem fileSystem, Path execRoot) throws IOException {
deleteOutputs(fileSystem, execRoot);
}
@Override
public 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 {
ActionOwner owner = getOwner();
ExtraActionInfo.Builder result =
ExtraActionInfo.newBuilder()
.setOwner(owner.getLabel().toString())
.setId(getKey(actionKeyContext))
.setMnemonic(getMnemonic());
Iterable