uvloop/setup.py

311 lines
9.7 KiB
Python

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.32,<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>=3.8.1',
'flake8~=5.0',
'psutil',
'pycodestyle~=2.9.0',
'pyOpenSSL~=23.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):
super().initialize_options()
self.use_system_libuv = False
self.cython_always = False
self.cython_annotate = None
self.cython_directives = None
def finalize_options(self):
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()
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',
)