Introduce new built-in "phony" tool.

This CL is the first in a series that will allow GN to generate
smaller Ninja files, by using phone rules instead of stamp files
whenever possible. This is a split of the working proof-of-concept
available at https://gn-review.googlesource.com/c/gn/+/11380.

This CL does not change GN's behaviour but:

- It introduces the new BuiltinTool class used to expressed
  built-in toolchain tools that cannot be customized like "stamp"
  or "copy".

- It adds a single "phony" tool definition, and ensures it is
  added to each toolchain() instance automatically, but not
  listed when exporting the build graph to JSON.

  Note that nothing actually uses it at the moment, to make
  reviewing this CL easier.

- Add a new build settings flag, named "no_stamp_files". In
  the future, enabling it will allow generating phony rules
  instead of stamp files in Ninja outputs. For now, assert
  with an error message if it is set to avoid any misuse.

Note that Copyright notices mention 2020 because this work
was started a long time ago (thanks to rjascani@google.com's
work). They could be updated to 2022 without issues.

For testing the output of `gn gen` has been compared before
and after this CL was applied with the Fuchsia source tree
to verify that the output is the same.

Bug: 172
Change-Id: I8f1140d1d953d38c318e3def8a4ccf8bdc304d58
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/12864
Reviewed-by: Brett Wilson <brettw@google.com>
Reviewed-by: Brett Wilson <brettw@chromium.org>
Commit-Queue: Brett Wilson <brettw@chromium.org>
diff --git a/build/gen.py b/build/gen.py
index ae9c40d..5fd5783 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -576,6 +576,7 @@
         'src/gn/bundle_data.cc',
         'src/gn/bundle_data_target_generator.cc',
         'src/gn/bundle_file_rule.cc',
+        'src/gn/builtin_tool.cc',
         'src/gn/c_include_iterator.cc',
         'src/gn/c_substitution_type.cc',
         'src/gn/c_tool.cc',
diff --git a/src/gn/build_settings.h b/src/gn/build_settings.h
index 9a6c514..6c925e9 100644
--- a/src/gn/build_settings.h
+++ b/src/gn/build_settings.h
@@ -61,6 +61,15 @@
   }
   void set_ninja_required_version(Version v) { ninja_required_version_ = v; }
 
+  // The 'no_stamp_files' boolean flag can be set to generate Ninja files
+  // that use phony rules instead of stamp files in most cases. This reduces
+  // the size of the generated Ninja build plans, but requires Ninja 1.11
+  // or greater to properly process them.
+  bool no_stamp_files() const { return no_stamp_files_; }
+  void set_no_stamp_files(bool no_stamp_files) {
+    no_stamp_files_ = no_stamp_files;
+  }
+
   const SourceFile& build_config_file() const { return build_config_file_; }
   void set_build_config_file(const SourceFile& f) { build_config_file_ = f; }
 
@@ -134,6 +143,7 @@
 
   // See 40045b9 for the reason behind using 1.7.2 as the default version.
   Version ninja_required_version_{1, 7, 2};
+  bool no_stamp_files_ = false;
 
   SourceFile build_config_file_;
   SourceFile arg_file_template_path_;
