Support public_inputs in gn desc

This change adds support for public_inputs in `gn desc` and
`DescBuilder`.

Targets declaring `public_inputs` (such as `action` and `group`) will
now include their `public_inputs` in descriptions, supporting both
individual queries (`gn desc <out> <target> public_inputs`) and JSON
export (`--format=json` / `--ide=json`).

Change-Id: I2fa1925e2896c63101345f87cefd42727582ee32
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/25940
Commit-Queue: Takuto Ikuta <tikuta@google.com>
Reviewed-by: David Turner <digit@google.com>
diff --git a/docs/reference.md b/docs/reference.md
index 4fbc5d9..1e7743f 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -574,6 +574,7 @@
   output_conversion
   outputs
   public_configs
+  public_inputs
   public
   rebase
   script
diff --git a/src/gn/command_desc.cc b/src/gn/command_desc.cc
index 5b9dd2b..fc6fa93 100644
--- a/src/gn/command_desc.cc
+++ b/src/gn/command_desc.cc
@@ -276,6 +276,7 @@
           {variables::kSources, DefaultHandler},
           {variables::kPublic, PublicHandler},
           {variables::kInputs, DefaultHandler},
+          {variables::kPublicInputs, DefaultHandler},
           {variables::kConfigs, ConfigsHandler},
           {variables::kPublicConfigs, ConfigsHandler},
           {variables::kAllDependentConfigs, ConfigsHandler},
@@ -376,6 +377,7 @@
   HandleProperty(variables::kSwiftBridgeHeader, handler_map, v, dict);
   HandleProperty(variables::kPublic, handler_map, v, dict);
   HandleProperty(variables::kInputs, handler_map, v, dict);
+  HandleProperty(variables::kPublicInputs, handler_map, v, dict);
   HandleProperty(variables::kConfigs, handler_map, v, dict);
   HandleProperty(variables::kPublicConfigs, handler_map, v, dict);
   HandleProperty(variables::kAllDependentConfigs, handler_map, v, dict);
@@ -528,6 +530,7 @@
   output_conversion
   outputs
   public_configs
+  public_inputs
   public
   rebase
   script
diff --git a/src/gn/desc_builder.cc b/src/gn/desc_builder.cc
index 67d28af..a9f46f3 100644
--- a/src/gn/desc_builder.cc
+++ b/src/gn/desc_builder.cc
@@ -37,6 +37,7 @@
 //   "sources" : [ list of source files ],
 //   "public" : either "*" or [ list of public headers],
 //   "inputs" : [ list of inputs for target ],
+//   "public_inputs" : [ list of public inputs for target ],
 //   "configs" : [ list of configs for this target ],
 //   "public_configs" : [ list of public configs for this target],
 //   "all_dependent_configs", [ list of all dependent configs for this target],
@@ -435,6 +436,11 @@
         res->SetWithoutPathExpansion(variables::kInputs, RenderValue(inputs));
     }
 
+    if (what(variables::kPublicInputs) && !target_->public_inputs().empty()) {
+      res->SetWithoutPathExpansion(variables::kPublicInputs,
+                                   RenderValue(target_->public_inputs()));
+    }
+
     if (is_binary_output && what(variables::kConfigs) &&
         !target_->configs().empty()) {
       auto configs = std::make_unique<base::ListValue>();
diff --git a/src/gn/desc_builder_unittest.cc b/src/gn/desc_builder_unittest.cc
index 23f1772..ad03017 100644
--- a/src/gn/desc_builder_unittest.cc
+++ b/src/gn/desc_builder_unittest.cc
@@ -36,3 +36,54 @@
   ASSERT_EQ(1u, validations->GetList().size());
   EXPECT_EQ("//foo:val()", validations->GetList()[0].GetString());
 }
+
+TEST(DescBuilder, TargetWithPublicInputs) {
+  TestWithScope setup;
+  Err err;
+
+  Target group_target(setup.settings(), Label(SourceDir("//foo/"), "group"));
+  group_target.set_output_type(Target::GROUP);
+  group_target.visibility().SetPublic();
+  group_target.SetToolchain(setup.toolchain());
+  group_target.public_inputs().push_back(SourceFile("//foo/bar.d.ts"));
+  ASSERT_TRUE(group_target.OnResolved(&err));
+
+  // 1. Overall description includes public_inputs.
+  {
+    std::unique_ptr<base::DictionaryValue> desc =
+        DescBuilder::DescriptionForTarget(&group_target, "", false, false,
+                                          false);
+    base::Value* public_inputs = desc->FindKey("public_inputs");
+    ASSERT_TRUE(public_inputs);
+    ASSERT_TRUE(public_inputs->is_list());
+    ASSERT_EQ(1u, public_inputs->GetList().size());
+    EXPECT_EQ("//foo/bar.d.ts", public_inputs->GetList()[0].GetString());
+  }
+
+  // 2. Specific "public_inputs" query.
+  {
+    std::unique_ptr<base::DictionaryValue> desc =
+        DescBuilder::DescriptionForTarget(&group_target, "public_inputs", false,
+                                          false, false);
+    EXPECT_EQ(1u, desc->size());
+    base::Value* public_inputs = desc->FindKey("public_inputs");
+    ASSERT_TRUE(public_inputs);
+    ASSERT_TRUE(public_inputs->is_list());
+    ASSERT_EQ(1u, public_inputs->GetList().size());
+    EXPECT_EQ("//foo/bar.d.ts", public_inputs->GetList()[0].GetString());
+  }
+
+  // 3. Target without public_inputs does not have public_inputs in description.
+  Target empty_group(setup.settings(), Label(SourceDir("//foo/"), "empty"));
+  empty_group.set_output_type(Target::GROUP);
+  empty_group.visibility().SetPublic();
+  empty_group.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(empty_group.OnResolved(&err));
+
+  {
+    std::unique_ptr<base::DictionaryValue> desc =
+        DescBuilder::DescriptionForTarget(&empty_group, "", false, false,
+                                          false);
+    EXPECT_FALSE(desc->FindKey("public_inputs"));
+  }
+}