Output template content and substitutions of TemplateExpansionAction actions in aquery result.

PiperOrigin-RevId: 405780883
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/Template.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/Template.java
index fa24165..28ecf53 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/actions/Template.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/Template.java
@@ -48,6 +48,11 @@
    */
   protected abstract String getKey();
 
+  @Override
+  public String toString() {
+    return getKey();
+  }
+
   private static final class ErrorTemplate extends Template {
     private final IOException e;
     private final String templateName;
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 8e8486c..32b665d 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/query2/BUILD
@@ -28,6 +28,8 @@
         "//src/main/java/com/google/devtools/build/lib/actions:commandline_item",
         "//src/main/java/com/google/devtools/build/lib/actions:file_metadata",
         "//src/main/java/com/google/devtools/build/lib/analysis:actions/parameter_file_write_action",
+        "//src/main/java/com/google/devtools/build/lib/analysis:actions/substitution",
+        "//src/main/java/com/google/devtools/build/lib/analysis:actions/template_expansion_action",
         "//src/main/java/com/google/devtools/build/lib/analysis:analysis_cluster",
         "//src/main/java/com/google/devtools/build/lib/analysis:config/build_configuration",
         "//src/main/java/com/google/devtools/build/lib/analysis:config/build_options",
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 78fe828..19b7fb8 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
@@ -29,6 +29,8 @@
 import com.google.devtools.build.lib.analysis.AspectValue;
 import com.google.devtools.build.lib.analysis.ConfiguredTargetValue;
 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;
 import com.google.devtools.build.lib.buildeventstream.BuildEvent;
 import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos;
 import com.google.devtools.build.lib.events.ExtendedEventHandler;
@@ -298,6 +300,23 @@
           .append("}\n");
     }
 
+    if (action instanceof TemplateExpansionAction) {
+      stringBuilder
+          .append("  Template: ")
+          .append(((TemplateExpansionAction) action).getTemplate())
+          .append("\n");
+      stringBuilder.append("  Substitutions: [\n");
+      for (Substitution substitution : ((TemplateExpansionAction) action).getSubstitutions()) {
+        stringBuilder
+            .append("    {")
+            .append(substitution.getKey())
+            .append(": ")
+            .append(substitution.getValue())
+            .append("}\n");
+      }
+      stringBuilder.append("  ]\n");
+    }
+
     stringBuilder.append('\n');
 
     printStream.write(stringBuilder.toString().getBytes(UTF_8));
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/BUILD b/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
index 99aa2d0..78f260b 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
@@ -228,6 +228,8 @@
         "//src/main/java/com/google/devtools/build/lib/actions:thread_state_receiver",
         "//src/main/java/com/google/devtools/build/lib/actionsketch:action_sketch",
         "//src/main/java/com/google/devtools/build/lib/analysis:actions/parameter_file_write_action",
+        "//src/main/java/com/google/devtools/build/lib/analysis:actions/substitution",
+        "//src/main/java/com/google/devtools/build/lib/analysis:actions/template_expansion_action",
         "//src/main/java/com/google/devtools/build/lib/analysis:analysis_cluster",
         "//src/main/java/com/google/devtools/build/lib/analysis:analysis_options",
         "//src/main/java/com/google/devtools/build/lib/analysis:aspect_collection",
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 5cc56c9..53f6879 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
@@ -30,6 +30,8 @@
 import com.google.devtools.build.lib.analysis.ConfiguredTargetValue;
 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;
+import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction;
 import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget;
 import com.google.devtools.build.lib.buildeventstream.BuildEvent;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
@@ -235,6 +237,16 @@
           knownArtifacts.dataToIdAndStreamOutputProto(action.getPrimaryOutput()));
     }
 
+    if (action instanceof TemplateExpansionAction) {
+      actionBuilder.setTemplateContent(((TemplateExpansionAction) action).getTemplate().toString());
+      for (Substitution substitution : ((TemplateExpansionAction) action).getSubstitutions()) {
+        actionBuilder.addSubstitutions(
+            AnalysisProtosV2.KeyValuePair.newBuilder()
+                .setKey(substitution.getKey())
+                .setValue(substitution.getValue()));
+      }
+    }
+
     aqueryOutputHandler.outputAction(actionBuilder.build());
   }
 
