Add "jsonproto" option to aquery/cquery --output flag.

Closes #10421

Closes #10426.

PiperOrigin-RevId: 286370369
diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD
index 1b2c39e..c1ed327 100644
--- a/src/main/java/com/google/devtools/build/lib/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/BUILD
@@ -685,6 +685,7 @@
         "//third_party:guava",
         "//third_party:jsr305",
         "//third_party/protobuf:protobuf_java",
+        "//third_party/protobuf:protobuf_java_util",
         "@remoteapis//:build_bazel_remote_execution_v2_remote_execution_java_proto",
     ],
 )
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 63d5a91..3a43ae3 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/query2/BUILD
@@ -53,6 +53,7 @@
         "//third_party:guava",
         "//third_party:jsr305",
         "//third_party/protobuf:protobuf_java",
+        "//third_party/protobuf:protobuf_java_util",
     ],
 )
 
diff --git a/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphProtoOutputFormatterCallback.java b/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphProtoOutputFormatterCallback.java
index ee4158b..9283a24 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphProtoOutputFormatterCallback.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphProtoOutputFormatterCallback.java
@@ -25,6 +25,7 @@
 import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
 import com.google.devtools.build.lib.skyframe.actiongraph.ActionGraphDump;
 import com.google.protobuf.TextFormat;
+import com.google.protobuf.util.JsonFormat;
 import java.io.IOException;
 import java.io.OutputStream;
 
@@ -34,7 +35,8 @@
   /** Defines the types of proto output this class can handle. */
   public enum OutputType {
     BINARY("proto"),
-    TEXT("textproto");
+    TEXT("textproto"),
+    JSON("jsonproto");
 
     private final String formatName;
 
@@ -50,6 +52,7 @@
   private final OutputType outputType;
   private final ActionGraphDump actionGraphDump;
   private final AqueryActionFilter actionFilters;
+  private final JsonFormat.Printer jsonPrinter = JsonFormat.printer();
 
   ActionGraphProtoOutputFormatterCallback(
       ExtendedEventHandler eventHandler,
@@ -110,6 +113,10 @@
         case TEXT:
           TextFormat.print(actionGraphContainer, printStream);
           break;
+        case JSON:
+          jsonPrinter.appendTo(actionGraphContainer, printStream);
+          printStream.println();
+          break;
         default:
           throw new IllegalStateException("Unknown outputType " + outputType.formatName());
       }
diff --git a/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphQueryEnvironment.java b/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphQueryEnvironment.java
index 5e0e91e..00d7772 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphQueryEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphQueryEnvironment.java
@@ -178,6 +178,14 @@
                 accessor,
                 StreamedOutputHandler.OutputType.TEXT,
                 actionFilters),
+            new ActionGraphProtoV2OutputFormatterCallback(
+                eventHandler,
+                aqueryOptions,
+                out,
+                skyframeExecutor,
+                accessor,
+                StreamedOutputHandler.OutputType.JSON,
+                actionFilters),
             new ActionGraphTextOutputFormatterCallback(
                 eventHandler, aqueryOptions, out, skyframeExecutor, accessor, actionFilters))
         : ImmutableList.of(
@@ -197,6 +205,14 @@
                 accessor,
                 OutputType.TEXT,
                 actionFilters),
+            new ActionGraphProtoOutputFormatterCallback(
+                eventHandler,
+                aqueryOptions,
+                out,
+                skyframeExecutor,
+                accessor,
+                OutputType.JSON,
+                actionFilters),
             new ActionGraphTextOutputFormatterCallback(
                 eventHandler, aqueryOptions, out, skyframeExecutor, accessor, actionFilters));
   }
diff --git a/src/main/java/com/google/devtools/build/lib/query2/aquery/AqueryOptions.java b/src/main/java/com/google/devtools/build/lib/query2/aquery/AqueryOptions.java
index da45a89..d0ed617 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/aquery/AqueryOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/aquery/AqueryOptions.java
@@ -28,7 +28,7 @@
       effectTags = {OptionEffectTag.TERMINAL_OUTPUT},
       help =
           "The format in which the aquery results should be printed. Allowed values for aquery "