diff --git a/src/gn/builtin_tool.cc b/src/gn/builtin_tool.cc
new file mode 100644
index 0000000..e9f17ef
--- /dev/null
+++ b/src/gn/builtin_tool.cc
@@ -0,0 +1,47 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/builtin_tool.h"
+#include "base/logging.h"
+#include "gn/target.h"
+
+// static
+const char BuiltinTool::kBuiltinToolPhony[] = "phony";
+
+BuiltinTool::BuiltinTool(const char* n) : Tool(n) {
+  CHECK(ValidateName(n));
+  // Unlike regular tools, which are read from a file,
+  // builtin-tools are always ready to go and do not need
+  // phased construction.
+  SetToolComplete();
+}
+
+BuiltinTool::~BuiltinTool() = default;
+
+BuiltinTool* BuiltinTool::AsBuiltin() {
+  return this;
+}
+const BuiltinTool* BuiltinTool::AsBuiltin() const {
+  return this;
+}
+
+bool BuiltinTool::ValidateName(const char* name) const {
+  return name == kBuiltinToolPhony;
+}
+
+void BuiltinTool::SetComplete() {
+  // Already performed in constructor
+}
+
+bool BuiltinTool::InitTool(Scope* scope, Toolchain* toolchain, Err* err) {
+  // Initialize default vars.
+  return Tool::InitTool(scope, toolchain, err);
+}
+
+bool BuiltinTool::ValidateSubstitution(const Substitution* sub_type) const {
+  if (name_ == kBuiltinToolPhony)
+    return IsValidToolSubstitution(sub_type);
+  NOTREACHED();
+  return false;
+}
diff --git a/src/gn/builtin_tool.h b/src/gn/builtin_tool.h
new file mode 100644
index 0000000..0aff49d
--- /dev/null
+++ b/src/gn/builtin_tool.h
@@ -0,0 +1,41 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_BUILTIN_TOOL_H_
+#define TOOLS_GN_BUILTIN_TOOL_H_
+
+#include <string>
+
+#include "gn/substitution_list.h"
+#include "gn/substitution_pattern.h"
+#include "gn/tool.h"
+
+// A built-in tool that is always available regardless of toolchain. So far, the
+// only example of this is the phony rule that ninja provides.
+class BuiltinTool : public Tool {
+ public:
+  // Builtin tools
+  static const char kBuiltinToolPhony[];
+
+  // Explicit constructor. Note that |name| must be one of the kBuiltinToolXXX
+  // constant pointers defined above.
+  explicit BuiltinTool(const char* name);
+  ~BuiltinTool();
+
+  // Manual RTTI and required functions ---------------------------------------
+
+  bool InitTool(Scope* block_scope, Toolchain* toolchain, Err* err);
+  bool ValidateName(const char* name) const override;
+  void SetComplete() override;
+  bool ValidateSubstitution(const Substitution* sub_type) const override;
+
+  BuiltinTool* AsBuiltin() override;
+  const BuiltinTool* AsBuiltin() const override;
+
+ private:
+  BuiltinTool(const BuiltinTool&) = delete;
+  BuiltinTool& operator=(const BuiltinTool&) = delete;
+};
+
+#endif  // TOOLS_GN_BUILTIN_TOOL_H_
diff --git a/src/gn/filesystem_utils.cc b/src/gn/filesystem_utils.cc
index 6471378..6de3fbd 100644
--- a/src/gn/filesystem_utils.cc
+++ b/src/gn/filesystem_utils.cc
@@ -1006,6 +1006,8 @@
     result.value().append("gen/");
   else if (type == BuildDirType::OBJ)
     result.value().append("obj/");
+  else if (type == BuildDirType::PHONY)
+    result.value().append("phony/");
   return result;
 }
 
diff --git a/src/gn/filesystem_utils.h b/src/gn/filesystem_utils.h
index 2c826b5..590f9e3 100644
--- a/src/gn/filesystem_utils.h
+++ b/src/gn/filesystem_utils.h
@@ -222,6 +222,12 @@
 
   // Output file directory.
   OBJ,
+
+  // Phony file directory. As the name implies, this is not a real file
+  // directory, but a path that is used for the declaration of phony targets.
+  // This is done to avoid duplicate target names between real files and phony
+  // aliases that point to them.
+  PHONY,
 };
 
 // In different contexts, different information is known about the toolchain in
diff --git a/src/gn/json_project_writer.cc b/src/gn/json_project_writer.cc
index a54e254..ba23749 100644
--- a/src/gn/json_project_writer.cc
+++ b/src/gn/json_project_writer.cc
@@ -455,6 +455,9 @@
       base::Value toolchain{base::Value::Type::DICTIONARY};
       const auto& tools = tool_chain_kv.second->tools();
       for (const auto& tool_kv : tools) {
+        // Do not list builtin tools
+        if (tool_kv.second->AsBuiltin())
+          continue;
         base::Value tool_info{base::Value::Type::DICTIONARY};
         auto setIfNotEmptry = [&](const auto& key, const auto& value) {
           if (value.size())
diff --git a/src/gn/ninja_toolchain_writer.cc b/src/gn/ninja_toolchain_writer.cc
index 7be7719..2314bae 100644
--- a/src/gn/ninja_toolchain_writer.cc
+++ b/src/gn/ninja_toolchain_writer.cc
@@ -9,6 +9,7 @@
 #include "base/files/file_util.h"
 #include "base/strings/stringize_macros.h"
 #include "gn/build_settings.h"
+#include "gn/builtin_tool.h"
 #include "gn/c_tool.h"
 #include "gn/filesystem_utils.h"
 #include "gn/general_tool.h"
@@ -43,8 +44,10 @@
   std::string rule_prefix = GetNinjaRulePrefixForToolchain(settings_);
 
   for (const auto& tool : toolchain_->tools()) {
-    if (tool.second->name() == GeneralTool::kGeneralToolAction)
+    if (tool.second->name() == GeneralTool::kGeneralToolAction ||
+        tool.second->AsBuiltin()) {
       continue;
+    }
     WriteToolRule(tool.second.get(), rule_prefix);
   }
   out_ << std::endl;
diff --git a/src/gn/setup.cc b/src/gn/setup.cc
index b6fdee2..9dba72e 100644
--- a/src/gn/setup.cc
+++ b/src/gn/setup.cc
@@ -1024,5 +1024,16 @@
     build_settings_.set_arg_file_template_path(path);
   }
 
+  // No stamp files.
+  const Value* no_stamp_files_value =
+      dotfile_scope_.GetValue("no_stamp_files", true);
+  if (no_stamp_files_value) {
+    if (!no_stamp_files_value->VerifyTypeIs(Value::BOOLEAN, err)) {
+      return false;
+    }
+    build_settings_.set_no_stamp_files(no_stamp_files_value->boolean_value());
+    CHECK(!build_settings_.no_stamp_files()) << "no_stamp_files does not work yet!";
+  }
+
   return true;
 }
