setup.py 18.2 KB
Newer Older
1 2
#!/usr/bin/env python
"""gevent build & installation script"""
3
from __future__ import print_function
Denis Bilenko's avatar
Denis Bilenko committed
4 5 6
import sys
import os
import re
7
import shutil
8
import traceback
9
from os.path import join, abspath, basename, dirname
10
from subprocess import check_call
11
from glob import glob
12

13

14
PYPY = hasattr(sys, 'pypy_version_info')
15
WIN = sys.platform.startswith('win')
16
CFFI_WIN_BUILD_ANYWAY = os.environ.get("PYPY_WIN_BUILD_ANYWAY")
17

18
if PYPY and WIN and not CFFI_WIN_BUILD_ANYWAY:
19 20 21 22 23 24
    # We can't properly handle (hah!) file-descriptors and
    # handle mapping on Windows/CFFI, because the file needed,
    # libev_vfd.h, can't be included, linked, and used: it uses
    # Python API functions, and you're not supposed to do that from
    # CFFI code. Plus I could never get the libraries= line to ffi.compile()
    # correct to make linking work.
25 26 27
    raise Exception("Unable to install on PyPy/Windows")

if WIN:
28
    # https://bugs.python.org/issue23246
29 30 31 32 33 34
    # We must have setuptools on windows
    __import__('setuptools')

    # Make sure the env vars that make.cmd needs are set
    if not os.environ.get('PYTHON_EXE'):
        os.environ['PYTHON_EXE'] = 'pypy' if PYPY else 'python'
35 36
    if not os.environ.get('PYEXE'):
        os.environ['PYEXE'] = os.environ['PYTHON_EXE']
37 38


39 40 41
import distutils
import distutils.sysconfig  # to get CFLAGS to pass into c-ares configure script

42

43
try:
44
    from setuptools import Extension, setup
45
except ImportError:
46 47 48
    if PYPY:
        # need setuptools for include_package_data to work
        raise
49
    from distutils.core import Extension, setup
50

51
from distutils.command.build_ext import build_ext
52
from distutils.command.sdist import sdist as _sdist
53 54 55
from distutils.errors import CCompilerError, DistutilsExecError, DistutilsPlatformError
ext_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError, IOError)

Denis Bilenko's avatar
Denis Bilenko committed
56

57 58
with open('gevent/__init__.py') as _:
    __version__ = re.search(r"__version__\s*=\s*'(.*)'", _.read(), re.M).group(1)
59
assert __version__
Denis Bilenko's avatar
Denis Bilenko committed
60

Jason Madden's avatar
Jason Madden committed
61

62 63
def _quoted_abspath(p):
    return '"' + abspath(p) + '"'
64

Jason Madden's avatar
Jason Madden committed
65

66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
def parse_environ(key):
    value = os.environ.get(key)
    if not value:
        return
    value = value.lower().strip()
    if value in ('1', 'true', 'on', 'yes'):
        return True
    elif value in ('0', 'false', 'off', 'no'):
        return False
    raise ValueError('Environment variable %r has invalid value %r. Please set it to 1, 0 or an empty string' % (key, value))


def get_config_value(key, defkey, path):
    value = parse_environ(key)
    if value is None:
        value = parse_environ(defkey)
82 83 84
    if value is not None:
        return value
    return os.path.exists(path)
85 86 87 88 89


LIBEV_EMBED = get_config_value('LIBEV_EMBED', 'EMBED', 'libev')
CARES_EMBED = get_config_value('CARES_EMBED', 'EMBED', 'c-ares')

90
define_macros = []
91
libraries = []
92 93 94 95 96 97 98 99 100 101 102 103
# Configure libev in place; but cp the config.h to the old directory;
# if we're building a CPython extension, the old directory will be
# the build/temp.XXX/libev/ directory. If we're building from a
# source checkout on pypy, OLDPWD will be the location of setup.py
# and the PyPy branch will clean it up.
libev_configure_command = ' '.join([
    "(cd ", _quoted_abspath('libev/'),
    " && /bin/sh ./configure ",
    " && cp config.h \"$OLDPWD\"",
    ")",
    '> configure-output.txt'
])
104

105
# See #616, trouble building for a 32-bit python against a 64-bit platform
106 107
_config_vars = distutils.sysconfig.get_config_var("CFLAGS")
if _config_vars and "m32" in _config_vars:
Kevin Chen's avatar
Kevin Chen committed
108
    _m32 = 'CFLAGS="' + os.getenv('CFLAGS', '') + ' -m32" '
