From c8d9be53a4e50eb7b0e3363f4fd66dbc337cf01d Mon Sep 17 00:00:00 2001 From: leba Date: Tue, 8 Jan 2019 06:57:00 -0800 Subject: [PATCH] [#7024] Allow chaining of the same function type in aquery. For example: inputs('a', inputs('b', expr)) would now filter all actions which results from evaluating expr and whose inputs matches both patterns 'a' and 'b'. RELNOTES: [#7024] Allow chaining of the same function type in aquery. PiperOrigin-RevId: 228322146 --- .../java/com/google/devtools/build/lib/BUILD | 1 + .../build/lib/buildtool/AqueryBuildTool.java | 11 +- ...tionGraphProtoOutputFormatterCallback.java | 6 +- .../query2/ActionGraphQueryEnvironment.java | 6 +- ...ctionGraphTextOutputFormatterCallback.java | 6 +- .../build/lib/query2/AqueryActionFilter.java | 80 ++++++++ .../build/lib/query2/AqueryUtils.java | 24 +-- .../google/devtools/build/lib/query2/BUILD | 2 + .../lib/query2/engine/MnemonicFunction.java | 2 +- .../skyframe/actiongraph/ActionGraphDump.java | 15 +- ...GraphProtoOutputFormatterCallbackTest.java | 176 ++++++++++++++---- .../google/devtools/build/lib/query2/BUILD | 16 +- src/test/shell/integration/aquery_test.sh | 104 +++++++++++ 13 files changed, 365 insertions(+), 84 deletions(-) create mode 100644 src/main/java/com/google/devtools/build/lib/query2/AqueryActionFilter.java diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD index 7658716768d4fb..72ceb6e411f4b3 100644 --- a/src/main/java/com/google/devtools/build/lib/BUILD +++ b/src/main/java/com/google/devtools/build/lib/BUILD @@ -1259,6 +1259,7 @@ java_library( "//src/main/java/com/google/devtools/build/lib/profiler/memory:allocationtracker", "//src/main/java/com/google/devtools/build/lib/query2", "//src/main/java/com/google/devtools/build/lib/query2:abstract-blaze-query-env", + "//src/main/java/com/google/devtools/build/lib/query2:aquery-utils", "//src/main/java/com/google/devtools/build/lib/query2:query-engine", "//src/main/java/com/google/devtools/build/lib/query2:query-output", "//src/main/java/com/google/devtools/build/lib/shell", diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/AqueryBuildTool.java b/src/main/java/com/google/devtools/build/lib/buildtool/AqueryBuildTool.java index 225a2e4f3b6b1b..879301ad8d47eb 100644 --- a/src/main/java/com/google/devtools/build/lib/buildtool/AqueryBuildTool.java +++ b/src/main/java/com/google/devtools/build/lib/buildtool/AqueryBuildTool.java @@ -14,9 +14,9 @@ package com.google.devtools.build.lib.buildtool; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.query2.ActionGraphQueryEnvironment; +import com.google.devtools.build.lib.query2.AqueryActionFilter; import com.google.devtools.build.lib.query2.PostAnalysisQueryEnvironment; import com.google.devtools.build.lib.query2.PostAnalysisQueryEnvironment.TopLevelConfigurations; import com.google.devtools.build.lib.query2.engine.ActionFilterFunction; @@ -34,12 +34,12 @@ /** A version of {@link BuildTool} that handles all aquery work. */ public class AqueryBuildTool extends PostAnalysisQueryBuildTool { - private final ImmutableMap actionFilters; + private final AqueryActionFilter actionFilters; public AqueryBuildTool(CommandEnvironment env, QueryExpression queryExpression) throws AqueryActionFilterException { super(env, queryExpression); - actionFilters = getActionFilters(queryExpression); + actionFilters = buildActionFilters(queryExpression); } @Override @@ -79,9 +79,9 @@ protected PostAnalysisQueryEnvironment getQueryEnvironmen * @throws AqueryActionFilterException if an aquery filter function is preceded by any other * function types */ - private ImmutableMap getActionFilters(QueryExpression queryExpression) + private AqueryActionFilter buildActionFilters(QueryExpression queryExpression) throws AqueryActionFilterException { - ImmutableMap.Builder actionFiltersBuilder = ImmutableMap.builder(); + AqueryActionFilter.Builder actionFiltersBuilder = AqueryActionFilter.builder(); if (!(queryExpression instanceof FunctionExpression)) { return actionFiltersBuilder.build(); @@ -108,7 +108,6 @@ private ImmutableMap getActionFilters(QueryExpression queryExpr ActionFilterFunction actionFilterFunction = (ActionFilterFunction) functionExpression.getFunction(); - // TODO(leba) support multiple patterns for 1 function type String patternString = functionExpression.getArgs().get(0).getWord(); actionFiltersBuilder.put(actionFilterFunction.getName(), Pattern.compile(patternString)); } else { diff --git a/src/main/java/com/google/devtools/build/lib/query2/ActionGraphProtoOutputFormatterCallback.java b/src/main/java/com/google/devtools/build/lib/query2/ActionGraphProtoOutputFormatterCallback.java index e821dd4b2ad411..68f263247d61e6 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/ActionGraphProtoOutputFormatterCallback.java +++ b/src/main/java/com/google/devtools/build/lib/query2/ActionGraphProtoOutputFormatterCallback.java @@ -14,7 +14,6 @@ package com.google.devtools.build.lib.query2; import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableMap; import com.google.devtools.build.lib.actions.CommandLineExpansionException; import com.google.devtools.build.lib.analysis.AnalysisProtos; import com.google.devtools.build.lib.analysis.AnalysisProtos.ActionGraphContainer; @@ -29,7 +28,6 @@ import com.google.protobuf.TextFormat; import java.io.IOException; import java.io.OutputStream; -import java.util.regex.Pattern; /** Default output callback for aquery, prints proto output. */ public class ActionGraphProtoOutputFormatterCallback extends AqueryThreadsafeCallback { @@ -52,7 +50,7 @@ public String formatName() { private final OutputType outputType; private final ActionGraphDump actionGraphDump; - private final ImmutableMap actionFilters; + private final AqueryActionFilter actionFilters; ActionGraphProtoOutputFormatterCallback( ExtendedEventHandler eventHandler, @@ -61,7 +59,7 @@ public String formatName() { SkyframeExecutor skyframeExecutor, TargetAccessor accessor, OutputType outputType, - ImmutableMap actionFilters) { + AqueryActionFilter actionFilters) { super(eventHandler, options, out, skyframeExecutor, accessor); this.outputType = outputType; this.actionFilters = actionFilters; diff --git a/src/main/java/com/google/devtools/build/lib/query2/ActionGraphQueryEnvironment.java b/src/main/java/com/google/devtools/build/lib/query2/ActionGraphQueryEnvironment.java index 2653b4a3f7ddf1..464df9345ad627 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/ActionGraphQueryEnvironment.java +++ b/src/main/java/com/google/devtools/build/lib/query2/ActionGraphQueryEnvironment.java @@ -14,7 +14,6 @@ package com.google.devtools.build.lib.query2; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.Futures; @@ -53,7 +52,6 @@ import java.util.List; import java.util.Set; import java.util.function.Supplier; -import java.util.regex.Pattern; import javax.annotation.Nullable; /** @@ -67,7 +65,7 @@ public class ActionGraphQueryEnvironment public static final ImmutableList FUNCTIONS = populateFunctions(); AqueryOptions aqueryOptions; - private ImmutableMap actionFilters; + private AqueryActionFilter actionFilters; private final KeyExtractor configuredTargetKeyExtractor; private final ConfiguredTargetValueAccessor accessor; @@ -336,7 +334,7 @@ public ThreadSafeMutableSet createThreadSafeMutableSet() SkyQueryEnvironment.DEFAULT_THREAD_COUNT); } - public void setActionFilters(ImmutableMap actionFilters) { + public void setActionFilters(AqueryActionFilter actionFilters) { this.actionFilters = actionFilters; } } diff --git a/src/main/java/com/google/devtools/build/lib/query2/ActionGraphTextOutputFormatterCallback.java b/src/main/java/com/google/devtools/build/lib/query2/ActionGraphTextOutputFormatterCallback.java index 73c128f8ccbc97..11fd95871764ab 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/ActionGraphTextOutputFormatterCallback.java +++ b/src/main/java/com/google/devtools/build/lib/query2/ActionGraphTextOutputFormatterCallback.java @@ -16,7 +16,6 @@ import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Streams; import com.google.devtools.build.lib.actions.ActionAnalysisMetadata; @@ -46,14 +45,13 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import java.util.regex.Pattern; import java.util.stream.Collectors; /** Output callback for aquery, prints human readable output. */ public class ActionGraphTextOutputFormatterCallback extends AqueryThreadsafeCallback { private final ActionKeyContext actionKeyContext = new ActionKeyContext(); - private final ImmutableMap actionFilters; + private final AqueryActionFilter actionFilters; ActionGraphTextOutputFormatterCallback( ExtendedEventHandler eventHandler, @@ -61,7 +59,7 @@ public class ActionGraphTextOutputFormatterCallback extends AqueryThreadsafeCall OutputStream out, SkyframeExecutor skyframeExecutor, TargetAccessor accessor, - ImmutableMap actionFilters) { + AqueryActionFilter actionFilters) { super(eventHandler, options, out, skyframeExecutor, accessor); this.actionFilters = actionFilters; } diff --git a/src/main/java/com/google/devtools/build/lib/query2/AqueryActionFilter.java b/src/main/java/com/google/devtools/build/lib/query2/AqueryActionFilter.java new file mode 100644 index 00000000000000..33efed76952021 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/query2/AqueryActionFilter.java @@ -0,0 +1,80 @@ +// Copyright 2019 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.query2; + +import com.google.common.collect.ImmutableMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +/** Encapsulate the action filters parsed from aquery command. */ +public class AqueryActionFilter { + // TODO(leba): Use Enum for list of filters. + private final ImmutableMap> filterMap; + + private AqueryActionFilter(Builder builder) { + filterMap = ImmutableMap.copyOf(builder.filterMap); + } + + public static AqueryActionFilter emptyInstance() { + return builder().build(); + } + + public static Builder builder() { + return new Builder(); + } + + public boolean hasFilterForFunction(String function) { + return filterMap.containsKey(function); + } + + /** + * Returns whether the input string matches ALL the filter patterns of a specific type parsed from + * aquery command. + * + * @param function the name of the aquery function (inputs, outputs, mnemonic) + * @param input the string to be matched against + */ + public boolean matchesAllPatternsForFunction(String function, String input) { + if (!hasFilterForFunction(function)) { + return false; + } + + return filterMap.get(function).stream() + .map(pattern -> pattern.matcher(input).matches()) + .reduce(true, Boolean::logicalAnd); + } + + /** Builder class for {@code AqueryActionFilter} */ + public static class Builder { + private final Map> filterMap; + + public Builder() { + filterMap = new HashMap<>(); + } + + public Builder put(String key, Pattern value) { + filterMap.putIfAbsent(key, new ArrayList<>()); + filterMap.get(key).add(value); + + return this; + } + + public AqueryActionFilter build() { + return new AqueryActionFilter(this); + } + } +} diff --git a/src/main/java/com/google/devtools/build/lib/query2/AqueryUtils.java b/src/main/java/com/google/devtools/build/lib/query2/AqueryUtils.java index 9a505a71426a31..4446f43aa99872 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/AqueryUtils.java +++ b/src/main/java/com/google/devtools/build/lib/query2/AqueryUtils.java @@ -17,11 +17,9 @@ import static com.google.devtools.build.lib.query2.engine.MnemonicFunction.MNEMONIC; import static com.google.devtools.build.lib.query2.engine.OutputsFunction.OUTPUTS; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.Streams; import com.google.devtools.build.lib.actions.ActionAnalysisMetadata; import com.google.devtools.build.lib.actions.Artifact; -import java.util.regex.Pattern; /** Utility class for Aquery */ public class AqueryUtils { @@ -34,22 +32,24 @@ public class AqueryUtils { * @return whether the action matches the filtering patterns */ public static boolean matchesAqueryFilters( - ActionAnalysisMetadata action, ImmutableMap actionFilters) { + ActionAnalysisMetadata action, AqueryActionFilter actionFilters) { Iterable inputs = action.getInputs(); Iterable outputs = action.getOutputs(); String mnemonic = action.getMnemonic(); - if (actionFilters.containsKey(MNEMONIC)) { - if (!actionFilters.get(MNEMONIC).matcher(mnemonic).matches()) { + if (actionFilters.hasFilterForFunction(MNEMONIC)) { + if (!actionFilters.matchesAllPatternsForFunction(MNEMONIC, mnemonic)) { return false; } } - if (actionFilters.containsKey(INPUTS)) { - Pattern inputsPattern = actionFilters.get(INPUTS); + if (actionFilters.hasFilterForFunction(INPUTS)) { Boolean containsFile = Streams.stream(inputs) - .map(artifact -> inputsPattern.matcher(artifact.getExecPathString()).matches()) + .map( + artifact -> + actionFilters.matchesAllPatternsForFunction( + INPUTS, artifact.getExecPathString())) .reduce(false, Boolean::logicalOr); if (!containsFile) { @@ -57,11 +57,13 @@ public static boolean matchesAqueryFilters( } } - if (actionFilters.containsKey(OUTPUTS)) { - Pattern outputsPattern = actionFilters.get(OUTPUTS); + if (actionFilters.hasFilterForFunction(OUTPUTS)) { Boolean containsFile = Streams.stream(outputs) - .map(artifact -> outputsPattern.matcher(artifact.getExecPathString()).matches()) + .map( + artifact -> + actionFilters.matchesAllPatternsForFunction( + OUTPUTS, artifact.getExecPathString())) .reduce(false, Boolean::logicalOr); return containsFile; diff --git a/src/main/java/com/google/devtools/build/lib/query2/BUILD b/src/main/java/com/google/devtools/build/lib/query2/BUILD index d26ddbb5462b48..0dd84f3f1ea3ba 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/BUILD +++ b/src/main/java/com/google/devtools/build/lib/query2/BUILD @@ -14,6 +14,7 @@ java_library( "AbstractBlazeQueryEnvironment.java", "FakeLoadTarget.java", "AqueryUtils.java", + "AqueryActionFilter.java", ], ), deps = [ @@ -149,6 +150,7 @@ java_library( java_library( name = "aquery-utils", srcs = [ + "AqueryActionFilter.java", "AqueryUtils.java", ], deps = [ diff --git a/src/main/java/com/google/devtools/build/lib/query2/engine/MnemonicFunction.java b/src/main/java/com/google/devtools/build/lib/query2/engine/MnemonicFunction.java index 0f001c12c3af74..58058e960106f1 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/engine/MnemonicFunction.java +++ b/src/main/java/com/google/devtools/build/lib/query2/engine/MnemonicFunction.java @@ -23,7 +23,7 @@ * *
expr ::= MNEMONIC '(' WORD ',' expr ')'
* - * Example patterns: TODO(leba) example patterns + * Example patterns: * *
  * 'CppCompile'   Match all actions whose mnemonic is CppComile
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/actiongraph/ActionGraphDump.java b/src/main/java/com/google/devtools/build/lib/skyframe/actiongraph/ActionGraphDump.java
index e57e48f170d8cd..a6a6fe2690aa87 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/actiongraph/ActionGraphDump.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/actiongraph/ActionGraphDump.java
@@ -15,7 +15,6 @@
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.devtools.build.lib.actions.Action;
@@ -37,13 +36,13 @@
 import com.google.devtools.build.lib.collect.nestedset.NestedSetView;
 import com.google.devtools.build.lib.collect.nestedset.Order;
 import com.google.devtools.build.lib.packages.AspectDescriptor;
+import com.google.devtools.build.lib.query2.AqueryActionFilter;
 import com.google.devtools.build.lib.query2.AqueryUtils;
 import com.google.devtools.build.lib.skyframe.AspectValue;
 import com.google.devtools.build.lib.skyframe.ConfiguredTargetValue;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.regex.Pattern;
 
 /**
  * Encapsulates necessary functionality to dump the current skyframe state of the action graph to
@@ -62,21 +61,23 @@ public class ActionGraphDump {
   private final KnownAspectDescriptors knownAspectDescriptors;
   private final KnownRuleConfiguredTargets knownRuleConfiguredTargets;
   private final boolean includeActionCmdLine;
-  private final ImmutableMap actionFilters;
+  private final AqueryActionFilter actionFilters;
 
-  public ActionGraphDump(
-      boolean includeActionCmdLine, ImmutableMap actionFilters) {
+  public ActionGraphDump(boolean includeActionCmdLine, AqueryActionFilter actionFilters) {
     this(/* actionGraphTargets= */ ImmutableList.of("..."), includeActionCmdLine, actionFilters);
   }
 
   public ActionGraphDump(List actionGraphTargets, boolean includeActionCmdLine) {
-    this(actionGraphTargets, includeActionCmdLine, /* actionFilters= */ ImmutableMap.of());
+    this(
+        actionGraphTargets,
+        includeActionCmdLine,
+        /* actionFilters= */ AqueryActionFilter.emptyInstance());
   }
 
   public ActionGraphDump(
       List actionGraphTargets,
       boolean includeActionCmdLine,
-      ImmutableMap actionFilters) {
+      AqueryActionFilter actionFilters) {
     this.actionGraphTargets = ImmutableSet.copyOf(actionGraphTargets);
     this.includeActionCmdLine = includeActionCmdLine;
     this.actionFilters = actionFilters;
diff --git a/src/test/java/com/google/devtools/build/lib/query2/ActionGraphProtoOutputFormatterCallbackTest.java b/src/test/java/com/google/devtools/build/lib/query2/ActionGraphProtoOutputFormatterCallbackTest.java
index 217ea49191352b..ece029902af0e9 100644
--- a/src/test/java/com/google/devtools/build/lib/query2/ActionGraphProtoOutputFormatterCallbackTest.java
+++ b/src/test/java/com/google/devtools/build/lib/query2/ActionGraphProtoOutputFormatterCallbackTest.java
@@ -15,6 +15,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.common.eventbus.EventBus;
@@ -98,7 +99,7 @@ public void testAqueryFilters_allFunctions_matchingOnlyFooAction() throws Except
     String inputs = ".*\\.java";
     String outputs = ".*matching_out";
     String mnemonic = "Genrule";
-    ImmutableMap actionFilters =
+    AqueryActionFilter actionFilters =
         constructActionFilter(
             ImmutableMap.of("inputs", inputs, "outputs", outputs, "mnemonic", mnemonic));
 
@@ -109,7 +110,7 @@ public void testAqueryFilters_allFunctions_matchingOnlyFooAction() throws Except
                 inputs, outputs, mnemonic),
             actionFilters);
 
