| // 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.analysis.actions; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.devtools.build.lib.actions.AbstractAction; |
| import com.google.devtools.build.lib.actions.ActionExecutionContext; |
| import com.google.devtools.build.lib.actions.ActionExecutionException; |
| import com.google.devtools.build.lib.actions.ActionKeyContext; |
| import com.google.devtools.build.lib.actions.ActionOwner; |
| import com.google.devtools.build.lib.actions.ActionResult; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander; |
| import com.google.devtools.build.lib.analysis.platform.PlatformInfo; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; |
| import com.google.devtools.build.lib.collect.nestedset.Order; |
| import com.google.devtools.build.lib.server.FailureDetails; |
| import com.google.devtools.build.lib.server.FailureDetails.FailureDetail; |
| import com.google.devtools.build.lib.server.FailureDetails.SymlinkAction.Code; |
| import com.google.devtools.build.lib.util.DetailedExitCode; |
| import com.google.devtools.build.lib.util.Fingerprint; |
| import com.google.devtools.build.lib.vfs.Path; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import java.io.IOException; |
| import javax.annotation.Nullable; |
| |
| /** |
| * Action to create a symlink to a known-to-exist target with alias semantics similar to a true copy |
| * of the input (if any). |
| */ |
| public final class SymlinkAction extends AbstractAction { |
| private static final String GUID = "7f4fab4d-d0a7-4f0f-8649-1d0337a21fee"; |
| |
| /** Null when {@link #getPrimaryInput} is the target of the symlink. */ |
| @Nullable private final PathFragment inputPath; |
| @Nullable private final String progressMessage; |
| |
| enum TargetType { |
| /** |
| * The symlink points into a Fileset. |
| * |
| * <p>If this is set, the action also updates the mtime for its target thus forcing actions |
| * depending on it to be re-executed. This would not be necessary in an ideal world, but |
| * dependency checking for Filesets output trees is unsound because they are directories, so we |
| * need to force them to be considered changed this way. Yet Another Reason why Filests should |
| * go away. |
| */ |
| FILESET, |
| |
| /** |
| * The symlink should point to an executable. |
| * |
| * <p>Blaze will verify that the target is indeed executable. |
| */ |
| EXECUTABLE, |
| |
| /** Just a vanilla symlink. Don't do anything else other than creating the symlink. */ |
| OTHER, |
| } |
| |
| private final TargetType targetType; |
| |
| public static SymlinkAction toArtifact( |
| ActionOwner owner, Artifact input, Artifact output, String progressMessage) { |
| return toArtifact(owner, input, output, progressMessage, /*useExecRootForSource=*/ false); |
| } |
| |
| /** |
| * Creates an action that creates a symlink pointing to an artifact. |
| * |
| * @param owner the action owner. |
| * @param input the {@link Artifact} the symlink will point to |
| * @param output the {@link Artifact} that will be created by executing this Action. |
| * @param progressMessage the progress message. |
| * @param useExecRootForSource whether to link source artifacts to exec root as opposed to the |
| * artifact itself. This indirection makes sure that the symlink is always in sync with exec |
| * root, which could go out of sync with it when re-planting the symlink tree and the header |
| * was unchanged. |
| */ |
| public static SymlinkAction toArtifact( |
| ActionOwner owner, |
| Artifact input, |
| Artifact output, |
| String progressMessage, |
| boolean useExecRootForSource) { |
| return new SymlinkAction( |
| owner, |
| useExecRootForSource && input.isSourceArtifact() ? input.getExecPath() : null, |
| input, |
| output, |
| progressMessage, |
| TargetType.OTHER); |
| } |
| |
| public static SymlinkAction toExecutable( |
| ActionOwner owner, Artifact input, Artifact output, String progressMessage) { |
| return new SymlinkAction(owner, null, input, output, progressMessage, TargetType.EXECUTABLE); |
| } |
| |
| private SymlinkAction( |
| ActionOwner owner, |
| PathFragment inputPath, |
| Artifact primaryInput, |
| Artifact primaryOutput, |
| String progressMessage, |
| TargetType targetType) { |
| super( |
| owner, |
| primaryInput != null |
| ? NestedSetBuilder.create(Order.STABLE_ORDER, primaryInput) |
| : NestedSetBuilder.emptySet(Order.STABLE_ORDER), |
| ImmutableSet.of(primaryOutput)); |
| this.inputPath = inputPath; |
| this.progressMessage = progressMessage; |
| this.targetType = targetType; |
| } |
| |
| /** |
| * Creates a symlink to a Fileset. |
| * |
| * <p>This is different from a regular {@link SymlinkAction} in that the target is in the output |
| * tree but not an artifact and that when running this action, the mtime of its target is updated |
| * (necessary because dependency checking of Filesets is unsound). For more information, see the |
| * Javadoc of {@code TargetType.FILESET}. |
| * |
| * <p><b>WARNING:</b>Do not use this for anything else other than Filesets. If you do, your |
| * correctness will depend on a subtle interaction between various parts of Blaze. |
| * |
| * @param owner the action owner. |
| * @param execPath where the symlink will point to |
| * @param primaryInput the {@link Artifact} that is required to build the inputPath. |
| * @param primaryOutput the {@link Artifact} that will be created by executing this Action. |
| * @param progressMessage the progress message. |
| */ |
| public static SymlinkAction toFileset( |
| ActionOwner owner, |
| PathFragment execPath, |
| Artifact primaryInput, |
| Artifact primaryOutput, |
| String progressMessage) { |
| Preconditions.checkState(!execPath.isAbsolute()); |
| return new SymlinkAction( |
| owner, execPath, primaryInput, primaryOutput, progressMessage, TargetType.FILESET); |
| } |
| |
| /** |
| * Creates a new SymlinkAction instance, where an input artifact is not present. This is useful |
| * when dealing with special cases where input paths that are outside the exec root directory |
| * tree. Currently, the only instance where this happens is for FDO builds where the profile file |
| * is outside the exec root structure. |
| * |
| * <p>Do <b>NOT</b> use this method unless there is no other way; unconditionally executed actions |
| * are costly: even if change pruning kicks in and downstream actions are not re-executed, they |
| * trigger unconditional Skyframe invalidation of their reverse dependencies. |
| * |
| * @param owner the action owner. |
| * @param absolutePath where the symlink will point to |
| * @param output the Artifact that will be created by executing this Action. |
| * @param progressMessage the progress message. |
| */ |
| public static SymlinkAction toAbsolutePath(ActionOwner owner, PathFragment absolutePath, |
| Artifact output, String progressMessage) { |
| Preconditions.checkState(absolutePath.isAbsolute()); |
| return new SymlinkAction(owner, absolutePath, null, output, progressMessage, TargetType.OTHER); |
| } |
| |
| public PathFragment getInputPath() { |
| return inputPath == null ? getPrimaryInput().getExecPath() : inputPath; |
| } |
| |
| public Path getOutputPath(ActionExecutionContext actionExecutionContext) { |
| return actionExecutionContext.getInputPath(getPrimaryOutput()); |
| } |
| |
| @Override |
| public ActionResult execute(ActionExecutionContext actionExecutionContext) |
| throws ActionExecutionException { |
| maybeVerifyTargetIsExecutable(actionExecutionContext); |
| |
| Path srcPath; |
| if (inputPath == null) { |
| srcPath = actionExecutionContext.getInputPath(getPrimaryInput()); |
| } else { |
| srcPath = actionExecutionContext.getExecRoot().getRelative(inputPath); |
| } |
| |
| Path outputPath = getOutputPath(actionExecutionContext); |
| |
| try { |
| // Delete the empty output directory created prior to the action execution when the output is |
| // a tree artifact. All other actions that produce tree artifacts expect it to exist prior to |
| // their execution. It's not worth complicating ActionOutputDirectoryHelper just to avoid this |
| // small amount of overhead. |
| outputPath.delete(); |
| |
| outputPath.createSymbolicLink(srcPath); |
| } catch (IOException e) { |
| String message = |
| String.format( |
| "failed to create symbolic link '%s' to '%s' due to I/O error: %s", |
| getPrimaryOutput().getExecPathString(), printInputs(), e.getMessage()); |
| DetailedExitCode code = createDetailedExitCode(message, Code.LINK_CREATION_IO_EXCEPTION); |
| throw new ActionExecutionException(message, e, this, false, code); |
| } |
| |
| updateInputMtimeIfNeeded(actionExecutionContext); |
| return ActionResult.EMPTY; |
| } |
| |
| private void maybeVerifyTargetIsExecutable(ActionExecutionContext actionExecutionContext) |
| throws ActionExecutionException { |
| if (targetType != TargetType.EXECUTABLE) { |
| return; |
| } |
| |
| Path inputPath = actionExecutionContext.getInputPath(getPrimaryInput()); |
| try { |
| // Validate that input path is a file with the executable bit set. |
| if (!inputPath.isFile()) { |
| String message = String.format("'%s' is not a file", getPrimaryInput().getExecPathString()); |
| throw new ActionExecutionException( |
| message, this, false, createDetailedExitCode(message, Code.EXECUTABLE_INPUT_NOT_FILE)); |
| } |
| if (!inputPath.isExecutable()) { |
| String message = |
| String.format( |
| "failed to create symbolic link '%s': file '%s' is not executable", |
| getPrimaryOutput().getExecPathString(), getPrimaryInput().getExecPathString()); |
| throw new ActionExecutionException( |
| message, this, false, createDetailedExitCode(message, Code.EXECUTABLE_INPUT_IS_NOT)); |
| } |
| } catch (IOException e) { |
| String message = |
| String.format( |
| "failed to create symbolic link '%s' to the '%s' due to I/O error: %s", |
| getPrimaryOutput().getExecPathString(), |
| getPrimaryInput().getExecPathString(), |
| e.getMessage()); |
| DetailedExitCode detailedExitCode = |
| createDetailedExitCode(message, Code.EXECUTABLE_INPUT_CHECK_IO_EXCEPTION); |
| throw new ActionExecutionException(message, e, this, false, detailedExitCode); |
| } |
| } |
| |
| private void updateInputMtimeIfNeeded(ActionExecutionContext actionExecutionContext) |
| throws ActionExecutionException { |
| if (targetType != TargetType.FILESET) { |
| return; |
| } |
| |
| try { |
| // Update the mtime of the target of the symlink to force downstream re-execution of actions. |
| // This is needed because dependency checking of Fileset output trees is unsound (it's a |
| // directory). |
| // Note that utime() on a symlink actually changes the mtime of its target. |
| Path linkPath = getOutputPath(actionExecutionContext); |
| if (linkPath.exists()) { |
| linkPath.setLastModifiedTime(Path.NOW_SENTINEL_TIME); |
| } else { |
| // Should only happen if the Fileset included no links. |
| actionExecutionContext |
| .getExecRoot() |
| .getRelative(getInputPath()) |
| .createDirectoryAndParents(); |
| } |
| } catch (IOException e) { |
| String message = |
| String.format( |
| "failed to touch symbolic link '%s' to the '%s' due to I/O error: %s", |
| getPrimaryOutput().getExecPathString(), |
| getPrimaryInput().getExecPathString(), |
| e.getMessage()); |
| DetailedExitCode code = createDetailedExitCode(message, Code.LINK_TOUCH_IO_EXCEPTION); |
| throw new ActionExecutionException(message, e, this, false, code); |
| } |
| } |
| |
| private String printInputs() { |
| if (getInputs().isEmpty()) { |
| return inputPath.getPathString(); |
| } else if (getInputs().isSingleton()) { |
| return getPrimaryInput().getExecPathString(); |
| } else { |
| throw new IllegalStateException( |
| "Inputs unexpectedly contains more than 1 element: " + getInputs()); |
| } |
| } |
| |
| @Override |
| protected void computeKey( |
| ActionKeyContext actionKeyContext, |
| @Nullable ArtifactExpander artifactExpander, |
| Fingerprint fp) { |
| fp.addString(GUID); |
| // We don't normally need to add inputs to the key. In this case, however, the inputPath can be |
| // different from the actual input artifact. |
| if (inputPath != null) { |
| fp.addPath(inputPath); |
| } |
| } |
| |
| @Override |
| public String getMnemonic() { |
| return targetType == TargetType.EXECUTABLE ? "ExecutableSymlink" : "Symlink"; |
| } |
| |
| @Override |
| public boolean isVolatile() { |
| return inputPath != null && inputPath.isAbsolute(); |
| } |
| |
| @Override |
| public boolean executeUnconditionally() { |
| // If the SymlinkAction points to an absolute path, we can't verify that its output artifact did |
| // not change purely by looking at the output tree. Thus, we re-execute the action just to be |
| // safe. Change pruning will take care of not re-running dependent actions and this is used only |
| // in very rare cases (only C++ FDO and even then, only twice per build at most) anyway. |
| return inputPath != null && inputPath.isAbsolute(); |
| } |
| |
| @Override |
| protected String getRawProgressMessage() { |
| return progressMessage; |
| } |
| |
| @Override |
| public boolean mayInsensitivelyPropagateInputs() { |
| return true; |
| } |
| |
| private static DetailedExitCode createDetailedExitCode(String message, Code detailedCode) { |
| return DetailedExitCode.of( |
| FailureDetail.newBuilder() |
| .setMessage(message) |
| .setSymlinkAction(FailureDetails.SymlinkAction.newBuilder().setCode(detailedCode)) |
| .build()); |
| } |
| |
| @Override |
| @Nullable |
| public PlatformInfo getExecutionPlatform() { |
| // SymlinkAction is platform agnostic. |
| return null; |
| } |
| |
| @Override |
| public ImmutableMap<String, String> getExecProperties() { |
| // SymlinkAction is platform agnostic. |
| return ImmutableMap.of(); |
| } |
| } |