blob: 171829b62fccf3429ad59f6f5589b2b0a7e54057 [file] [log] [blame]
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -07001# Copyright 2015, Google Inc.
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are
6# met:
7#
8# * Redistributions of source code must retain the above copyright
9# notice, this list of conditions and the following disclaimer.
10# * Redistributions in binary form must reproduce the above
11# copyright notice, this list of conditions and the following disclaimer
12# in the documentation and/or other materials provided with the
13# distribution.
14# * Neither the name of Google Inc. nor the names of its
15# contributors may be used to endorse or promote products derived from
16# this software without specific prior written permission.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30"""Provides distutils command classes for the gRPC Python setup process."""
31
32import distutils
33import glob
34import os
35import os.path
36import platform
37import re
38import shutil
39import subprocess
40import sys
41import traceback
42
43import setuptools
44from setuptools.command import build_ext
45from setuptools.command import build_py
46from setuptools.command import easy_install
47from setuptools.command import install
48from setuptools.command import test
49
50PYTHON_STEM = os.path.dirname(os.path.abspath(__file__))
51GRPC_STEM = os.path.abspath(PYTHON_STEM + '../../../../')
52GRPC_PROTO_STEM = os.path.join(GRPC_STEM, 'src', 'proto')
53PROTO_STEM = os.path.join(PYTHON_STEM, 'src', 'proto')
54PYTHON_PROTO_TOP_LEVEL = os.path.join(PYTHON_STEM, 'src')
55
56
57class CommandError(object):
58 pass
59
60
61class GatherProto(setuptools.Command):
62
63 description = 'gather proto dependencies'
64 user_options = []
65
66 def initialize_options(self):
67 pass
68
69 def finalize_options(self):
70 pass
71
72 def run(self):
73 # TODO(atash) ensure that we're running from the repository directory when
74 # this command is used
75 try:
76 shutil.rmtree(PROTO_STEM)
77 except Exception as error:
78 # We don't care if this command fails
79 pass
80 shutil.copytree(GRPC_PROTO_STEM, PROTO_STEM)
81 for root, _, _ in os.walk(PYTHON_PROTO_TOP_LEVEL):
82 path = os.path.join(root, '__init__.py')
83 open(path, 'a').close()
84
85
86class BuildProtoModules(setuptools.Command):
87 """Command to generate project *_pb2.py modules from proto files."""
88
89 description = 'build protobuf modules'
90 user_options = [
91 ('include=', None, 'path patterns to include in protobuf generation'),
92 ('exclude=', None, 'path patterns to exclude from protobuf generation')
93 ]
94
95 def initialize_options(self):
96 self.exclude = None
97 self.include = r'.*\.proto$'
98
99 def finalize_options(self):
100 pass
101
102 def run(self):
103 import grpc.tools.protoc as protoc
104
105 include_regex = re.compile(self.include)
106 exclude_regex = re.compile(self.exclude) if self.exclude else None
107 paths = []
108 for walk_root, directories, filenames in os.walk(PROTO_STEM):
109 for filename in filenames:
110 path = os.path.join(walk_root, filename)
111 if include_regex.match(path) and not (
112 exclude_regex and exclude_regex.match(path)):
113 paths.append(path)
114
115 # TODO(kpayson): It would be nice to do this in a batch command,
116 # but we currently have name conflicts in src/proto
117 for path in paths:
118 command = [
119 'grpc.tools.protoc',
120 '-I {}'.format(PROTO_STEM),
121 '--python_out={}'.format(PROTO_STEM),
122 '--grpc_python_out={}'.format(PROTO_STEM),
123 ] + [path]
124 if protoc.main(command) != 0:
125 sys.stderr.write(
126 'warning: Command:\n{}\nFailed'.format(
127 command))
128
129 # Generated proto directories dont include __init__.py, but
130 # these are needed for python package resolution
131 for walk_root, _, _ in os.walk(PROTO_STEM):
132 path = os.path.join(walk_root, '__init__.py')
133 open(path, 'a').close()
134
135
136class BuildPy(build_py.build_py):
137 """Custom project build command."""
138
139 def run(self):
140 try:
141 self.run_command('build_proto_modules')
142 except CommandError as error:
143 sys.stderr.write('warning: %s\n' % error.message)
144 build_py.build_py.run(self)
145
146
147class TestLite(setuptools.Command):
148 """Command to run tests without fetching or building anything."""
149
150 description = 'run tests without fetching or building anything.'
151 user_options = []
152
153 def initialize_options(self):
154 pass
155
156 def finalize_options(self):
157 # distutils requires this override.
158 pass
159
160 def run(self):
161 self._add_eggs_to_path()
162
163 import tests
164 loader = tests.Loader()
165 loader.loadTestsFromNames(['tests'])
166 runner = tests.Runner()
167 result = runner.run(loader.suite)
168 if not result.wasSuccessful():
169 sys.exit('Test failure')
170
171 def _add_eggs_to_path(self):
172 """Fetch install and test requirements"""
173 self.distribution.fetch_build_eggs(self.distribution.install_requires)
174 self.distribution.fetch_build_eggs(self.distribution.tests_require)
175
176
177class RunInterop(test.test):
178
179 description = 'run interop test client/server'
180 user_options = [
181 ('args=', 'a', 'pass-thru arguments for the client/server'),
182 ('client', 'c', 'flag indicating to run the client'),
183 ('server', 's', 'flag indicating to run the server')
184 ]
185
186 def initialize_options(self):
187 self.args = ''
188 self.client = False
189 self.server = False
190
191 def finalize_options(self):
192 if self.client and self.server:
193 raise DistutilsOptionError('you may only specify one of client or server')
194
195 def run(self):
196 if self.distribution.install_requires:
197 self.distribution.fetch_build_eggs(self.distribution.install_requires)
198 if self.distribution.tests_require:
199 self.distribution.fetch_build_eggs(self.distribution.tests_require)
200 if self.client:
201 self.run_client()
202 elif self.server:
203 self.run_server()
204
205 def run_server(self):
206 # We import here to ensure that our setuptools parent has had a chance to
207 # edit the Python system path.
208 from tests.interop import server
209 sys.argv[1:] = self.args.split()
210 server.serve()
211
212 def run_client(self):
213 # We import here to ensure that our setuptools parent has had a chance to
214 # edit the Python system path.
215 from tests.interop import client
216 sys.argv[1:] = self.args.split()
217 client.test_interoperability()