Add --fatal_warnings flag to treat warnings as errors

Add a --fatal_warnings flag that requests that protoc exit with a
failing status code if any warnings are generated during compilation.

Partially address #3980.
diff --git a/src/google/protobuf/compiler/command_line_interface.cc b/src/google/protobuf/compiler/command_line_interface.cc
index 28b0bfa..7b66f01 100644
--- a/src/google/protobuf/compiler/command_line_interface.cc
+++ b/src/google/protobuf/compiler/command_line_interface.cc
@@ -288,7 +288,7 @@
       public DescriptorPool::ErrorCollector {
  public:
   ErrorPrinter(ErrorFormat format, DiskSourceTree* tree = NULL)
-      : format_(format), tree_(tree), found_errors_(false) {}
+      : format_(format), tree_(tree), found_errors_(false), found_warnings_(false) {}
   ~ErrorPrinter() {}
 
   // implements MultiFileErrorCollector ------------------------------
@@ -300,6 +300,7 @@
 
   void AddWarning(const std::string& filename, int line, int column,
                   const std::string& message) {
+    found_warnings_ = true;
     AddErrorOrWarning(filename, line, column, message, "warning", std::clog);
   }
 
@@ -327,6 +328,8 @@
 
   bool FoundErrors() const { return found_errors_; }
 
+  bool FoundWarnings() const { return found_warnings_; }
+
  private:
   void AddErrorOrWarning(const std::string& filename, int line, int column,
                          const std::string& message, const std::string& type,
@@ -365,6 +368,7 @@
   const ErrorFormat format_;
   DiskSourceTree* tree_;
   bool found_errors_;
+  bool found_warnings_;
 };
 
 // -------------------------------------------------------------------
@@ -1117,7 +1121,8 @@
     }
   }
 
-  if (error_collector->FoundErrors()) {
+  if (error_collector->FoundErrors() ||
+      (fatal_warnings_ && error_collector->FoundWarnings())) {
     return 1;
   }
 
@@ -1630,7 +1635,8 @@
       *name == "--version" || *name == "--decode_raw" ||
       *name == "--print_free_field_numbers" ||
       *name == "--experimental_allow_proto3_optional" ||
-      *name == "--deterministic_output") {
+      *name == "--deterministic_output" ||
+      *name == "--fatal_warnings") {
     // HACK:  These are the only flags that don't take a value.
     //   They probably should not be hard-coded like this but for now it's
     //   not worth doing better.
@@ -1883,6 +1889,12 @@
       return PARSE_ARGUMENT_FAIL;
     }
 
+  } else if (name == "--fatal_warnings") {
+    if (fatal_warnings_) {
+      std::cerr << name << " may only be passed once." << std::endl;
+      return PARSE_ARGUMENT_FAIL;
+    }
+    fatal_warnings_ = true;
   } else if (name == "--plugin") {
     if (plugin_prefix_.empty()) {
       std::cerr << "This compiler does not support plugins." << std::endl;
diff --git a/src/google/protobuf/compiler/command_line_interface.h b/src/google/protobuf/compiler/command_line_interface.h
index 4ff2471..e2361ff 100644
--- a/src/google/protobuf/compiler/command_line_interface.h
+++ b/src/google/protobuf/compiler/command_line_interface.h
@@ -392,6 +392,9 @@
 
   ErrorFormat error_format_ = ERROR_FORMAT_GCC;
 
+  // True if we should treat warnings as errors that fail the compilation.
+  bool fatal_warnings_;
+
   std::vector<std::pair<std::string, std::string> >
       proto_path_;                        // Search path for proto files.
   std::vector<std::string> input_files_;  // Names of the input proto files.
diff --git a/src/google/protobuf/compiler/command_line_interface_unittest.cc b/src/google/protobuf/compiler/command_line_interface_unittest.cc
index 74dc9bf..a0e7987 100644
--- a/src/google/protobuf/compiler/command_line_interface_unittest.cc
+++ b/src/google/protobuf/compiler/command_line_interface_unittest.cc
@@ -132,6 +132,9 @@
   // -----------------------------------------------------------------
   // Methods to check the test results (called after Run()).
 
+  // Checks that Run() returned code r.
+  void ExpectReturnCode(int r);
+
   // Checks that no text was written to stderr during Run(), and Run()
   // returned 0.
   void ExpectNoErrors();
@@ -406,6 +409,10 @@
 
 // -------------------------------------------------------------------
 
+void CommandLineInterfaceTest::ExpectReturnCode(int r) {
+  EXPECT_EQ(r, return_code_);
+}
+
 void CommandLineInterfaceTest::ExpectNoErrors() {
   EXPECT_EQ(0, return_code_);
   EXPECT_EQ("", error_text_);
@@ -2309,12 +2316,32 @@
                  "syntax = \"proto2\";\n"
                  "badsyntax\n");
 
-  Run("protocol_compiler --test_out=$tmpdir "
-      "--proto_path=$tmpdir --error_format=invalid foo.proto");
+  Run("protocol_compiler --test_out=$tmpdir --proto_path=$tmpdir foo.proto");
 
   ExpectErrorText("Unknown error format: invalid\n");
 }
 
+TEST_F(CommandLineInterfaceTest, Warnings) {
+  // Test --fatal_warnings.
+
+  CreateTempFile("foo.proto",
+    "syntax = \"proto2\";\n"
+    "import \"bar.proto\";\n");
+  CreateTempFile("bar.proto",
+    "syntax = \"proto2\";\n");
+
+  Run("protocol_compiler --test_out=$tmpdir "
+    "--proto_path=$tmpdir foo.proto");
+  ExpectReturnCode(0);
+  ExpectErrorSubstringWithZeroReturnCode(
+    "foo.proto: warning: Import bar.proto but not used.");
+
+  Run("protocol_compiler --test_out=$tmpdir --fatal_warnings "
+    "--proto_path=$tmpdir foo.proto");
+  ExpectErrorSubstring(
+    "foo.proto: warning: Import bar.proto but not used.");
+}
+
 // -------------------------------------------------------------------
 // Flag parsing tests