-              + "are: text, textproto, proto.")
+              + "are: text, textproto, proto, jsonproto.")
   public String outputFormat;
 
   @Option(
diff --git a/src/main/java/com/google/devtools/build/lib/query2/cquery/ConfiguredTargetQueryEnvironment.java b/src/main/java/com/google/devtools/build/lib/query2/cquery/ConfiguredTargetQueryEnvironment.java
index e6d82f7..5b05015 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/cquery/ConfiguredTargetQueryEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/cquery/ConfiguredTargetQueryEnvironment.java
@@ -193,6 +193,14 @@
             accessor,
             aspectResolver,
             OutputType.TEXT),
+        new ProtoOutputFormatterCallback(
+            eventHandler,
+            cqueryOptions,
+            out,
+            skyframeExecutor,
+            accessor,
+            aspectResolver,
+            OutputType.JSON),
         new BuildOutputFormatterCallback(
             eventHandler, cqueryOptions, out, skyframeExecutor, accessor));
   }
diff --git a/src/main/java/com/google/devtools/build/lib/query2/cquery/CqueryOptions.java b/src/main/java/com/google/devtools/build/lib/query2/cquery/CqueryOptions.java
index 11884c2..2de9e3a 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/cquery/CqueryOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/cquery/CqueryOptions.java
@@ -45,8 +45,8 @@
       effectTags = {OptionEffectTag.TERMINAL_OUTPUT},
       help =
           "The format in which the cquery results should be printed. Allowed values for cquery "
-              + "are: label, textproto, transitions, proto. If you select 'transitions', you also "
-              + "have to specify the --transitions=(lite|full) option.")
+              + "are: label, textproto, transitions, proto, jsonproto. If you select "
+              + "'transitions', you also have to specify the --transitions=(lite|full) option.")
   public String outputFormat;
 
   @Option(
diff --git a/src/main/java/com/google/devtools/build/lib/query2/cquery/ProtoOutputFormatterCallback.java b/src/main/java/com/google/devtools/build/lib/query2/cquery/ProtoOutputFormatterCallback.java
index 10eaf03..cc79c8b 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/cquery/ProtoOutputFormatterCallback.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/cquery/ProtoOutputFormatterCallback.java
@@ -35,6 +35,7 @@
 import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
 import com.google.protobuf.Message;
 import com.google.protobuf.TextFormat;
+import com.google.protobuf.util.JsonFormat;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.util.Map;
@@ -45,7 +46,8 @@
   /** Defines the types of proto output this class can handle. */
   public enum OutputType {
     BINARY("proto"),
-    TEXT("textproto");
+    TEXT("textproto"),
+    JSON("jsonproto");
 
     private final String formatName;
 
@@ -60,6 +62,7 @@
 
   private final OutputType outputType;
   private final AspectResolver resolver;
+  private final JsonFormat.Printer jsonPrinter = JsonFormat.printer();
 
   private AnalysisProtos.CqueryResult.Builder protoResult;
 
@@ -108,6 +111,10 @@
       case TEXT:
         TextFormat.print(message, printStream);
         break;
+      case JSON:
+        jsonPrinter.appendTo(message, printStream);
+        printStream.append('\n');
+        break;
       default:
         throw new IllegalStateException("Unknown outputType " + outputType.formatName());
     }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/actiongraph/v2/StreamedOutputHandler.java b/src/main/java/com/google/devtools/build/lib/skyframe/actiongraph/v2/StreamedOutputHandler.java
index b857b6e..6412f03 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/actiongraph/v2/StreamedOutputHandler.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/actiongraph/v2/StreamedOutputHandler.java
@@ -16,6 +16,7 @@
 import com.google.devtools.build.lib.analysis.AnalysisProtosV2.ActionGraphComponent;
 import com.google.devtools.build.lib.analysis.AnalysisProtosV2.ActionGraphContainer;
 import com.google.protobuf.CodedOutputStream;
+import com.google.protobuf.util.JsonFormat;
 import java.io.IOException;
 import java.io.PrintStream;
 
@@ -24,7 +25,8 @@
   /** Defines the types of proto output this class can handle. */
   public enum OutputType {
     BINARY("proto"),
-    TEXT("textproto");
+    TEXT("textproto"),
+    JSON("jsonproto");
 
     private final String formatName;
 
@@ -40,6 +42,7 @@
   private final OutputType outputType;
   private final CodedOutputStream outputStream;
   private final PrintStream printStream;
+  private final JsonFormat.Printer jsonPrinter = JsonFormat.printer();
 
   public StreamedOutputHandler(
       OutputType outputType, CodedOutputStream outputStream, PrintStream printStream) {
@@ -62,6 +65,10 @@
       case TEXT:
         printStream.print(wrapperActionGraphComponent(message));
         break;
+      case JSON:
+        jsonPrinter.appendTo(message, printStream);
+        printStream.println();
+        break;
     }
   }
 
@@ -76,6 +83,7 @@
         outputStream.flush();
         break;
       case TEXT:
+      case JSON:
         printStream.flush();
         printStream.close();
         break;
diff --git a/src/test/shell/integration/aquery_test.sh b/src/test/shell/integration/aquery_test.sh
index 30425c1..e57d63e 100755
--- a/src/test/shell/integration/aquery_test.sh
+++ b/src/test/shell/integration/aquery_test.sh
@@ -132,7 +132,6 @@
   assert_not_contains "echo unused" output
 }
 