diff --git a/src/gn/tool.cc b/src/gn/tool.cc
index bf3aba6..684dc3e 100644
--- a/src/gn/tool.cc
+++ b/src/gn/tool.cc
@@ -4,6 +4,7 @@
 
 #include "gn/tool.h"
 
+#include "gn/builtin_tool.h"
 #include "gn/c_tool.h"
 #include "gn/general_tool.h"
 #include "gn/rust_tool.h"
@@ -52,6 +53,13 @@
   return nullptr;
 }
 
+BuiltinTool* Tool::AsBuiltin() {
+  return nullptr;
+}
+const BuiltinTool* Tool::AsBuiltin() const {
+  return nullptr;
+}
+
 bool Tool::IsPatternInOutputList(const SubstitutionList& output_list,
                                  const SubstitutionPattern& pattern) const {
   for (const auto& cur : output_list.list()) {
@@ -342,7 +350,7 @@
 
 // static
 const char* Tool::GetToolTypeForTargetFinalOutput(const Target* target) {
-  // The contents of this list might be surprising (i.e. stamp tool for copy
+  // The contents of this list might be surprising (i.e. phony tool for copy
   // rules). See the header for why.
   // TODO(crbug.com/gn/39): Don't emit stamp files for single-output targets.
   if (target->source_types_used().RustSourceUsed()) {
@@ -366,8 +374,6 @@
     }
   }
   switch (target->output_type()) {
-    case Target::GROUP:
-      return GeneralTool::kGeneralToolStamp;
     case Target::EXECUTABLE:
       return CTool::kCToolLink;
     case Target::SHARED_LIBRARY:
@@ -376,15 +382,19 @@
       return CTool::kCToolSolinkModule;
     case Target::STATIC_LIBRARY:
       return CTool::kCToolAlink;
-    case Target::SOURCE_SET:
-      return GeneralTool::kGeneralToolStamp;
     case Target::ACTION:
     case Target::ACTION_FOREACH:
     case Target::BUNDLE_DATA:
-    case Target::CREATE_BUNDLE:
     case Target::COPY_FILES:
+    case Target::CREATE_BUNDLE:
     case Target::GENERATED_FILE:
-      return GeneralTool::kGeneralToolStamp;
+    case Target::GROUP:
+    case Target::SOURCE_SET:
+      if (target->settings()->build_settings()->no_stamp_files()) {
+        return BuiltinTool::kBuiltinToolPhony;
+      } else {
+        return GeneralTool::kGeneralToolStamp;
+      }
     default:
       NOTREACHED();
       return kToolNone;
diff --git a/src/gn/tool.h b/src/gn/tool.h
index 1e01af7..b95c64b 100644
--- a/src/gn/tool.h
+++ b/src/gn/tool.h
@@ -23,6 +23,7 @@
 class CTool;
 class GeneralTool;
 class RustTool;
+class BuiltinTool;
 
 // To add a new Tool category, create a subclass implementing SetComplete()
 // Add a new category to ToolCategories
@@ -62,6 +63,8 @@
   virtual const GeneralTool* AsGeneral() const;
   virtual RustTool* AsRust();
   virtual const RustTool* AsRust() const;
+  virtual BuiltinTool* AsBuiltin();
+  virtual const BuiltinTool* AsBuiltin() const;
 
   // Basic information ---------------------------------------------------------
 
@@ -233,6 +236,7 @@
                                           Err* err);
 
   static const char* GetToolTypeForSourceType(SourceFile::Type type);
+
   static const char* GetToolTypeForTargetFinalOutput(const Target* target);
 
  protected:
diff --git a/src/gn/toolchain.cc b/src/gn/toolchain.cc
index bfad81d..a656184 100644
--- a/src/gn/toolchain.cc
+++ b/src/gn/toolchain.cc
@@ -9,13 +9,19 @@
 #include <utility>
 
 #include "base/logging.h"
+#include "gn/builtin_tool.h"
+#include "gn/settings.h"
 #include "gn/target.h"
 #include "gn/value.h"
 
 Toolchain::Toolchain(const Settings* settings,
                      const Label& label,
                      const SourceFileSet& build_dependency_files)
-    : Item(settings, label, build_dependency_files) {}
+    : Item(settings, label, build_dependency_files) {
+  // Ensure "phony" tool is part of all toolchains by default.
+  const char* phony_name = BuiltinTool::kBuiltinToolPhony;
+  tools_.emplace(phony_name, std::make_unique<BuiltinTool>(phony_name));
+}
 
 Toolchain::~Toolchain() = default;
 
@@ -87,6 +93,20 @@
   return nullptr;
 }
 
+BuiltinTool* Toolchain::GetToolAsBuiltin(const char* name) {
+  if (Tool* tool = GetTool(name)) {
+    return tool->AsBuiltin();
+  }
+  return nullptr;
+}
+
+const BuiltinTool* Toolchain::GetToolAsBuiltin(const char* name) const {
+  if (const Tool* tool = GetTool(name)) {
+    return tool->AsBuiltin();
+  }
+  return nullptr;
+}
+
 void Toolchain::SetTool(std::unique_ptr<Tool> t) {
   DCHECK(t->name() != Tool::kToolNone);
   DCHECK(tools_.find(t->name()) == tools_.end());
@@ -120,6 +140,11 @@
   return GetToolAsRust(Tool::GetToolTypeForSourceType(type));
 }
 
+const BuiltinTool* Toolchain::GetToolForSourceTypeAsBuiltin(
+    SourceFile::Type type) const {
+  return GetToolAsBuiltin(Tool::GetToolTypeForSourceType(type));
+}
+
 const Tool* Toolchain::GetToolForTargetFinalOutput(const Target* target) const {
   return GetTool(Tool::GetToolTypeForTargetFinalOutput(target));
 }
@@ -138,3 +163,8 @@
     const Target* target) const {
   return GetToolAsRust(Tool::GetToolTypeForTargetFinalOutput(target));
 }
