blob: 5ba3fe2663b87dcb5c3596aa5ae00584941c4b24 [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.analysis.extra;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.actions.Action;
import com.google.devtools.build.lib.actions.ActionKeyContext;
import com.google.devtools.build.lib.actions.ActionOwner;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.CommandLine;
import com.google.devtools.build.lib.analysis.BashCommandConstructor;
import com.google.devtools.build.lib.analysis.CommandHelper;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.ShToolchain;
import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.packages.AspectDescriptor;
import com.google.devtools.build.lib.util.Fingerprint;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
/**
* The specification for a particular extra action type.
*/
@Immutable
public final class ExtraActionSpec implements TransitiveInfoProvider {
private final NestedSet<Artifact> resolvedTools;
private final ImmutableList<Artifact> resolvedData;
private final ImmutableList<String> outputTemplates;
private final ImmutableMap<String, String> executionInfo;
private final String command;
private final boolean requiresActionOutput;
private final Label label;
public ExtraActionSpec(
NestedSet<Artifact> resolvedTools,
List<Artifact> resolvedData,
List<String> outputTemplates,
String command,
Label label,
Map<String, String> executionInfo,
boolean requiresActionOutput) {
this.resolvedTools = resolvedTools;
this.resolvedData = ImmutableList.copyOf(resolvedData);
this.outputTemplates = ImmutableList.copyOf(outputTemplates);
this.command = command;
this.label = label;
this.executionInfo = ImmutableMap.copyOf(executionInfo);
this.requiresActionOutput = requiresActionOutput;
}
public Label getLabel() {
return label;
}
/** Adds an extra_action to the action graph based on the action to shadow. */
public Collection<Artifact.DerivedArtifact> addExtraAction(
RuleContext owningRule, Action actionToShadow) throws InterruptedException {
Collection<Artifact.DerivedArtifact> extraActionOutputs = new LinkedHashSet<>();
Collection<Artifact.DerivedArtifact> protoOutputs = new ArrayList<>();
NestedSetBuilder<Artifact> extraActionInputs = NestedSetBuilder.stableOrder();
Label ownerLabel = owningRule.getLabel();
if (requiresActionOutput) {
extraActionInputs.addAll(actionToShadow.getOutputs());
}
extraActionInputs.addTransitive(resolvedTools);
extraActionInputs.addAll(resolvedData);
boolean createDummyOutput = false;
for (String outputTemplate : outputTemplates) {
// We create output for the extra_action based on the 'out_template' attribute.
// See {link #getExtraActionOutputArtifact} for supported variables.
extraActionOutputs.add(getExtraActionOutputArtifact(
owningRule, actionToShadow, outputTemplate));
}
// extra_action has no output, we need to create some dummy output to keep the build up-to-date.
if (extraActionOutputs.isEmpty()) {
createDummyOutput = true;
extraActionOutputs.add(getExtraActionOutputArtifact(
owningRule, actionToShadow, "$(ACTION_ID).dummy"));
}
// We generate a file containing a protocol buffer describing the action that is being shadowed.
// It is up to each action being shadowed to decide what contents to store here.
Artifact.DerivedArtifact extraActionInfoFile =
getExtraActionOutputArtifact(owningRule, actionToShadow, "$(ACTION_ID).xa");
owningRule.registerAction(new ExtraActionInfoFileWriteAction(
actionToShadow.getOwner(), extraActionInfoFile, actionToShadow));
extraActionInputs.add(extraActionInfoFile);
protoOutputs.add(extraActionInfoFile);
// Expand extra_action specific variables from the provided command-line.
// See {@link #createExpandedCommand} for list of supported variables.
String command = createExpandedCommand(owningRule, actionToShadow, extraActionInfoFile);
CommandHelper commandHelper = CommandHelper.builder(owningRule).build();
// Multiple actions in the same configured target need to have different names for the artifact
// that might be created here, so we append something that should be unique for each action.
String actionUniquifier =
actionToShadow.getPrimaryOutput().getExecPath().getBaseName()
+ "."
+ actionToShadow.getKey(owningRule.getActionKeyContext(), /* artifactExpander= */ null);
PathFragment shExecutable =
ShToolchain.getPathForPlatform(
owningRule.getConfiguration(), owningRule.getExecutionPlatform());
BashCommandConstructor constructor =
CommandHelper.buildBashCommandConstructor(
executionInfo, shExecutable, "." + actionUniquifier + ".extra_action_script.sh");
ImmutableList<String> argv =
commandHelper.buildCommandLine(command, extraActionInputs, constructor);
String commandMessage = String.format("Executing extra_action %s on %s", label, ownerLabel);
owningRule.registerAction(
new ExtraAction(
owningRule.getActionOwner(),
extraActionInputs.build(),
extraActionOutputs,
actionToShadow,
createDummyOutput,
CommandLine.of(argv),
owningRule.getConfiguration().getActionEnvironment(),
owningRule.getConfiguration().modifiedExecutionInfo(executionInfo, label.getName()),
commandMessage,
label.getName()));
return ImmutableSet.<Artifact.DerivedArtifact>builder()
.addAll(extraActionOutputs)
.addAll(protoOutputs)
.build();
}
/**
* Expand extra_action specific variables: $(EXTRA_ACTION_FILE): expands to a path of the file
* containing a protocol buffer describing the action being shadowed. $(output <out_template>):
* expands the output template to the execPath of the file. e.g. $(output $(ACTION_ID).out) ->
* <build_path>/extra_actions/bar/baz/devtools/build/test_A41234.out
*/
private String createExpandedCommand(
RuleContext owningRule, Action action, Artifact extraActionInfoFile)
throws InterruptedException {
String realCommand = command.replace(
"$(EXTRA_ACTION_FILE)", extraActionInfoFile.getExecPathString());
for (String outputTemplate : outputTemplates) {
String outFile = getExtraActionOutputArtifact(owningRule, action, outputTemplate)
.getExecPathString();
realCommand = realCommand.replace("$(output " + outputTemplate + ")", outFile);
}
return realCommand;
}
/**
* Creates an output artifact for the extra_action based on the output_template. The path will be
* in the following form: <output
* dir>/<target-configuration-specific-path>/extra_actions/<extra_action_label>/ +
* <configured_target_label>/<expanded_template>
*
* <p>The template can use the following variables: $(ACTION_ID): a unique id for the
* extra_action.
*
* <p>Sample: extra_action: foo/bar:extra template: $(ACTION_ID).analysis target: foo/bar:main
* expands to: output/configuration/extra_actions/\
* foo/bar/extra/foo/bar/4683026f7ac1dd1a873ccc8c3d764132.analysis
*/
private Artifact.DerivedArtifact getExtraActionOutputArtifact(
RuleContext ruleContext, Action action, String template) throws InterruptedException {
String actionId =
getActionId(ruleContext.getActionKeyContext(), ruleContext.getActionOwner(), action);
template = template.replace("$(ACTION_ID)", actionId);
template = template.replace("$(OWNER_LABEL_DIGEST)", getOwnerDigest(ruleContext));
return getRootRelativePath(template, ruleContext);
}
private Artifact.DerivedArtifact getRootRelativePath(String template, RuleContext ruleContext) {
PathFragment extraActionPackageFragment = label.getPackageIdentifier().getSourceRoot();
PathFragment extraActionPrefix = extraActionPackageFragment.getRelative(label.getName());
PathFragment rootRelativePath = PathFragment.create("extra_actions")
.getRelative(extraActionPrefix)
.getRelative(ruleContext.getPackageDirectory())
.getRelative(template);
// We need to use getDerivedArtifact here because extra actions are at
// <EXTRA ACTION LABEL> / <RULE LABEL> instead of <RULE LABEL> / <EXTRA ACTION LABEL>. Bummer.
return ruleContext.getAnalysisEnvironment().getDerivedArtifact(rootRelativePath,
ruleContext.getConfiguration().getOutputDirectory(ruleContext.getRule().getRepository()));
}
/**
* Calculates a digest representing the rule context. We use the digest instead of the
* original value as the original value might lead to a filename that is too long.
* By using a digest, tools can deterministically find all extra_action outputs for a given
* target, without having to open every file in the package.
*/
private static String getOwnerDigest(RuleContext ruleContext) {
Fingerprint f = new Fingerprint();
f.addString(ruleContext.getLabel().toString());
return f.hexDigestAndReset();
}
/**
* Creates a unique id for the action shadowed by this extra_action.
*
* <p>We need to have a unique id for the extra_action to use. We build this from the owner's
* label and the shadowed action id (which is only guaranteed to be unique per target). Together
* with the subfolder matching the original target's package name, we believe this is enough of a
* uniqueness guarantee.
*/
@VisibleForTesting
public static String getActionId(
ActionKeyContext actionKeyContext, ActionOwner owner, Action action)
throws InterruptedException {
Fingerprint f = new Fingerprint();
f.addString(owner.getLabel().toString());
ImmutableList<AspectDescriptor> aspectDescriptors = owner.getAspectDescriptors();
f.addInt(aspectDescriptors.size());
for (AspectDescriptor aspectDescriptor : aspectDescriptors) {
f.addString(aspectDescriptor.getDescription());
}
f.addString(action.getKey(actionKeyContext, /*artifactExpander=*/ null));
return f.hexDigestAndReset();
}
}