109
else:
110
    _m32 = ''
111

Jason Madden's avatar
Jason Madden committed
112 113
ares_configure_command = ' '.join(["(cd ", _quoted_abspath('c-ares/'),
                                   " && if [ -e ares_build.h ]; then cp ares_build.h ares_build.h.orig; fi ",
114
                                   " && /bin/sh ./configure " + _m32 + "CONFIG_COMMANDS= CONFIG_FILES= ",
Jason Madden's avatar
Jason Madden committed
115 116 117
                                   " && cp ares_config.h ares_build.h \"$OLDPWD\" ",
                                   " && mv ares_build.h.orig ares_build.h)",
                                   "> configure-output.txt"])
118 119


120
if WIN:
121 122
    libraries += ['ws2_32']
    define_macros += [('FD_SETSIZE', '1024'), ('_WIN32', '1')]
123 124


125 126 127 128 129 130 131 132
def expand(*lst):
    result = []
    for item in lst:
        for name in sorted(glob(item)):
            result.append(name)
    return result


133
CORE = Extension(name='gevent.corecext',
134
                 sources=['gevent/gevent.corecext.c'],
135
                 include_dirs=['libev'] if LIBEV_EMBED else [],
136
                 libraries=libraries,
137
                 define_macros=define_macros,
138
                 depends=expand('gevent/callbacks.*', 'gevent/stathelper.c', 'gevent/libev*.h', 'libev/*.*'))
Denis Bilenko's avatar
Denis Bilenko committed
139
# QQQ libev can also use -lm, however it seems to be added implicitly
140

141 142
ARES = Extension(name='gevent.ares',
                 sources=['gevent/gevent.ares.c'],
143
                 include_dirs=['c-ares'] if CARES_EMBED else [],
144
                 libraries=libraries,
145 146
                 define_macros=define_macros,
                 depends=expand('gevent/dnshelper.c', 'gevent/cares_*.*'))
147 148 149
ARES.optional = True


Alexey Borzenkov's avatar
Alexey Borzenkov committed
150 151
def make_universal_header(filename, *defines):
    defines = [('#define %s ' % define, define) for define in defines]
152 153
    with open(filename, 'r') as f:
        lines = f.read().split('\n')
Alexey Borzenkov's avatar
Alexey Borzenkov committed
154
    ifdef = 0
155 156 157 158 159 160 161 162 163 164 165 166
    with open(filename, 'w') as f:
        for line in lines:
            if line.startswith('#ifdef'):
                ifdef += 1
            elif line.startswith('#endif'):
                ifdef -= 1
            elif not ifdef:
                for prefix, define in defines:
                    if line.startswith(prefix):
                        line = '#ifdef __LP64__\n#define %s 8\n#else\n#define %s 4\n#endif' % (define, define)
                        break
            print(line, file=f)
Alexey Borzenkov's avatar
Alexey Borzenkov committed
167 168


169
def _system(cmd):
170
    sys.stdout.write('Running %r in %s\n' % (cmd, os.getcwd()))
171
    return check_call(cmd, shell=True)
172 173


174 175 176 177 178
def system(cmd):
    if _system(cmd):
        sys.exit(1)


179
def configure_libev(bext, ext):
180
    if WIN:
181 182 183 184 185 186 187 188 189 190
        CORE.define_macros.append(('EV_STANDALONE', '1'))
        return

    bdir = os.path.join(bext.build_temp, 'libev')
    ext.include_dirs.insert(0, bdir)

    if not os.path.isdir(bdir):
        os.makedirs(bdir)

    cwd = os.getcwd()
191
    os.chdir(bdir)
192 193 194 195 196 197 198 199 200 201
    try:
        if os.path.exists('config.h'):
            return
        rc = _system(libev_configure_command)
        if rc == 0 and sys.platform == 'darwin':
            make_universal_header('config.h', 'SIZEOF_LONG', 'SIZEOF_SIZE_T', 'SIZEOF_TIME_T')
    finally:
        os.chdir(cwd)


202 203 204 205 206 207 208
def configure_ares(bext, ext):
    bdir = os.path.join(bext.build_temp, 'c-ares')
    ext.include_dirs.insert(0, bdir)

    if not os.path.isdir(bdir):
        os.makedirs(bdir)

209
    if WIN:
210 211 212 213
        shutil.copy("c-ares\\ares_build.h.dist", os.path.join(bdir, "ares_build.h"))
        return

    cwd = os.getcwd()
214
    os.chdir(bdir)
