#!/usr/bin/env python
"""gevent build & installation script"""
from __future__ import print_function
import sys
import os
import os.path
import sysconfig

# setuptools is *required* on Windows
# (https://bugs.python.org/issue23246) and for PyPy. No reason not to
# use it everywhere. v24.2.0 is needed for python_requires
from setuptools import Extension, setup
from setuptools import find_packages


from _setuputils import read
from _setuputils import read_version
from _setuputils import PYPY, WIN
from _setuputils import ConfiguringBuildExt
from _setuputils import GeventClean
from _setuputils import BuildFailed
from _setuputils import cythonize1

# Environment variables that are intended to be used outside of our own
# CI should be documented in ``installing_from_source.rst``

if WIN:
    # 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'
    if not os.environ.get('PYEXE'):
        os.environ['PYEXE'] = os.environ['PYTHON_EXE']


if PYPY and sys.pypy_version_info[:3] < (2, 6, 1): # pylint:disable=no-member
    # We have to have CFFI >= 1.3.0, and this platform cannot upgrade
    # it.
    raise Exception("PyPy >= 2.6.1 is required")



__version__ = read_version()


from _setuplibev import build_extension as build_libev_extension
from _setupares import ARES

CORE = cythonize1(build_libev_extension())

# Get access to the greenlet header file.
# The sysconfig dir is not enough if we're in a virtualenv
# See https://github.com/pypa/pip/issues/4610
include_dirs = [sysconfig.get_path("include")]
venv_include_dir = os.path.join(sys.prefix, 'include', 'site',
                                'python' + sysconfig.get_python_version())
venv_include_dir = os.path.abspath(venv_include_dir)
if os.path.exists(venv_include_dir):
    include_dirs.append(venv_include_dir)

# If we're installed via buildout, and buildout also installs
# greenlet, we have *NO* access to greenlet.h at all. So include
# our own copy as a fallback.
include_dirs.append('deps')

SEMAPHORE = Extension(name="gevent.__semaphore",
                      sources=["src/gevent/_semaphore.py"],
                      depends=['src/gevent/__semaphore.pxd'],
                      include_dirs=include_dirs)


LOCAL = Extension(name="gevent._local",
                  sources=["src/gevent/local.py"],
                  depends=['src/gevent/_local.pxd'],
                  include_dirs=include_dirs)


GREENLET = Extension(name="gevent._greenlet",
                     sources=[
                         "src/gevent/greenlet.py",
                     ],
                     depends=[
                         'src/gevent/_greenlet.pxd',
                         'src/gevent/__ident.pxd',
                         'src/gevent/_ident.py'
                     ],
                     include_dirs=include_dirs)

ABSTRACT_LINKABLE = Extension(name="gevent.__abstract_linkable",
                              sources=["src/gevent/_abstract_linkable.py"],
                              depends=['src/gevent/__abstract_linkable.pxd'],
                              include_dirs=include_dirs)


IDENT = Extension(name="gevent.__ident",
                  sources=["src/gevent/_ident.py"],
                  depends=['src/gevent/__ident.pxd'],
                  include_dirs=include_dirs)


IMAP = Extension(name="gevent.__imap",
                 sources=["src/gevent/_imap.py"],
                 depends=['src/gevent/__imap.pxd'],
                 include_dirs=include_dirs)

EVENT = Extension(name="gevent._event",
                  sources=["src/gevent/event.py"],
                  depends=['src/gevent/_event.pxd'],
                  include_dirs=include_dirs)

QUEUE = Extension(name="gevent._queue",
                  sources=["src/gevent/queue.py"],
                  depends=['src/gevent/_queue.pxd'],
                  include_dirs=include_dirs)

HUB_LOCAL = Extension(name="gevent.__hub_local",
                      sources=["src/gevent/_hub_local.py"],
                      depends=['src/gevent/__hub_local.pxd'],
                      include_dirs=include_dirs)

WAITER = Extension(name="gevent.__waiter",
                   sources=["src/gevent/_waiter.py"],
                   depends=['src/gevent/__waiter.pxd'],
                   include_dirs=include_dirs)

HUB_PRIMITIVES = Extension(name="gevent.__hub_primitives",
                           sources=["src/gevent/_hub_primitives.py"],
                           depends=['src/gevent/__hub_primitives.pxd'],
                           include_dirs=include_dirs)

GLT_PRIMITIVES = Extension(name="gevent.__greenlet_primitives",
                           sources=["src/gevent/_greenlet_primitives.py"],
                           depends=['src/gevent/__greenlet_primitives.pxd'],
                           include_dirs=include_dirs)

TRACER = Extension(name="gevent.__tracer",
                   sources=["src/gevent/_tracer.py"],
                   depends=['src/gevent/__tracer.pxd'],
                   include_dirs=include_dirs)


