#! /usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
import sys
import os
import glob
import platform

# distutils is deprecated and vendored into setuptools now.
from setuptools import setup
from setuptools import Extension
from setuptools import find_packages

# Extra compiler arguments passed to *all* extensions.
global_compile_args = []

# Extra compiler arguments passed to C++ extensions
cpp_compile_args = []

# Extra linker arguments passed to C++ extensions
cpp_link_args = []

# Extra compiler arguments passed to the main extension
main_compile_args = []

is_win = sys.platform.startswith("win")

# workaround segfaults on openbsd and RHEL 3 / CentOS 3 . see
# https://bitbucket.org/ambroff/greenlet/issue/11/segfault-on-openbsd-i386
# https://github.com/python-greenlet/greenlet/issues/4
# https://github.com/python-greenlet/greenlet/issues/94
# pylint:disable=too-many-boolean-expressions
is_linux = sys.platform.startswith('linux') # could be linux or linux2
plat_platform = platform.platform()
plat_machine = platform.machine()
plat_compiler = platform.python_compiler()
try:
    # (sysname, nodename, release, version, machine)
    unam_machine = os.uname()[-1]
except AttributeError:
    unam_machine = ''
if (
       (sys.platform == "openbsd4" and unam_machine == "i386")
    or ("-with-redhat-3." in plat_platform and plat_machine == 'i686')
    or (sys.platform == "sunos5" and unam_machine == "sun4v") # SysV-based Solaris
    or ("SunOS" in plat_platform and plat_machine == "sun4v") # Old BSD-based SunOS
    or (is_linux and plat_machine == "ppc")
    # https://github.com/python-greenlet/greenlet/pull/300: When compiling for RISC-V the command
    # ``riscv64-linux-gnu-gcc -pthread -fno-strict-aliasing -Wdate-time \
    #   -D_FORTIFY_SOURCE=2 -g -ffile-prefix-map=/build/python2.7-7GU7VT/python2.7-2.7.18=. \
    #   -fstack-protector-strong -Wformat -Werror=format-security -fPIC \
    #   -I/usr/include/python2.7
    #   -c src/greenlet/greenlet.cpp  -o build/temp.linux-riscv64-2.7/src/greenlet/greenlet.o``
    #
    # fails with:
    #
    # src/greenlet/platform/switch_riscv_unix.h:30:1: error: s0 cannot be used in 'asm' here
    #
    # Adding the -Os flag fixes the problem.
    or (is_linux and plat_machine == "riscv64")
):
    global_compile_args.append("-Os")


if sys.platform == 'darwin' or 'clang' in plat_compiler:
    # The clang compiler doesn't use --std=c++11 by default
    cpp_compile_args.append("--std=gnu++11")
elif is_win and "MSC" in plat_compiler:
    # Older versions of MSVC (Python 2.7) don't handle C++ exceptions
    # correctly by default. While newer versions do handle exceptions
    # by default, they don't do it fully correctly ("By default....the
    # compiler generates code that only partially supports C++
    # exceptions."). So we need an argument on all versions.

    #"/EH" == exception handling.
    #    "s" == standard C++,
    #    "c" == extern C functions don't throw
    # OR
    #   "a" == standard C++, and Windows SEH; anything may throw, compiler optimizations
    #          around try blocks are less aggressive. Because this catches SEH,
    #          which Windows uses internally, the MS docs say this can be a security issue.
    #          DO NOT USE.
    # /EHsc is suggested, and /EHa isn't supposed to be linked to other things not built
    # with it. Leaving off the "c" should just result in slower, safer code.
    # Other options:
    #    "r" == Always generate standard confirming checks for noexcept blocks, terminating
    #           if violated. IMPORTANT: We rely on this.
    # See https://docs.microsoft.com/en-us/cpp/build/reference/eh-exception-handling-model?view=msvc-170
    handler = "/EHsr"
    cpp_compile_args.append(handler)
    # To disable most optimizations:
    #cpp_compile_args.append('/Od')

    # To enable assertions:
    #cpp_compile_args.append('/UNDEBUG')

    # To enable more compile-time warnings (/Wall produces a mountain of output).
    #cpp_compile_args.append('/W4')

    # To link with the debug C runtime...except we can't because we need
    # the Python debug lib too, and they're not around by default
    # cpp_compile_args.append('/MDd')

    # Support fiber-safe thread-local storage: "the compiler mustn't
    # cache the address of the TLS array, or optimize it as a common
    # subexpression across a function call." This would probably solve
    # some of the issues we had with MSVC caching the thread local
    # variables on the stack, leading to having to split some
    # functions up. Revisit those.
    cpp_compile_args.append("/GT")

def readfile(filename):
    with open(filename, 'r') as f: # pylint:disable=unspecified-encoding
        return f.read()

def abspath(rel):
    return os.path.join(os.path.dirname(__file__), rel)