215 216 217
    try:
        if os.path.exists('ares_config.h') and os.path.exists('ares_build.h'):
            return
218 219 220 221 222 223
        try:
            rc = _system(ares_configure_command)
        except:
            with open('configure-output.txt', 'r') as t:
                print(t.read(), file=sys.stderr)
            raise
Alexey Borzenkov's avatar
Alexey Borzenkov committed
224
        if rc == 0 and sys.platform == 'darwin':
225 226 227 228
            make_universal_header('ares_build.h', 'CARES_SIZEOF_LONG')
            make_universal_header('ares_config.h', 'SIZEOF_LONG', 'SIZEOF_SIZE_T', 'SIZEOF_TIME_T')
    finally:
        os.chdir(cwd)
229 230


231
if LIBEV_EMBED:
232 233 234 235 236 237 238
    CORE.define_macros += [('LIBEV_EMBED', '1'),
                           ('EV_COMMON', ''),  # we don't use void* data
                           # libev watchers that we don't use currently:
                           ('EV_CLEANUP_ENABLE', '0'),
                           ('EV_EMBED_ENABLE', '0'),
                           ("EV_PERIODIC_ENABLE", '0')]
    CORE.configure = configure_libev
239
    if sys.platform == "darwin":
240
        os.environ["CPPFLAGS"] = ("%s %s" % (os.environ.get("CPPFLAGS", ""), "-U__llvm__")).lstrip()
Denis Bilenko's avatar
Denis Bilenko committed
241 242
    if os.environ.get('GEVENTSETUP_EV_VERIFY') is not None:
        CORE.define_macros.append(('EV_VERIFY', os.environ['GEVENTSETUP_EV_VERIFY']))
243 244 245
else:
    CORE.libraries.append('ev')

246

247
if CARES_EMBED:
248
    ARES.sources += expand('c-ares/*.c')
249 250 251 252 253 254 255
    # Strip the standalone binaries that would otherwise
    # cause linking issues
    for bin_c in ('acountry', 'adig', 'ahost'):
        try:
            ARES.sources.remove('c-ares/' + bin_c + '.c')
        except ValueError:
            pass
256
    ARES.configure = configure_ares
257
    if WIN:
258
        ARES.libraries += ['advapi32']
259
        ARES.define_macros += [('CARES_STATICLIB', '')]
260 261
    else:
        ARES.define_macros += [('HAVE_CONFIG_H', '')]
Alexey Borzenkov's avatar
Alexey Borzenkov committed
262 263
        if sys.platform != 'darwin':
            ARES.libraries += ['rt']
264
    ARES.define_macros += [('CARES_EMBED', '1')]
265 266 267
else:
    ARES.libraries.append('cares')
    ARES.define_macros += [('HAVE_NETDB_H', '')]
268

269
_ran_make = []
Jason Madden's avatar
Jason Madden committed
270 271


272
def make(targets=''):
273
    # NOTE: We have two copies of the makefile, one
274 275 276 277 278 279
    # for posix, one for windows. Our sdist command takes
    # care of renaming the posix one so it doesn't get into
    # the .tar.gz file (we don't want to re-run make in a released
    # file). We trigger off the presence/absence of that file altogether
    # to skip both posix and unix branches.
    # See https://github.com/gevent/gevent/issues/757
280
    if not _ran_make:
281 282 283 284 285 286
        if os.path.exists('Makefile'):
            if WIN:
                # make.cmd handles checking for PyPy and only making the
                # right things, so we can ignore the targets
                system("appveyor\\make.cmd")
            else:
287 288 289
                if "PYTHON" not in os.environ:
                    os.environ["PYTHON"] = sys.executable
                system('make ' + targets)
290
        _ran_make.append(1)
291

292

293
class sdist(_sdist):
294

295
    def run(self):
296 297 298 299 300 301 302 303 304 305
        renamed = False
        if os.path.exists('Makefile'):
            make()
            os.rename('Makefile', 'Makefile.ext')
            renamed = True
        try:
            return _sdist.run(self)
        finally:
            if renamed:
                os.rename('Makefile.ext', 'Makefile')
306

307

308
class my_build_ext(build_ext):
309

310 311 312
    def gevent_prepare(self, ext):
        configure = getattr(ext, 'configure', None)
        if configure:
313
            configure(self, ext)
314

315
    def build_extension(self, ext):
316
        self.gevent_prepare(ext)
317
        try:
318 319 320 321 322 323
            result = build_ext.build_extension(self, ext)
        except ext_errors:
            if getattr(ext, 'optional', False):
                raise BuildFailed
            else:
                raise
