Optionally include file contents for the file write action.

For the action created by the `ctx.actions.write` call, return the files's contents in the `bazel aquery --include_file_write_contents ...` output.
With `--output=<...>proto`, the file contents is the value of the `file_contents` field. With `--output=text`, show  Base64-encoded file contents as the value of `FileWriteContents:` (e.g., if the file contains "hello world", the output will have ` FileWriteContents: [aGVsbG8gd29ybGQ=]` line.

PiperOrigin-RevId: 452789828
Change-Id: I0bde308cc1ebd1ed45f800542307009c005bb33d
diff --git a/site/en/docs/aquery.md b/site/en/docs/aquery.md
index 445d547..2656f9a 100644
--- a/site/en/docs/aquery.md
+++ b/site/en/docs/aquery.md
@@ -133,6 +133,16 @@
 
 Warning: Enabling this flag will automatically enable the `--include_commandline` flag.
 
+#### `--include_file_write_contents, default=false` {:#include-file-write-contents}
+
+Include file contents for the `actions.write()` action. The file contents is
+returned in the `file_contents` field with `--output=`xxx`proto`.
+With `--output=text`, the output has
+```
+FileWriteContents: [<base64-encoded file contents>]
+```
+line
+
 #### `--skyframe_state, default=false` {:#skyframe-state}
 
 Without performing extra analysis, dump the Action Graph from Skyframe.
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/AqueryProcessor.java b/src/main/java/com/google/devtools/build/lib/buildtool/AqueryProcessor.java
index f37ff34..3e78649 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/AqueryProcessor.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/AqueryProcessor.java
@@ -86,6 +86,7 @@
                 actionFilters,
                 aqueryOptions.includeParamFiles,
                 aqueryOptions.deduplicateDepsets,
+                aqueryOptions.includeFileWriteContents,
                 aqueryOutputHandler);
         ((SequencedSkyframeExecutor) env.getSkyframeExecutor()).dumpSkyframeState(actionGraphDump);
       } catch (InvalidAqueryOutputFormatException e) {
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java b/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
index b4936ab..c7c7a8e 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
@@ -437,6 +437,7 @@
               /* actionFilters= */ null,
               /* includeParamFiles= */ false,
               /* deduplicateDepsets= */ true,
+              /* includeFileWriteContents */ false,
               aqueryOutputHandler);
       ((SequencedSkyframeExecutor) env.getSkyframeExecutor()).dumpSkyframeState(actionGraphDump);
     }
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 7dc7dc3..fa2c7e5 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
@@ -65,6 +65,7 @@
             this.actionFilters,
             options.includeParamFiles,
             options.deduplicateDepsets,
+            options.includeFileWriteContents,
             aqueryOutputHandler);
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphTextOutputFormatterCallback.java b/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphTextOutputFormatterCallback.java
index c276eb3..1ad9e5c 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphTextOutputFormatterCallback.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphTextOutputFormatterCallback.java
@@ -31,6 +31,7 @@
 import com.google.devtools.build.lib.actions.CommandLineExpansionException;
 import com.google.devtools.build.lib.analysis.AspectValue;
 import com.google.devtools.build.lib.analysis.ConfiguredTargetValue;
+import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
 import com.google.devtools.build.lib.analysis.actions.ParameterFileWriteAction;
 import com.google.devtools.build.lib.analysis.actions.Substitution;
 import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction;
@@ -47,6 +48,7 @@
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.PrintStream;
+import java.util.Base64;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -327,6 +329,15 @@
       stringBuilder.append("  ]\n");
     }
 
+    if (options.includeFileWriteContents && action instanceof FileWriteAction) {
+      FileWriteAction fileWriteAction = (FileWriteAction) action;
+      stringBuilder
+          .append("  FileWriteContents: [")
+          .append(
+              Base64.getEncoder().encodeToString(fileWriteAction.getFileContents().getBytes(UTF_8)))
+          .append("]");
+    }
+
     stringBuilder.append('\n');
 
     printStream.write(stringBuilder.toString().getBytes(UTF_8));
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 58a841a..0117996 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
@@ -59,6 +59,14 @@
   public boolean includeParamFiles;
 
   @Option(
+      name = "include_file_write_contents",
+      defaultValue = "false",
+      documentationCategory = OptionDocumentationCategory.QUERY,
+      effectTags = {OptionEffectTag.TERMINAL_OUTPUT},
+      help = "Include the file contents for the FileWrite action (potentially large). ")
+  public boolean includeFileWriteContents;
+
+  @Option(
       name = "skyframe_state",
       defaultValue = "false",
       documentationCategory = OptionDocumentationCategory.QUERY,
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/actiongraph/v2/ActionGraphDump.java b/src/main/java/com/google/devtools/build/lib/skyframe/actiongraph/v2/ActionGraphDump.java
index c1a443f..c8fd5be 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/actiongraph/v2/ActionGraphDump.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/actiongraph/v2/ActionGraphDump.java
@@ -29,6 +29,7 @@
 import com.google.devtools.build.lib.analysis.AspectValue;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.ConfiguredTargetValue;
+import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
 import com.google.devtools.build.lib.analysis.actions.ParameterFileWriteAction;
 import com.google.devtools.build.lib.analysis.actions.SpawnAction;
 import com.google.devtools.build.lib.analysis.actions.Substitution;
@@ -64,6 +65,7 @@
   private final boolean includeActionCmdLine;
   private final boolean includeArtifacts;
   private final boolean includeParamFiles;
+  private final boolean includeFileWriteContents;
   private final AqueryOutputHandler aqueryOutputHandler;
 
   private Map<String, Iterable<String>> paramFileNameToContentMap;
@@ -74,6 +76,7 @@
       AqueryActionFilter actionFilters,
       boolean includeParamFiles,
       boolean deduplicateDepsets,
+      boolean includeFileWriteContents,
       AqueryOutputHandler aqueryOutputHandler) {
     this(
         /* actionGraphTargets= */ ImmutableList.of("..."),
@@ -82,6 +85,7 @@
         actionFilters,
         includeParamFiles,
         deduplicateDepsets,
+        includeFileWriteContents,
         aqueryOutputHandler);
   }
 