-    assertMatchingOnlyFoo(actionGraphContainer);
+    assertMatchingOnlyActionFromFoo(actionGraphContainer);
   }
 
   @Test
@@ -121,13 +122,12 @@ public void testInputsFilter_regex_matchingOnlyFooAction() throws Exception {
         "java_library(name='bar', srcs=['in_bar.java'])");
 
     String inputs = ".*matching_in.java";
-    ImmutableMap actionFilters =
-        constructActionFilter(ImmutableMap.of("inputs", inputs));
+    AqueryActionFilter actionFilters = constructActionFilter(ImmutableMap.of("inputs", inputs));
 
     ActionGraphContainer actionGraphContainer =
         getOutput(String.format("inputs('%s', deps(//test:all))", inputs), actionFilters);
 
-    assertMatchingOnlyFoo(actionGraphContainer);
+    assertMatchingOnlyActionFromFoo(actionGraphContainer);
   }
 
   @Test
@@ -140,13 +140,12 @@ public void testOutputsFilter_regex_matchingOnlyFooAction() throws Exception {
         "        cmd='cat $(SRCS) > $(OUTS)')");
 
     String outputs = ".*matching_out";
-    ImmutableMap actionFilters =
-        constructActionFilter(ImmutableMap.of("outputs", outputs));
+    AqueryActionFilter actionFilters = constructActionFilter(ImmutableMap.of("outputs", outputs));
 
     ActionGraphContainer actionGraphContainer =
         getOutput(String.format("outputs('%s', deps(//test:all))", outputs), actionFilters);
 
