Add --error-limit flag to control error output count

This introduces a new command-line flag --error-limit to allow users to
specify the maximum number of errors displayed. It sets a default limit
of 10 errors when the flag is not provided, preventing terminal flooding
during builds with numerous errors.

This change also modifies Err::PrintToStdout() and
Err::PrintNonfatalToStdout() to return a boolean indicating whether the
error was actually printed or suppressed. This allows callers to
gracefully handle cases where the error output is suppressed by the
limit, avoiding unnecessary formatting additions such as empty newlines
or separators.

Change-Id: I3b53be631575a4b444c75e4762353fa4eb8f113a
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/21380
Commit-Queue: Takuto Ikuta <tikuta@google.com>
Reviewed-by: Sylvain Defresne <sdefresne@chromium.org>
diff --git a/docs/reference.md b/docs/reference.md
index 6d951a5..4c67971 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -8484,6 +8484,7 @@
     *   --args: Specifies build arguments overrides.
     *   --color: Force colored output.
     *   --dotfile: Override the name of the ".gn" file.
+    *   --error-limit: Limit the number of errors or warnings to print.
     *   --fail-on-unused-args: Treat unused build args as fatal errors.
     *   --markdown: Write help output in the Markdown format.
     *   --ninja-executable: Set the Ninja executable.
diff --git a/src/gn/command_check.cc b/src/gn/command_check.cc
index fccfebb..eba8c70 100644
--- a/src/gn/command_check.cc
+++ b/src/gn/command_check.cc
@@ -277,7 +277,8 @@
   for (size_t i = 0; i < header_errors.size(); i++) {
     if (i > 0)
       OutputString("___________________\n", DECORATION_YELLOW);
-    header_errors[i].PrintToStdout();
+    if (!header_errors[i].PrintToStdout())
+      break;
   }
   return header_errors.empty();
 }
diff --git a/src/gn/command_gen.cc b/src/gn/command_gen.cc
index 8c2a45a..4f951d4 100644
--- a/src/gn/command_gen.cc
+++ b/src/gn/command_gen.cc
@@ -155,7 +155,8 @@
 
 // Prints an error that the given file was present as a source or input in
 // the given target(s) but was not generated by any of its dependencies.
-void PrintInvalidGeneratedInput(const Builder& builder,
+// Returns true if the error was actually printed, or false if it was suppressed.
+bool PrintInvalidGeneratedInput(const Builder& builder,
                                 const SourceFile& file,
                                 const std::vector<const Target*>& targets) {
   std::string err;
@@ -192,8 +193,8 @@
     err += "but no targets in the build generate that file.";
   }
 
-  Err(Location(), "Input to " + target_str + " not generated by a dependency.",
-      err)
+  return Err(Location(), "Input to " + target_str + " not generated by a dependency.",
+             err)
       .PrintToStdout();
 }
 
@@ -204,19 +205,28 @@
     return true;  // No bad files.
 
   int errors_found = 0;