@@ -92,12 +96,14 @@
       AqueryActionFilter actionFilters,
       boolean includeParamFiles,
       boolean deduplicateDepsets,
+      boolean includeFileWriteContents,
       AqueryOutputHandler aqueryOutputHandler) {
     this.actionGraphTargets = ImmutableSet.copyOf(actionGraphTargets);
     this.includeActionCmdLine = includeActionCmdLine;
     this.includeArtifacts = includeArtifacts;
     this.actionFilters = actionFilters;
     this.includeParamFiles = includeParamFiles;
+    this.includeFileWriteContents = includeFileWriteContents;
     this.aqueryOutputHandler = aqueryOutputHandler;
 
     KnownRuleClassStrings knownRuleClassStrings = new KnownRuleClassStrings(aqueryOutputHandler);
@@ -177,6 +183,11 @@
       actionBuilder.addAllArguments(commandAction.getArguments());
     }
 
+    if (includeFileWriteContents && action instanceof FileWriteAction) {
+      FileWriteAction fileWriteAction = (FileWriteAction) action;
+      actionBuilder.setFileContents(fileWriteAction.getFileContents());
+    }
+
     // Include the content of param files in output.
     if (includeParamFiles) {
       // Assumption: if an Action takes a params file as an input, it will be used
diff --git a/src/main/protobuf/analysis_v2.proto b/src/main/protobuf/analysis_v2.proto
index d04916b..d3ae80b 100644
--- a/src/main/protobuf/analysis_v2.proto
+++ b/src/main/protobuf/analysis_v2.proto
@@ -112,6 +112,10 @@
   // the string to be substituted and the value is the string to be substituted
   // to.
   repeated KeyValuePair substitutions = 16;
+
+  // The contents of the file for the actions.write() action
+  // (guarded by the --include_file_write_contents flag).
+  string file_contents = 17;
 }
 
 // Represents a single target (without configuration information) that is
diff --git a/src/test/shell/integration/aquery_test.sh b/src/test/shell/integration/aquery_test.sh
index 39522d0..eee4a15 100755
--- a/src/test/shell/integration/aquery_test.sh
+++ b/src/test/shell/integration/aquery_test.sh
@@ -1599,6 +1599,41 @@
   assert_contains 'Outputs: \[.*/output-\\u00FCn\\u00EFc\\u00F6d\\u00EB.txt' output
 }
 
+function test_file_write() {
+  local pkg="${FUNCNAME[0]}"
+  mkdir -p "$pkg" || fail "mkdir -p $pkg"
+  cat > "$pkg/rule.bzl" <<'EOF'
+def _impl(ctx):
+    ctx.actions.write(content = "hello world", output = ctx.outputs.out)
+
+hello = rule(
+    implementation = _impl,
+    attrs = {
+    },
+    outputs = {"out": "%{name}.count"},
+)
+EOF
+  cat > "$pkg/BUILD" <<'EOF'
+load(":rule.bzl", "hello")
+
+hello(
+    name = 'bar',
+)
+EOF
+
+  bazel aquery --output=text --include_file_write_contents "//$pkg:bar" > output 2> "$TEST_log" \
+    || fail "Expected success"
+  cat output >> "$TEST_log"
+  assert_contains "Mnemonic: FileWrite" output
+  # FileWrite contents is  base64-encoded 'hello world'
+  assert_contains "FileWriteContents: \[aGVsbG8gd29ybGQ=\]" output
+
+  bazel aquery --output=textproto --include_file_write_contents "//$pkg:bar" > output 2> "$TEST_log" \
+    || fail "Expected success"
+  cat output >> "$TEST_log"
+  assert_contains 'file_contents: "hello world"' output
+}
+
 # TODO(bazel-team): The non-text aquery output formats don't correctly handle
 # non-ASCII fields (input/output paths, environment variables, etc).
 function DISABLED_test_unicode_textproto() {