+
+const BuiltinTool* Toolchain::GetToolForTargetFinalOutputAsBuiltin(
+    const Target* target) const {
+  return GetToolAsBuiltin(Tool::GetToolTypeForTargetFinalOutput(target));
+}
diff --git a/src/gn/toolchain.h b/src/gn/toolchain.h
index eb5a60c..270fada 100644
--- a/src/gn/toolchain.h
+++ b/src/gn/toolchain.h
@@ -16,6 +16,8 @@
 #include "gn/tool.h"
 #include "gn/value.h"
 
+class BuiltinTool;
+
 // Holds information on a specific toolchain. This data is filled in when we
 // encounter a toolchain definition.
 //
@@ -62,6 +64,8 @@
   const CTool* GetToolAsC(const char* name) const;
   RustTool* GetToolAsRust(const char* name);
   const RustTool* GetToolAsRust(const char* name) const;
+  BuiltinTool* GetToolAsBuiltin(const char* name);
+  const BuiltinTool* GetToolAsBuiltin(const char* name) const;
 
   // Set a tool. When all tools are configured, you should call
   // ToolchainSetupComplete().
@@ -93,6 +97,7 @@
   const CTool* GetToolForSourceTypeAsC(SourceFile::Type type) const;
   const GeneralTool* GetToolForSourceTypeAsGeneral(SourceFile::Type type) const;
   const RustTool* GetToolForSourceTypeAsRust(SourceFile::Type type) const;
+  const BuiltinTool* GetToolForSourceTypeAsBuiltin(SourceFile::Type type) const;
 
   // Returns the tool that produces the final output for the given target type.
   // This isn't necessarily the tool you would expect. For copy target, this
@@ -103,6 +108,8 @@
   const GeneralTool* GetToolForTargetFinalOutputAsGeneral(
       const Target* target) const;
   const RustTool* GetToolForTargetFinalOutputAsRust(const Target* target) const;
+  const BuiltinTool* GetToolForTargetFinalOutputAsBuiltin(
+      const Target* target) const;
 
   const SubstitutionBits& substitution_bits() const {
     DCHECK(setup_complete_);