-
 function test_aquery_include_artifacts() {
   local pkg="${FUNCNAME[0]}"
   mkdir -p "$pkg" || fail "mkdir -p $pkg"
@@ -187,6 +186,32 @@
   assert_not_contains "echo unused" output
 }
 
+function test_aquery_jsonproto() {
+  local pkg="${FUNCNAME[0]}"
+  mkdir -p "$pkg" || fail "mkdir -p $pkg"
+  cat > "$pkg/BUILD" <<'EOF'
+genrule(
+    name = "bar",
+    srcs = ["dummy.txt"],
+    outs = ["bar_out.txt"],
+    cmd = "echo unused > $(OUTS)",
+)
+EOF
+  echo "hello aquery" > "$pkg/in.txt"
+
+  bazel aquery --output=jsonproto "//$pkg:bar" > output 2> "$TEST_log" \
+    || fail "Expected success"
+  cat output >> "$TEST_log"
+  assert_contains "\"execPath\": \"$pkg/dummy.txt\"" output
+  assert_contains "\"mnemonic\": \"Genrule\"" output
+  assert_contains "\"mnemonic\": \".*-fastbuild\"" output
+  assert_contains "echo unused" output
+
+  bazel aquery --output=jsonproto --noinclude_commandline "//$pkg:bar" > output \
+    2> "$TEST_log" || fail "Expected success"
+  assert_not_contains "echo unused" output
+}
+
 function test_aquery_skylark_env() {
   local pkg="${FUNCNAME[0]}"
   mkdir -p "$pkg" || fail "mkdir -p $pkg"
@@ -1113,4 +1138,33 @@
   assert_contains "mnemonic: \".*-fastbuild\"" output
   assert_contains "echo unused" output
 }
+
+function test_basic_aquery_jsonproto_v2() {
+  local pkg="${FUNCNAME[0]}"
+  mkdir -p "$pkg" || fail "mkdir -p $pkg"
+  cat > "$pkg/BUILD" <<'EOF'
+genrule(
+    name = "bar",
+    srcs = ["dummy.txt"],
+    outs = ["bar_out.txt"],
+    cmd = "echo unused > $(OUTS)",
+)
+EOF
+  bazel aquery --incompatible_proto_output_v2 --output=jsonproto "//$pkg:bar" > output 2> "$TEST_log" \
+    || fail "Expected success"
+  cat output >> "$TEST_log"
+
+  # Verify than ids come in integers instead of strings.
+  assert_contains "\"id\": 1," output
+  assert_not_contains "\"id\": \"1\"" output
+
+  # Verify that paths are broken down to path fragments.
+  assert_contains "\"pathFragment\": {" output
+
+  # Verify that the appropriate action was included.
+  assert_contains "\"label\": \"dummy.txt\"" output
+  assert_contains "\"mnemonic\": \"Genrule\"" output
+  assert_contains "\"mnemonic\": \".*-fastbuild\"" output
+  assert_contains "echo unused" output
+}
 run_suite "${PRODUCT_NAME} action graph query tests"