// 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.rules.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.ActionOwner; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.RunfilesSupplier; import com.google.devtools.build.lib.analysis.CommandHelper; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; import com.google.devtools.build.lib.analysis.TransitiveInfoProvider; import com.google.devtools.build.lib.analysis.actions.CommandLine; import com.google.devtools.build.lib.cmdline.Label; 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; import java.util.Set; /** * The specification for a particular extra action type. */ @Immutable public final class ExtraActionSpec implements TransitiveInfoProvider { private final ImmutableList<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; ExtraActionSpec( Iterable<Artifact> resolvedTools, RunfilesSupplier runfilesSupplier, Iterable<Artifact> resolvedData, Iterable<String> outputTemplates, String command, Label label, Map<String, String> executionInfo, boolean requiresActionOutput) { this.resolvedTools = ImmutableList.copyOf(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> addExtraAction(RuleContext owningRule, Action actionToShadow) { Collection<Artifact> extraActionOutputs = new LinkedHashSet<>(); Collection<Artifact> protoOutputs = new ArrayList<>(); NestedSetBuilder<Artifact> extraActionInputs = NestedSetBuilder.stableOrder(); Label ownerLabel = owningRule.getLabel(); if (requiresActionOutput || actionToShadow.discoversInputs()) { extraActionInputs.addAll(actionToShadow.getOutputs()); } extraActionInputs.addAll(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 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); Map<String, String> env = owningRule.getConfiguration().getLocalShellEnvironment(); Set<String> clientEnvVars = owningRule.getConfiguration().getVariableShellEnvironment(); CommandHelper commandHelper = new CommandHelper( owningRule, ImmutableList.<TransitiveInfoCollection>of(), ImmutableMap.<Label, Iterable<Artifact>>of()); // 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(); List<String> argv = commandHelper.buildCommandLine(command, extraActionInputs, "." + actionUniquifier + ".extra_action_script.sh", executionInfo); String commandMessage = String.format("Executing extra_action %s on %s", label, ownerLabel); owningRule.registerAction( new ExtraAction( ImmutableSet.copyOf(extraActionInputs.build()), runfilesSupplier, extraActionOutputs, actionToShadow, createDummyOutput, CommandLine.of(argv), env, clientEnvVars, executionInfo, commandMessage, label.getName())); return ImmutableSet.<Artifact>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> * * The template can use the following variables: * $(ACTION_ID): a unique id for the extra_action. * * 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 getExtraActionOutputArtifact( RuleContext ruleContext, Action action, String template) { String actionId = getActionId(ruleContext.getActionOwner(), action); template = template.replace("$(ACTION_ID)", actionId); template = template.replace("$(OWNER_LABEL_DIGEST)", getOwnerDigest(ruleContext)); return getRootRelativePath(template, ruleContext); } private Artifact 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. * * 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(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()); return f.hexDigestAndReset(); } }