-    assertMatchingOnlyFoo(actionGraphContainer);
+    assertMatchingOnlyActionFromFoo(actionGraphContainer);
   }
 
   @Test
@@ -158,13 +157,12 @@ public void testMnemonicFilter_regex_matchingOnlyFooAction() throws Exception {
         "java_library(name='bar', srcs=['in_bar.java'])");
 
     String mnemonic = ".*rule";
-    ImmutableMap actionFilters =
-        constructActionFilter(ImmutableMap.of("mnemonic", mnemonic));
+    AqueryActionFilter actionFilters = constructActionFilter(ImmutableMap.of("mnemonic", mnemonic));
 
     ActionGraphContainer actionGraphContainer =
         getOutput(String.format("mnemonic('%s', deps(//test:all))", mnemonic), actionFilters);
 
-    assertMatchingOnlyFoo(actionGraphContainer);
+    assertMatchingOnlyActionFromFoo(actionGraphContainer);
   }
 
   @Test
@@ -176,13 +174,12 @@ public void testInputsFilter_exactFileName_matchingOnlyFooAction() throws Except
         "java_library(name='bar', srcs=['in_bar.java'])");
 
     String inputs = "test/foo_matching_in.java";
-    ImmutableMap actionFilters =
-        constructActionFilter(ImmutableMap.of("inputs", inputs));
+    AqueryActionFilter actionFilters = constructActionFilter(ImmutableMap.of("inputs", inputs));
 
     ActionGraphContainer actionGraphContainer =
         getOutput(String.format("inputs('%s', deps(//test:all))", inputs), actionFilters);
 
