Add new grpc_tools setuptools command BuildPackageProtosStrict
diff --git a/tools/distrib/python/grpcio_tools/grpc_tools/command.py b/tools/distrib/python/grpcio_tools/grpc_tools/command.py
index 93503c4..ee31114 100644
--- a/tools/distrib/python/grpcio_tools/grpc_tools/command.py
+++ b/tools/distrib/python/grpcio_tools/grpc_tools/command.py
@@ -15,11 +15,36 @@
 import os
 import pkg_resources
 import sys
+import tempfile
 
 import setuptools
 
 from grpc_tools import protoc
 
+_WELL_KNOWN_PROTOS_INCLUDE = pkg_resources.resource_filename(
+    'grpc_tools', '_proto')
+
+
+def _compile_proto(proto_file,
+                   include='',
+                   python_out='',
+                   grpc_python_out='',
+                   strict=False):
+    command = [
+        'grpc_tools.protoc',
+        '--proto_path={}'.format(include),
+        '--proto_path={}'.format(_WELL_KNOWN_PROTOS_INCLUDE),
+        '--python_out={}'.format(python_out),
+        '--grpc_python_out={}'.format(grpc_python_out),
+    ] + [proto_file]
+    if protoc.main(command) != 0:
+        if strict:
+            sys.stderr.write('error: {} failed'.format(command))
+        else:
+            sys.stderr.write('warning: {} failed'.format(command))
+        return False
+    return True
+
 
 def build_package_protos(package_root):
     proto_files = []
@@ -30,19 +55,49 @@
                 proto_files.append(
                     os.path.abspath(os.path.join(root, filename)))
 
-    well_known_protos_include = pkg_resources.resource_filename(
-        'grpc_tools', '_proto')
+    for proto_file in proto_files:
+        _compile_proto(
+            proto_file,
+            include=inclusion_root,
+            python_out=inclusion_root,
+            grpc_python_out=inclusion_root,
+            strict=False,
+        )
+
+
+def build_package_protos_strict(package_root):
+    proto_files = []
+    inclusion_root = os.path.abspath(package_root)
+    for root, _, files in os.walk(inclusion_root):
+        for filename in files:
+            if filename.endswith('.proto'):
+                proto_files.append(
+                    os.path.abspath(os.path.join(root, filename)))
+
+    tmp_out_directory = tempfile.mkdtemp()
+    compile_failed = False
+    for proto_file in proto_files:
+        # Output all the errors across all the files instead of exiting on the
+        # first error proto file.
+        compile_failed |= not _compile_proto(
+            proto_file,
+            include=inclusion_root,
+            python_out=tmp_out_directory,
+            grpc_python_out=tmp_out_directory,
+            strict=True,
+        )
+
+    if compile_failed:
+        sys.exit(1)
 
     for proto_file in proto_files:
-        command = [
-            'grpc_tools.protoc',
-            '--proto_path={}'.format(inclusion_root),
-            '--proto_path={}'.format(well_known_protos_include),
-            '--python_out={}'.format(inclusion_root),
-            '--grpc_python_out={}'.format(inclusion_root),
-        ] + [proto_file]
-        if protoc.main(command) != 0:
-            raise RuntimeError('error: {} failed'.format(command))
+        _compile_proto(
+            proto_file,
+            include=inclusion_root,
+            python_out=inclusion_root,
+            grpc_python_out=inclusion_root,
+            strict=False,
+        )
 
 
 class BuildPackageProtos(setuptools.Command):
@@ -63,3 +118,26 @@
         # to `self.distribution.package_dir` (and get a key error if it's not
         # there).
         build_package_protos(self.distribution.package_dir[''])
+
+
+class BuildPackageProtosStrict(setuptools.Command):
+    """Command to strictly generate project *_pb2.py modules from proto files.
+
+    The generation will abort if any of the proto files contains error.
+    """
+
+    description = 'strictly build grpc protobuf modules'
+    user_options = []
+
+    def initialize_options(self):
+        pass
+
+    def finalize_options(self):
+        pass
+
+    def run(self):
+        # due to limitations of the proto generator, we require that only *one*
+        # directory is provided as an 'include' directory. We assume it's the '' key
+        # to `self.distribution.package_dir` (and get a key error if it's not
+        # there).
+        build_package_protos_strict(self.distribution.package_dir[''])