1# Copyright 2023 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 15import os 16import os.path 17import platform 18import re 19import shlex 20import subprocess 21from subprocess import PIPE 22import sys 23import sysconfig 24 25import setuptools 26from setuptools import Extension 27from setuptools.command import build_ext 28 29PYTHON_STEM = os.path.realpath(os.path.dirname(__file__)) 30README_PATH = os.path.join(PYTHON_STEM, "README.rst") 31 32os.chdir(os.path.dirname(os.path.abspath(__file__))) 33sys.path.insert(0, os.path.abspath(".")) 34 35import _parallel_compile_patch 36import observability_lib_deps 37 38import grpc_version 39 40_parallel_compile_patch.monkeypatch_compile_maybe() 41 42CLASSIFIERS = [ 43 "Development Status :: 4 - Beta", 44 "Programming Language :: Python", 45 "Programming Language :: Python :: 3", 46 "License :: OSI Approved :: Apache Software License", 47] 48 49O11Y_CC_SRCS = [ 50 "server_call_tracer.cc", 51 "client_call_tracer.cc", 52 "observability_util.cc", 53 "python_census_context.cc", 54 "sampler.cc", 55 "rpc_encoding.cc", 56] 57 58 59def _env_bool_value(env_name, default): 60 """Parses a bool option from an environment variable""" 61 return os.environ.get(env_name, default).upper() not in ["FALSE", "0", ""] 62 63 64def _is_alpine(): 65 """Checks if it's building Alpine""" 66 os_release_content = "" 67 try: 68 with open("/etc/os-release", "r") as f: 69 os_release_content = f.read() 70 if "alpine" in os_release_content: 71 return True 72 except Exception: 73 return False 74 75 76# Environment variable to determine whether or not the Cython extension should 77# *use* Cython or use the generated C files. Note that this requires the C files 78# to have been generated by building first *with* Cython support. 79BUILD_WITH_CYTHON = _env_bool_value("GRPC_PYTHON_BUILD_WITH_CYTHON", "False") 80 81# Export this variable to force building the python extension with a statically linked libstdc++. 82# At least on linux, this is normally not needed as we can build manylinux-compatible wheels on linux just fine 83# without statically linking libstdc++ (which leads to a slight increase in the wheel size). 84# This option is useful when crosscompiling wheels for aarch64 where 85# it's difficult to ensure that the crosscompilation toolchain has a high-enough version 86# of GCC (we require >=5.1) but still uses old-enough libstdc++ symbols. 87# TODO(jtattermusch): remove this workaround once issues with crosscompiler version are resolved. 88BUILD_WITH_STATIC_LIBSTDCXX = _env_bool_value( 89 "GRPC_PYTHON_BUILD_WITH_STATIC_LIBSTDCXX", "False" 90) 91 92 93def check_linker_need_libatomic(): 94 """Test if linker on system needs libatomic.""" 95 code_test = ( 96 b"#include <atomic>\n" 97 + b"int main() { return std::atomic<int64_t>{}; }" 98 ) 99 cxx = shlex.split(os.environ.get("CXX", "c++")) 100 cpp_test = subprocess.Popen( 101 cxx + ["-x", "c++", "-std=c++14", "-"], 102 stdin=PIPE, 103 stdout=PIPE, 104 stderr=PIPE, 105 ) 106 cpp_test.communicate(input=code_test) 107 if cpp_test.returncode == 0: 108 return False 109 # Double-check to see if -latomic actually can solve the problem. 110 # https://github.com/grpc/grpc/issues/22491 111 cpp_test = subprocess.Popen( 112 cxx + ["-x", "c++", "-std=c++14", "-", "-latomic"], 113 stdin=PIPE, 114 stdout=PIPE, 115 stderr=PIPE, 116 ) 117 cpp_test.communicate(input=code_test) 118 return cpp_test.returncode == 0 119 120 121class BuildExt(build_ext.build_ext): 122 """Custom build_ext command.""" 123 124 def get_ext_filename(self, ext_name): 125 # since python3.5, python extensions' shared libraries use a suffix that corresponds to the value 126 # of sysconfig.get_config_var('EXT_SUFFIX') and contains info about the architecture the library targets. 127 # E.g. on x64 linux the suffix is ".cpython-XYZ-x86_64-linux-gnu.so" 128 # When crosscompiling python wheels, we need to be able to override this suffix 129 # so that the resulting file name matches the target architecture and we end up with a well-formed 130 # wheel. 131 filename = build_ext.build_ext.get_ext_filename(self, ext_name) 132 orig_ext_suffix = sysconfig.get_config_var("EXT_SUFFIX") 133 new_ext_suffix = os.getenv("GRPC_PYTHON_OVERRIDE_EXT_SUFFIX") 134 if new_ext_suffix and filename.endswith(orig_ext_suffix): 135 filename = filename[: -len(orig_ext_suffix)] + new_ext_suffix 136 return filename 137 138 139# There are some situations (like on Windows) where CC, CFLAGS, and LDFLAGS are 140# entirely ignored/dropped/forgotten by distutils and its Cygwin/MinGW support. 141# We use these environment variables to thus get around that without locking 142# ourselves in w.r.t. the multitude of operating systems this ought to build on. 143# We can also use these variables as a way to inject environment-specific 144# compiler/linker flags. We assume GCC-like compilers and/or MinGW as a 145# reasonable default. 146EXTRA_ENV_COMPILE_ARGS = os.environ.get("GRPC_PYTHON_CFLAGS", None) 147EXTRA_ENV_LINK_ARGS = os.environ.get("GRPC_PYTHON_LDFLAGS", None) 148if EXTRA_ENV_COMPILE_ARGS is None: 149 EXTRA_ENV_COMPILE_ARGS = "-std=c++14" 150 if "win32" in sys.platform: 151 # We need to statically link the C++ Runtime, only the C runtime is 152 # available dynamically 153 EXTRA_ENV_COMPILE_ARGS += " /MT" 154 elif "linux" in sys.platform or "darwin" in sys.platform: 155 EXTRA_ENV_COMPILE_ARGS += " -fno-wrapv -frtti -fvisibility=hidden" 156 157if EXTRA_ENV_LINK_ARGS is None: 158 EXTRA_ENV_LINK_ARGS = "" 159 if "linux" in sys.platform or "darwin" in sys.platform: 160 EXTRA_ENV_LINK_ARGS += " -lpthread" 161 if check_linker_need_libatomic(): 162 EXTRA_ENV_LINK_ARGS += " -latomic" 163 164# This enables the standard link-time optimizer, which help us prevent some undefined symbol errors by 165# remove some unused symbols from .so file. 166# Note that it does not work for MSCV on windows. 167if "win32" not in sys.platform: 168 EXTRA_ENV_COMPILE_ARGS += " -flto" 169 # Compile with fail with error: `lto-wrapper failed` when lto flag was enabled in Alpine using musl libc. 170 # As a work around we need to disable ipa-cp. 171 if _is_alpine(): 172 EXTRA_ENV_COMPILE_ARGS += " -fno-ipa-cp" 173 174EXTRA_COMPILE_ARGS = shlex.split(EXTRA_ENV_COMPILE_ARGS) 175EXTRA_LINK_ARGS = shlex.split(EXTRA_ENV_LINK_ARGS) 176 177if BUILD_WITH_STATIC_LIBSTDCXX: 178 EXTRA_LINK_ARGS.append("-static-libstdc++") 179 180CC_FILES = [ 181 os.path.normpath(cc_file) for cc_file in observability_lib_deps.CC_FILES 182] 183CC_INCLUDES = [ 184 os.path.normpath(include_dir) 185 for include_dir in observability_lib_deps.CC_INCLUDES 186] 187 188DEFINE_MACROS = (("_WIN32_WINNT", 0x600),) 189 190if "win32" in sys.platform: 191 DEFINE_MACROS += ( 192 ("WIN32_LEAN_AND_MEAN", 1), 193 ("CARES_STATICLIB", 1), 194 ("GRPC_ARES", 0), 195 ("NTDDI_VERSION", 0x06000000), 196 # avoid https://github.com/abseil/abseil-cpp/issues/1425 197 ("NOMINMAX", 1), 198 ) 199 if "64bit" in platform.architecture()[0]: 200 DEFINE_MACROS += (("MS_WIN64", 1),) 201 else: 202 # For some reason, this is needed to get access to inet_pton/inet_ntop 203 # on msvc, but only for 32 bits 204 DEFINE_MACROS += (("NTDDI_VERSION", 0x06000000),) 205elif "linux" in sys.platform or "darwin" in sys.platform: 206 DEFINE_MACROS += (("HAVE_PTHREAD", 1),) 207 208# Fix for Cython build issue in aarch64. 209# It's required to define this macro before include <inttypes.h>. 210# <inttypes.h> was included in core/lib/channel/call_tracer.h. 211# This macro should already be defined in grpc/grpc.h through port_platform.h, 212# but we're still having issue in aarch64, so we manually define the macro here. 213# TODO(xuanwn): Figure out what's going on in the aarch64 build so we can support 214# gcc + Bazel. 215DEFINE_MACROS += (("__STDC_FORMAT_MACROS", None),) 216 217 218# Use `-fvisibility=hidden` will hide cython init symbol, we need that symbol exported 219# in order to import cython module. 220if "linux" in sys.platform or "darwin" in sys.platform: 221 pymodinit = 'extern "C" __attribute__((visibility ("default"))) PyObject*' 222 DEFINE_MACROS += (("PyMODINIT_FUNC", pymodinit),) 223 224 225def extension_modules(): 226 if BUILD_WITH_CYTHON: 227 cython_module_files = [ 228 os.path.join("grpc_observability", "_cyobservability.pyx") 229 ] 230 else: 231 cython_module_files = [ 232 os.path.join("grpc_observability", "_cyobservability.cpp") 233 ] 234 235 plugin_include = [ 236 ".", 237 "grpc_root", 238 os.path.join("grpc_root", "include"), 239 ] + CC_INCLUDES 240 241 plugin_sources = CC_FILES 242 243 O11Y_CC_PATHS = ( 244 os.path.join("grpc_observability", f) for f in O11Y_CC_SRCS 245 ) 246 plugin_sources += O11Y_CC_PATHS 247 248 plugin_sources += cython_module_files 249 250 plugin_ext = Extension( 251 name="grpc_observability._cyobservability", 252 sources=plugin_sources, 253 include_dirs=plugin_include, 254 language="c++", 255 define_macros=list(DEFINE_MACROS), 256 extra_compile_args=list(EXTRA_COMPILE_ARGS), 257 extra_link_args=list(EXTRA_LINK_ARGS), 258 ) 259 extensions = [plugin_ext] 260 if BUILD_WITH_CYTHON: 261 from Cython import Build 262 263 return Build.cythonize( 264 extensions, compiler_directives={"language_level": "3"} 265 ) 266 else: 267 return extensions 268 269 270PACKAGES = setuptools.find_packages(PYTHON_STEM) 271 272setuptools.setup( 273 name="grpcio-observability", 274 version=grpc_version.VERSION, 275 description="gRPC Python observability package", 276 long_description_content_type="text/x-rst", 277 long_description=open(README_PATH, "r").read(), 278 author="The gRPC Authors", 279 author_email="[email protected]", 280 url="https://grpc.io", 281 project_urls={ 282 "Source Code": "https://github.com/grpc/grpc/tree/master/src/python/grpcio_observability", 283 "Bug Tracker": "https://github.com/grpc/grpc/issues", 284 }, 285 license="Apache License 2.0", 286 classifiers=CLASSIFIERS, 287 ext_modules=extension_modules(), 288 packages=list(PACKAGES), 289 python_requires=">=3.8", 290 install_requires=[ 291 "grpcio=={version}".format(version=grpc_version.VERSION), 292 "setuptools>=59.6.0", 293 "opentelemetry-api==1.21.0", 294 ], 295 cmdclass={ 296 "build_ext": BuildExt, 297 }, 298) 299