commands.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. # Copyright 2015 gRPC authors.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """Provides distutils command classes for the gRPC Python setup process."""
  15. from distutils import errors as _errors
  16. import glob
  17. import os
  18. import os.path
  19. import platform
  20. import re
  21. import shutil
  22. import subprocess
  23. import sys
  24. import traceback
  25. import setuptools
  26. from setuptools.command import build_ext
  27. from setuptools.command import build_py
  28. from setuptools.command import easy_install
  29. from setuptools.command import install
  30. from setuptools.command import test
  31. PYTHON_STEM = os.path.dirname(os.path.abspath(__file__))
  32. GRPC_STEM = os.path.abspath(PYTHON_STEM + '../../../../')
  33. GRPC_PROTO_STEM = os.path.join(GRPC_STEM, 'src', 'proto')
  34. PROTO_STEM = os.path.join(PYTHON_STEM, 'src', 'proto')
  35. PYTHON_PROTO_TOP_LEVEL = os.path.join(PYTHON_STEM, 'src')
  36. class CommandError(object):
  37. pass
  38. class GatherProto(setuptools.Command):
  39. description = 'gather proto dependencies'
  40. user_options = []
  41. def initialize_options(self):
  42. pass
  43. def finalize_options(self):
  44. pass
  45. def run(self):
  46. # TODO(atash) ensure that we're running from the repository directory when
  47. # this command is used
  48. try:
  49. shutil.rmtree(PROTO_STEM)
  50. except Exception as error:
  51. # We don't care if this command fails
  52. pass
  53. shutil.copytree(GRPC_PROTO_STEM, PROTO_STEM)
  54. for root, _, _ in os.walk(PYTHON_PROTO_TOP_LEVEL):
  55. path = os.path.join(root, '__init__.py')
  56. open(path, 'a').close()
  57. class BuildProtoModules(setuptools.Command):
  58. """Command to generate project *_pb2.py modules from proto files."""
  59. description = 'build protobuf modules'
  60. user_options = [
  61. ('include=', None, 'path patterns to include in protobuf generation'),
  62. ('exclude=', None, 'path patterns to exclude from protobuf generation')
  63. ]
  64. def initialize_options(self):
  65. self.exclude = None
  66. self.include = r'.*\.proto$'
  67. def finalize_options(self):
  68. pass
  69. def run(self):
  70. import grpc_tools.protoc as protoc
  71. include_regex = re.compile(self.include)
  72. exclude_regex = re.compile(self.exclude) if self.exclude else None
  73. paths = []
  74. for walk_root, directories, filenames in os.walk(PROTO_STEM):
  75. for filename in filenames:
  76. path = os.path.join(walk_root, filename)
  77. if include_regex.match(path) and not (
  78. exclude_regex and exclude_regex.match(path)):
  79. paths.append(path)
  80. # TODO(kpayson): It would be nice to do this in a batch command,
  81. # but we currently have name conflicts in src/proto
  82. for path in paths:
  83. command = [
  84. 'grpc_tools.protoc',
  85. '-I {}'.format(PROTO_STEM),
  86. '--python_out={}'.format(PROTO_STEM),
  87. '--grpc_python_out={}'.format(PROTO_STEM),
  88. ] + [path]
  89. if protoc.main(command) != 0:
  90. sys.stderr.write(
  91. 'warning: Command:\n{}\nFailed'.format(command))
  92. # Generated proto directories dont include __init__.py, but
  93. # these are needed for python package resolution
  94. for walk_root, _, _ in os.walk(PROTO_STEM):
  95. path = os.path.join(walk_root, '__init__.py')
  96. open(path, 'a').close()
  97. class BuildPy(build_py.build_py):
  98. """Custom project build command."""
  99. def run(self):
  100. try:
  101. self.run_command('build_package_protos')
  102. except CommandError as error:
  103. sys.stderr.write('warning: %s\n' % error.message)
  104. build_py.build_py.run(self)
  105. class TestLite(setuptools.Command):
  106. """Command to run tests without fetching or building anything."""
  107. description = 'run tests without fetching or building anything.'
  108. user_options = []
  109. def initialize_options(self):
  110. pass
  111. def finalize_options(self):
  112. # distutils requires this override.
  113. pass
  114. def run(self):
  115. self._add_eggs_to_path()
  116. import tests
  117. loader = tests.Loader()
  118. loader.loadTestsFromNames(['tests'])
  119. runner = tests.Runner()
  120. result = runner.run(loader.suite)
  121. if not result.wasSuccessful():
  122. sys.exit('Test failure')
  123. def _add_eggs_to_path(self):
  124. """Fetch install and test requirements"""
  125. self.distribution.fetch_build_eggs(self.distribution.install_requires)
  126. self.distribution.fetch_build_eggs(self.distribution.tests_require)
  127. class RunInterop(test.test):
  128. description = 'run interop test client/server'
  129. user_options = [('args=', 'a', 'pass-thru arguments for the client/server'),
  130. ('client', 'c', 'flag indicating to run the client'),
  131. ('server', 's', 'flag indicating to run the server')]
  132. def initialize_options(self):
  133. self.args = ''
  134. self.client = False
  135. self.server = False
  136. def finalize_options(self):
  137. if self.client and self.server:
  138. raise _errors.DistutilsOptionError(
  139. 'you may only specify one of client or server')
  140. def run(self):
  141. if self.distribution.install_requires:
  142. self.distribution.fetch_build_eggs(
  143. self.distribution.install_requires)
  144. if self.distribution.tests_require:
  145. self.distribution.fetch_build_eggs(self.distribution.tests_require)
  146. if self.client:
  147. self.run_client()
  148. elif self.server:
  149. self.run_server()
  150. def run_server(self):
  151. # We import here to ensure that our setuptools parent has had a chance to
  152. # edit the Python system path.
  153. from tests.interop import server
  154. sys.argv[1:] = self.args.split()
  155. server.serve()
  156. def run_client(self):
  157. # We import here to ensure that our setuptools parent has had a chance to
  158. # edit the Python system path.
  159. from tests.interop import client
  160. sys.argv[1:] = self.args.split()
  161. client.test_interoperability()