-    assertMatchingOnlyFoo(actionGraphContainer);
+    assertMatchingOnlyActionFromFoo(actionGraphContainer);
   }
 
   @Test
@@ -195,13 +192,12 @@ public void testOutputsFilter_exactFileName_matchingOnlyFooAction() throws Excep
         "        cmd='cat $(SRCS) > $(OUTS)')");
 
     String outputs = ".*/genfiles/test/foo_matching_out";
-    ImmutableMap actionFilters =
-        constructActionFilter(ImmutableMap.of("outputs", outputs));
+    AqueryActionFilter actionFilters = constructActionFilter(ImmutableMap.of("outputs", outputs));
 
     ActionGraphContainer actionGraphContainer =
         getOutput(String.format("outputs('%s', deps(//test:all))", outputs), actionFilters);
 
-    assertMatchingOnlyFoo(actionGraphContainer);
+    assertMatchingOnlyActionFromFoo(actionGraphContainer);
   }
 
   @Test
@@ -213,13 +209,109 @@ public void testMnemonicFilter_exactMnemonicValue_matchingOnlyFooAction() throws
         "java_library(name='bar', srcs=['in_bar.java'])");
 
     String mnemonic = "Genrule";
-    ImmutableMap actionFilters =
-        constructActionFilter(ImmutableMap.of("mnemonic", mnemonic));
+    AqueryActionFilter actionFilters = constructActionFilter(ImmutableMap.of("mnemonic", mnemonic));
 
     ActionGraphContainer actionGraphContainer =
         getOutput(String.format("mnemonic('%s', deps(//test:all))", mnemonic), actionFilters);
 