324 325 326 327 328
        if not PYPY:
            self.gevent_symlink(ext)
        return result

    def gevent_symlink(self, ext):
329 330 331 332 333 334 335 336 337
        # hack: create a symlink from build/../core.so to gevent/core.so
        # to prevent "ImportError: cannot import name core" failures
        try:
            fullname = self.get_ext_fullname(ext.name)
            modpath = fullname.split('.')
            filename = self.get_ext_filename(ext.name)
            filename = os.path.split(filename)[-1]
            if not self.inplace:
                filename = os.path.join(*modpath[:-1] + [filename])
338 339 340
                path_to_build_core_so = os.path.join(self.build_lib, filename)
                path_to_core_so = join('gevent', basename(path_to_build_core_so))
                link(path_to_build_core_so, path_to_core_so)
341 342 343
        except Exception:
            traceback.print_exc()

344

345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
def link(source, dest):
    source = abspath(source)
    dest = abspath(dest)
    if source == dest:
        return
    try:
        os.unlink(dest)
    except OSError:
        pass
    try:
        os.symlink(source, dest)
        sys.stdout.write('Linking %s to %s\n' % (source, dest))
    except (OSError, AttributeError):
        sys.stdout.write('Copying %s to %s\n' % (source, dest))
        shutil.copyfile(source, dest)


362 363
class BuildFailed(Exception):
    pass
Denis Bilenko's avatar
Denis Bilenko committed
364

Denis Bilenko's avatar
pep8  
Denis Bilenko committed
365

366
def read(name, *args):
367
    try:
368 369
        with open(join(dirname(__file__), name)) as f:
            return f.read(*args)
370 371 372
    except OSError:
        return ''

373 374
cffi_modules = ['gevent/_corecffi_build.py:ffi']

375 376 377
if PYPY:
    install_requires = []
else:
378
    install_requires = ['greenlet >= 0.4.9']
379 380
    setup_kwds = {}

381
try:
382
    cffi = __import__('cffi')
383
except ImportError:
384 385
    setup_kwds = {}
else:
386 387
    _min_cffi_version = (1, 3, 0)
    _cffi_version_is_supported = cffi.__version_info__ >= _min_cffi_version
388 389 390
    _kwds = {'cffi_modules': cffi_modules}
    # We already checked for PyPy on Windows above and excluded it
    if PYPY:
391 392
        if not _cffi_version_is_supported:
            raise Exception("PyPy 2.6.1 or higher is required")
393 394
        setup_kwds = _kwds
    elif LIBEV_EMBED and (not WIN or CFFI_WIN_BUILD_ANYWAY):
395 396 397 398 399 400 401 402
        if not _cffi_version_is_supported:
            print("WARNING: CFFI version 1.3.0 is required to build CFFI backend", file=sys.stderr)
        else:
            # If we're on CPython, we can only reliably build
            # the CFFI module if we're embedding libev (in some cases
            # we wind up embedding it anyway, which may not be what the
            # distributor wanted).
            setup_kwds = _kwds
403

404 405
# If we are running info / help commands, or we're being imported by
# tools like pyroma, we don't need to build anything
Jason Madden's avatar
Jason Madden committed
406 407 408 409 410 411 412 413
if ((len(sys.argv) >= 2
     and ('--help' in sys.argv[1:]
          or sys.argv[1] in ('--help-commands',
                             'egg_info',
                             '--version',
                             'clean',
                             '--long-description')))
    or __name__ != '__main__'):
414 415 416 417
    ext_modules = []
    include_package_data = PYPY
    run_make = False
elif PYPY:
418 419 420 421 422 423
    if not WIN:
        # We need to configure libev because the CORE Extension
        # won't do it (since we're not building it)
        system(libev_configure_command)
        # Then get rid of the extra copy created in place
        system('rm config.h')
424
    # NOTE that we're NOT adding the distutils extension module, as
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
    # doing so compiles the module already: import gevent._corecffi_build
    # imports gevent, which imports the hub, which imports the core,
    # which compiles the module in-place. Instead we use the setup-time
    # support of cffi_modules
    #from gevent import _corecffi_build
    ext_modules = [
        #_corecffi_build.ffi.distutils_extension(),
        ARES,
        # By building the semaphore with Cython under PyPy, we get
        # atomic operations (specifically, exiting/releasing), at the
        # cost of some speed (one trivial semaphore micro-benchmark put the pure-python version
        # at around 1s and the compiled version at around 4s). Some clever subclassing
        # and having only the bare minimum be in cython might help reduce that penalty.
        # NOTE: You must use version 0.23.4 or later to avoid a memory leak.
        # https://mail.python.org/pipermail/cython-devel/2015-October/004571.html
440 441 442 443 444 445
        # However, that's all for naught on up to and including PyPy 4.0.1 which
        # have some serious crashing bugs with GC interacting with cython,
        # so this is disabled (would need to add gevent/gevent._semaphore.c back to
        # the run_make line)
        #Extension(name="gevent._semaphore",
        #          sources=["gevent/gevent._semaphore.c"]),
446
    ]
