blob: 4ae6ca2b9cc4f51f9fd6ad4f0e7eeb42ee11e9cf [file] [log] [blame]
// Copyright 2016 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.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
import com.google.devtools.build.lib.actions.ActionExecutionContext;
import com.google.devtools.build.lib.actions.ActionInputHelper;
import com.google.devtools.build.lib.actions.ActionKeyCacher;
import com.google.devtools.build.lib.actions.ActionKeyContext;
import com.google.devtools.build.lib.actions.ActionOwner;
import com.google.devtools.build.lib.actions.ActionTemplate;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
import com.google.devtools.build.lib.actions.ArtifactOwner;
import com.google.devtools.build.lib.actions.CommandLine;
import com.google.devtools.build.lib.actions.CommandLineExpansionException;
import com.google.devtools.build.lib.analysis.FilesToRunProvider;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.util.Fingerprint;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.Map;
/** An {@link ActionTemplate} that expands into {@link SpawnAction}s at execution time. */
public final class SpawnActionTemplate extends ActionKeyCacher
implements ActionTemplate<SpawnAction> {
private final SpecialArtifact inputTreeArtifact;
private final SpecialArtifact outputTreeArtifact;
private final NestedSet<Artifact> commonInputs;
private final NestedSet<Artifact> allInputs;
private final NestedSet<Artifact> commonTools;
private final ActionOwner actionOwner;
private final String mnemonic;
private final OutputPathMapper outputPathMapper;
private final SpawnAction.Builder spawnActionBuilder;
private final CustomCommandLine commandLineTemplate;
/**
* Interface providing mapping between expanded input files under the input TreeArtifact and
* parent-relative paths of their associated output file under the output TreeArtifact.
*
* <p>Users of SpawnActionTemplate must provide a mapper object implementing this interface.
* SpawnActionTemplate uses the mapper to query for the path of output artifact associated with
* each input {@link TreeFileArtifact} resolved at execution time.
*/
public interface OutputPathMapper {
/**
* Given the input {@link TreeFileArtifact}, returns the parent-relative path of the associated
* output {@link TreeFileArtifact}.
*
* @param input the input {@link TreeFileArtifact}
*/
PathFragment parentRelativeOutputPath(TreeFileArtifact input);
}
private SpawnActionTemplate(
ActionOwner actionOwner,
SpecialArtifact inputTreeArtifact,
SpecialArtifact outputTreeArtifact,
NestedSet<Artifact> commonInputs,
NestedSet<Artifact> commonTools,
OutputPathMapper outputPathMapper,
CustomCommandLine commandLineTemplate,
String mnemonic,
SpawnAction.Builder spawnActionBuilder) {
this.inputTreeArtifact = inputTreeArtifact;
this.outputTreeArtifact = outputTreeArtifact;
this.commonTools = commonTools;
this.commonInputs = NestedSetBuilder.<Artifact>stableOrder()
.addTransitive(commonInputs)
.addTransitive(commonTools)
.build();
this.allInputs = NestedSetBuilder.<Artifact>stableOrder()
.add(inputTreeArtifact)
.addTransitive(this.commonInputs)
.build();
this.outputPathMapper = outputPathMapper;
this.actionOwner = actionOwner;
this.mnemonic = mnemonic;
this.spawnActionBuilder = spawnActionBuilder;
this.commandLineTemplate = commandLineTemplate;
}
@Override
public Iterable<SpawnAction> generateActionForInputArtifacts(
Iterable<TreeFileArtifact> inputTreeFileArtifacts, ArtifactOwner artifactOwner) {
ImmutableList.Builder<SpawnAction> expandedActions = new ImmutableList.Builder<>();
for (TreeFileArtifact inputTreeFileArtifact : inputTreeFileArtifacts) {
PathFragment parentRelativeOutputPath =
outputPathMapper.parentRelativeOutputPath(inputTreeFileArtifact);
TreeFileArtifact outputTreeFileArtifact = ActionInputHelper.treeFileArtifact(
outputTreeArtifact,
parentRelativeOutputPath,
artifactOwner);
expandedActions.add(createAction(inputTreeFileArtifact, outputTreeFileArtifact));
}
return expandedActions.build();
}
@Override
protected void computeKey(ActionKeyContext actionKeyContext, Fingerprint fp)
throws CommandLineExpansionException {
TreeFileArtifact inputTreeFileArtifact =
ActionInputHelper.treeFileArtifact(inputTreeArtifact, "dummy_for_key");
TreeFileArtifact outputTreeFileArtifact =
ActionInputHelper.treeFileArtifact(
outputTreeArtifact, outputPathMapper.parentRelativeOutputPath(inputTreeFileArtifact));
SpawnAction dummyAction = createAction(inputTreeFileArtifact, outputTreeFileArtifact);
dummyAction.computeKey(actionKeyContext, fp);
}
/**
* Returns a SpawnAction that takes inputTreeFileArtifact as input and generates
* outputTreeFileArtifact.
*/
private SpawnAction createAction(
TreeFileArtifact inputTreeFileArtifact, TreeFileArtifact outputTreeFileArtifact) {
SpawnAction.Builder actionBuilder = new SpawnAction.Builder(spawnActionBuilder);
actionBuilder.addInput(inputTreeFileArtifact);
actionBuilder.addOutput(outputTreeFileArtifact);
CommandLine commandLine = commandLineTemplate.evaluateTreeFileArtifacts(
ImmutableList.of(inputTreeFileArtifact, outputTreeFileArtifact));
actionBuilder.addCommandLine(commandLine);
// Note that we pass in nulls below because SpawnActionTemplate does not support param file, and
// it does not use any default value for executable or shell environment. They must be set
// explicitly via builder method #setExecutable and #setEnvironment.
return actionBuilder.buildForActionTemplate(getOwner());
}
/**
* Returns the input TreeArtifact.
*
* <p>This method is called by Skyframe to expand the input TreeArtifact into child
* TreeFileArtifacts. Skyframe then expands this SpawnActionTemplate with the TreeFileArtifacts
* through {@link #generateActionForInputArtifacts}.
*/
@Override
public Artifact getInputTreeArtifact() {
return inputTreeArtifact;
}
/** Returns the output TreeArtifact. */
@Override
public Artifact getOutputTreeArtifact() {
return outputTreeArtifact;
}
@Override
public ActionOwner getOwner() {
return actionOwner;
}
@Override
public boolean isShareable() {
return true;
}
@Override
public final String getMnemonic() {
return mnemonic;
}
@Override
public Iterable<Artifact> getTools() {
return commonTools;
}
@Override
public Iterable<Artifact> getInputs() {
return allInputs;
}
@Override
public ImmutableSet<Artifact> getOutputs() {
return ImmutableSet.of(outputTreeArtifact);
}
@Override
public Iterable<Artifact> getMandatoryInputs() {
return getInputs();
}
@Override
public Iterable<Artifact> getInputFilesForExtraAction(
ActionExecutionContext actionExecutionContext) {
return ImmutableList.of();
}
@Override
public ImmutableSet<Artifact> getMandatoryOutputs() {
return ImmutableSet.of();
}
@Override
public Artifact getPrimaryInput() {
return inputTreeArtifact;
}
@Override
public Artifact getPrimaryOutput() {
return outputTreeArtifact;
}
@Override
public Iterable<String> getClientEnvironmentVariables() {
return spawnActionBuilder.buildForActionTemplate(getOwner()).getClientEnvironmentVariables();
}
@Override
public boolean shouldReportPathPrefixConflict(ActionAnalysisMetadata action) {
return this != action;
}
@Override
public MiddlemanType getActionType() {
return MiddlemanType.NORMAL;
}
@Override
public String prettyPrint() {
return String.format("action template with output TreeArtifact %s",
outputTreeArtifact.prettyPrint());
}
/** Builder class to construct {@link SpawnActionTemplate} instances. */
public static class Builder {
private String actionTemplateMnemonic = "Unknown";
private OutputPathMapper outputPathMapper;
private CustomCommandLine commandLineTemplate;
private PathFragment executable;
private final SpecialArtifact inputTreeArtifact;
private final SpecialArtifact outputTreeArtifact;
private final NestedSetBuilder<Artifact> inputsBuilder = NestedSetBuilder.stableOrder();
private final NestedSetBuilder<Artifact> toolsBuilder = NestedSetBuilder.stableOrder();
private final SpawnAction.Builder spawnActionBuilder;
/**
* Creates a {@link SpawnActionTemplate} builder.
*
* @param inputTreeArtifact the required input TreeArtifact.
* @param outputTreeArtifact the required output TreeArtifact.
*/
public Builder(SpecialArtifact inputTreeArtifact, SpecialArtifact outputTreeArtifact) {
Preconditions.checkState(
inputTreeArtifact.isTreeArtifact() && outputTreeArtifact.isTreeArtifact(),
"Either %s or %s is not a TreeArtifact",
inputTreeArtifact,
outputTreeArtifact);
this.inputTreeArtifact = inputTreeArtifact;
this.outputTreeArtifact = outputTreeArtifact;
this.spawnActionBuilder = new SpawnAction.Builder();
}
/**
* Sets the mnemonics for both the {@link SpawnActionTemplate} and expanded {@link SpawnAction}.
*/
public Builder setMnemonics(String actionTemplateMnemonic, String expandedActionMnemonic) {
this.actionTemplateMnemonic = actionTemplateMnemonic;
spawnActionBuilder.setMnemonic(expandedActionMnemonic);
return this;
}
/**
* Adds common tool artifacts. All common tool artifacts will be added as tool artifacts for
* expanded actions.
*/
public Builder addCommonTools(Iterable<Artifact> artifacts) {
toolsBuilder.addAll(artifacts);
spawnActionBuilder.addTools(artifacts);
return this;
}
/**
* Adds common tool artifacts. All common tool artifacts will be added as input tool artifacts
* for expanded actions.
*/
public Builder addCommonTool(FilesToRunProvider tool) {
toolsBuilder.addTransitive(tool.getFilesToRun());
spawnActionBuilder.addTool(tool);
return this;
}
/**
* Adds common input artifacts. All common input artifacts will be added as input artifacts for
* expanded actions.
*/
public Builder addCommonInputs(Iterable<Artifact> artifacts) {
inputsBuilder.addAll(artifacts);
spawnActionBuilder.addInputs(artifacts);
return this;
}
/**
* Adds transitive common input artifacts. All common input artifacts will be added as input
* artifacts for expanded actions.
*/
public Builder addCommonTransitiveInputs(NestedSet<Artifact> artifacts) {
inputsBuilder.addTransitive(artifacts);
spawnActionBuilder.addTransitiveInputs(artifacts);
return this;
}
/** Sets the map of environment variables for expanded actions. */
@Deprecated // TODO(ulfjack): Add env variables to the common environment, rather than replacing
// it wholesale, which ignores --action_env (unless the client code explicitly handles it).
public Builder setEnvironment(Map<String, String> environment) {
spawnActionBuilder.setEnvironment(environment);
return this;
}
/**
* Sets the map of execution info for expanded actions.
*/
public Builder setExecutionInfo(Map<String, String> executionInfo) {
spawnActionBuilder.setExecutionInfo(executionInfo);
return this;
}
/**
* Sets the executable used by expanded actions as a configured target. Automatically adds the
* files to run to the tools and uses the executable of the target as the executable.
*
* <p>Calling this method overrides any previous values set via calls to
* {@link #setExecutable(Artifact)} and {@link #setExecutable(PathFragment)}.
*/
public Builder setExecutable(FilesToRunProvider executableProvider) {
Preconditions.checkArgument(
executableProvider.getExecutable() != null, "The target does not have an executable");
spawnActionBuilder.setExecutable(executableProvider);
addCommonTool(executableProvider);
this.executable = executableProvider.getExecutable().getExecPath();
return this;
}
/**
* Sets the executable path used by expanded actions. The path is interpreted relative to the
* execution root.
*
* <p>Calling this method overrides any previous values set via calls to
* {@link #setExecutable(Artifact)} and {@link #setExecutable(FilesToRunProvider)}.
*/
public Builder setExecutable(PathFragment executable) {
spawnActionBuilder.setExecutable(executable);
this.executable = executable;
return this;
}
/**
* Sets the executable artifact used by expanded actions. The path is interpreted relative to
* the execution root.
*
* <p>Calling this method overrides any previous values set via calls to
* {@link #setExecutable(FilesToRunProvider)} and {@link #setExecutable(PathFragment)}.
*/
public Builder setExecutable(Artifact artifact) {
spawnActionBuilder.setExecutable(artifact);
addCommonTools(ImmutableList.of(artifact));
this.executable = artifact.getExecPath();
return this;
}
/**
* Sets the command line template used to expand actions.
*/
public Builder setCommandLineTemplate(CustomCommandLine commandLineTemplate) {
this.commandLineTemplate = commandLineTemplate;
return this;
}
/**
* Sets the {@link OutputPathMapper} object used to get the parent-relative paths of output
* {@link TreeFileArtifact}.
*/
public Builder setOutputPathMapper(OutputPathMapper outputPathMapper) {
this.outputPathMapper = outputPathMapper;
return this;
}
/**
* Builds and returns the {@link SpawnActionTemplate} using the accumulated builder information.
*
* @param actionOwner the action owner of the SpawnActionTemplate to be built.
*/
public SpawnActionTemplate build(ActionOwner actionOwner) {
Preconditions.checkNotNull(executable);
return new SpawnActionTemplate(
actionOwner,
Preconditions.checkNotNull(inputTreeArtifact),
Preconditions.checkNotNull(outputTreeArtifact),
inputsBuilder.build(),
toolsBuilder.build(),
Preconditions.checkNotNull(outputPathMapper),
Preconditions.checkNotNull(commandLineTemplate),
actionTemplateMnemonic,
spawnActionBuilder);
}
}
}