+  bool print_errors = true;
   auto cur = unknown_inputs.begin();
   while (cur != unknown_inputs.end()) {
     errors_found++;
     auto end_of_range = unknown_inputs.upper_bound(cur->first);
 
-    // Package the values more conveniently for printing.
-    SourceFile bad_input = cur->first;
-    std::vector<const Target*> targets;
-    while (cur != end_of_range)
-      targets.push_back((cur++)->second);
+    if (print_errors) {
+      // Package the values more conveniently for printing.
+      SourceFile bad_input = cur->first;
+      std::vector<const Target*> targets;
+      auto temp_cur = cur;
+      while (temp_cur != end_of_range)
+        targets.push_back((temp_cur++)->second);
 
-    PrintInvalidGeneratedInput(setup->builder(), bad_input, targets);
-    OutputString("\n");
+      if (PrintInvalidGeneratedInput(setup->builder(), bad_input, targets)) {
+        OutputString("\n");
+      } else {
+        print_errors = false;
+      }
+    }
+
+    cur = end_of_range;
   }
 
   OutputString(
diff --git a/src/gn/err.cc b/src/gn/err.cc
index e3f71c9..627fdf8 100644
--- a/src/gn/err.cc
+++ b/src/gn/err.cc
@@ -6,17 +6,42 @@
 
 #include <stddef.h>
 
+#include <atomic>
+#include <mutex>
+
+#include "base/command_line.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "gn/filesystem_utils.h"
 #include "gn/input_file.h"
 #include "gn/parse_tree.h"
 #include "gn/standard_out.h"
+#include "gn/switches.h"
 #include "gn/tokenizer.h"
 #include "gn/value.h"
 
 namespace {
 
+std::atomic<int> g_num_errors_printed{0};
+
+int GetErrorLimit() {
+  static int g_error_limit = 10;
+  static std::once_flag flag;
+  std::call_once(flag, []() {
+    if (base::CommandLine::InitializedForCurrentProcess()) {
+      const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
+      if (cmdline && cmdline->HasSwitch(switches::kErrorLimit)) {
+        std::string limit_str = cmdline->GetSwitchValueString(switches::kErrorLimit);
+        int parsed_limit;
+        if (base::StringToInt(limit_str, &parsed_limit)) {
+          g_error_limit = parsed_limit;
+        }
+      }
+    }
+  });
+  return g_error_limit;
+}
+
 std::string GetNthLine(std::string_view data, int n) {
   size_t line_off = Tokenizer::ByteOffsetOfNthLine(data, n);
   size_t end = line_off + 1;
@@ -140,22 +165,36 @@
   return *this;
 }
 
-void Err::PrintToStdout() const {
-  InternalPrintToStdout(false, true);
+bool Err::PrintToStdout() const {
+  return InternalPrintToStdout(false, true);
 }
 
-void Err::PrintNonfatalToStdout() const {
-  InternalPrintToStdout(false, false);
+bool Err::PrintNonfatalToStdout() const {
+  return InternalPrintToStdout(false, false);
 }
 
 void Err::AppendSubErr(const Err& err) {
   info_->sub_errs.push_back(err);
 }
 
-void Err::InternalPrintToStdout(bool is_sub_err, bool is_fatal) const {
+bool Err::InternalPrintToStdout(bool is_sub_err, bool is_fatal) const {
   DCHECK(info_);
 
   if (!is_sub_err) {
+    int limit = GetErrorLimit();
+    if (limit >= 0) {
+      int printed = ++g_num_errors_printed;
+      if (printed > limit) {
+        if (printed == limit + 1) {
+          OutputString(
+              "Too many errors/warnings. Suppressing further messages.\n"
+              "You can change the limit by passing --error-limit=<number>.\n",
+              DECORATION_RED);
+        }
+        return false;
+      }
+    }
+
     if (is_fatal)
       OutputString("ERROR ", DECORATION_RED);
     else
@@ -200,4 +239,6 @@
   // Sub errors.
   for (const auto& sub_err : info_->sub_errs)
     sub_err.InternalPrintToStdout(true, is_fatal);
+
+  return true;
 }
diff --git a/src/gn/err.h b/src/gn/err.h
index 6e50b37..c67da5d 100644
--- a/src/gn/err.h
+++ b/src/gn/err.h
@@ -98,7 +98,12 @@
 
   void AppendSubErr(const Err& err);
 
-  void PrintToStdout() const;
+  // Prints the error to standard out.
+  // Returns true if the error was actually printed, or false if it was
+  // suppressed (e.g., due to reaching the error limit). Callers can use this
+  // return value to determine whether to print additional formatting like
+  // newlines or separators.
+  bool PrintToStdout() const;
 
   // Prints to standard out but uses a "WARNING" messaging instead of the
   // normal "ERROR" messaging. This is a property of the printing system rather
@@ -110,10 +115,12 @@
   // nonfatal error to the screen instead of returning it. In these cases, that
   // code can decide at printing time whether it will continue (and use this
   // method) or not (and use PrintToStdout()).
-  void PrintNonfatalToStdout() const;
+  //
+  // Returns true if the warning was actually printed, or false if suppressed.
+  bool PrintNonfatalToStdout() const;
 
  private:
-  void InternalPrintToStdout(bool is_sub_err, bool is_fatal) const;
+  bool InternalPrintToStdout(bool is_sub_err, bool is_fatal) const;
 
   std::unique_ptr<ErrInfo> info_;  // Non-null indicates error.
 };
diff --git a/src/gn/switches.cc b/src/gn/switches.cc
index bf0c753..4081695 100644
--- a/src/gn/switches.cc
+++ b/src/gn/switches.cc
@@ -65,6 +65,17 @@
   use a different file.
 )";
 
+const char kErrorLimit[] = "error-limit";
+const char kErrorLimit_HelpShort[] =
+    "--error-limit: Limit the number of errors or warnings to print.";
+const char kErrorLimit_Help[] =
+    R"(--error-limit: Limit the number of errors or warnings to print.
+
+  By default, GN limits the number of printed errors or warnings to 10.
+  Setting this will change the limit. Passing -1 will print all errors
+  and warnings without any limit.
+)";
+
 const char kFailOnUnusedArgs[] = "fail-on-unused-args";
 const char kFailOnUnusedArgs_HelpShort[] =
     "--fail-on-unused-args: Treat unused build args as fatal errors.";
@@ -333,6 +344,7 @@
     INSERT_VARIABLE(Args)
     INSERT_VARIABLE(Color)
     INSERT_VARIABLE(Dotfile)
+    INSERT_VARIABLE(ErrorLimit)
     INSERT_VARIABLE(FailOnUnusedArgs)
     INSERT_VARIABLE(Markdown)
     INSERT_VARIABLE(NinjaExecutable)
diff --git a/src/gn/switches.h b/src/gn/switches.h
index 57d3828..5fe34e7 100644
--- a/src/gn/switches.h
+++ b/src/gn/switches.h
@@ -38,6 +38,10 @@
 extern const char kDotfile_HelpShort[];
 extern const char kDotfile_Help[];
 
+extern const char kErrorLimit[];
+extern const char kErrorLimit_HelpShort[];
+extern const char kErrorLimit_Help[];
+
 extern const char kFailOnUnusedArgs[];
 extern const char kFailOnUnusedArgs_HelpShort[];
 extern const char kFailOnUnusedArgs_Help[];