Add make_executable to aquery results

Android converts bazel actions to a ninja build graph by querying the
actions with aquery, but was always outputting non-executable FileWrite
files because the executable bit was not included in the aquery results.

RELNOTES: Added whether or not a FileWrite action's output is executable to the aquery results
PiperOrigin-RevId: 559485729
Change-Id: Id3ac9d77c884bd92645b40be48f774898664dac8
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/AbstractFileWriteAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/AbstractFileWriteAction.java
index a0da40e..a6bf082 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/actions/AbstractFileWriteAction.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/AbstractFileWriteAction.java
@@ -113,5 +113,7 @@
    */
   public interface FileContentsProvider {
     String getFileContents(@Nullable EventHandler eventHandler) throws IOException;
+
+    public boolean makeExecutable();
   }
 }
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 99609af..a659fbb 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
@@ -317,14 +317,17 @@
       stringBuilder.append("  ]\n");
     }
 
-    if (options.includeFileWriteContents
-        && action instanceof AbstractFileWriteAction.FileContentsProvider) {
-      String contents =
-          ((AbstractFileWriteAction.FileContentsProvider) action).getFileContents(eventHandler);
-      stringBuilder
-          .append("  FileWriteContents: [")
-          .append(Base64.getEncoder().encodeToString(contents.getBytes(UTF_8)))
-          .append("]\n");
+    if (action instanceof AbstractFileWriteAction.FileContentsProvider) {
+      AbstractFileWriteAction.FileContentsProvider fileAction =
+          (AbstractFileWriteAction.FileContentsProvider) action;
+      stringBuilder.append(String.format("  IsExecutable: %b\n", fileAction.makeExecutable()));
+      if (options.includeFileWriteContents) {
+        String contents = fileAction.getFileContents(eventHandler);
+        stringBuilder
+            .append("  FileWriteContents: [")
+            .append(Base64.getEncoder().encodeToString(contents.getBytes(UTF_8)))
+            .append("]\n");
+      }
     }
 
     if (action instanceof UnresolvedSymlinkAction) {
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 0dc5c0b..9f247f5 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
@@ -195,13 +195,17 @@
       actionBuilder.addAllArguments(commandAction.getArguments());
     }
 
-    if (includeFileWriteContents
-        && action instanceof AbstractFileWriteAction.FileContentsProvider) {
-      String contents =
-          ((AbstractFileWriteAction.FileContentsProvider) action).getFileContents(eventHandler);
-      actionBuilder.setFileContents(contents);
+    if (action instanceof AbstractFileWriteAction.FileContentsProvider) {
+      actionBuilder.setIsExecutable(
+          ((AbstractFileWriteAction.FileContentsProvider) action).makeExecutable());
+      if (includeFileWriteContents) {
+        String contents =
+            ((AbstractFileWriteAction.FileContentsProvider) action).getFileContents(eventHandler);
+        actionBuilder.setFileContents(contents);
+      }
     }
 
+
     if (action instanceof UnresolvedSymlinkAction) {
       actionBuilder.setUnresolvedSymlinkTarget(
           ((UnresolvedSymlinkAction) action).getTarget().toString());
diff --git a/src/main/protobuf/analysis_v2.proto b/src/main/protobuf/analysis_v2.proto
index cfe4d76..20fa8e7 100644
--- a/src/main/protobuf/analysis_v2.proto
+++ b/src/main/protobuf/analysis_v2.proto
@@ -113,13 +113,17 @@
   // to.
   repeated KeyValuePair substitutions = 16;
 
-  // The contents of the file for the actions.write() action
+  // The contents of the file for the ctx.actions.write() action
   // (guarded by the --include_file_write_contents flag).
   string file_contents = 17;
 
   // The target of the symlink created by UnresolvedSymlink actions.
   // For regular Symlink actions, the target is represented as an input.
   string unresolved_symlink_target = 18;
+
+  // If FileWrite actions should make their output executable.
+  // (ctx.actions.write(is_executable=True))
+  bool is_executable = 19;
 }
 
 // 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 1fa9009..70fc640 100755
--- a/src/test/shell/integration/aquery_test.sh
+++ b/src/test/shell/integration/aquery_test.sh
@@ -1703,11 +1703,54 @@
   assert_contains "Mnemonic: FileWrite" output
   # FileWrite contents is  base64-encoded 'hello world'
   assert_contains "FileWriteContents: \[aGVsbG8gd29ybGQ=\]" output
+  assert_contains "^ *IsExecutable: false" 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
+  assert_not_contains "^is_executable: true" output
+}
+
+function test_file_write_is_executable() {
+  local pkg="${FUNCNAME[0]}"
+  mkdir -p "$pkg" || fail "mkdir -p $pkg"
+  touch "$pkg/foo.sh"
+  cat > "$pkg/write_executable_file.bzl" <<'EOF'
+def _impl(ctx):
+    out = ctx.actions.declare_symlink(ctx.label.name)
+    ctx.actions.write(
+        output = out,
+        content = "Hello",
+        is_executable = True,
+    )
+    return [
+        DefaultInfo(files = depset([out]))
+    ]
+write_executable_file = rule(
+    implementation = _impl,
+    attrs = {
+        "path": attr.string(),
+    },
+)
+EOF
+  cat > "$pkg/BUILD" <<'EOF'
+load(":write_executable_file.bzl", "write_executable_file")
+write_executable_file(
+  name = "foo",
+  path = "bar/baz.txt",
+)
+EOF
+  bazel aquery --output=textproto \
+     "//$pkg:foo" >output 2> "$TEST_log" || fail "Expected success"
+  cat output >> "$TEST_log"
+  assert_contains "^is_executable: true" output
+
+  bazel aquery --output=text "//$pkg:foo" | \
+    sed -nr '/Mnemonic: FileWrite/,/^ *$/p' >output \
+      2> "$TEST_log" || fail "Expected success"
+  cat output >> "$TEST_log"
+  assert_contains "^ *IsExecutable: true" output
 }
 
 function test_source_symlink_manifest() {