-    assertMatchingOnlyFoo(actionGraphContainer);
+    assertMatchingOnlyActionFromFoo(actionGraphContainer);
+  }
+
+  @Test
+  public void testInputsFilter_chainInputs_noMatchingAction() throws Exception {
+    writeFile(
+        "test/BUILD",
+        "genrule(name='foo', srcs=['in'], outs=['out'], tags=['requires-x'], ",
+        "        cmd='cat $(SRCS) > $(OUTS)')");
+    AqueryActionFilter actionFilters =
+        constructActionFilterChainSameFunction("inputs", ImmutableList.of("in", "something"));
+    ActionGraphContainer actionGraphContainer =
+        getOutput("inputs('in', inputs('something', //test:foo))", actionFilters);
+    assertThat(actionGraphContainer.getActionsCount()).isEqualTo(0);
+  }
+
+  @Test
+  public void testOutputsFilter_chainOutputs_noMatchingAction() throws Exception {
+    writeFile(
+        "test/BUILD",
+        "genrule(name='foo', srcs=['in'], outs=['out'], tags=['requires-x'], ",
+        "        cmd='cat $(SRCS) > $(OUTS)')");
+    AqueryActionFilter actionFilters =
+        constructActionFilterChainSameFunction("outputs", ImmutableList.of("out", "something"));
+    ActionGraphContainer actionGraphContainer =
+        getOutput("outputs('out', outputs('something', //test:foo))", actionFilters);
+    assertThat(actionGraphContainer.getActionsCount()).isEqualTo(0);
+  }
+
+  @Test
+  public void testMnemonicFilter_chainMnemonics_noMatchingAction() throws Exception {
+    writeFile(
+        "test/BUILD",
+        "genrule(name='foo', srcs=['in'], outs=['out'], tags=['requires-x'], ",
+        "        cmd='cat $(SRCS) > $(OUTS)')");
+    AqueryActionFilter actionFilters =
+        constructActionFilterChainSameFunction("mnemonic", ImmutableList.of(".*rule", "something"));
+    ActionGraphContainer actionGraphContainer =
+        getOutput("mnemonic('.*rule', mnemonic('something', //test:foo))", actionFilters);
+    assertThat(actionGraphContainer.getActionsCount()).isEqualTo(0);
+  }
+
+  @Test
+  public void testInputsFilter_chainInputs_matchingOnlyFooAction() throws Exception {
+    writeFile(
+        "test/BUILD",
+        "genrule(name='foo', srcs=['foo_matching_in.java'], outs=['foo_matching_out'],",
+        "        cmd='cat $(SRCS) > $(OUTS)')",
+        "java_library(name='bar', srcs=['in_bar.java'])",
+        "genrule(name='foo2', srcs=['foo_matching_in.java_not'], outs=['foo_matching_out2'],",
+        "        cmd='cat $(SRCS) > $(OUTS)')");
+
+    AqueryActionFilter actionFilters =
+        constructActionFilterChainSameFunction("inputs", ImmutableList.of(".*java", ".*foo.*"));
+
+    ActionGraphContainer actionGraphContainer =
+        getOutput("inputs('.*java', inputs('.*foo.*', deps(//test:all)))", actionFilters);
+
+    assertMatchingOnlyActionFromFoo(actionGraphContainer);
+  }
+
+  @Test
+  public void testOutputsFilter_chainOutputs_matchingOnlyFooAction() throws Exception {
+    writeFile(
+        "test/BUILD",
+        "genrule(name='foo', srcs=['foo_matching_in.java'], outs=['foo_matching_out'],",
+        "        cmd='cat $(SRCS) > $(OUTS)')",
+        "genrule(name='foo2', srcs=['foo_matching_in.java'], outs=['foo_matching_out_not'],",
+        "        cmd='cat $(SRCS) > $(OUTS)')",
+        "genrule(name='foo3', srcs=['foo_matching_in.java'], outs=['not_matching_out'],",
+        "        cmd='cat $(SRCS) > $(OUTS)')");
+
+    AqueryActionFilter actionFilters =
+        constructActionFilterChainSameFunction("outputs", ImmutableList.of(".*out", ".*foo.*"));
+
+    ActionGraphContainer actionGraphContainer =
+        getOutput("outputs('.*out', outputs('.*foo.*', deps(//test:all)))", actionFilters);
+
+    assertMatchingOnlyActionFromFoo(actionGraphContainer);
+  }
+
+  @Test
+  public void testMnemonicFilter_chainMnemonic_matchingOnlyFooAction() throws Exception {
+    // java_library targets generate actions of the following mnemonics:
+    // - Javac
+    // - JavaSourceJar
+    // - Turbine
+    writeFile("test/BUILD", "java_library(", "    name = 'foo',", "    srcs = ['Foo.java'],", ")");
+
+    AqueryActionFilter actionFilters =
+        constructActionFilterChainSameFunction("mnemonic", ImmutableList.of("Java.*", ".*e.*"));
+
+    ActionGraphContainer actionGraphContainer =
+        getOutput("mnemonic('Java.*', mnemonic('.*e.*', deps(//test:all)))", actionFilters);
+
+    assertThat(actionGraphContainer.getActionsCount()).isEqualTo(1);
+    Action action = Iterables.getOnlyElement(actionGraphContainer.getActionsList());
+    assertThat(action.getMnemonic()).isEqualTo("JavaSourceJar");
   }
 
   @Test
