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