_to_cythonize = [
    GLT_PRIMITIVES,
    HUB_PRIMITIVES,
    HUB_LOCAL,
    WAITER,
    GREENLET,
    TRACER,

    ABSTRACT_LINKABLE,
    SEMAPHORE,
    LOCAL,

    IDENT,
    IMAP,
    EVENT,
    QUEUE,
]

EXT_MODULES = [
    CORE,
    ARES,
    ABSTRACT_LINKABLE,
    SEMAPHORE,
    LOCAL,
    GREENLET,
    IDENT,
    IMAP,
    EVENT,
    QUEUE,
    HUB_LOCAL,
    WAITER,
    HUB_PRIMITIVES,
    GLT_PRIMITIVES,
    TRACER,
]

LIBEV_CFFI_MODULE = 'src/gevent/libev/_corecffi_build.py:ffi'
LIBUV_CFFI_MODULE = 'src/gevent/libuv/_corecffi_build.py:ffi'
cffi_modules = []

if not WIN:
    # We can't properly handle (hah!) file-descriptors and
    # handle mapping on Windows/CFFI with libev, 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.
    # Also, we use the type `nlink_t`, which is not defined on Windows.
    cffi_modules.append(
        LIBEV_CFFI_MODULE
    )

cffi_modules.append(LIBUV_CFFI_MODULE)

greenlet_requires = [
    # We need to watch our greenlet version fairly carefully,
    # since we compile cython code that extends the greenlet object.
    # Binary compatibility would break if the greenlet struct changes.
    # (Which it did in 0.4.14 for Python 3.7)
    'greenlet >= 0.4.14; platform_python_implementation=="CPython"',
]

# Note that we don't add cffi to install_requires, it's
# optional. We tend to build and distribute wheels with the CFFI
# modules built and they can be imported if CFFI is installed.
# We need cffi 1.4.0 for new style callbacks;
# we need cffi 1.11.3 (on CPython 3) to avoid test errors.

# The exception is on Windows, where we want the libuv backend we distribute
# to be the default, and that requires cffi; but don't try to install it
# on PyPy or it messes up the build
CFFI_DEP = "cffi >= 1.12.2 ; platform_python_implementation == 'CPython'"
CFFI_REQUIRES = [
    CFFI_DEP + " and sys_platform == 'win32'"
]


install_requires = greenlet_requires + CFFI_REQUIRES

# We use headers from greenlet, so it needs to be installed before we
# can compile. If it isn't already installed before we start
# installing, and we say 'pip install gevent', a 'setup_requires'
# doesn't save us: pip happily downloads greenlet and drops it in a
# .eggs/ directory in the build directory, but that directory doesn't
# have includes! So we fail to build a wheel, pip goes ahead and
# installs greenlet, and builds gevent again, which works.

# Since we ship the greenlet header for buildout support (which fails
# to install the headers at all, AFAICS, we don't need to bother with
# the buggy setup_requires.)

setup_requires = CFFI_REQUIRES + []

if PYPY:
    # These use greenlet/greenlet.h, which doesn't exist on PyPy
    EXT_MODULES.remove(LOCAL)
    EXT_MODULES.remove(GREENLET)
    EXT_MODULES.remove(SEMAPHORE)
    EXT_MODULES.remove(ABSTRACT_LINKABLE)

    # As of PyPy 5.10, this builds, but won't import (missing _Py_ReprEnter)
    EXT_MODULES.remove(CORE)

    # This uses PyWeakReference and doesn't compile on PyPy
    EXT_MODULES.remove(IDENT)

    _to_cythonize.remove(LOCAL)
    _to_cythonize.remove(GREENLET)
    _to_cythonize.remove(SEMAPHORE)
    _to_cythonize.remove(IDENT)
    _to_cythonize.remove(ABSTRACT_LINKABLE)

    EXT_MODULES.remove(IMAP)
    _to_cythonize.remove(IMAP)

    EXT_MODULES.remove(EVENT)
    _to_cythonize.remove(EVENT)

    EXT_MODULES.remove(QUEUE)
    _to_cythonize.remove(QUEUE)

    EXT_MODULES.remove(HUB_LOCAL)
    _to_cythonize.remove(HUB_LOCAL)

    EXT_MODULES.remove(WAITER)
    _to_cythonize.remove(WAITER)

    EXT_MODULES.remove(GLT_PRIMITIVES)
    _to_cythonize.remove(GLT_PRIMITIVES)

    EXT_MODULES.remove(HUB_PRIMITIVES)
    _to_cythonize.remove(HUB_PRIMITIVES)

    EXT_MODULES.remove(TRACER)
    _to_cythonize.remove(TRACER)


for mod in _to_cythonize:
    EXT_MODULES.remove(mod)
    EXT_MODULES.append(cythonize1(mod))