GREENLET_SRC_DIR = abspath('src/greenlet/')
GREENLET_HEADER_DIR = GREENLET_SRC_DIR
GREENLET_HEADER = GREENLET_HEADER_DIR + 'greenlet.h'
GREENLET_TEST_DIR = abspath('src/greenlet/tests/')
# The location of the platform specific assembly files
# for switching.
GREENLET_PLATFORM_DIR = GREENLET_SRC_DIR + 'platform/'

def _find_platform_headers():
    return glob.glob(GREENLET_PLATFORM_DIR + "switch_*.h")

def _find_impl_headers():
    return glob.glob(GREENLET_SRC_DIR + "*.hpp") + glob.glob(GREENLET_SRC_DIR + "*.cpp")

if hasattr(sys, "pypy_version_info"):
    ext_modules = []
    headers = []
else:

    headers = [GREENLET_HEADER]

    if is_win and '64 bit' in sys.version:
        # this works when building with msvc, not with 64 bit gcc
        # switch_<platform>_masm.obj can be created with setup_switch_<platform>_masm.cmd
        obj_fn = 'switch_arm64_masm.obj' if plat_machine == 'ARM64' else 'switch_x64_masm.obj'
        extra_objects = [os.path.join(GREENLET_PLATFORM_DIR, obj_fn)]
    else:
        extra_objects = []

    if is_win and os.environ.get('GREENLET_STATIC_RUNTIME') in ('1', 'yes'):
        main_compile_args.append('/MT')
    elif unam_machine in ('ppc64el', 'ppc64le'):
        main_compile_args.append('-fno-tree-dominator-opts')

    ext_modules = [
        Extension(
            name='greenlet._greenlet',
            sources=[
                GREENLET_SRC_DIR + 'greenlet.cpp',
            ],
            language='c++',
            extra_objects=extra_objects,
            extra_compile_args=global_compile_args + main_compile_args + cpp_compile_args,
            extra_link_args=cpp_link_args,
            depends=[
                GREENLET_HEADER,
                GREENLET_SRC_DIR + 'slp_platformselect.h',
            ] + _find_platform_headers() + _find_impl_headers(),
            define_macros=[
            ] + ([
                ('WIN32', '1'),
            ] if is_win else [
            ])
        ),
        # Test extensions.
        #
        # We used to try hard to not include these in built
        # distributions, because we only distributed ``greenlet.so``.
        # That's really not important, now we have a clean layout with
        # the test directory nested inside a greenlet directory. See
        # https://github.com/python-greenlet/greenlet/issues/184 and
        # 189
        Extension(
            name='greenlet.tests._test_extension',
            sources=[GREENLET_TEST_DIR + '_test_extension.c'],
            include_dirs=[GREENLET_HEADER_DIR],
            extra_compile_args=global_compile_args,
        ),
        Extension(
            name='greenlet.tests._test_extension_cpp',
            sources=[GREENLET_TEST_DIR + '_test_extension_cpp.cpp'],
            language="c++",
            include_dirs=[GREENLET_HEADER_DIR],
            extra_compile_args=global_compile_args + cpp_compile_args,
            extra_link_args=cpp_link_args,
        ),
    ]


def get_greenlet_version():
    with open(abspath('src/greenlet/__init__.py')) as f:
        looking_for = '__version__ = \''
        for line in f:
            if line.startswith(looking_for):
                version = line[len(looking_for):-2]
                return version
    raise ValueError("Unable to find version")


setup(
    name="greenlet",
    version=get_greenlet_version(),
    description='Lightweight in-process concurrent programming',
    long_description=readfile(abspath("README.rst")),
    long_description_content_type="text/x-rst",
    url="https://greenlet.readthedocs.io/",
    keywords="greenlet coroutine concurrency threads cooperative",
    author="Alexey Borzenkov",
    author_email="snaury@gmail.com",
    maintainer='Jason Madden',
    maintainer_email='jason@seecoresoftware.com',
    project_urls={
        'Bug Tracker': 'https://github.com/python-greenlet/greenlet/issues',
        'Source Code': 'https://github.com/python-greenlet/greenlet/',
        'Documentation': 'https://greenlet.readthedocs.io/',
    },
    license="MIT License",
    platforms=['any'],
    package_dir={'': 'src'},
    packages=find_packages('src'),
    include_package_data=True,
    headers=headers,
    ext_modules=ext_modules,
    classifiers=[
        "Development Status :: 5 - Production/Stable",
        'Intended Audience :: Developers',
        'License :: OSI Approved :: MIT License',
        'Natural Language :: English',
        'Programming Language :: C',
        'Programming Language :: Python',
        'Programming Language :: Python :: 3',
        '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',
        'Programming Language :: Python :: 3.11',
        'Programming Language :: Python :: 3.12',
        'Programming Language :: Python :: 3.13',
        'Operating System :: OS Independent',
        'Topic :: Software Development :: Libraries :: Python Modules'
    ],
    extras_require={
        'docs': [
            'Sphinx',
            'furo',
        ],
        'test': [
            'objgraph',
            'psutil',
        ],
    },
    python_requires=">=3.7",
    zip_safe=False,
)
