RELNOTES[NEW]: Add --modify_execution_info, a flag to customize action execution info.
Execution info is a map of string key-value pairs used by certain actions. The info forms part of the cache key and is available to the execution platform. Action types which support execution info include tests, genrules, skylark actions, c++ build/link actions, and java compiler actions.
The flag contains some simple syntax and filtering: each modifier takes two arguments, a regular expression and a string value with a required "+" or "-" prefix. The regular expression specifies which actions to apply to based on mnemonic. The "-" prefix indicates that the string value should be removed from the execution info map; the "+" means it should be added or replaced.
The execution info items added by this flag all have empty string values.
The flag applies with tests and genrules which currently specify execution info using "requires-* tags. For example, if a rule has tags=["requires-x"], "--modify_execution_info.*=-requires-x" will remove it, and "--modify_execution_info.*=+requires-y" will add to it.
PiperOrigin-RevId: 211013061
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java
index 7a6d05f..a818dec 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java
@@ -667,7 +667,8 @@
? configuration.getActionEnvironment()
: ActionEnvironment.create(environment, inheritedEnvironment);
Action spawnAction =
- buildSpawnAction(owner, commandLines, configuration.getCommandLineLimits(), env);
+ buildSpawnAction(
+ owner, commandLines, configuration.getCommandLineLimits(), configuration, env);
actions[0] = spawnAction;
return actions;
}
@@ -684,6 +685,7 @@
owner,
result.build(),
CommandLineLimits.UNLIMITED,
+ null,
ActionEnvironment.create(environment, inheritedEnvironment));
}
@@ -772,6 +774,7 @@
ActionOwner owner,
CommandLines commandLines,
CommandLineLimits commandLineLimits,
+ @Nullable BuildConfiguration configuration,
ActionEnvironment env) {
NestedSet<Artifact> tools = toolsBuilder.build();
@@ -800,7 +803,9 @@
commandLineLimits,
isShellCommand,
env,
- ImmutableMap.copyOf(executionInfo),
+ configuration == null
+ ? executionInfo
+ : configuration.modifiedExecutionInfo(executionInfo, mnemonic),
progressMessage,
new CompositeRunfilesSupplier(
Iterables.concat(this.inputRunfilesSuppliers, this.toolRunfilesSuppliers)),
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
index 5a92a1b..6c78cd7 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
@@ -903,6 +903,32 @@
)
public boolean windowsExeLauncher;
+ @Option(
+ name = "modify_execution_info",
+ converter = ExecutionInfoModifier.Converter.class,
+ documentationCategory = OptionDocumentationCategory.EXECUTION_STRATEGY,
+ effectTags = {
+ OptionEffectTag.EXECUTION,
+ OptionEffectTag.AFFECTS_OUTPUTS,
+ OptionEffectTag.LOADING_AND_ANALYSIS,
+ },
+ defaultValue = "null",
+ help =
+ "Add or remove keys from an action's execution info based on action mnemonic. "
+ + "Applies only to actions which support execution info. Many common actions "
+ + "support execution info, e.g. Genrule, CppCompile, Javac, SkylarkAction, "
+ + "TestRunner. When specifying multiple values, order matters because "
+ + "many regexes may apply to the same mnemonic.\n\n"
+ + "Syntax: \"regex=[+-]key,[+-]key,...\".\n\n"
+ + "Examples:\n"
+ + " '.*=+x,.*=-y,.*=+z' adds 'x' and 'z' to, and removes 'y' from, "
+ + "the execution info for all actions.\n"
+ + " 'Genrule=+requires-x' adds 'requires-x' to the execution info for "
+ + "all Genrule actions.\n"
+ + " '(?!Genrule).*=-requires-x' removes 'requires-x' from the execution info for "
+ + "all non-Genrule actions.\n")
+ public ExecutionInfoModifier executionInfoModifier;
+
@Override
public FragmentOptions getHost() {
Options host = (Options) getDefault();
@@ -913,6 +939,7 @@
host.configsMode = configsMode;
host.enableRunfiles = enableRunfiles;
host.windowsExeLauncher = windowsExeLauncher;
+ host.executionInfoModifier = executionInfoModifier;
host.commandLineBuildVariables = commandLineBuildVariables;
host.enforceConstraints = enforceConstraints;
host.separateGenfilesDirectory = separateGenfilesDirectory;
@@ -1816,8 +1843,27 @@
}
/**
- * @return the list of default features used for all packages.
+ * Returns a modified copy of {@code executionInfo} if any {@code executionInfoModifiers} apply to
+ * the given {@code mnemonic}. Otherwise returns {@code executionInfo} unchanged.
*/
+ public ImmutableMap<String, String> modifiedExecutionInfo(
+ ImmutableMap<String, String> executionInfo, String mnemonic) {
+ if (options.executionInfoModifier == null || !options.executionInfoModifier.matches(mnemonic)) {
+ return executionInfo;
+ }
+ HashMap<String, String> mutableCopy = new HashMap<>(executionInfo);
+ modifyExecutionInfo(mutableCopy, mnemonic);
+ return ImmutableMap.copyOf(mutableCopy);
+ }
+
+ /** Applies {@code executionInfoModifiers} to the given {@code executionInfo}. */
+ public void modifyExecutionInfo(Map<String, String> executionInfo, String mnemonic) {
+ if (options.executionInfoModifier != null) {
+ options.executionInfoModifier.apply(mnemonic, executionInfo);
+ }
+ }
+
+ /** @return the list of default features used for all packages. */
public List<String> getDefaultFeatures() {
return options.defaultFeatures;
}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/ExecutionInfoModifier.java b/src/main/java/com/google/devtools/build/lib/analysis/config/ExecutionInfoModifier.java
new file mode 100644
index 0000000..1a71fc8
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/ExecutionInfoModifier.java
@@ -0,0 +1,107 @@
+// Copyright 2018 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.config;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.common.options.Converters.RegexPatternConverter;
+import com.google.devtools.common.options.OptionsParsingException;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Represents a list of regexes over mnemonics and changes to add or remove keys, parsed from a
+ * {@code --modify_execution_info} option.
+ */
+public final class ExecutionInfoModifier {
+
+ private static final Pattern MODIFIER_PATTERN =
+ Pattern.compile("^(?<pattern>.+)=(?<sign>[+-])(?<key>.+)$");
+
+ private final ImmutableList<Expression> expressions;
+ private final String option;
+
+ @AutoValue
+ abstract static class Expression {
+ abstract Pattern pattern();
+
+ abstract boolean remove();
+
+ abstract String key();
+ }
+
+ private ExecutionInfoModifier(String option, ImmutableList<Expression> expressions) {
+ this.expressions = expressions;
+ this.option = option;
+ }
+
+ /** Constructs an instance of ExecutionInfoModifier by parsing an option string. */
+ public static class Converter
+ implements com.google.devtools.common.options.Converter<ExecutionInfoModifier> {
+ @Override
+ public ExecutionInfoModifier convert(String input) throws OptionsParsingException {
+
+ String[] expressionSpecs = input.split(",");
+ if (expressionSpecs.length == 0) {
+ throw new OptionsParsingException("expected 'regex=[+-]key,regex=[+-]key,...'", input);
+ }
+ ImmutableList.Builder<Expression> expressionBuilder = ImmutableList.builder();
+ for (String spec : expressionSpecs) {
+ Matcher specMatcher = MODIFIER_PATTERN.matcher(spec);
+ if (!specMatcher.matches()) {
+ throw new OptionsParsingException(
+ String.format("malformed expression '%s'", spec), input);
+ }
+ expressionBuilder.add(
+ new AutoValue_ExecutionInfoModifier_Expression(
+ new RegexPatternConverter().convert(specMatcher.group("pattern")),
+ specMatcher.group("sign").equals("-"),
+ specMatcher.group("key")));
+ }
+ return new ExecutionInfoModifier(input, expressionBuilder.build());
+ }
+
+ @Override
+ public String getTypeDescription() {
+ return "regex=[+-]key,regex=[+-]key,...";
+ }
+ }
+
+ /**
+ * Determines whether the given {@code mnemonic} (e.g. "CppCompile") matches any of the patterns.
+ */
+ public boolean matches(String mnemonic) {
+ return expressions.stream().anyMatch(expr -> expr.pattern().matcher(mnemonic).matches());
+ }
+
+ /** Modifies the given map of {@code executionInfo} to add or remove the keys for this option. */
+ void apply(String mnemonic, Map<String, String> executionInfo) {
+ for (Expression expr : expressions) {
+ if (expr.pattern().matcher(mnemonic).matches()) {
+ if (expr.remove()) {
+ executionInfo.remove(expr.key());
+ } else {
+ executionInfo.put(expr.key(), "");
+ }
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return option;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/extra/ExtraActionSpec.java b/src/main/java/com/google/devtools/build/lib/analysis/extra/ExtraActionSpec.java
index 9d32753..9e4cdd2 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/extra/ExtraActionSpec.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/extra/ExtraActionSpec.java
@@ -148,7 +148,7 @@
createDummyOutput,
CommandLine.of(argv),
owningRule.getConfiguration().getActionEnvironment(),
- executionInfo,
+ owningRule.getConfiguration().modifiedExecutionInfo(executionInfo, label.getName()),
commandMessage,
label.getName()));
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/TestRunnerAction.java b/src/main/java/com/google/devtools/build/lib/analysis/test/TestRunnerAction.java
index 02c2b16..6c7c66d 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/test/TestRunnerAction.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/TestRunnerAction.java
@@ -32,6 +32,7 @@
import com.google.devtools.build.lib.actions.ArtifactPathResolver;
import com.google.devtools.build.lib.actions.CommandLineExpansionException;
import com.google.devtools.build.lib.actions.ExecException;
+import com.google.devtools.build.lib.actions.ExecutionInfoSpecifier;
import com.google.devtools.build.lib.actions.NotifyOnActionCacheHit;
import com.google.devtools.build.lib.analysis.RunfilesSupplierImpl;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
@@ -64,13 +65,15 @@
* and test status artifacts.
*/
// Not final so that we can mock it in tests.
-public class TestRunnerAction extends AbstractAction implements NotifyOnActionCacheHit {
+public class TestRunnerAction extends AbstractAction
+ implements NotifyOnActionCacheHit, ExecutionInfoSpecifier {
public static final PathFragment COVERAGE_TMP_ROOT = PathFragment.create("_coverage");
// Used for selecting subset of testcase / testmethods.
private static final String TEST_BRIDGE_TEST_FILTER_ENV = "TESTBRIDGE_TEST_ONLY";
private static final String GUID = "cc41f9d0-47a6-11e7-8726-eb6ce83a8cc8";
+ public static final String MNEMONIC = "TestRunner";
private final Artifact testSetupScript;
private final Artifact testXmlGeneratorScript;
@@ -691,6 +694,11 @@
return testProperties;
}
+ @Override
+ public Map<String, String> getExecutionInfo() {
+ return testProperties.getExecutionInfo();
+ }
+
public TestTargetExecutionSettings getExecutionSettings() {
return executionSettings;
}
@@ -737,7 +745,7 @@
@Override
public String getMnemonic() {
- return "TestRunner";
+ return MNEMONIC;
}
@Override
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/TestTargetProperties.java b/src/main/java/com/google/devtools/build/lib/analysis/test/TestTargetProperties.java
index ef04a2e..324c2b67 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/test/TestTargetProperties.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/TestTargetProperties.java
@@ -96,6 +96,7 @@
// This will overwrite whatever TargetUtils put there, which might be confusing.
executionInfo.putAll(executionRequirements.getExecutionInfo());
}
+ ruleContext.getConfiguration().modifyExecutionInfo(executionInfo, TestRunnerAction.MNEMONIC);
this.executionInfo = ImmutableMap.copyOf(executionInfo);
isLocal = executionInfo.containsKey(ExecutionRequirements.LOCAL);
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java
index 6387161..69a8890 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java
@@ -1218,8 +1218,7 @@
ImmutableSet.<Artifact>copyOf(getInputs()));
}
- @Override
- public String getMnemonic() {
+ static String actionNameToMnemonic(String actionName) {
switch (actionName) {
case CppActionNames.OBJC_COMPILE:
case CppActionNames.OBJCPP_COMPILE:
@@ -1241,6 +1240,11 @@
}
@Override
+ public String getMnemonic() {
+ return actionNameToMnemonic(actionName);
+ }
+
+ @Override
public String describeKey() {
StringBuilder message = new StringBuilder();
message.append(getProgressMessage());
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionBuilder.java
index 4a1f9fb..000eca8 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionBuilder.java
@@ -289,6 +289,9 @@
.addAll(additionalPrunableHeaders)
.build();
+ configuration.modifyExecutionInfo(
+ executionInfo, CppCompileAction.actionNameToMnemonic(getActionName()));
+
// Copying the collections is needed to make the builder reusable.
CppCompileAction action;
boolean fake = tempOutputFile != null;
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkAction.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkAction.java
index 53801f3..d0e6b74 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkAction.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkAction.java
@@ -112,7 +112,7 @@
private final Artifact linkOutput;
private final LibraryToLink interfaceOutputLibrary;
private final ImmutableMap<String, String> toolchainEnv;
- private final ImmutableSet<String> executionRequirements;
+ private final ImmutableMap<String, String> executionRequirements;
private final ImmutableList<Artifact> linkstampObjects;
private final LinkCommandLine linkCommandLine;
@@ -168,16 +168,12 @@
LinkCommandLine linkCommandLine,
ActionEnvironment env,
ImmutableMap<String, String> toolchainEnv,
- ImmutableSet<String> executionRequirements,
+ ImmutableMap<String, String> executionRequirements,
PathFragment ldExecutable,
String hostSystemName,
String targetCpu) {
super(owner, inputs, outputs, env);
- if (mnemonic == null) {
- this.mnemonic = (isLtoIndexing) ? "CppLTOIndexing" : "CppLink";
- } else {
- this.mnemonic = mnemonic;
- }
+ this.mnemonic = getMnemonic(mnemonic, isLtoIndexing);
this.mandatoryInputs = inputs;
this.outputLibrary = outputLibrary;
this.linkOutput = linkOutput;
@@ -221,7 +217,7 @@
result.putAll(toolchainEnv);
- if (!executionRequirements.contains(ExecutionRequirements.REQUIRES_DARWIN)) {
+ if (!executionRequirements.containsKey(ExecutionRequirements.REQUIRES_DARWIN)) {
// This prevents gcc from writing the unpredictable (and often irrelevant)
// value of getcwd() into the debug info.
result.put("PWD", "/proc/self/cwd");
@@ -266,11 +262,7 @@
@Override
public ImmutableMap<String, String> getExecutionInfo() {
- ImmutableMap.Builder<String, String> result = ImmutableMap.<String, String>builder();
- for (String requirement : executionRequirements) {
- result.put(requirement, "");
- }
- return result.build();
+ return executionRequirements;
}
@Override
@@ -487,6 +479,13 @@
return mnemonic;
}
+ static String getMnemonic(String mnemonic, boolean isLtoIndexing) {
+ if (mnemonic == null) {
+ return isLtoIndexing ? "CppLTOIndexing" : "CppLink";
+ }
+ return mnemonic;
+ }
+
@Override
protected String getRawProgressMessage() {
return (isLtoIndexing ? "LTO indexing " : "Linking ") + linkOutput.prettyPrint();
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionBuilder.java
index 67a937a..7f99f92 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionBuilder.java
@@ -1077,11 +1077,15 @@
// If the crosstool uses action_configs to configure cc compilation, collect execution info
// from there, otherwise, use no execution info.
// TODO(b/27903698): Assert that the crosstool has an action_config for this action.
- ImmutableSet.Builder<String> executionRequirements = ImmutableSet.builder();
+ Map<String, String> executionRequirements = new HashMap<>();
+
if (featureConfiguration.actionIsConfigured(getActionName())) {
- executionRequirements.addAll(
- featureConfiguration.getToolRequirementsForAction(getActionName()));
+ for (String req : featureConfiguration.getToolRequirementsForAction(getActionName())) {
+ executionRequirements.put(req, "");
+ }
}
+ configuration.modifyExecutionInfo(
+ executionRequirements, CppLinkAction.getMnemonic(mnemonic, isLtoIndexing));
if (!isLtoIndexing) {
for (Map.Entry<Linkstamp, Artifact> linkstampEntry : linkstampMap.entrySet()) {
@@ -1133,15 +1137,13 @@
fake,
fakeLinkerInputArtifacts,
isLtoIndexing,
- linkstampMap
- .keySet()
- .stream()
+ linkstampMap.keySet().stream()
.map(Linkstamp::getArtifact)
.collect(ImmutableList.toImmutableList()),
linkCommandLine,
configuration.getActionEnvironment(),
toolchainEnv,
- executionRequirements.build(),
+ ImmutableMap.copyOf(executionRequirements),
toolchain.getToolPathFragment(Tool.LD),
toolchain.getHostSystemName(),
toolchain.getTargetCpu());
diff --git a/src/main/java/com/google/devtools/build/lib/rules/genrule/GenRuleAction.java b/src/main/java/com/google/devtools/build/lib/rules/genrule/GenRuleAction.java
index e78072a..f412ad7 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/genrule/GenRuleAction.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/genrule/GenRuleAction.java
@@ -40,6 +40,7 @@
@AutoCodec
public class GenRuleAction extends SpawnAction {
+ public static final String MNEMONIC = "Genrule";
private static final ResourceSet GENRULE_RESOURCES =
// Not chosen scientifically/carefully. 300MB memory, 100% CPU, no I/O.
ResourceSet.createWithRamCpuIo(300, 1.0, 0.0);
@@ -68,7 +69,7 @@
executionInfo,
progressMessage,
runfilesSupplier,
- "Genrule",
+ MNEMONIC,
false,
null);
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/genrule/GenRuleBase.java b/src/main/java/com/google/devtools/build/lib/rules/genrule/GenRuleBase.java
index 0966ce5..3b61a21 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/genrule/GenRuleBase.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/genrule/GenRuleBase.java
@@ -191,6 +191,7 @@
}
executionInfo.putAll(getExtraExecutionInfo(ruleContext, baseCommand));
+ ruleContext.getConfiguration().modifyExecutionInfo(executionInfo, GenRuleAction.MNEMONIC);
NestedSetBuilder<Artifact> inputs = NestedSetBuilder.stableOrder();
inputs.addTransitive(resolvedSrcs);
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java
index 11ba92c..c3e4e7f 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java
@@ -243,10 +243,12 @@
}
private ImmutableMap<String, String> getExecutionInfo() {
- if (javaToolchain.getJavacSupportsWorkers()) {
- return ExecutionRequirements.WORKER_MODE_ENABLED;
- }
- return ImmutableMap.of();
+ return getConfiguration()
+ .modifiedExecutionInfo(
+ javaToolchain.getJavacSupportsWorkers()
+ ? ExecutionRequirements.WORKER_MODE_ENABLED
+ : ImmutableMap.of(),
+ JavaCompileAction.MNEMONIC);
}
/** Returns the bootclasspath explicit set in attributes if present, or else the default. */
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileAction.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileAction.java
index a7158d3..4692485 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileAction.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileAction.java
@@ -75,6 +75,7 @@
@Immutable
@AutoCodec
public final class JavaCompileAction extends SpawnAction {
+
private static final String JACOCO_INSTRUMENTATION_PROCESSOR = "jacoco";
private static final ResourceSet LOCAL_RESOURCES =
@@ -87,6 +88,7 @@
// TODO(#3320): This is missing the configuration's action environment!
static final ActionEnvironment UTF8_ACTION_ENVIRONMENT =
ActionEnvironment.create(UTF8_ENVIRONMENT);
+ public static final String MNEMONIC = "Javac";
private final CommandLine javaCompileCommandLine;
@@ -215,7 +217,7 @@
ImmutableMap.copyOf(executionInfo),
progressMessage,
runfilesSupplier,
- "Javac",
+ MNEMONIC,
/* executeUnconditionally= */ false,
/* extraActionInfoSupplier= */ null);
this.javaCompileCommandLine = javaCompileCommandLine;
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/config/ExecutionInfoModifierTest.java b/src/test/java/com/google/devtools/build/lib/analysis/config/ExecutionInfoModifierTest.java
new file mode 100644
index 0000000..4b42fa0
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/config/ExecutionInfoModifierTest.java
@@ -0,0 +1,91 @@
+// Copyright 2018 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.config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.analysis.config.ExecutionInfoModifier.Converter;
+import com.google.devtools.common.options.OptionsParsingException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests {@link ExecutionInfoModifier}. */
+@RunWith(JUnit4.class)
+public class ExecutionInfoModifierTest {
+
+ private final ExecutionInfoModifier.Converter converter = new Converter();
+
+ @Test
+ public void executionInfoModifier_singleAdd() throws Exception {
+ ExecutionInfoModifier modifier = converter.convert("Genrule=+x");
+ assertThat(modifier.matches("SomethingElse")).isFalse();
+ assertModifierMatchesAndResults(modifier, "Genrule", ImmutableSet.of("x"));
+ }
+
+ @Test
+ public void executionInfoModifier_singleRemove() throws Exception {
+ ExecutionInfoModifier modifier = converter.convert("Genrule=-x");
+ Map<String, String> info = new HashMap<>();
+ info.put("x", "");
+
+ modifier.apply("Genrule", info);
+
+ assertThat(info).isEmpty();
+ }
+
+ @Test
+ public void executionInfoModifier_multipleExpressions() throws Exception {
+ ExecutionInfoModifier modifier = converter.convert("Genrule=+x,.*=+y,CppCompile=+z");
+ assertModifierMatchesAndResults(modifier, "Genrule", ImmutableSet.of("x", "y"));
+ assertModifierMatchesAndResults(modifier, "CppCompile", ImmutableSet.of("y", "z"));
+ assertModifierMatchesAndResults(modifier, "GenericAction", ImmutableSet.of("y"));
+ }
+
+ @Test
+ public void executionInfoModifier_invalidFormat_throws() throws Exception {
+ List<String> invalidModifiers =
+ ImmutableList.of("", "A", "=", "A=", "A=+", "=+", "A=-B,A", "A=B", "A", ",");
+ for (String invalidModifer : invalidModifiers) {
+ assertThrows(OptionsParsingException.class, () -> converter.convert(invalidModifer));
+ }
+ }
+
+ @Test
+ public void executionInfoModifier_invalidFormat_exceptionShowsOffender() throws Exception {
+ OptionsParsingException thrown =
+ assertThrows(OptionsParsingException.class, () -> converter.convert("A=+1,B=2,C=-3"));
+ assertThat(thrown).hasMessageThat().contains("malformed");
+ assertThat(thrown).hasMessageThat().contains("'B=2'");
+ }
+
+ private void assertModifierMatchesAndResults(
+ ExecutionInfoModifier modifier, String mnemonic, Set<String> expectedKeys) {
+ Map<String, String> copy = new HashMap<>();
+ modifier.apply(mnemonic, copy);
+ assertThat(modifier.matches(mnemonic)).isTrue();
+ assertThat(copy)
+ .containsExactlyEntriesIn(
+ expectedKeys.stream().collect(ImmutableMap.toImmutableMap(k -> k, unused -> "")));
+ }
+}
diff --git a/src/test/shell/integration/modify_execution_info_test.sh b/src/test/shell/integration/modify_execution_info_test.sh
new file mode 100755
index 0000000..e885365
--- /dev/null
+++ b/src/test/shell/integration/modify_execution_info_test.sh
@@ -0,0 +1,197 @@
+#!/bin/bash
+#
+# Copyright 2018 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.
+#
+# modify_execution_info_test.sh: tests of the --modify_execution_info flag.
+
+# Load the test setup defined in the parent directory
+CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+source "${CURRENT_DIR}/../integration_test_setup.sh" \
+ || { echo "integration_test_setup.sh not found!" >&2; exit 1; }
+
+#### HELPER FUNCTIONS ##################################################
+
+function set_up() {
+ cd ${WORKSPACE_DIR}
+}
+
+function tear_down() {
+ bazel shutdown
+}
+
+#### TESTS #############################################################
+
+function test_aquery_respects_modify_execution_info_changes {
+ local pkg="${FUNCNAME[0]}"
+ mkdir -p "$pkg" || fail "mkdir -p $pkg"
+ cat > "$pkg/BUILD" <<'EOF'
+genrule(name = "bar", outs = ["bar_out.txt"], cmd = "touch $(OUTS)")
+EOF
+ bazel aquery --output=text "//$pkg:bar" \
+ --modify_execution_info=Genrule=+requires-x \
+ > output1 2> "$TEST_log" || fail "Expected success"
+ assert_contains "ExecutionInfo: {requires-x: ''}" output1
+
+ bazel aquery --output=text "//$pkg:bar" \
+ --modify_execution_info=Genrule=+requires-y \
+ > output2 2> "$TEST_log" || fail "Expected success"
+ assert_contains "ExecutionInfo: {requires-y: ''}" output2
+}
+
+function test_modify_execution_info_multiple {
+ local pkg="${FUNCNAME[0]}"
+ mkdir -p "$pkg" || fail "mkdir -p $pkg"
+ cat > "$pkg/BUILD" <<'EOF'
+genrule(
+ name = "bar",
+ outs = ["bar_out.txt"],
+ cmd = "touch $(OUTS)",
+ tags = ["requires-x"],
+)
+cc_binary(name="zero", srcs=["zero.cc"])
+EOF
+ echo "int main(void) {}" > "$pkg/zero.cc"
+
+ # multiple elements in the value list that match the same mnemonic.
+ bazel aquery --output=text "//$pkg:bar" \
+ --modify_execution_info=Genrule=+requires-y,Genrule=+requires-z \
+ > output 2> "$TEST_log" || fail "Expected success"
+ assert_contains "ExecutionInfo: {requires-x: '', requires-y: '', "\
+"requires-z: ''}" output
+
+ # multiple elements in the value list, the first of which adds an
+ # ExecutionInfo and the second of which removes it.
+ bazel aquery --output=text "//$pkg:bar" \
+ --modify_execution_info=Genrule=+requires-z,.*=-requires-z \
+ > output 2> "$TEST_log" || fail "Expected success"
+ assert_contains "ExecutionInfo: {requires-x: ''}" output
+
+ # multiple elements in the value list, the first of which removes an
+ # ExecutionInfo (previously absent) and the second of which adds it.
+ bazel aquery --output=text "//$pkg:bar" \
+ --modify_execution_info=Genrule=-requires-z,.*=+requires-z \
+ > output 2> "$TEST_log" || fail "Expected success"
+ assert_contains "ExecutionInfo: {requires-x: '', requires-z: ''}" output
+
+ # multiple elements in the value list, the first of which removes an
+ # ExecutionInfo (previously present) and the second of which adds it back.
+ bazel aquery --output=text "//$pkg:bar" \
+ --modify_execution_info=Genrule=-requires-x,.*=+requires-x \
+ > output 2> "$TEST_log" || fail "Expected success"
+ assert_contains "ExecutionInfo: {requires-x: ''}" output
+
+ # multiple elements with multiple values
+ bazel aquery --output=text "//$pkg:all" \
+ --modify_execution_info=Genrule=-requires-x,Genrule=+requires-z,\
+Genrule=+requires-a,CppCompile=+requires-b,CppCompile=+requires-c \
+ > output 2> "$TEST_log" || fail "Expected success"
+ assert_contains "ExecutionInfo: {requires-a: '', requires-z: ''}" output
+ assert_contains "ExecutionInfo: {requires-b: '', requires-c: ''}" output
+
+ # negative lookahead
+ bazel aquery --output=text "//$pkg:all" \
+ --modify_execution_info='(?!Genrule).*=+requires-a,(?!CppCompile).*=+requires-z' \
+ > output 2> "$TEST_log" || fail "Expected success"
+ assert_contains "ExecutionInfo: {requires-x: '', requires-z: ''}" output
+ assert_contains "ExecutionInfo: {requires-a: ''}" output
+}
+
+function test_modify_execution_info_various_types() {
+ local pkg="${FUNCNAME[0]}"
+ mkdir -p "$pkg" || fail "mkdir -p $pkg"
+ echo "load('//$pkg:shell.bzl', 'skylark_shell')" > "$pkg/BUILD"
+ cat >> "$pkg/BUILD" <<'EOF'
+skylark_shell(
+ name = "shelly",
+ output = "ok.txt",
+)
+
+cc_binary(name="zero", srcs=["zero.cc"])
+
+sh_test(name="test_a", srcs=["a.sh"])
+
+java_library(name = "javalib", srcs = ["HelloWorld.java"])
+
+action_listener(
+ name = "al",
+ extra_actions = [":echo-filename"],
+ mnemonics = ["Javac"]
+)
+
+extra_action(name = "echo-filename", cmd = "echo Hi \$(EXTRA_ACTION_FILE)")
+
+py_binary(name = "pybar", srcs=["pybar.py"],)
+
+proto_library(name = "proto", srcs=["foo.proto"])
+EOF
+ cat > "$pkg/shell.bzl" <<'EOF'
+def _impl(ctx):
+ ctx.actions.run_shell(
+ outputs = [ ctx.outputs.output ],
+ command = "touch %s" % ctx.outputs.output.path,
+ )
+
+skylark_shell = rule(
+ _impl,
+ attrs = {
+ "output": attr.output(mandatory=True),
+ }
+)
+EOF
+ cat > "$pkg/a.sh" <<'EOF'
+#!/bin/sh
+exit 0
+EOF
+ chmod 755 "$pkg/a.sh"
+ echo "int main(void) {}" > "$pkg/zero.cc"
+ echo "public class HelloWorld {}" > "$pkg/HelloWorld.java"
+ echo 'print("Hi")' > "$pkg/pybar.py"
+ echo 'syntax="proto2"; package foo;' > "$pkg/foo.proto"
+
+ bazel aquery --output=text "//$pkg:all" \
+ --experimental_action_listener=$pkg:al \
+ --modify_execution_info=\
+echo.*=+requires-extra-action,\
+.*Proto.*=+requires-proto,\
+CppCompile=+requires-cpp-compile,\
+CppLink=+requires-cpp-link,\
+TestRunner=+requires-test-runner,\
+Turbine=+requires-turbine,\
+JavaSourceJar=+requires-java-source-jar,\
+Javac=+requires-javac,\
+PyTinypar=+requires-py-tinypar,\
+SkylarkAction=+requires-skylark-action \
+ > output 2> "$TEST_log" || fail "Expected success"
+
+ # There are sometimes other elements in ExecutionInfo, e.g. requires-darwin
+ # for obj-c, supports-workers for java. Since testing for these combinations
+ # would be brittle, irrelevant to the operation of the flag, and in some
+ # cases platform-dependent, we just search for the key itself, not the whole
+ # ExecutionInfo: {...} line.
+ assert_contains "requires-skylark-action: ''" output
+ assert_contains "requires-cpp-compile: ''" output
+ assert_contains "requires-cpp-link: ''" output
+ assert_contains "requires-extra-action: ''" output
+ assert_contains "requires-test-runner: ''" output
+ assert_contains "requires-javac: ''" output
+ assert_contains "requires-turbine: ''" output
+ assert_contains "requires-java-source-jar: ''" output
+ assert_contains "requires-proto: ''" output # GenProtoDescriptorSet should match
+ # Python rules generate some cpp actions and local actions, but py-tinypar
+ # is the main unique-to-python rule which runs remotely for a py_binary.
+ assert_contains "requires-py-tinypar: ''" output
+}
+
+run_suite "Integration tests of the --modify_execution_info option."