@@ -228,7 +320,7 @@ public void testInputsFilter_noMatchingAction() throws Exception {
         "test/BUILD",
         "genrule(name='foo', srcs=['in'], outs=['out'], tags=['requires-x'], ",
         "        cmd='cat $(SRCS) > $(OUTS)')");
-    ImmutableMap actionFilters =
+    AqueryActionFilter actionFilters =
         constructActionFilter(ImmutableMap.of("inputs", "something"));
     ActionGraphContainer actionGraphContainer =
         getOutput("inputs('something', //test:foo)", actionFilters);
@@ -241,7 +333,7 @@ public void testOutputsFilter_noMatchingAction() throws Exception {
         "test/BUILD",
         "genrule(name='foo', srcs=['in'], outs=['out'], tags=['requires-x'], ",
         "        cmd='cat $(SRCS) > $(OUTS)')");
-    ImmutableMap actionFilters =
+    AqueryActionFilter actionFilters =
         constructActionFilter(ImmutableMap.of("outputs", "something"));
     ActionGraphContainer actionGraphContainer =
         getOutput("outputs('something', //test:foo)", actionFilters);
@@ -254,7 +346,7 @@ public void testMnemonicFilter_noMatchingAction() throws Exception {
         "test/BUILD",
         "genrule(name='foo', srcs=['in'], outs=['out'], tags=['requires-x'], ",
         "        cmd='cat $(SRCS) > $(OUTS)')");
