Browse Source

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.
Nikhil Benesch 7 years ago
parent
commit
dedbd63306

+ 15 - 3
src/google/protobuf/compiler/command_line_interface.cc

@@ -288,7 +288,7 @@ class CommandLineInterface::ErrorPrinter
       public DescriptorPool::ErrorCollector {
       public DescriptorPool::ErrorCollector {
  public:
  public:
   ErrorPrinter(ErrorFormat format, DiskSourceTree* tree = NULL)
   ErrorPrinter(ErrorFormat format, DiskSourceTree* tree = NULL)
-      : format_(format), tree_(tree), found_errors_(false) {}
+      : format_(format), tree_(tree), found_errors_(false), found_warnings_(false) {}
   ~ErrorPrinter() {}
   ~ErrorPrinter() {}
 
 
   // implements MultiFileErrorCollector ------------------------------
   // implements MultiFileErrorCollector ------------------------------
@@ -300,6 +300,7 @@ class CommandLineInterface::ErrorPrinter
 
 
   void AddWarning(const std::string& filename, int line, int column,
   void AddWarning(const std::string& filename, int line, int column,
                   const std::string& message) {
                   const std::string& message) {
+    found_warnings_ = true;
     AddErrorOrWarning(filename, line, column, message, "warning", std::clog);
     AddErrorOrWarning(filename, line, column, message, "warning", std::clog);
   }
   }
 
 
@@ -327,6 +328,8 @@ class CommandLineInterface::ErrorPrinter
 
 
   bool FoundErrors() const { return found_errors_; }
   bool FoundErrors() const { return found_errors_; }
 
 
+  bool FoundWarnings() const { return found_warnings_; }
+
  private:
  private:
   void AddErrorOrWarning(const std::string& filename, int line, int column,
   void AddErrorOrWarning(const std::string& filename, int line, int column,
                          const std::string& message, const std::string& type,
                          const std::string& message, const std::string& type,
@@ -365,6 +368,7 @@ class CommandLineInterface::ErrorPrinter
   const ErrorFormat format_;
   const ErrorFormat format_;
   DiskSourceTree* tree_;
   DiskSourceTree* tree_;
   bool found_errors_;
   bool found_errors_;
+  bool found_warnings_;
 };
 };
 
 
 // -------------------------------------------------------------------
 // -------------------------------------------------------------------
@@ -1117,7 +1121,8 @@ int CommandLineInterface::Run(int argc, const char* const argv[]) {
     }
     }
   }
   }
 
 
-  if (error_collector->FoundErrors()) {
+  if (error_collector->FoundErrors() ||
+      (fatal_warnings_ && error_collector->FoundWarnings())) {
     return 1;
     return 1;
   }
   }
 
 
@@ -1630,7 +1635,8 @@ bool CommandLineInterface::ParseArgument(const char* arg, std::string* name,
       *name == "--version" || *name == "--decode_raw" ||
       *name == "--version" || *name == "--decode_raw" ||
       *name == "--print_free_field_numbers" ||
       *name == "--print_free_field_numbers" ||
       *name == "--experimental_allow_proto3_optional" ||
       *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.
     // 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
     //   They probably should not be hard-coded like this but for now it's
     //   not worth doing better.
     //   not worth doing better.
@@ -1883,6 +1889,12 @@ CommandLineInterface::InterpretArgument(const std::string& name,
       return PARSE_ARGUMENT_FAIL;
       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") {
   } else if (name == "--plugin") {
     if (plugin_prefix_.empty()) {
     if (plugin_prefix_.empty()) {
       std::cerr << "This compiler does not support plugins." << std::endl;
       std::cerr << "This compiler does not support plugins." << std::endl;

+ 3 - 0
src/google/protobuf/compiler/command_line_interface.h

@@ -392,6 +392,9 @@ class PROTOC_EXPORT CommandLineInterface {
 
 
   ErrorFormat error_format_ = ERROR_FORMAT_GCC;
   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> >
   std::vector<std::pair<std::string, std::string> >
       proto_path_;                        // Search path for proto files.
       proto_path_;                        // Search path for proto files.
   std::vector<std::string> input_files_;  // Names of the input proto files.
   std::vector<std::string> input_files_;  // Names of the input proto files.

+ 29 - 2
src/google/protobuf/compiler/command_line_interface_unittest.cc

@@ -132,6 +132,9 @@ class CommandLineInterfaceTest : public testing::Test {
   // -----------------------------------------------------------------
   // -----------------------------------------------------------------
   // Methods to check the test results (called after Run()).
   // 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()
   // Checks that no text was written to stderr during Run(), and Run()
   // returned 0.
   // returned 0.
   void ExpectNoErrors();
   void ExpectNoErrors();
@@ -406,6 +409,10 @@ void CommandLineInterfaceTest::CreateTempDir(const std::string& name) {
 
 
 // -------------------------------------------------------------------
 // -------------------------------------------------------------------
 
 
+void CommandLineInterfaceTest::ExpectReturnCode(int r) {
+  EXPECT_EQ(r, return_code_);
+}
+
 void CommandLineInterfaceTest::ExpectNoErrors() {
 void CommandLineInterfaceTest::ExpectNoErrors() {
   EXPECT_EQ(0, return_code_);
   EXPECT_EQ(0, return_code_);
   EXPECT_EQ("", error_text_);
   EXPECT_EQ("", error_text_);
@@ -2309,12 +2316,32 @@ TEST_F(CommandLineInterfaceTest, InvalidErrorFormat) {
                  "syntax = \"proto2\";\n"
                  "syntax = \"proto2\";\n"
                  "badsyntax\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");
   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
 // Flag parsing tests