blob: af0ffe347507e5c4fae8f81ed8fe90ba6566040f [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.
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -070029"""Provides distutils command classes for the gRPC Python setup process."""
30
31import distutils
32import glob
33import os
34import os.path
35import platform
36import re
37import shutil
38import subprocess
39import sys
40import traceback
41
42import setuptools
43from setuptools.command import build_ext
44from setuptools.command import build_py
45from setuptools.command import easy_install
46from setuptools.command import install
47from setuptools.command import test
48
49PYTHON_STEM = os.path.dirname(os.path.abspath(__file__))
50GRPC_STEM = os.path.abspath(PYTHON_STEM + '../../../../')
51GRPC_PROTO_STEM = os.path.join(GRPC_STEM, 'src', 'proto')
52PROTO_STEM = os.path.join(PYTHON_STEM, 'src', 'proto')
53PYTHON_PROTO_TOP_LEVEL = os.path.join(PYTHON_STEM, 'src')
54
55
56class CommandError(object):
Masood Malekghassemicc793702017-01-13 19:20:10 -080057 pass
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -070058
59
60class GatherProto(setuptools.Command):
61
Masood Malekghassemicc793702017-01-13 19:20:10 -080062 description = 'gather proto dependencies'
63 user_options = []
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -070064
Masood Malekghassemicc793702017-01-13 19:20:10 -080065 def initialize_options(self):
66 pass
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -070067
Masood Malekghassemicc793702017-01-13 19:20:10 -080068 def finalize_options(self):
69 pass
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -070070
Masood Malekghassemicc793702017-01-13 19:20:10 -080071 def run(self):
72 # TODO(atash) ensure that we're running from the repository directory when
73 # this command is used
74 try:
75 shutil.rmtree(PROTO_STEM)
76 except Exception as error:
77 # We don't care if this command fails
78 pass
79 shutil.copytree(GRPC_PROTO_STEM, PROTO_STEM)
80 for root, _, _ in os.walk(PYTHON_PROTO_TOP_LEVEL):
81 path = os.path.join(root, '__init__.py')
82 open(path, 'a').close()
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -070083
84
85class BuildProtoModules(setuptools.Command):
Masood Malekghassemicc793702017-01-13 19:20:10 -080086 """Command to generate project *_pb2.py modules from proto files."""
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -070087
Masood Malekghassemicc793702017-01-13 19:20:10 -080088 description = 'build protobuf modules'
89 user_options = [
90 ('include=', None, 'path patterns to include in protobuf generation'),
91 ('exclude=', None, 'path patterns to exclude from protobuf generation')
92 ]
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -070093
Masood Malekghassemicc793702017-01-13 19:20:10 -080094 def initialize_options(self):
95 self.exclude = None
96 self.include = r'.*\.proto$'
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -070097
Masood Malekghassemicc793702017-01-13 19:20:10 -080098 def finalize_options(self):
99 pass
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -0700100
Masood Malekghassemicc793702017-01-13 19:20:10 -0800101 def run(self):
102 import grpc_tools.protoc as protoc
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -0700103
Masood Malekghassemicc793702017-01-13 19:20:10 -0800104 include_regex = re.compile(self.include)
105 exclude_regex = re.compile(self.exclude) if self.exclude else None
106 paths = []
107 for walk_root, directories, filenames in os.walk(PROTO_STEM):
108 for filename in filenames:
109 path = os.path.join(walk_root, filename)
110 if include_regex.match(path) and not (
111 exclude_regex and exclude_regex.match(path)):
112 paths.append(path)
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -0700113
Masood Malekghassemicc793702017-01-13 19:20:10 -0800114 # TODO(kpayson): It would be nice to do this in a batch command,
115 # but we currently have name conflicts in src/proto
116 for path in paths:
117 command = [
118 'grpc_tools.protoc',
119 '-I {}'.format(PROTO_STEM),
120 '--python_out={}'.format(PROTO_STEM),
121 '--grpc_python_out={}'.format(PROTO_STEM),
122 ] + [path]
123 if protoc.main(command) != 0:
Masood Malekghassemi6b890d12017-01-23 12:02:08 -0500124 sys.stderr.write(
125 'warning: Command:\n{}\nFailed'.format(command))
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -0700126
Masood Malekghassemicc793702017-01-13 19:20:10 -0800127 # Generated proto directories dont include __init__.py, but
128 # these are needed for python package resolution
129 for walk_root, _, _ in os.walk(PROTO_STEM):
130 path = os.path.join(walk_root, '__init__.py')
131 open(path, 'a').close()
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -0700132
133
134class BuildPy(build_py.build_py):
Masood Malekghassemicc793702017-01-13 19:20:10 -0800135 """Custom project build command."""
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -0700136
Masood Malekghassemicc793702017-01-13 19:20:10 -0800137 def run(self):
138 try:
139 self.run_command('build_package_protos')
140 except CommandError as error:
141 sys.stderr.write('warning: %s\n' % error.message)
142 build_py.build_py.run(self)
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -0700143
144
145class TestLite(setuptools.Command):
Masood Malekghassemicc793702017-01-13 19:20:10 -0800146 """Command to run tests without fetching or building anything."""
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -0700147
Masood Malekghassemicc793702017-01-13 19:20:10 -0800148 description = 'run tests without fetching or building anything.'
149 user_options = []
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -0700150
Masood Malekghassemicc793702017-01-13 19:20:10 -0800151 def initialize_options(self):
152 pass
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -0700153
Masood Malekghassemicc793702017-01-13 19:20:10 -0800154 def finalize_options(self):
155 # distutils requires this override.
156 pass
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -0700157
Masood Malekghassemicc793702017-01-13 19:20:10 -0800158 def run(self):
159 self._add_eggs_to_path()
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -0700160
Masood Malekghassemicc793702017-01-13 19:20:10 -0800161 import tests
162 loader = tests.Loader()
163 loader.loadTestsFromNames(['tests'])
164 runner = tests.Runner()
165 result = runner.run(loader.suite)
166 if not result.wasSuccessful():
167 sys.exit('Test failure')
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -0700168
Masood Malekghassemicc793702017-01-13 19:20:10 -0800169 def _add_eggs_to_path(self):
170 """Fetch install and test requirements"""
171 self.distribution.fetch_build_eggs(self.distribution.install_requires)
172 self.distribution.fetch_build_eggs(self.distribution.tests_require)
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -0700173
174
175class RunInterop(test.test):
176
Masood Malekghassemicc793702017-01-13 19:20:10 -0800177 description = 'run interop test client/server'
178 user_options = [('args=', 'a', 'pass-thru arguments for the client/server'),
179 ('client', 'c', 'flag indicating to run the client'),
180 ('server', 's', 'flag indicating to run the server')]
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -0700181
Masood Malekghassemicc793702017-01-13 19:20:10 -0800182 def initialize_options(self):
183 self.args = ''
184 self.client = False
185 self.server = False
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -0700186
Masood Malekghassemicc793702017-01-13 19:20:10 -0800187 def finalize_options(self):
188 if self.client and self.server:
189 raise DistutilsOptionError(
190 'you may only specify one of client or server')
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -0700191
Masood Malekghassemicc793702017-01-13 19:20:10 -0800192 def run(self):
193 if self.distribution.install_requires:
194 self.distribution.fetch_build_eggs(
195 self.distribution.install_requires)
196 if self.distribution.tests_require:
197 self.distribution.fetch_build_eggs(self.distribution.tests_require)
198 if self.client:
199 self.run_client()
200 elif self.server:
201 self.run_server()
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -0700202
Masood Malekghassemicc793702017-01-13 19:20:10 -0800203 def run_server(self):
204 # We import here to ensure that our setuptools parent has had a chance to
205 # edit the Python system path.
206 from tests.interop import server
207 sys.argv[1:] = self.args.split()
208 server.serve()
Masood Malekghassemi1ff429d2016-06-02 16:39:20 -0700209
Masood Malekghassemicc793702017-01-13 19:20:10 -0800210 def run_client(self):
211 # We import here to ensure that our setuptools parent has had a chance to
212 # edit the Python system path.
213 from tests.interop import client
214 sys.argv[1:] = self.args.split()
215 client.test_interoperability()