setup.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  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. from distutils import cygwinccompiler
  15. from distutils import extension
  16. from distutils import util
  17. import errno
  18. import os
  19. import os.path
  20. import platform
  21. import re
  22. import shlex
  23. import shutil
  24. import subprocess
  25. from subprocess import PIPE
  26. import sys
  27. import sysconfig
  28. import pkg_resources
  29. import setuptools
  30. from setuptools.command import build_ext
  31. # TODO(atash) add flag to disable Cython use
  32. _PACKAGE_PATH = os.path.realpath(os.path.dirname(__file__))
  33. _README_PATH = os.path.join(_PACKAGE_PATH, 'README.rst')
  34. os.chdir(os.path.dirname(os.path.abspath(__file__)))
  35. sys.path.insert(0, os.path.abspath('.'))
  36. import _parallel_compile_patch
  37. import protoc_lib_deps
  38. import grpc_version
  39. _EXT_INIT_SYMBOL = None
  40. if sys.version_info[0] == 2:
  41. _EXT_INIT_SYMBOL = "init_protoc_compiler"
  42. else:
  43. _EXT_INIT_SYMBOL = "PyInit__protoc_compiler"
  44. _parallel_compile_patch.monkeypatch_compile_maybe()
  45. CLASSIFIERS = [
  46. 'Development Status :: 5 - Production/Stable',
  47. 'Programming Language :: Python',
  48. 'Programming Language :: Python :: 3',
  49. 'License :: OSI Approved :: Apache Software License',
  50. ]
  51. PY3 = sys.version_info.major == 3
  52. def _env_bool_value(env_name, default):
  53. """Parses a bool option from an environment variable"""
  54. return os.environ.get(env_name, default).upper() not in ['FALSE', '0', '']
  55. # Environment variable to determine whether or not the Cython extension should
  56. # *use* Cython or use the generated C files. Note that this requires the C files
  57. # to have been generated by building first *with* Cython support.
  58. BUILD_WITH_CYTHON = _env_bool_value('GRPC_PYTHON_BUILD_WITH_CYTHON', 'False')
  59. # Export this variable to force building the python extension with a statically linked libstdc++.
  60. # At least on linux, this is normally not needed as we can build manylinux-compatible wheels on linux just fine
  61. # without statically linking libstdc++ (which leads to a slight increase in the wheel size).
  62. # This option is useful when crosscompiling wheels for aarch64 where
  63. # it's difficult to ensure that the crosscompilation toolchain has a high-enough version
  64. # of GCC (we require >=5.1) but still uses old-enough libstdc++ symbols.
  65. # TODO(jtattermusch): remove this workaround once issues with crosscompiler version are resolved.
  66. BUILD_WITH_STATIC_LIBSTDCXX = _env_bool_value(
  67. 'GRPC_PYTHON_BUILD_WITH_STATIC_LIBSTDCXX', 'False')
  68. def check_linker_need_libatomic():
  69. """Test if linker on system needs libatomic."""
  70. code_test = (b'#include <atomic>\n' +
  71. b'int main() { return std::atomic<int64_t>{}; }')
  72. cxx = os.environ.get('CXX', 'c++')
  73. cpp_test = subprocess.Popen([cxx, '-x', 'c++', '-std=c++11', '-'],
  74. stdin=PIPE,
  75. stdout=PIPE,
  76. stderr=PIPE)
  77. cpp_test.communicate(input=code_test)
  78. if cpp_test.returncode == 0:
  79. return False
  80. # Double-check to see if -latomic actually can solve the problem.
  81. # https://github.com/grpc/grpc/issues/22491
  82. cpp_test = subprocess.Popen(
  83. [cxx, '-x', 'c++', '-std=c++11', '-', '-latomic'],
  84. stdin=PIPE,
  85. stdout=PIPE,
  86. stderr=PIPE)
  87. cpp_test.communicate(input=code_test)
  88. return cpp_test.returncode == 0
  89. class BuildExt(build_ext.build_ext):
  90. """Custom build_ext command."""
  91. def get_ext_filename(self, ext_name):
  92. # since python3.5, python extensions' shared libraries use a suffix that corresponds to the value
  93. # of sysconfig.get_config_var('EXT_SUFFIX') and contains info about the architecture the library targets.
  94. # E.g. on x64 linux the suffix is ".cpython-XYZ-x86_64-linux-gnu.so"
  95. # When crosscompiling python wheels, we need to be able to override this suffix
  96. # so that the resulting file name matches the target architecture and we end up with a well-formed
  97. # wheel.
  98. filename = build_ext.build_ext.get_ext_filename(self, ext_name)
  99. orig_ext_suffix = sysconfig.get_config_var('EXT_SUFFIX')
  100. new_ext_suffix = os.getenv('GRPC_PYTHON_OVERRIDE_EXT_SUFFIX')
  101. if new_ext_suffix and filename.endswith(orig_ext_suffix):
  102. filename = filename[:-len(orig_ext_suffix)] + new_ext_suffix
  103. return filename
  104. # There are some situations (like on Windows) where CC, CFLAGS, and LDFLAGS are
  105. # entirely ignored/dropped/forgotten by distutils and its Cygwin/MinGW support.
  106. # We use these environment variables to thus get around that without locking
  107. # ourselves in w.r.t. the multitude of operating systems this ought to build on.
  108. # We can also use these variables as a way to inject environment-specific
  109. # compiler/linker flags. We assume GCC-like compilers and/or MinGW as a
  110. # reasonable default.
  111. EXTRA_ENV_COMPILE_ARGS = os.environ.get('GRPC_PYTHON_CFLAGS', None)
  112. EXTRA_ENV_LINK_ARGS = os.environ.get('GRPC_PYTHON_LDFLAGS', None)
  113. if EXTRA_ENV_COMPILE_ARGS is None:
  114. EXTRA_ENV_COMPILE_ARGS = '-std=c++11'
  115. if 'win32' in sys.platform:
  116. if sys.version_info < (3, 5):
  117. # We use define flags here and don't directly add to DEFINE_MACROS below to
  118. # ensure that the expert user/builder has a way of turning it off (via the
  119. # envvars) without adding yet more GRPC-specific envvars.
  120. # See https://sourceforge.net/p/mingw-w64/bugs/363/
  121. if '32' in platform.architecture()[0]:
  122. EXTRA_ENV_COMPILE_ARGS += ' -D_ftime=_ftime32 -D_timeb=__timeb32 -D_ftime_s=_ftime32_s -D_hypot=hypot'
  123. else:
  124. EXTRA_ENV_COMPILE_ARGS += ' -D_ftime=_ftime64 -D_timeb=__timeb64 -D_hypot=hypot'
  125. else:
  126. # We need to statically link the C++ Runtime, only the C runtime is
  127. # available dynamically
  128. EXTRA_ENV_COMPILE_ARGS += ' /MT'
  129. elif "linux" in sys.platform or "darwin" in sys.platform:
  130. EXTRA_ENV_COMPILE_ARGS += ' -fno-wrapv -frtti'
  131. if EXTRA_ENV_LINK_ARGS is None:
  132. EXTRA_ENV_LINK_ARGS = ''
  133. # NOTE(rbellevi): Clang on Mac OS will make all static symbols (both
  134. # variables and objects) global weak symbols. When a process loads the
  135. # protobuf wheel's shared object library before loading *this* C extension,
  136. # the runtime linker will prefer the protobuf module's version of symbols.
  137. # This results in the process using a mixture of symbols from the protobuf
  138. # wheel and this wheel, which may be using different versions of
  139. # libprotobuf. In the case that they *are* using different versions of
  140. # libprotobuf *and* there has been a change in data layout (or in other
  141. # invariants) segfaults, data corruption, or "bad things" may happen.
  142. #
  143. # This flag ensures that on Mac, the only global symbol is the one loaded by
  144. # the Python interpreter. The problematic global weak symbols become local
  145. # weak symbols. This is not required on Linux since the compiler does not
  146. # produce global weak symbols. This is not required on Windows as our ".pyd"
  147. # file does not contain any symbols.
  148. #
  149. # Finally, the leading underscore here is part of the Mach-O ABI. Unlike
  150. # more modern ABIs (ELF et al.), Mach-O prepends an underscore to the names
  151. # of C functions.
  152. if "darwin" in sys.platform:
  153. EXTRA_ENV_LINK_ARGS += ' -Wl,-exported_symbol,_{}'.format(
  154. _EXT_INIT_SYMBOL)
  155. if "linux" in sys.platform or "darwin" in sys.platform:
  156. EXTRA_ENV_LINK_ARGS += ' -lpthread'
  157. if check_linker_need_libatomic():
  158. EXTRA_ENV_LINK_ARGS += ' -latomic'
  159. elif "win32" in sys.platform and sys.version_info < (3, 5):
  160. msvcr = cygwinccompiler.get_msvcr()[0]
  161. EXTRA_ENV_LINK_ARGS += (
  162. ' -static-libgcc -static-libstdc++ -mcrtdll={msvcr}'
  163. ' -static -lshlwapi'.format(msvcr=msvcr))
  164. EXTRA_COMPILE_ARGS = shlex.split(EXTRA_ENV_COMPILE_ARGS)
  165. EXTRA_LINK_ARGS = shlex.split(EXTRA_ENV_LINK_ARGS)
  166. if BUILD_WITH_STATIC_LIBSTDCXX:
  167. EXTRA_LINK_ARGS.append('-static-libstdc++')
  168. CC_FILES = [os.path.normpath(cc_file) for cc_file in protoc_lib_deps.CC_FILES]
  169. PROTO_FILES = [
  170. os.path.normpath(proto_file) for proto_file in protoc_lib_deps.PROTO_FILES
  171. ]
  172. CC_INCLUDE = os.path.normpath(protoc_lib_deps.CC_INCLUDE)
  173. PROTO_INCLUDE = os.path.normpath(protoc_lib_deps.PROTO_INCLUDE)
  174. GRPC_PYTHON_TOOLS_PACKAGE = 'grpc_tools'
  175. GRPC_PYTHON_PROTO_RESOURCES_NAME = '_proto'
  176. DEFINE_MACROS = ()
  177. if "win32" in sys.platform:
  178. DEFINE_MACROS += (('WIN32_LEAN_AND_MEAN', 1),)
  179. if '64bit' in platform.architecture()[0]:
  180. DEFINE_MACROS += (('MS_WIN64', 1),)
  181. elif "linux" in sys.platform or "darwin" in sys.platform:
  182. DEFINE_MACROS += (('HAVE_PTHREAD', 1),)
  183. # By default, Python3 distutils enforces compatibility of
  184. # c plugins (.so files) with the OSX version Python was built with.
  185. # We need OSX 10.10, the oldest which supports C++ thread_local.
  186. if 'darwin' in sys.platform:
  187. mac_target = sysconfig.get_config_var('MACOSX_DEPLOYMENT_TARGET')
  188. if mac_target and (pkg_resources.parse_version(mac_target) <
  189. pkg_resources.parse_version('10.10.0')):
  190. os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.10'
  191. os.environ['_PYTHON_HOST_PLATFORM'] = re.sub(
  192. r'macosx-[0-9]+\.[0-9]+-(.+)', r'macosx-10.10-\1',
  193. util.get_platform())
  194. def package_data():
  195. tools_path = GRPC_PYTHON_TOOLS_PACKAGE.replace('.', os.path.sep)
  196. proto_resources_path = os.path.join(tools_path,
  197. GRPC_PYTHON_PROTO_RESOURCES_NAME)
  198. proto_files = []
  199. for proto_file in PROTO_FILES:
  200. source = os.path.join(PROTO_INCLUDE, proto_file)
  201. target = os.path.join(proto_resources_path, proto_file)
  202. relative_target = os.path.join(GRPC_PYTHON_PROTO_RESOURCES_NAME,
  203. proto_file)
  204. try:
  205. os.makedirs(os.path.dirname(target))
  206. except OSError as error:
  207. if error.errno == errno.EEXIST:
  208. pass
  209. else:
  210. raise
  211. shutil.copy(source, target)
  212. proto_files.append(relative_target)
  213. return {GRPC_PYTHON_TOOLS_PACKAGE: proto_files}
  214. def extension_modules():
  215. if BUILD_WITH_CYTHON:
  216. plugin_sources = [os.path.join('grpc_tools', '_protoc_compiler.pyx')]
  217. else:
  218. plugin_sources = [os.path.join('grpc_tools', '_protoc_compiler.cpp')]
  219. plugin_sources += [
  220. os.path.join('grpc_tools', 'main.cc'),
  221. os.path.join('grpc_root', 'src', 'compiler', 'python_generator.cc')
  222. ] + [os.path.join(CC_INCLUDE, cc_file) for cc_file in CC_FILES]
  223. plugin_ext = extension.Extension(
  224. name='grpc_tools._protoc_compiler',
  225. sources=plugin_sources,
  226. include_dirs=[
  227. '.',
  228. 'grpc_root',
  229. os.path.join('grpc_root', 'include'),
  230. CC_INCLUDE,
  231. ],
  232. language='c++',
  233. define_macros=list(DEFINE_MACROS),
  234. extra_compile_args=list(EXTRA_COMPILE_ARGS),
  235. extra_link_args=list(EXTRA_LINK_ARGS),
  236. )
  237. extensions = [plugin_ext]
  238. if BUILD_WITH_CYTHON:
  239. from Cython import Build
  240. return Build.cythonize(extensions)
  241. else:
  242. return extensions
  243. setuptools.setup(name='grpcio-tools',
  244. version=grpc_version.VERSION,
  245. description='Protobuf code generator for gRPC',
  246. long_description=open(_README_PATH, 'r').read(),
  247. author='The gRPC Authors',
  248. author_email='grpc-io@googlegroups.com',
  249. url='https://grpc.io',
  250. license='Apache License 2.0',
  251. classifiers=CLASSIFIERS,
  252. ext_modules=extension_modules(),
  253. packages=setuptools.find_packages('.'),
  254. python_requires='>=3.6',
  255. install_requires=[
  256. 'protobuf>=3.5.0.post1, < 4.0dev',
  257. 'grpcio>={version}'.format(version=grpc_version.VERSION),
  258. 'setuptools',
  259. ],
  260. package_data=package_data(),
  261. cmdclass={
  262. 'build_ext': BuildExt,
  263. })