diff --git a/src/main/protobuf/analysis_v2.proto b/src/main/protobuf/analysis_v2.proto
index a828925..b886fde 100644
--- a/src/main/protobuf/analysis_v2.proto
+++ b/src/main/protobuf/analysis_v2.proto
@@ -104,6 +104,14 @@
   // The execution platform for this action. Empty if the action has no
   // execution platform.
   string execution_platform = 14;
+
+  // The template content of the action, if it is TemplateExpand action.
+  string template_content = 15;
+
+  // The list of substitution should be performed on the template. The key is
+  // the string to be substituted and the value is the string to be substituted
+  // to.
+  repeated KeyValuePair substitutions = 16;
 }
 
 // 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 f51ee77..f69ada1 100755
--- a/src/test/shell/integration/aquery_test.sh
+++ b/src/test/shell/integration/aquery_test.sh
@@ -1406,6 +1406,89 @@
   expect_log "-fastbuild: 1"
 }
 
+function test_aquery_include_template_substitution_for_template_expand_of_py_binary() {
+  local pkg="${FUNCNAME[0]}"
+  mkdir -p "$pkg" || fail "mkdir -p $pkg"
+  cat > "$pkg/BUILD" <<'EOF'
+py_binary(
+    name='foo',
+    srcs=['foo.py']
+)
+EOF
+
+  # aquery returns template content and substitutions in TemplateExpand actions
+  # of py_binary targets.
+  QUERY="//$pkg:foo"
+
+  bazel aquery --output=text ${QUERY} > output 2> "$TEST_log" \
+    || fail "Expected success"
+  cat output >> "$TEST_log"
+
+  assert_contains "PYTHON_BINARY = '%python_binary%'" output
+  assert_contains "{%python_binary%:" output
+
+  bazel aquery --output=jsonproto ${QUERY} > output 2> "$TEST_log" \
+    || fail "Expected success"
+
+  assert_contains "\"templateContent\":" output
+  assert_contains "\"key\": \"%python_binary%\"" output
+}
+
+function test_aquery_include_template_substitution_for_template_expand_action() {
+  local pkg="${FUNCNAME[0]}"
+  mkdir -p "$pkg" || fail "mkdir -p $pkg"
+
+  cat > "$pkg/template.txt" <<'EOF'
+The token should be substituted: {TOKEN1}
+EOF
+
+  cat > "$pkg/test.bzl" <<'EOF'
+def test_template(**kwargs):
+    _test_template(
+        **kwargs
+    )
+def _test_template_impl(ctx):
+    ctx.actions.expand_template(
+        template = ctx.file.template,
+        output = ctx.outputs.output,
+        substitutions = {
+            "{TOKEN1}": "123456",
+        },
+    )
+_test_template = rule(
+    implementation = _test_template_impl,
+    attrs = {
+        "template": attr.label(
+            allow_single_file = True,
+        ),
+        "output": attr.output(mandatory = True),
+    },
+)
+EOF
+
+  cat > "$pkg/BUILD" <<'EOF'
+load('test.bzl', 'test_template')
+test_template(name='foo', template='template.txt', output='output.txt')
+EOF
+
+  # aquery returns template content and substitutions of TemplateExpand actions.
+  QUERY="//$pkg:foo"
+
+  bazel aquery --output=text ${QUERY} > output 2> "$TEST_log" \
+    || fail "Expected success"
+  cat output >> "$TEST_log"
+
+  assert_contains "Template: ARTIFACT: $pkg/template.txt" output
+  assert_contains "{{TOKEN1}: 123456}" output
+
+  bazel aquery --output=jsonproto ${QUERY} > output 2> "$TEST_log" \
+    || fail "Expected success"
+
+  assert_contains "\"templateContent\": \"ARTIFACT: $pkg/template.txt\"" output
+  assert_contains "\"key\": \"{TOKEN1}\"" output
+  assert_contains "\"value\": \"123456\"" output
+}
+
 # Usage: assert_matches expected_pattern actual
 function assert_matches() {
   [[ "$2" =~ $1 ]] || fail "Expected to match '$1', was: $2"