del _to_cythonize


## Extras

EXTRA_DNSPYTHON = [
    'dnspython >= 1.16.0',
    'idna',
]
EXTRA_EVENTS = [
    'zope.event',
    'zope.interface',
]
# Fails to build on PyPy on Windows.
# TODO: Is that still the case?
EXTRA_PSUTIL_DEP = 'psutil >= 5.6.1 ; platform_python_implementation == "CPython" or sys_platform != "win32"'

EXTRA_MONITOR = [
    EXTRA_PSUTIL_DEP,
]

EXTRA_RECOMMENDED = [
    # We need this at runtime to use the libev-CFFI and libuv backends
    CFFI_DEP,
] + EXTRA_DNSPYTHON + EXTRA_EVENTS + EXTRA_MONITOR


def make_long_description():
    readme = read('README.rst')
    about = read('docs', '_about.rst')
    install = read('docs', 'install.rst')
    readme = readme.replace('.. include:: docs/_about.rst',
                            about)
    readme = readme.replace('.. include:: docs/install.rst',
                            install)

    return readme


def run_setup(ext_modules):
    setup(
        name='gevent',
        version=__version__,
        description='Coroutine-based network library',
        long_description=make_long_description(),
        license='MIT',
        keywords='greenlet coroutine cooperative multitasking light threads monkey',
        author='Denis Bilenko',
        author_email='denis.bilenko@gmail.com',
        maintainer='Jason Madden',
        maintainer_email='jason@nextthought.com',
        url='http://www.gevent.org/',
        project_urls={
            'Bug Tracker': 'https://github.com/gevent/gevent/issues',
            'Source Code': 'https://github.com/gevent/gevent/',
            'Documentation': 'http://www.gevent.org',
        },
        package_dir={'': 'src'},
        packages=find_packages('src'),
        include_package_data=True,
        ext_modules=ext_modules,
        cmdclass={
            'build_ext': ConfiguringBuildExt,
            'clean': GeventClean,
        },
        install_requires=install_requires,
        setup_requires=setup_requires,
        extras_require={
            # Each extra intended for end users must be documented in install.rst
            'dnspython': EXTRA_DNSPYTHON,
            'events': EXTRA_EVENTS,
            'monitor': EXTRA_MONITOR,
            'recommended': EXTRA_RECOMMENDED,
            # End end-user extras
            'docs': [
                'repoze.sphinx.autointerface',
                'sphinxcontrib-programoutput',
            ],
            # To the extent possible, we should work to make sure
            # our tests run, at least a basic set, without any of
            # these extra dependencies (i.e., skip things when they are
            # missing). This helps serve as a smoketest for users.
            'test': EXTRA_RECOMMENDED + [
                # examples, called from tests, use this
                'requests',

                # We don't run coverage on Windows, and pypy can't build it there
                # anyway (coveralls -> cryptopgraphy -> openssl).
                # As of coverage 5.0a6, coveralls (up to 1.9) won't work at all.
                'coverage<5.0 ; sys_platform != "win32"',
                'coveralls>=1.7.0 ; sys_platform != "win32"',

                'futures ; python_version == "2.7"',
                'mock ; python_version == "2.7"',

                # leak checks. previously we had a hand-rolled version.
                'objgraph',
            ],
        },
        # It's always safe to pass the CFFI keyword, even if
        # cffi is not installed: it's just ignored in that case.
        cffi_modules=cffi_modules,
        zip_safe=False,
        test_suite="greentest.testrunner",
        classifiers=[
            "License :: OSI Approved :: MIT License",
            "Programming Language :: Python :: 2.7",
            "Programming Language :: Python :: 3.4",
            "Programming Language :: Python :: 3.5",
            "Programming Language :: Python :: 3.6",
            "Programming Language :: Python :: 3.7",
            "Programming Language :: Python :: Implementation :: CPython",
            "Programming Language :: Python :: Implementation :: PyPy",
            "Operating System :: MacOS :: MacOS X",
            "Operating System :: POSIX",
            "Operating System :: Microsoft :: Windows",
            "Topic :: Internet",
            "Topic :: Software Development :: Libraries :: Python Modules",
            "Intended Audience :: Developers",
            "Development Status :: 4 - Beta"
        ],
        python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*",
        entry_points={
            'gevent.plugins.monkey.will_patch_all': [
                "signal_os_incompat = gevent.monkey:_subscribe_signal_os",
            ],
        },
    )

# 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__'

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

try:
    run_setup(EXT_MODULES)
except BuildFailed:
    if ARES not in EXT_MODULES or not ARES.optional:
        raise
    sys.stderr.write('\nWARNING: The gevent.ares extension has been disabled.\n')
    EXT_MODULES.remove(ARES)
    run_setup(EXT_MODULES)
