blob: d22c6596038c841f52929bbbc073035dbf2d9422 [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.actions.RunfilesSupplier;
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.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 PathFragment shExecutable;
private final NestedSet<Artifact> resolvedTools;
private final RunfilesSupplier runfilesSupplier;
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(
PathFragment shExecutable,
NestedSet<Artifact> resolvedTools,
RunfilesSupplier runfilesSupplier,
List<Artifact> resolvedData,
List<String> outputTemplates,
String command,
Label label,
Map<String, String> executionInfo,
boolean requiresActionOutput) {
this.shExecutable = shExecutable;
this.resolvedTools = resolvedTools;
this.runfilesSupplier = runfilesSupplier;
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) {
Collection<Artifact.DerivedArtifact> extraActionOutputs = new LinkedHashSet<>();
Collection<Artifact.DerivedArtifact> protoOutputs = new ArrayList<>();
NestedSetBuilder<Artifact> extraActionInputs = NestedSetBuilder.stableOrder();
Label ownerLabel = owningRule.getLabel();
if (requiresActionOutput || actionToShadow.discoversInputs()) {
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);
BashCommandConstructor constructor =
CommandHelper.buildBashCommandConstructor(
executionInfo, shExecutable, "." + actionUniquifier + ".extra_action_script.sh");
List<String> argv = commandHelper.buildCommandLine(command, extraActionInputs, constructor);
String commandMessage = String.format("Executing extra_action %s on %s", label, ownerLabel);
owningRule.registerAction(
new ExtraAction(
extraActionInputs.build(),
runfilesSupplier,
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) {
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) {
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) {
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();
}
}