import sys vi = sys.version_info if vi < (3, 7): raise RuntimeError('uvloop requires Python 3.7 or greater') if sys.platform in ('win32', 'cygwin', 'cli'): raise RuntimeError('uvloop does not support Windows at the moment') import os import os.path import pathlib import platform import re import shutil import subprocess import sys from setuptools import setup, Extension from setuptools.command.build_ext import build_ext from setuptools.command.sdist import sdist CYTHON_DEPENDENCY = 'Cython(>=0.29.24,<0.30.0)' # Minimal dependencies required to test uvloop. TEST_DEPENDENCIES = [ # pycodestyle is a dependency of flake8, but it must be frozen because # their combination breaks too often # (example breakage: https://gitlab.com/pycqa/flake8/issues/427) 'aiohttp', 'flake8~=3.9.2', 'psutil', 'pycodestyle~=2.7.0', 'pyOpenSSL~=19.0.0', 'mypy>=0.800', CYTHON_DEPENDENCY, ] # Dependencies required to build documentation. DOC_DEPENDENCIES = [ 'Sphinx~=4.1.2', 'sphinxcontrib-asyncio~=0.3.0', 'sphinx_rtd_theme~=0.5.2', ] EXTRA_DEPENDENCIES = { 'docs': DOC_DEPENDENCIES, 'test': TEST_DEPENDENCIES, # Dependencies required to develop uvloop. 'dev': [ CYTHON_DEPENDENCY, 'pytest>=3.6.0', ] + DOC_DEPENDENCIES + TEST_DEPENDENCIES } MACHINE = platform.machine() MODULES_CFLAGS = [os.getenv('UVLOOP_OPT_CFLAGS', '-O2')] _ROOT = pathlib.Path(__file__).parent LIBUV_DIR = str(_ROOT / 'vendor' / 'libuv') LIBUV_BUILD_DIR = str(_ROOT / 'build' / 'libuv-{}'.format(MACHINE)) def _libuv_build_env(): env = os.environ.copy() cur_cflags = env.get('CFLAGS', '') if not re.search(r'-O\d', cur_cflags): cur_cflags += ' -O2' env['CFLAGS'] = (cur_cflags + ' -fPIC ' + env.get('ARCHFLAGS', '')) return env def _libuv_autogen(env): if os.path.exists(os.path.join(LIBUV_DIR, 'configure')): # No need to use autogen, the configure script is there. return if not os.path.exists(os.path.join(LIBUV_DIR, 'autogen.sh')): raise RuntimeError( 'the libuv submodule has not been checked out; ' 'try running "git submodule init; git submodule update"') subprocess.run( ['/bin/sh', 'autogen.sh'], cwd=LIBUV_DIR, env=env, check=True) class uvloop_sdist(sdist): def run(self): # Make sure sdist archive contains configure # to avoid the dependency on autotools. _libuv_autogen(_libuv_build_env()) super().run() class uvloop_build_ext(build_ext): user_options = build_ext.user_options + [ ('cython-always', None, 'run cythonize() even if .c files are present'), ('cython-annotate', None, 'Produce a colorized HTML version of the Cython source.'), ('cython-directives=', None, 'Cythion compiler directives'), ('use-system-libuv', None, 'Use the system provided libuv, instead of the bundled one'), ] boolean_options = build_ext.boolean_options + [ 'cython-always', 'cython-annotate', 'use-system-libuv', ] def initialize_options(self): # initialize_options() may be called multiple times on the # same command object, so make sure not to override previously # set options. if getattr(self, '_initialized', False): return super().initialize_options() self.use_system_libuv = False self.cython_always = False self.cython_annotate = None self.cython_directives = None def finalize_options(self): # finalize_options() may be called multiple times on the # same command object, so make sure not to override previously # set options. if getattr(self, '_initialized', False): return need_cythonize = self.cython_always cfiles = {} for extension in self.distribution.ext_modules: for i, sfile in enumerate(extension.sources): if sfile.endswith('.pyx'): prefix, ext = os.path.splitext(sfile) cfile = prefix + '.c' if os.path.exists(cfile) and not self.cython_always: extension.sources[i] = cfile else: if os.path.exists(cfile): cfiles[cfile] = os.path.getmtime(cfile) else: cfiles[cfile] = 0 need_cythonize = True if need_cythonize: import pkg_resources # Double check Cython presence in case setup_requires # didn't go into effect (most likely because someone # imported Cython before setup_requires injected the # correct egg into sys.path. try: import Cython except ImportError: raise RuntimeError( 'please install {} to compile uvloop from source'.format( CYTHON_DEPENDENCY)) cython_dep = pkg_resources.Requirement.parse(CYTHON_DEPENDENCY) if Cython.__version__ not in cython_dep: raise RuntimeError( 'uvloop requires {}, got Cython=={}'.format( CYTHON_DEPENDENCY, Cython.__version__ )) from Cython.Build import cythonize directives = {} if self.cython_directives: for directive in self.cython_directives.split(','): k, _, v = directive.partition('=') if v.lower() == 'false': v = False if v.lower() == 'true': v = True directives[k] = v self.distribution.ext_modules[:] = cythonize( self.distribution.ext_modules, compiler_directives=directives, annotate=self.cython_annotate) super().finalize_options() self._initialized = True def build_libuv(self): env = _libuv_build_env() # Make sure configure and friends are present in case # we are building from a git checkout. _libuv_autogen(env) # Copy the libuv tree to build/ so that its build # products don't pollute sdist accidentally. if os.path.exists(LIBUV_BUILD_DIR): shutil.rmtree(LIBUV_BUILD_DIR) shutil.copytree(LIBUV_DIR, LIBUV_BUILD_DIR) # Sometimes pip fails to preserve the timestamps correctly, # in which case, make will try to run autotools again. subprocess.run( ['touch', 'configure.ac', 'aclocal.m4', 'configure', 'Makefile.am', 'Makefile.in'], cwd=LIBUV_BUILD_DIR, env=env, check=True) if 'LIBUV_CONFIGURE_HOST' in env: cmd = ['./configure', '--host=' + env['LIBUV_CONFIGURE_HOST']] else: cmd = ['./configure'] subprocess.run( cmd, cwd=LIBUV_BUILD_DIR, env=env, check=True) j_flag = '-j{}'.format(os.cpu_count() or 1) c_flag = "CFLAGS={}".format(env['CFLAGS']) subprocess.run( ['make', j_flag, c_flag], cwd=LIBUV_BUILD_DIR, env=env, check=True) def build_extensions(self): if self.use_system_libuv: self.compiler.add_library('uv') if sys.platform == 'darwin' and \ os.path.exists('/opt/local/include'): # Support macports on Mac OS X. self.compiler.add_include_dir('/opt/local/include') else: libuv_lib = os.path.join(LIBUV_BUILD_DIR, '.libs', 'libuv.a') if not os.path.exists(libuv_lib): self.build_libuv() if not os.path.exists(libuv_lib): raise RuntimeError('failed to build libuv') self.extensions[-1].extra_objects.extend([libuv_lib]) self.compiler.add_include_dir(os.path.join(LIBUV_DIR, 'include')) if sys.platform.startswith('linux'): self.compiler.add_library('rt') elif sys.platform.startswith(('freebsd', 'dragonfly')): self.compiler.add_library('kvm') elif sys.platform.startswith('sunos'): self.compiler.add_library('kstat') self.compiler.add_library('pthread') super().build_extensions() with open(str(_ROOT / 'README.rst')) as f: readme = f.read() with open(str(_ROOT / 'uvloop' / '_version.py')) as f: for line in f: if line.startswith('__version__ ='): _, _, version = line.partition('=') VERSION = version.strip(" \n'\"") break else: raise RuntimeError( 'unable to read the version from uvloop/_version.py') setup_requires = [] if not (_ROOT / 'uvloop' / 'loop.c').exists() or '--cython-always' in sys.argv: # No Cython output, require Cython to build. setup_requires.append(CYTHON_DEPENDENCY) setup( name='uvloop', description='Fast implementation of asyncio event loop on top of libuv', long_description=readme, url='http://github.com/MagicStack/uvloop', license='MIT', author='Yury Selivanov', author_email='yury@magic.io', platforms=['macOS', 'POSIX'], version=VERSION, packages=['uvloop'], cmdclass={ 'sdist': uvloop_sdist, 'build_ext': uvloop_build_ext }, ext_modules=[ Extension( "uvloop.loop", sources=[ "uvloop/loop.pyx", ], extra_compile_args=MODULES_CFLAGS ), ], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Framework :: AsyncIO', 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'License :: OSI Approved :: Apache Software License', 'License :: OSI Approved :: MIT License', 'Intended Audience :: Developers', ], include_package_data=True, extras_require=EXTRA_DEPENDENCIES, setup_requires=setup_requires, python_requires='>=3.7', )