Add optional 'mnemonic' var to actions

This will replace the "ACTION" prefix for the ninja
description of a given action.

Change-Id: Ib394f193e0aaa9ff3453c7b8e10a07a2b84150ad
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/15260
Reviewed-by: Takuto Ikuta <tikuta@google.com>
Commit-Queue: Takuto Ikuta <tikuta@google.com>
diff --git a/docs/reference.md b/docs/reference.md
index 76ef2a9..093c711 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -132,6 +132,7 @@
     *   [lib_dirs: [directory list] Additional library directories.](#var_lib_dirs)
     *   [libs: [string list] Additional libraries to link.](#var_libs)
     *   [metadata: [scope] Metadata of this target.](#var_metadata)
+    *   [mnemonic: [string] Prefix displayed when ninja runs this action.](#var_mnemonic)
     *   [module_name: [string] The name for the compiled module.](#var_module_name)
     *   [output_conversion: Data format for generated_file targets.](#var_output_conversion)
     *   [output_dir: [directory] Directory to put output file in.](#var_output_dir)
@@ -1416,9 +1417,9 @@
            output_extension, output_name, public, sources, testonly,
            visibility
   Action variables: args, bridge_header, configs, data, depfile,
-                    framework_dirs, inputs, module_deps, module_name,
-                    outputs*, pool, response_file_contents, script*,
-                    sources
+                    framework_dirs, inputs, mnemonic, module_deps,
+                    module_name, outputs*, pool, response_file_contents,
+                    script*, sources
   * = required
 ```
 
@@ -1517,9 +1518,9 @@
            output_extension, output_name, public, sources, testonly,
            visibility
   Action variables: args, bridge_header, configs, data, depfile,
-                    framework_dirs, inputs, module_deps, module_name,
-                    outputs*, pool, response_file_contents, script*,
-                    sources
+                    framework_dirs, inputs, mnemonic, module_deps,
+                    module_name, outputs*, pool, response_file_contents,
+                    script*, sources
   * = required
 ```
 
@@ -1532,6 +1533,10 @@
     script = "idl_processor.py"
     sources = [ "foo.idl", "bar.idl" ]
 
+    # Causes ninja to output "IDL <label>" rather than the default
+    # "ACTION <label>" when building this action.
+    mnemonic = "IDL"
+
     # Our script reads this file each time, so we need to list it as a
     # dependency so we can rebuild if it changes.
     inputs = [ "my_configuration.txt" ]
@@ -2454,6 +2459,13 @@
   which will return true or false depending on whether bar is defined in the
   named scope foo. It will throw an error if foo is not defined or is not a
   scope.
+
+  You can also check a named scope using a subscript string expression:
+    defined(foo[bar + "_name"])
+  Which will return true or false depending on whether the subscript
+  expression expands to the name of a member of the scope foo. It will
+  throw an error if foo is not defined or is not a scope, or if the
+  expression does not expand to a string, or if it is an empty string.
 ```
 
 #### **Example**
@@ -6058,6 +6070,17 @@
     }
   }
 ```
+### <a name="var_mnemonic"></a>**mnemonic**: [string] Prefix displayed when ninja runs this action.
+
+```
+  Tools in GN can set their ninja "description" which is displayed when
+  building a target. These are commonly set with the format "CXX $output"
+  or "LINK $label". By default, all GN actions will have the description
+  "ACTION $label". Setting a mnemonic will override the "ACTION" prefix
+  with another string, but the label will still be unconditionally displayed.
+
+  Whitespace is not allowed within a mnemonic.
+```
 ### <a name="var_module_name"></a>**module_name**: [string] The name for the compiled module.
 
 ```
diff --git a/misc/vim/syntax/gn.vim b/misc/vim/syntax/gn.vim
index df2d0d8..2719560 100644
--- a/misc/vim/syntax/gn.vim
+++ b/misc/vim/syntax/gn.vim
@@ -51,7 +51,7 @@
 syn keyword     gnVariable lib_dirs libs output_extension output_name outputs
 syn keyword     gnVariable public public_configs public_deps scripte sources
 syn keyword     gnVariable testonly visibility contents output_conversion rebase
-syn keyword     gnVariable walk_keys gen_deps
+syn keyword     gnVariable walk_keys gen_deps mnemonic
 hi def link     gnVariable          Keyword
 
 " Strings
diff --git a/src/gn/action_target_generator.cc b/src/gn/action_target_generator.cc
index 513015b..9cc9c83 100644
--- a/src/gn/action_target_generator.cc
+++ b/src/gn/action_target_generator.cc
@@ -5,6 +5,8 @@
 #include "gn/action_target_generator.h"
 
 #include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
 #include "gn/build_settings.h"
 #include "gn/config_values_generator.h"
 #include "gn/err.h"
@@ -58,6 +60,9 @@
   if (!FillDepfile())
     return;
 
+  if (!FillMnemonic())
+    return;
+
   if (!FillPool())
     return;
 
@@ -168,6 +173,29 @@
   return true;
 }
 
+bool ActionTargetGenerator::FillMnemonic() {
+  const Value* value = scope_->GetValue(variables::kMnemonic, true);
+  if (!value)
+    return true;
+
+  if (!value->VerifyTypeIs(Value::STRING, err_))
+    return false;
+
+  auto s = value->string_value();
+  if (!base::IsStringUTF8(s)) {
+    *err_ = Err(value->origin(), "Mnemonics must be valid UTF-8");
+    return false;
+  }
+  auto widestr = base::UTF8ToUTF16(s);
+  if (std::any_of(widestr.begin(), widestr.end(), base::IsUnicodeWhitespace)) {
+    *err_ = Err(value->origin(), "Mnemonics can't contain whitespace");
+    return false;
+  }
+
+  target_->action_values().mnemonic() = std::move(s);
+  return true;
+}
+
 bool ActionTargetGenerator::FillPool() {
   const Value* value = scope_->GetValue(variables::kPool, true);
   if (!value)
diff --git a/src/gn/action_target_generator.h b/src/gn/action_target_generator.h
index 9073cd7..6cbe8ae 100644
--- a/src/gn/action_target_generator.h
+++ b/src/gn/action_target_generator.h
@@ -26,6 +26,7 @@
   bool FillScriptArgs();
   bool FillResponseFileContents();
   bool FillDepfile();
+  bool FillMnemonic();
   bool FillPool();
   bool FillInputs();
 
diff --git a/src/gn/action_values.h b/src/gn/action_values.h
index 9daf918..5ac1a7a 100644
--- a/src/gn/action_values.h
+++ b/src/gn/action_values.h
@@ -44,6 +44,10 @@
   bool has_depfile() const { return !depfile_.ranges().empty(); }
   void set_depfile(const SubstitutionPattern& depfile) { depfile_ = depfile; }
 
+  // prefix to the ninja output description for this action.
+  std::string& mnemonic() { return mnemonic_; }
+  const std::string& mnemonic() const { return mnemonic_; }
+
   // Response file contents. Empty means no response file.
   SubstitutionList& rsp_file_contents() { return rsp_file_contents_; }
   const SubstitutionList& rsp_file_contents() const {
@@ -57,6 +61,7 @@
   SubstitutionList outputs_;
   SubstitutionPattern depfile_;
   SubstitutionList rsp_file_contents_;
+  std::string mnemonic_;
 
   ActionValues(const ActionValues&) = delete;
   ActionValues& operator=(const ActionValues&) = delete;
diff --git a/src/gn/command_desc.cc b/src/gn/command_desc.cc
index e3cd1eb..99f7e45 100644
--- a/src/gn/command_desc.cc
+++ b/src/gn/command_desc.cc
@@ -308,6 +308,7 @@
           {variables::kRustCrateRoot, DefaultHandler},
           {variables::kSwiftModuleName, DefaultHandler},
           {variables::kSwiftBridgeHeader, DefaultHandler},
+          {variables::kMnemonic, DefaultHandler},
           {"runtime_deps", DefaultHandler}};
 }
 
diff --git a/src/gn/functions_target.cc b/src/gn/functions_target.cc
index 4ca2c10..72c95e2 100644
--- a/src/gn/functions_target.cc
+++ b/src/gn/functions_target.cc
@@ -28,9 +28,9 @@
   "  Rust variables: aliased_deps, crate_root, crate_name, crate_type\n"
 #define ACTION_VARS \
   "  Action variables: args, bridge_header, configs, data, depfile,\n" \
-  "                    framework_dirs, inputs, module_deps, module_name,\n" \
-  "                    outputs*, pool, response_file_contents, script*,\n" \
-  "                    sources\n"
+  "                    framework_dirs, inputs, mnemonic, module_deps,\n" \
+  "                    module_name, outputs*, pool, response_file_contents,\n" \
+  "                    script*, sources\n"
 
 namespace functions {
 
@@ -257,6 +257,10 @@
     script = "idl_processor.py"
     sources = [ "foo.idl", "bar.idl" ]
 
+    # Causes ninja to output "IDL <label>" rather than the default
+    # "ACTION <label>" when building this action.
+    mnemonic = "IDL"
+
     # Our script reads this file each time, so we need to list it as a
     # dependency so we can rebuild if it changes.
     inputs = [ "my_configuration.txt" ]
diff --git a/src/gn/ninja_action_target_writer.cc b/src/gn/ninja_action_target_writer.cc
index c33fc6e..f4f0867 100644
--- a/src/gn/ninja_action_target_writer.cc
+++ b/src/gn/ninja_action_target_writer.cc
@@ -156,7 +156,10 @@
     SubstitutionWriter::WriteWithNinjaVariables(arg, args_escape_options, out_);
   }
   out_ << std::endl;
-  out_ << "  description = ACTION " << target_label << std::endl;
+  auto mnemonic = target_->action_values().mnemonic();
+  if (mnemonic.empty())
+    mnemonic = "ACTION";
+  out_ << "  description = " << mnemonic << " " << target_label << std::endl;
   out_ << "  restat = 1" << std::endl;
   const Tool* tool =
       target_->toolchain()->GetTool(GeneralTool::kGeneralToolAction);
diff --git a/src/gn/variables.cc b/src/gn/variables.cc
index 28f174d..07ce016 100644
--- a/src/gn/variables.cc
+++ b/src/gn/variables.cc
@@ -572,6 +572,21 @@
   See also "gn help action" and "gn help action_foreach".
 )";
 
+const char kMnemonic[] = "mnemonic";
+const char kMnemonic_HelpShort[] =
+    "mnemonic: [string] Prefix displayed when ninja runs this action.";
+const char kMnemonic_Help[] =
+    R"(mnemonic: [string] Prefix displayed when ninja runs this action.
+
+  Tools in GN can set their ninja "description" which is displayed when
+  building a target. These are commonly set with the format "CXX $output"
+  or "LINK $label". By default, all GN actions will have the description
+  "ACTION $label". Setting a mnemonic will override the "ACTION" prefix
+  with another string, but the label will still be unconditionally displayed.
+
+  Whitespace is not allowed within a mnemonic.
+)";
+
 const char kAssertNoDeps[] = "assert_no_deps";
 const char kAssertNoDeps_HelpShort[] =
     "assert_no_deps: [label pattern list] Ensure no deps on these targets.";
@@ -2330,6 +2345,7 @@
     INSERT_VARIABLE(Defines)
     INSERT_VARIABLE(Depfile)
     INSERT_VARIABLE(Deps)
+    INSERT_VARIABLE(Mnemonic)
     INSERT_VARIABLE(Externs)
     INSERT_VARIABLE(Friend)
     INSERT_VARIABLE(FrameworkDirs)
diff --git a/src/gn/variables.h b/src/gn/variables.h
index 169a5af..ac392d9 100644
--- a/src/gn/variables.h
+++ b/src/gn/variables.h
@@ -358,6 +358,10 @@
 extern const char kGenDeps_HelpShort[];
 extern const char kGenDeps_Help[];
 
+extern const char kMnemonic[];
+extern const char kMnemonic_HelpShort[];
+extern const char kMnemonic_Help[];
+
 // -----------------------------------------------------------------------------
 
 struct VariableInfo {