-    ImmutableMap actionFilters =
+    AqueryActionFilter actionFilters =
         constructActionFilter(ImmutableMap.of("mnemonic", "something"));
     ActionGraphContainer actionGraphContainer =
         getOutput("mnemonic('something', //test:foo)", actionFilters);
@@ -262,11 +354,11 @@ public void testMnemonicFilter_noMatchingAction() throws Exception {
   }
 
   private AnalysisProtos.ActionGraphContainer getOutput(String queryExpression) throws Exception {
-    return getOutput(queryExpression, /* actionFilters= */ ImmutableMap.of());
+    return getOutput(queryExpression, /* actionFilters= */ AqueryActionFilter.emptyInstance());
   }
 
   private AnalysisProtos.ActionGraphContainer getOutput(
-      String queryExpression, ImmutableMap actionFilters) throws Exception {
+      String queryExpression, AqueryActionFilter actionFilters) throws Exception {
     QueryExpression expression = QueryParser.parse(queryExpression, getDefaultFunctions());
     Set targetPatternSet = new LinkedHashSet<>();
     expression.collectTargetPatterns(targetPatternSet);
@@ -287,17 +379,29 @@ private AnalysisProtos.ActionGraphContainer getOutput(
     return callback.getProtoResult();
   }
 
-  private void assertMatchingOnlyFoo(ActionGraphContainer actionGraphContainer) {
+  private void assertMatchingOnlyActionFromFoo(ActionGraphContainer actionGraphContainer) {
+    assertMatchingOnlyAction(
+        actionGraphContainer,
+        "Genrule",
+        "test/foo_matching_in.java",
+        "/genfiles/test/foo_matching_out");
+  }
+
+  private void assertMatchingOnlyAction(
+      ActionGraphContainer actionGraphContainer,
+      String mnemonic,
+      String onlyInput,
+      String onlyOutput) {
     assertThat(actionGraphContainer.getActionsCount()).isEqualTo(1);
     Action action = Iterables.getOnlyElement(actionGraphContainer.getActionsList());
 
     // Verify mnemonic
-    assertThat(action.getMnemonic()).isEqualTo("Genrule");
+    assertThat(action.getMnemonic()).isEqualTo(mnemonic);
 
     // Verify input
     String inputId = null;
     for (Artifact artifact : actionGraphContainer.getArtifactsList()) {
-      if (artifact.getExecPath().equals("test/foo_matching_in.java")) {
+      if (artifact.getExecPath().endsWith(onlyInput)) {
         inputId = artifact.getId();
         break;
       }
@@ -307,7 +411,7 @@ private void assertMatchingOnlyFoo(ActionGraphContainer actionGraphContainer) {
     // Verify output
     String outputId = null;
     for (Artifact artifact : actionGraphContainer.getArtifactsList()) {
-      if (artifact.getExecPath().endsWith("/genfiles/test/foo_matching_out")) {
+      if (artifact.getExecPath().endsWith(onlyOutput)) {
         outputId = artifact.getId();
         break;
       }
@@ -315,12 +419,20 @@ private void assertMatchingOnlyFoo(ActionGraphContainer actionGraphContainer) {
     assertThat(action.getOutputIdsList()).contains(outputId);
   }
 
-  private ImmutableMap constructActionFilter(
-      ImmutableMap patternStrings) {
-    ImmutableMap.Builder builder = ImmutableMap.builder();
+  private AqueryActionFilter constructActionFilter(ImmutableMap patternStrings) {
+    AqueryActionFilter.Builder builder = AqueryActionFilter.builder();
     for (Entry e : patternStrings.entrySet()) {
       builder.put(e.getKey(), Pattern.compile(e.getValue()));
     }
     return builder.build();
   }
+
+  private AqueryActionFilter constructActionFilterChainSameFunction(
+      String function, List patternStrings) {
+    AqueryActionFilter.Builder builder = AqueryActionFilter.builder();
+    for (String s : patternStrings) {
+      builder.put(function, Pattern.compile(s));
+    }
+    return builder.build();
+  }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/query2/BUILD b/src/test/java/com/google/devtools/build/lib/query2/BUILD
index 04c72dfc0cde5e..c05a385b81dd67 100644
--- a/src/test/java/com/google/devtools/build/lib/query2/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/query2/BUILD
@@ -68,6 +68,7 @@ java_library(
         "//src/main/java/com/google/devtools/build/lib:syntax",
         "//src/main/java/com/google/devtools/build/lib/cmdline",
         "//src/main/java/com/google/devtools/build/lib/query2",
+        "//src/main/java/com/google/devtools/build/lib/query2:aquery-utils",
         "//src/main/java/com/google/devtools/build/lib/query2:aspect-resolver",
         "//src/main/java/com/google/devtools/build/lib/query2:query-engine",
         "//src/main/java/com/google/devtools/build/lib/query2:query-output",
@@ -87,18 +88,3 @@ java_library(
         "//third_party:truth",
     ],
 )
-
-java_test(
-    name = "Query2Tests",
-    size = "large",
-    shard_count = 20,
-    tags = [
-        "no_windows",
-        "query2",
-    ],
-    test_class = "com.google.devtools.build.lib.AllTests",
-    runtime_deps = [
-        ":Query2Tests_lib",
-        "//src/test/java/com/google/devtools/build/lib:test_runner",
-    ],
-)
diff --git a/src/test/shell/integration/aquery_test.sh b/src/test/shell/integration/aquery_test.sh
index d5fa2b81d5c2c5..9250596f0165d0 100755
--- a/src/test/shell/integration/aquery_test.sh
+++ b/src/test/shell/integration/aquery_test.sh
@@ -554,4 +554,108 @@ ${QUERY}"
   expect_log "${EXPECTED_LOG}"
 }
 
+function test_aquery_filters_chain_inputs_only_match_one() {
+  local pkg="${FUNCNAME[0]}"
+  mkdir -p "$pkg" || fail "mkdir -p $pkg"
+  cat > "$pkg/BUILD" <<'EOF'
+genrule(
+    name='foo',
+    srcs=['foo_matching_in.java'],
+    outs=['foo_matching_out'],
+    cmd='cat $(SRCS) > $(OUTS)'
+)
+
+java_library(
+    name='bar',
+    srcs=['in_bar.java']
+)
+
+genrule(
+    name='foo2',
+    srcs=['foo_matching_in.java_not'],
+    outs=['foo_matching_out2'],
+    cmd='cat $(SRCS) > $(OUTS)'
+)
+EOF
+
+  QUERY="inputs('.*java', inputs('.*foo.*', //$pkg:all))"
+
+  bazel aquery --output=text ${QUERY} > output 2> "$TEST_log" \
+    || fail "Expected success"
+  cat output >> "$TEST_log"
+
+  assert_only_action_foo output
+
+  bazel aquery --output=textproto --noinclude_commandline ${QUERY} > output \
+    2> "$TEST_log" || fail "Expected success"
+}
+
+function test_aquery_filters_chain_outputs_only_match_one() {
+  local pkg="${FUNCNAME[0]}"
+  mkdir -p "$pkg" || fail "mkdir -p $pkg"
+  cat > "$pkg/BUILD" <<'EOF'
+genrule(
+    name='foo',
+    srcs=['foo_matching_in.java'],
+    outs=['foo_matching_out'],
+    cmd='cat $(SRCS) > $(OUTS)'
+)
+
+genrule(
+    name='foo2',
+    srcs=['foo_matching_in.java'],
+    outs=['foo_matching_out_not'],
+    cmd='cat $(SRCS) > $(OUTS)'
+)
+
+genrule(
+    name='foo3',
+    srcs=['foo_matching_in.java'],
+    outs=['not_matching_out'],
+    cmd='cat $(SRCS) > $(OUTS)'
+)
+EOF
+
+  QUERY="outputs('.*out', outputs('.*foo.*', //$pkg:all))"
+
+  bazel aquery --output=text ${QUERY} > output 2> "$TEST_log" \
+    || fail "Expected success"
+  cat output >> "$TEST_log"
+
+  assert_only_action_foo output
+
+  bazel aquery --output=textproto --noinclude_commandline ${QUERY} > output \
+    2> "$TEST_log" || fail "Expected success"
+}
+
+function test_aquery_filters_chain_mnemonic_only_match_one() {
+  local pkg="${FUNCNAME[0]}"
+  mkdir -p "$pkg" || fail "mkdir -p $pkg"
+  cat > "$pkg/BUILD" <<'EOF'
+java_library(
+    name='foo',
+    srcs=['Foo.java']
+)
+EOF
+
+  # java_library targets generate actions of the following mnemonics:
+  # - Javac
+  # - JavaSourceJar
+  # - Turbine
+  QUERY="mnemonic('Java.*', mnemonic('.*e.*', //$pkg:all))"
+
+  bazel aquery --output=text ${QUERY} > output 2> "$TEST_log" \
+    || fail "Expected success"
+  cat output >> "$TEST_log"
+
+  expect_log_n "^action '" 1 "Expected exactly one action."
+  assert_contains "action.*foo" output
+  assert_contains "Inputs: \[.*Foo.java.*\]" output
+  assert_contains "Outputs: \[.*jar\]" output
+  assert_contains "Mnemonic: JavaSourceJar" output
+
+  bazel aquery --output=textproto --noinclude_commandline ${QUERY} > output \
+    2> "$TEST_log" || fail "Expected success"
+}
+
 run_suite "${PRODUCT_NAME} action graph query tests"