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"""A setup module for the GRPC Python package."""
15
16# NOTE(https://github.com/grpc/grpc/issues/24028): allow setuptools to monkey
17# patch distutils
18import setuptools  # isort:skip
19
20# Monkey Patch the unix compiler to accept ASM
21# files used by boring SSL.
22from distutils.unixccompiler import UnixCCompiler
23
24UnixCCompiler.src_extensions.append('.S')
25del UnixCCompiler
26
27from distutils import cygwinccompiler
28from distutils import extension as _extension
29from distutils import util
30import os
31import os.path
32import pathlib
33import platform
34import re
35import shlex
36import shutil
37import subprocess
38from subprocess import PIPE
39import sys
40import sysconfig
41
42import _metadata
43import pkg_resources
44from setuptools.command import egg_info
45
46# Redirect the manifest template from MANIFEST.in to PYTHON-MANIFEST.in.
47egg_info.manifest_maker.template = 'PYTHON-MANIFEST.in'
48
49PY3 = sys.version_info.major == 3
50PYTHON_STEM = os.path.join('src', 'python', 'grpcio')
51CORE_INCLUDE = (
52    'include',
53    '.',
54)
55ABSL_INCLUDE = (os.path.join('third_party', 'abseil-cpp'),)
56ADDRESS_SORTING_INCLUDE = (os.path.join('third_party', 'address_sorting',
57                                        'include'),)
58CARES_INCLUDE = (
59    os.path.join('third_party', 'cares', 'cares', 'include'),
60    os.path.join('third_party', 'cares'),
61    os.path.join('third_party', 'cares', 'cares'),
62)
63if 'darwin' in sys.platform:
64    CARES_INCLUDE += (os.path.join('third_party', 'cares', 'config_darwin'),)
65if 'freebsd' in sys.platform:
66    CARES_INCLUDE += (os.path.join('third_party', 'cares', 'config_freebsd'),)
67if 'linux' in sys.platform:
68    CARES_INCLUDE += (os.path.join('third_party', 'cares', 'config_linux'),)
69if 'openbsd' in sys.platform:
70    CARES_INCLUDE += (os.path.join('third_party', 'cares', 'config_openbsd'),)
71RE2_INCLUDE = (os.path.join('third_party', 're2'),)
72SSL_INCLUDE = (os.path.join('third_party', 'boringssl-with-bazel', 'src',
73                            'include'),)
74UPB_INCLUDE = (os.path.join('third_party', 'upb'),)
75UPB_GRPC_GENERATED_INCLUDE = (os.path.join('src', 'core', 'ext',
76                                           'upb-generated'),)
77UPBDEFS_GRPC_GENERATED_INCLUDE = (os.path.join('src', 'core', 'ext',
78                                               'upbdefs-generated'),)
79UTF8_RANGE_INCLUDE = (os.path.join('third_party', 'utf8_range'),)
80XXHASH_INCLUDE = (os.path.join('third_party', 'xxhash'),)
81ZLIB_INCLUDE = (os.path.join('third_party', 'zlib'),)
82README = os.path.join(PYTHON_STEM, 'README.rst')
83
84# Ensure we're in the proper directory whether or not we're being used by pip.
85os.chdir(os.path.dirname(os.path.abspath(__file__)))
86sys.path.insert(0, os.path.abspath(PYTHON_STEM))
87
88# Break import-style to ensure we can actually find our in-repo dependencies.
89import _parallel_compile_patch
90import _spawn_patch
91import grpc_core_dependencies
92
93import commands
94import grpc_version
95
96_parallel_compile_patch.monkeypatch_compile_maybe()
97_spawn_patch.monkeypatch_spawn()
98
99LICENSE = 'Apache License 2.0'
100
101CLASSIFIERS = [
102    'Development Status :: 5 - Production/Stable',
103    'Programming Language :: Python',
104    'Programming Language :: Python :: 3',
105    'Programming Language :: Python :: 3.7',
106    'Programming Language :: Python :: 3.8',
107    'Programming Language :: Python :: 3.9',
108    'Programming Language :: Python :: 3.10',
109    'Programming Language :: Python :: 3.11',
110    'License :: OSI Approved :: Apache Software License',
111]
112
113
114def _env_bool_value(env_name, default):
115    """Parses a bool option from an environment variable"""
116    return os.environ.get(env_name, default).upper() not in ['FALSE', '0', '']
117
118
119BUILD_WITH_BORING_SSL_ASM = _env_bool_value('GRPC_BUILD_WITH_BORING_SSL_ASM',
120                                            'True')
121
122# Export this environment variable to override the platform variant that will
123# be chosen for boringssl assembly optimizations. This option is useful when
124# crosscompiling and the host platform as obtained by distutils.utils.get_platform()
125# doesn't match the platform we are targetting.
126# Example value: "linux-aarch64"
127BUILD_OVERRIDE_BORING_SSL_ASM_PLATFORM = os.environ.get(
128    'GRPC_BUILD_OVERRIDE_BORING_SSL_ASM_PLATFORM', '')
129
130# Environment variable to determine whether or not the Cython extension should
131# *use* Cython or use the generated C files. Note that this requires the C files
132# to have been generated by building first *with* Cython support. Even if this
133# is set to false, if the script detects that the generated `.c` file isn't
134# present, then it will still attempt to use Cython.
135BUILD_WITH_CYTHON = _env_bool_value('GRPC_PYTHON_BUILD_WITH_CYTHON', 'False')
136
137# Export this variable to use the system installation of openssl. You need to
138# have the header files installed (in /usr/include/openssl) and during
139# runtime, the shared library must be installed
140BUILD_WITH_SYSTEM_OPENSSL = _env_bool_value('GRPC_PYTHON_BUILD_SYSTEM_OPENSSL',
141                                            'False')
142
143# Export this variable to use the system installation of zlib. You need to
144# have the header files installed (in /usr/include/) and during
145# runtime, the shared library must be installed
146BUILD_WITH_SYSTEM_ZLIB = _env_bool_value('GRPC_PYTHON_BUILD_SYSTEM_ZLIB',
147                                         'False')
148
149# Export this variable to use the system installation of cares. You need to
150# have the header files installed (in /usr/include/) and during
151# runtime, the shared library must be installed
152BUILD_WITH_SYSTEM_CARES = _env_bool_value('GRPC_PYTHON_BUILD_SYSTEM_CARES',
153                                          'False')
154
155# Export this variable to use the system installation of re2. You need to
156# have the header files installed (in /usr/include/re2) and during
157# runtime, the shared library must be installed
158BUILD_WITH_SYSTEM_RE2 = _env_bool_value('GRPC_PYTHON_BUILD_SYSTEM_RE2', 'False')
159
160# Export this variable to use the system installation of abseil. You need to
161# have the header files installed (in /usr/include/absl) and during
162# runtime, the shared library must be installed
163BUILD_WITH_SYSTEM_ABSL = os.environ.get('GRPC_PYTHON_BUILD_SYSTEM_ABSL', False)
164
165# Export this variable to force building the python extension with a statically linked libstdc++.
166# At least on linux, this is normally not needed as we can build manylinux-compatible wheels on linux just fine
167# without statically linking libstdc++ (which leads to a slight increase in the wheel size).
168# This option is useful when crosscompiling wheels for aarch64 where
169# it's difficult to ensure that the crosscompilation toolchain has a high-enough version
170# of GCC (we require >=5.1) but still uses old-enough libstdc++ symbols.
171# TODO(jtattermusch): remove this workaround once issues with crosscompiler version are resolved.
172BUILD_WITH_STATIC_LIBSTDCXX = _env_bool_value(
173    'GRPC_PYTHON_BUILD_WITH_STATIC_LIBSTDCXX', 'False')
174
175# For local development use only: This skips building gRPC Core and its
176# dependencies, including protobuf and boringssl. This allows "incremental"
177# compilation by first building gRPC Core using make, then building only the
178# Python/Cython layers here.
179#
180# Note that this requires libboringssl.a in the libs/{dbg,opt}/ directory, which
181# may require configuring make to not use the system openssl implementation:
182#
183#    make HAS_SYSTEM_OPENSSL_ALPN=0
184#
185# TODO(ericgribkoff) Respect the BUILD_WITH_SYSTEM_* flags alongside this option
186USE_PREBUILT_GRPC_CORE = _env_bool_value('GRPC_PYTHON_USE_PREBUILT_GRPC_CORE',
187                                         'False')
188
189# If this environmental variable is set, GRPC will not try to be compatible with
190# libc versions old than the one it was compiled against.
191DISABLE_LIBC_COMPATIBILITY = _env_bool_value(
192    'GRPC_PYTHON_DISABLE_LIBC_COMPATIBILITY', 'False')
193
194# Environment variable to determine whether or not to enable coverage analysis
195# in Cython modules.
196ENABLE_CYTHON_TRACING = _env_bool_value('GRPC_PYTHON_ENABLE_CYTHON_TRACING',
197                                        'False')
198
199# Environment variable specifying whether or not there's interest in setting up
200# documentation building.
201ENABLE_DOCUMENTATION_BUILD = _env_bool_value(
202    'GRPC_PYTHON_ENABLE_DOCUMENTATION_BUILD', 'False')
203
204
205def check_linker_need_libatomic():
206    """Test if linker on system needs libatomic."""
207    code_test = (b'#include <atomic>\n' +
208                 b'int main() { return std::atomic<int64_t>{}; }')
209    cxx = shlex.split(os.environ.get('CXX', 'c++'))
210    cpp_test = subprocess.Popen(cxx + ['-x', 'c++', '-std=c++14', '-'],
211                                stdin=PIPE,
212                                stdout=PIPE,
213                                stderr=PIPE)
214    cpp_test.communicate(input=code_test)
215    if cpp_test.returncode == 0:
216        return False
217    # Double-check to see if -latomic actually can solve the problem.
218    # https://github.com/grpc/grpc/issues/22491
219    cpp_test = subprocess.Popen(cxx +
220                                ['-x', 'c++', '-std=c++14', '-', '-latomic'],
221                                stdin=PIPE,
222                                stdout=PIPE,
223                                stderr=PIPE)
224    cpp_test.communicate(input=code_test)
225    return cpp_test.returncode == 0
226
227
228# There are some situations (like on Windows) where CC, CFLAGS, and LDFLAGS are
229# entirely ignored/dropped/forgotten by distutils and its Cygwin/MinGW support.
230# We use these environment variables to thus get around that without locking
231# ourselves in w.r.t. the multitude of operating systems this ought to build on.
232# We can also use these variables as a way to inject environment-specific
233# compiler/linker flags. We assume GCC-like compilers and/or MinGW as a
234# reasonable default.
235EXTRA_ENV_COMPILE_ARGS = os.environ.get('GRPC_PYTHON_CFLAGS', None)
236EXTRA_ENV_LINK_ARGS = os.environ.get('GRPC_PYTHON_LDFLAGS', None)
237if EXTRA_ENV_COMPILE_ARGS is None:
238    EXTRA_ENV_COMPILE_ARGS = ' -std=c++14'
239    if 'win32' in sys.platform:
240        if sys.version_info < (3, 5):
241            EXTRA_ENV_COMPILE_ARGS += ' -D_hypot=hypot'
242            # We use define flags here and don't directly add to DEFINE_MACROS below to
243            # ensure that the expert user/builder has a way of turning it off (via the
244            # envvars) without adding yet more GRPC-specific envvars.
245            # See https://sourceforge.net/p/mingw-w64/bugs/363/
246            if '32' in platform.architecture()[0]:
247                EXTRA_ENV_COMPILE_ARGS += ' -D_ftime=_ftime32 -D_timeb=__timeb32 -D_ftime_s=_ftime32_s'
248            else:
249                EXTRA_ENV_COMPILE_ARGS += ' -D_ftime=_ftime64 -D_timeb=__timeb64'
250        else:
251            # We need to statically link the C++ Runtime, only the C runtime is
252            # available dynamically
253            EXTRA_ENV_COMPILE_ARGS += ' /MT'
254    elif "linux" in sys.platform:
255        EXTRA_ENV_COMPILE_ARGS += ' -fvisibility=hidden -fno-wrapv -fno-exceptions'
256    elif "darwin" in sys.platform:
257        EXTRA_ENV_COMPILE_ARGS += ' -stdlib=libc++ -fvisibility=hidden -fno-wrapv -fno-exceptions -DHAVE_UNISTD_H'
258
259if EXTRA_ENV_LINK_ARGS is None:
260    EXTRA_ENV_LINK_ARGS = ''
261    if "linux" in sys.platform or "darwin" in sys.platform:
262        EXTRA_ENV_LINK_ARGS += ' -lpthread'
263        if check_linker_need_libatomic():
264            EXTRA_ENV_LINK_ARGS += ' -latomic'
265    elif "win32" in sys.platform and sys.version_info < (3, 5):
266        msvcr = cygwinccompiler.get_msvcr()[0]
267        EXTRA_ENV_LINK_ARGS += (
268            ' -static-libgcc -static-libstdc++ -mcrtdll={msvcr}'
269            ' -static -lshlwapi'.format(msvcr=msvcr))
270    if "linux" in sys.platform:
271        EXTRA_ENV_LINK_ARGS += ' -static-libgcc'
272
273EXTRA_COMPILE_ARGS = shlex.split(EXTRA_ENV_COMPILE_ARGS)
274EXTRA_LINK_ARGS = shlex.split(EXTRA_ENV_LINK_ARGS)
275
276if BUILD_WITH_STATIC_LIBSTDCXX:
277    EXTRA_LINK_ARGS.append('-static-libstdc++')
278
279CYTHON_EXTENSION_PACKAGE_NAMES = ()
280
281CYTHON_EXTENSION_MODULE_NAMES = ('grpc._cython.cygrpc',)
282
283CYTHON_HELPER_C_FILES = ()
284
285CORE_C_FILES = tuple(grpc_core_dependencies.CORE_SOURCE_FILES)
286if "win32" in sys.platform:
287    CORE_C_FILES = filter(lambda x: 'third_party/cares' not in x, CORE_C_FILES)
288
289if BUILD_WITH_SYSTEM_OPENSSL:
290    CORE_C_FILES = filter(lambda x: 'third_party/boringssl' not in x,
291                          CORE_C_FILES)
292    CORE_C_FILES = filter(lambda x: 'src/boringssl' not in x, CORE_C_FILES)
293    SSL_INCLUDE = (os.path.join('/usr', 'include', 'openssl'),)
294
295if BUILD_WITH_SYSTEM_ZLIB:
296    CORE_C_FILES = filter(lambda x: 'third_party/zlib' not in x, CORE_C_FILES)
297    ZLIB_INCLUDE = (os.path.join('/usr', 'include'),)
298
299if BUILD_WITH_SYSTEM_CARES:
300    CORE_C_FILES = filter(lambda x: 'third_party/cares' not in x, CORE_C_FILES)
301    CARES_INCLUDE = (os.path.join('/usr', 'include'),)
302
303if BUILD_WITH_SYSTEM_RE2:
304    CORE_C_FILES = filter(lambda x: 'third_party/re2' not in x, CORE_C_FILES)
305    RE2_INCLUDE = (os.path.join('/usr', 'include', 're2'),)
306
307if BUILD_WITH_SYSTEM_ABSL:
308    CORE_C_FILES = filter(lambda x: 'third_party/abseil-cpp' not in x,
309                          CORE_C_FILES)
310    ABSL_INCLUDE = (os.path.join('/usr', 'include'),)
311
312EXTENSION_INCLUDE_DIRECTORIES = ((PYTHON_STEM,) + CORE_INCLUDE + ABSL_INCLUDE +
313                                 ADDRESS_SORTING_INCLUDE + CARES_INCLUDE +
314                                 RE2_INCLUDE + SSL_INCLUDE + UPB_INCLUDE +
315                                 UPB_GRPC_GENERATED_INCLUDE +
316                                 UPBDEFS_GRPC_GENERATED_INCLUDE +
317                                 UTF8_RANGE_INCLUDE + XXHASH_INCLUDE +
318                                 ZLIB_INCLUDE)
319
320EXTENSION_LIBRARIES = ()
321if "linux" in sys.platform:
322    EXTENSION_LIBRARIES += ('rt',)
323if not "win32" in sys.platform:
324    EXTENSION_LIBRARIES += ('m',)
325if "win32" in sys.platform:
326    EXTENSION_LIBRARIES += (
327        'advapi32',
328        'bcrypt',
329        'dbghelp',
330        'ws2_32',
331    )
332if BUILD_WITH_SYSTEM_OPENSSL:
333    EXTENSION_LIBRARIES += (
334        'ssl',
335        'crypto',
336    )
337if BUILD_WITH_SYSTEM_ZLIB:
338    EXTENSION_LIBRARIES += ('z',)
339if BUILD_WITH_SYSTEM_CARES:
340    EXTENSION_LIBRARIES += ('cares',)
341if BUILD_WITH_SYSTEM_RE2:
342    EXTENSION_LIBRARIES += ('re2',)
343if BUILD_WITH_SYSTEM_ABSL:
344    EXTENSION_LIBRARIES += tuple(
345        lib.stem[3:] for lib in pathlib.Path('/usr').glob('lib*/libabsl_*.so'))
346
347DEFINE_MACROS = (('_WIN32_WINNT', 0x600),)
348asm_files = []
349
350
351# Quotes on Windows build macros are evaluated differently from other platforms,
352# so we must apply quotes asymmetrically in order to yield the proper result in
353# the binary.
354def _quote_build_define(argument):
355    if "win32" in sys.platform:
356        return '"\\\"{}\\\""'.format(argument)
357    return '"{}"'.format(argument)
358
359
360DEFINE_MACROS += (
361    ("GRPC_XDS_USER_AGENT_NAME_SUFFIX", _quote_build_define("Python")),
362    ("GRPC_XDS_USER_AGENT_VERSION_SUFFIX",
363     _quote_build_define(_metadata.__version__)),
364)
365
366asm_key = ''
367if BUILD_WITH_BORING_SSL_ASM and not BUILD_WITH_SYSTEM_OPENSSL:
368    boringssl_asm_platform = BUILD_OVERRIDE_BORING_SSL_ASM_PLATFORM if BUILD_OVERRIDE_BORING_SSL_ASM_PLATFORM else util.get_platform(
369    )
370    LINUX_X86_64 = 'linux-x86_64'
371    LINUX_ARM = 'linux-arm'
372    LINUX_AARCH64 = 'linux-aarch64'
373    if LINUX_X86_64 == boringssl_asm_platform:
374        asm_key = 'crypto_linux_x86_64'
375    elif LINUX_ARM == boringssl_asm_platform:
376        asm_key = 'crypto_linux_arm'
377    elif LINUX_AARCH64 == boringssl_asm_platform:
378        asm_key = 'crypto_linux_aarch64'
379    elif "mac" in boringssl_asm_platform and "x86_64" in boringssl_asm_platform:
380        asm_key = 'crypto_apple_x86_64'
381    elif "mac" in boringssl_asm_platform and "arm64" in boringssl_asm_platform:
382        asm_key = 'crypto_apple_aarch64'
383    else:
384        print("ASM Builds for BoringSSL currently not supported on:",
385              boringssl_asm_platform)
386if asm_key:
387    asm_files = grpc_core_dependencies.ASM_SOURCE_FILES[asm_key]
388else:
389    DEFINE_MACROS += (('OPENSSL_NO_ASM', 1),)
390
391if not DISABLE_LIBC_COMPATIBILITY:
392    DEFINE_MACROS += (('GPR_BACKWARDS_COMPATIBILITY_MODE', 1),)
393
394if "win32" in sys.platform:
395    # TODO(zyc): Re-enable c-ares on x64 and x86 windows after fixing the
396    # ares_library_init compilation issue
397    DEFINE_MACROS += (
398        ('WIN32_LEAN_AND_MEAN', 1),
399        ('CARES_STATICLIB', 1),
400        ('GRPC_ARES', 0),
401        ('NTDDI_VERSION', 0x06000000),
402        ('NOMINMAX', 1),
403    )
404    if '64bit' in platform.architecture()[0]:
405        DEFINE_MACROS += (('MS_WIN64', 1),)
406    elif sys.version_info >= (3, 5):
407        # For some reason, this is needed to get access to inet_pton/inet_ntop
408        # on msvc, but only for 32 bits
409        DEFINE_MACROS += (('NTDDI_VERSION', 0x06000000),)
410else:
411    DEFINE_MACROS += (
412        ('HAVE_CONFIG_H', 1),
413        ('GRPC_ENABLE_FORK_SUPPORT', 1),
414    )
415
416# Fix for multiprocessing support on Apple devices.
417# TODO(vigneshbabu): Remove this once the poll poller gets fork support.
418DEFINE_MACROS += (('GRPC_DO_NOT_INSTANTIATE_POSIX_POLLER', 1),)
419
420LDFLAGS = tuple(EXTRA_LINK_ARGS)
421CFLAGS = tuple(EXTRA_COMPILE_ARGS)
422if "linux" in sys.platform or "darwin" in sys.platform:
423    pymodinit_type = 'PyObject*' if PY3 else 'void'
424    pymodinit = 'extern "C" __attribute__((visibility ("default"))) {}'.format(
425        pymodinit_type)
426    DEFINE_MACROS += (('PyMODINIT_FUNC', pymodinit),)
427    DEFINE_MACROS += (('GRPC_POSIX_FORK_ALLOW_PTHREAD_ATFORK', 1),)
428
429# By default, Python3 distutils enforces compatibility of
430# c plugins (.so files) with the OSX version Python was built with.
431# We need OSX 10.10, the oldest which supports C++ thread_local.
432# Python 3.9: Mac OS Big Sur sysconfig.get_config_var('MACOSX_DEPLOYMENT_TARGET') returns int (11)
433if 'darwin' in sys.platform:
434    mac_target = sysconfig.get_config_var('MACOSX_DEPLOYMENT_TARGET')
435    if mac_target:
436        mac_target = pkg_resources.parse_version(str(mac_target))
437        if mac_target < pkg_resources.parse_version('10.10.0'):
438            os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.10'
439            os.environ['_PYTHON_HOST_PLATFORM'] = re.sub(
440                r'macosx-[0-9]+\.[0-9]+-(.+)', r'macosx-10.10-\1',
441                util.get_platform())
442
443
444def cython_extensions_and_necessity():
445    cython_module_files = [
446        os.path.join(PYTHON_STEM,
447                     name.replace('.', '/') + '.pyx')
448        for name in CYTHON_EXTENSION_MODULE_NAMES
449    ]
450    config = os.environ.get('CONFIG', 'opt')
451    prefix = 'libs/' + config + '/'
452    if USE_PREBUILT_GRPC_CORE:
453        extra_objects = [
454            prefix + 'libares.a', prefix + 'libboringssl.a',
455            prefix + 'libgpr.a', prefix + 'libgrpc.a'
456        ]
457        core_c_files = []
458    else:
459        core_c_files = list(CORE_C_FILES)
460        extra_objects = []
461    extensions = [
462        _extension.Extension(
463            name=module_name,
464            sources=([module_file] + list(CYTHON_HELPER_C_FILES) +
465                     core_c_files + asm_files),
466            include_dirs=list(EXTENSION_INCLUDE_DIRECTORIES),
467            libraries=list(EXTENSION_LIBRARIES),
468            define_macros=list(DEFINE_MACROS),
469            extra_objects=extra_objects,
470            extra_compile_args=list(CFLAGS),
471            extra_link_args=list(LDFLAGS),
472        ) for (module_name, module_file
473              ) in zip(list(CYTHON_EXTENSION_MODULE_NAMES), cython_module_files)
474    ]
475    need_cython = BUILD_WITH_CYTHON
476    if not BUILD_WITH_CYTHON:
477        need_cython = need_cython or not commands.check_and_update_cythonization(
478            extensions)
479    # TODO: the strategy for conditional compiling and exposing the aio Cython
480    # dependencies will be revisited by https://github.com/grpc/grpc/issues/19728
481    return commands.try_cythonize(extensions,
482                                  linetracing=ENABLE_CYTHON_TRACING,
483                                  mandatory=BUILD_WITH_CYTHON), need_cython
484
485
486CYTHON_EXTENSION_MODULES, need_cython = cython_extensions_and_necessity()
487
488PACKAGE_DIRECTORIES = {
489    '': PYTHON_STEM,
490}
491
492INSTALL_REQUIRES = ()
493
494EXTRAS_REQUIRES = {
495    'protobuf': 'grpcio-tools>={version}'.format(version=grpc_version.VERSION),
496}
497
498SETUP_REQUIRES = INSTALL_REQUIRES + (
499    'Sphinx~=1.8.1',) if ENABLE_DOCUMENTATION_BUILD else ()
500
501try:
502    import Cython
503except ImportError:
504    if BUILD_WITH_CYTHON:
505        sys.stderr.write(
506            "You requested a Cython build via GRPC_PYTHON_BUILD_WITH_CYTHON, "
507            "but do not have Cython installed. We won't stop you from using "
508            "other commands, but the extension files will fail to build.\n")
509    elif need_cython:
510        sys.stderr.write(
511            'We could not find Cython. Setup may take 10-20 minutes.\n')
512        SETUP_REQUIRES += ('cython>=0.23',)
513
514COMMAND_CLASS = {
515    'doc': commands.SphinxDocumentation,
516    'build_project_metadata': commands.BuildProjectMetadata,
517    'build_py': commands.BuildPy,
518    'build_ext': commands.BuildExt,
519    'gather': commands.Gather,
520    'clean': commands.Clean,
521}
522
523# Ensure that package data is copied over before any commands have been run:
524credentials_dir = os.path.join(PYTHON_STEM, 'grpc', '_cython', '_credentials')
525try:
526    os.mkdir(credentials_dir)
527except OSError:
528    pass
529shutil.copyfile(os.path.join('etc', 'roots.pem'),
530                os.path.join(credentials_dir, 'roots.pem'))
531
532PACKAGE_DATA = {
533    # Binaries that may or may not be present in the final installation, but are
534    # mentioned here for completeness.
535    'grpc._cython': [
536        '_credentials/roots.pem',
537        '_windows/grpc_c.32.python',
538        '_windows/grpc_c.64.python',
539    ],
540}
541PACKAGES = setuptools.find_packages(PYTHON_STEM)
542
543setuptools.setup(
544    name='grpcio',
545    version=grpc_version.VERSION,
546    description='HTTP/2-based RPC framework',
547    author='The gRPC Authors',
548    author_email='[email protected]',
549    url='https://grpc.io',
550    project_urls={
551        "Source Code": "https://github.com/grpc/grpc",
552        "Bug Tracker": "https://github.com/grpc/grpc/issues",
553        'Documentation': 'https://grpc.github.io/grpc/python',
554    },
555    license=LICENSE,
556    classifiers=CLASSIFIERS,
557    long_description_content_type='text/x-rst',
558    long_description=open(README).read(),
559    ext_modules=CYTHON_EXTENSION_MODULES,
560    packages=list(PACKAGES),
561    package_dir=PACKAGE_DIRECTORIES,
562    package_data=PACKAGE_DATA,
563    python_requires='>=3.7',
564    install_requires=INSTALL_REQUIRES,
565    extras_require=EXTRAS_REQUIRES,
566    setup_requires=SETUP_REQUIRES,
567    cmdclass=COMMAND_CLASS,
568)
569