447
    include_package_data = True
448
    run_make = 'gevent/gevent.ares.c'
449
else:
450 451 452 453 454 455
    ext_modules = [
        CORE,
        ARES,
        Extension(name="gevent._semaphore",
                  sources=["gevent/gevent._semaphore.c"]),
    ]
456 457 458
    include_package_data = False
    run_make = True

Jason Madden's avatar
Jason Madden committed
459
if run_make and os.path.exists("Makefile"):
460 461 462 463
    # The 'sdist' command renames our makefile after it
    # runs so we don't try to use it from a release tarball.

    # NOTE: This is effectively pointless and serves only for
464 465 466
    # documentation/metadata, because we run 'make' *before* we run
    # setup(), so installing cython happens too late.
    setup_requires = ['cython >= 0.23.4']
Jason Madden's avatar
Jason Madden committed
467 468
else:
    setup_requires = []
469

470

471
def run_setup(ext_modules, run_make):
472
    if run_make:
473 474 475 476
        if isinstance(run_make, str):
            make(run_make)
        else:
            make()
477 478 479 480 481
    setup(
        name='gevent',
        version=__version__,
        description='Coroutine-based network library',
        long_description=read('README.rst'),
482 483
        license='MIT',
        keywords='greenlet coroutine cooperative multitasking light threads monkey',
484 485
        author='Denis Bilenko',
        author_email='denis.bilenko@gmail.com',
486 487
        maintainer='Jason Madden',
        maintainer_email='jason@nextthought.com',
488 489
        url='http://www.gevent.org/',
        packages=['gevent'],
490
        include_package_data=include_package_data,
491
        ext_modules=ext_modules,
492
        cmdclass=dict(build_ext=my_build_ext, sdist=sdist),
493
        install_requires=install_requires,
Jason Madden's avatar
Jason Madden committed
494
        setup_requires=setup_requires,
495
        zip_safe=False,
496
        test_suite="greentest.testrunner",
497
        classifiers=[
Denis Bilenko's avatar
Denis Bilenko committed
498 499 500
            "License :: OSI Approved :: MIT License",
            "Programming Language :: Python :: 2.6",
            "Programming Language :: Python :: 2.7",
501 502
            "Programming Language :: Python :: 3.3",
            "Programming Language :: Python :: 3.4",
Jason Madden's avatar
Jason Madden committed
503
            "Programming Language :: Python :: 3.5",
504 505
            "Programming Language :: Python :: Implementation :: CPython",
            "Programming Language :: Python :: Implementation :: PyPy",
Denis Bilenko's avatar
Denis Bilenko committed
506 507 508 509 510 511
            "Operating System :: MacOS :: MacOS X",
            "Operating System :: POSIX",
            "Operating System :: Microsoft :: Windows",
            "Topic :: Internet",
            "Topic :: Software Development :: Libraries :: Python Modules",
            "Intended Audience :: Developers",
512 513
            "Development Status :: 4 - Beta"],
        **setup_kwds
514
    )
515

516 517 518
# Tools like pyroma expect the actual call to `setup` to be performed
# at the top-level at import time, so don't stash it away behind 'if
# __name__ == __main__'
Denis Bilenko's avatar
Denis Bilenko committed
519

520 521 522 523 524
if os.getenv('READTHEDOCS'):
    # Sometimes RTD fails to put our virtualenv bin directory
    # on the PATH, meaning we can't run cython. Fix that.
    new_path = os.environ['PATH'] + os.pathsep + os.path.dirname(sys.executable)
    os.environ['PATH'] = new_path
525

526 527 528
try:
    run_setup(ext_modules, run_make=run_make)
except BuildFailed:
529
    if ARES not in ext_modules:
530 531 532 533 534
        raise
    ext_modules.remove(ARES)
    run_setup(ext_modules, run_make=run_make)
if ARES not in ext_modules and __name__ == '__main__':
    sys.stderr.write('\nWARNING: The gevent.ares extension has been disabled.\n')