164088bf7bfaadf6123a5521122f6a2a89bab9f0
[platform/upstream/grpc.git] / tools / distrib / python / grpcio_tools / setup.py
1 # Copyright 2016 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
15 from distutils import cygwinccompiler
16 from distutils import extension
17 from distutils import util
18 import errno
19 import os
20 import os.path
21 import pkg_resources
22 import platform
23 import re
24 import shlex
25 import shutil
26 import sys
27 import sysconfig
28
29 import setuptools
30 from setuptools.command import build_ext
31
32 import subprocess
33 from subprocess import PIPE
34
35 # TODO(atash) add flag to disable Cython use
36
37 _PACKAGE_PATH = os.path.realpath(os.path.dirname(__file__))
38 _README_PATH = os.path.join(_PACKAGE_PATH, 'README.rst')
39
40 os.chdir(os.path.dirname(os.path.abspath(__file__)))
41 sys.path.insert(0, os.path.abspath('.'))
42
43 import _parallel_compile_patch
44 import protoc_lib_deps
45 import grpc_version
46
47 _parallel_compile_patch.monkeypatch_compile_maybe()
48
49 CLASSIFIERS = [
50     'Development Status :: 5 - Production/Stable',
51     'Programming Language :: Python',
52     'Programming Language :: Python :: 2',
53     'Programming Language :: Python :: 2.7',
54     'Programming Language :: Python :: 3',
55     'Programming Language :: Python :: 3.4',
56     'Programming Language :: Python :: 3.5',
57     'Programming Language :: Python :: 3.6',
58     'License :: OSI Approved :: Apache Software License',
59 ]
60
61 PY3 = sys.version_info.major == 3
62
63 # Environment variable to determine whether or not the Cython extension should
64 # *use* Cython or use the generated C files. Note that this requires the C files
65 # to have been generated by building first *with* Cython support.
66 BUILD_WITH_CYTHON = os.environ.get('GRPC_PYTHON_BUILD_WITH_CYTHON', False)
67
68
69 def check_linker_need_libatomic():
70     """Test if linker on system needs libatomic."""
71     code_test = (b'#include <atomic>\n' +
72                  b'int main() { return std::atomic<int64_t>{}; }')
73     cxx = os.environ.get('CXX', 'c++')
74     cpp_test = subprocess.Popen([cxx, '-x', 'c++', '-std=c++11', '-'],
75                                 stdin=PIPE,
76                                 stdout=PIPE,
77                                 stderr=PIPE)
78     cpp_test.communicate(input=code_test)
79     if cpp_test.returncode == 0:
80         return False
81     # Double-check to see if -latomic actually can solve the problem.
82     # https://github.com/grpc/grpc/issues/22491
83     cpp_test = subprocess.Popen(
84         [cxx, '-x', 'c++', '-std=c++11', '-latomic', '-'],
85         stdin=PIPE,
86         stdout=PIPE,
87         stderr=PIPE)
88     cpp_test.communicate(input=code_test)
89     return cpp_test.returncode == 0
90
91
92 # There are some situations (like on Windows) where CC, CFLAGS, and LDFLAGS are
93 # entirely ignored/dropped/forgotten by distutils and its Cygwin/MinGW support.
94 # We use these environment variables to thus get around that without locking
95 # ourselves in w.r.t. the multitude of operating systems this ought to build on.
96 # We can also use these variables as a way to inject environment-specific
97 # compiler/linker flags. We assume GCC-like compilers and/or MinGW as a
98 # reasonable default.
99 EXTRA_ENV_COMPILE_ARGS = os.environ.get('GRPC_PYTHON_CFLAGS', None)
100 EXTRA_ENV_LINK_ARGS = os.environ.get('GRPC_PYTHON_LDFLAGS', None)
101 if EXTRA_ENV_COMPILE_ARGS is None:
102     EXTRA_ENV_COMPILE_ARGS = '-std=c++11'
103     if 'win32' in sys.platform:
104         if sys.version_info < (3, 5):
105             # We use define flags here and don't directly add to DEFINE_MACROS below to
106             # ensure that the expert user/builder has a way of turning it off (via the
107             # envvars) without adding yet more GRPC-specific envvars.
108             # See https://sourceforge.net/p/mingw-w64/bugs/363/
109             if '32' in platform.architecture()[0]:
110                 EXTRA_ENV_COMPILE_ARGS += ' -D_ftime=_ftime32 -D_timeb=__timeb32 -D_ftime_s=_ftime32_s -D_hypot=hypot'
111             else:
112                 EXTRA_ENV_COMPILE_ARGS += ' -D_ftime=_ftime64 -D_timeb=__timeb64 -D_hypot=hypot'
113         else:
114             # We need to statically link the C++ Runtime, only the C runtime is
115             # available dynamically
116             EXTRA_ENV_COMPILE_ARGS += ' /MT'
117     elif "linux" in sys.platform or "darwin" in sys.platform:
118         EXTRA_ENV_COMPILE_ARGS += ' -fno-wrapv -frtti'
119 if EXTRA_ENV_LINK_ARGS is None:
120     EXTRA_ENV_LINK_ARGS = ''
121     if "linux" in sys.platform or "darwin" in sys.platform:
122         EXTRA_ENV_LINK_ARGS += ' -lpthread'
123         if check_linker_need_libatomic():
124             EXTRA_ENV_LINK_ARGS += ' -latomic'
125     elif "win32" in sys.platform and sys.version_info < (3, 5):
126         msvcr = cygwinccompiler.get_msvcr()[0]
127         EXTRA_ENV_LINK_ARGS += (
128             ' -static-libgcc -static-libstdc++ -mcrtdll={msvcr}'
129             ' -static -lshlwapi'.format(msvcr=msvcr))
130
131 EXTRA_COMPILE_ARGS = shlex.split(EXTRA_ENV_COMPILE_ARGS)
132 EXTRA_LINK_ARGS = shlex.split(EXTRA_ENV_LINK_ARGS)
133
134 CC_FILES = [os.path.normpath(cc_file) for cc_file in protoc_lib_deps.CC_FILES]
135 PROTO_FILES = [
136     os.path.normpath(proto_file) for proto_file in protoc_lib_deps.PROTO_FILES
137 ]
138 CC_INCLUDE = os.path.normpath(protoc_lib_deps.CC_INCLUDE)
139 PROTO_INCLUDE = os.path.normpath(protoc_lib_deps.PROTO_INCLUDE)
140
141 GRPC_PYTHON_TOOLS_PACKAGE = 'grpc_tools'
142 GRPC_PYTHON_PROTO_RESOURCES_NAME = '_proto'
143
144 DEFINE_MACROS = ()
145 if "win32" in sys.platform:
146     DEFINE_MACROS += (('WIN32_LEAN_AND_MEAN', 1),)
147     if '64bit' in platform.architecture()[0]:
148         DEFINE_MACROS += (('MS_WIN64', 1),)
149 elif "linux" in sys.platform or "darwin" in sys.platform:
150     DEFINE_MACROS += (('HAVE_PTHREAD', 1),)
151
152 # By default, Python3 distutils enforces compatibility of
153 # c plugins (.so files) with the OSX version Python was built with.
154 # We need OSX 10.10, the oldest which supports C++ thread_local.
155 if 'darwin' in sys.platform:
156     mac_target = sysconfig.get_config_var('MACOSX_DEPLOYMENT_TARGET')
157     if mac_target and (pkg_resources.parse_version(mac_target) <
158                        pkg_resources.parse_version('10.10.0')):
159         os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.10'
160         os.environ['_PYTHON_HOST_PLATFORM'] = re.sub(
161             r'macosx-[0-9]+\.[0-9]+-(.+)', r'macosx-10.10-\1',
162             util.get_platform())
163
164
165 def package_data():
166     tools_path = GRPC_PYTHON_TOOLS_PACKAGE.replace('.', os.path.sep)
167     proto_resources_path = os.path.join(tools_path,
168                                         GRPC_PYTHON_PROTO_RESOURCES_NAME)
169     proto_files = []
170     for proto_file in PROTO_FILES:
171         source = os.path.join(PROTO_INCLUDE, proto_file)
172         target = os.path.join(proto_resources_path, proto_file)
173         relative_target = os.path.join(GRPC_PYTHON_PROTO_RESOURCES_NAME,
174                                        proto_file)
175         try:
176             os.makedirs(os.path.dirname(target))
177         except OSError as error:
178             if error.errno == errno.EEXIST:
179                 pass
180             else:
181                 raise
182         shutil.copy(source, target)
183         proto_files.append(relative_target)
184     return {GRPC_PYTHON_TOOLS_PACKAGE: proto_files}
185
186
187 def extension_modules():
188     if BUILD_WITH_CYTHON:
189         plugin_sources = [os.path.join('grpc_tools', '_protoc_compiler.pyx')]
190     else:
191         plugin_sources = [os.path.join('grpc_tools', '_protoc_compiler.cpp')]
192
193     plugin_sources += [
194         os.path.join('grpc_tools', 'main.cc'),
195         os.path.join('grpc_root', 'src', 'compiler', 'python_generator.cc')
196     ] + [os.path.join(CC_INCLUDE, cc_file) for cc_file in CC_FILES]
197
198     plugin_ext = extension.Extension(
199         name='grpc_tools._protoc_compiler',
200         sources=plugin_sources,
201         include_dirs=[
202             '.',
203             'grpc_root',
204             os.path.join('grpc_root', 'include'),
205             CC_INCLUDE,
206         ],
207         language='c++',
208         define_macros=list(DEFINE_MACROS),
209         extra_compile_args=list(EXTRA_COMPILE_ARGS),
210         extra_link_args=list(EXTRA_LINK_ARGS),
211     )
212     extensions = [plugin_ext]
213     if BUILD_WITH_CYTHON:
214         from Cython import Build
215         return Build.cythonize(extensions)
216     else:
217         return extensions
218
219
220 setuptools.setup(
221     name='grpcio-tools',
222     version=grpc_version.VERSION,
223     description='Protobuf code generator for gRPC',
224     long_description=open(_README_PATH, 'r').read(),
225     author='The gRPC Authors',
226     author_email='grpc-io@googlegroups.com',
227     url='https://grpc.io',
228     license='Apache License 2.0',
229     classifiers=CLASSIFIERS,
230     ext_modules=extension_modules(),
231     packages=setuptools.find_packages('.'),
232     install_requires=[
233         'protobuf>=3.5.0.post1, < 4.0dev',
234         'grpcio>={version}'.format(version=grpc_version.VERSION),
235         'setuptools',
236     ],
237     package_data=package_data(),
238 )