1from distutils.command.build_ext import build_ext
2from distutils.errors import CCompilerError
3from distutils.errors import DistutilsExecError
4from distutils.errors import DistutilsPlatformError
5import os
6import platform
7import re
8import sys
9
10from setuptools import Distribution as _Distribution
11from setuptools import Extension
12from setuptools import setup
13from setuptools.command.test import test as TestCommand
14
15
16cmdclass = {}
17
18cpython = platform.python_implementation() == "CPython"
19
20ext_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError)
21if sys.platform == "win32":
22    # Work around issue https://github.com/pypa/setuptools/issues/1902
23    ext_errors += (IOError, TypeError)
24    extra_compile_args = []
25elif sys.platform in ("linux", "linux2"):
26    # warn for undefined symbols in .c files
27    extra_compile_args = ["-Wundef", "-Werror=implicit-function-declaration"]
28else:
29    extra_compile_args = []
30
31ext_modules = [
32    Extension(
33        "sqlalchemy.cprocessors",
34        sources=["lib/sqlalchemy/cextension/processors.c"],
35        extra_compile_args=extra_compile_args,
36    ),
37    Extension(
38        "sqlalchemy.cresultproxy",
39        sources=["lib/sqlalchemy/cextension/resultproxy.c"],
40        extra_compile_args=extra_compile_args,
41    ),
42    Extension(
43        "sqlalchemy.cimmutabledict",
44        sources=["lib/sqlalchemy/cextension/immutabledict.c"],
45        extra_compile_args=extra_compile_args,
46    ),
47]
48
49
50class BuildFailed(Exception):
51    def __init__(self):
52        self.cause = sys.exc_info()[1]  # work around py 2/3 different syntax
53
54
55class ve_build_ext(build_ext):
56    # This class allows C extension building to fail.
57
58    def run(self):
59        try:
60            build_ext.run(self)
61        except DistutilsPlatformError:
62            raise BuildFailed()
63
64    def build_extension(self, ext):
65        try:
66            build_ext.build_extension(self, ext)
67        except ext_errors:
68            raise BuildFailed()
69        except ValueError:
70            # this can happen on Windows 64 bit, see Python issue 7511
71            if "'path'" in str(sys.exc_info()[1]):  # works with both py 2/3
72                raise BuildFailed()
73            raise
74
75
76cmdclass["build_ext"] = ve_build_ext
77
78
79class Distribution(_Distribution):
80    def has_ext_modules(self):
81        # We want to always claim that we have ext_modules. This will be fine
82        # if we don't actually have them (such as on PyPy) because nothing
83        # will get built, however we don't want to provide an overally broad
84        # Wheel package when building a wheel without C support. This will
85        # ensure that Wheel knows to treat us as if the build output is
86        # platform specific.
87        return True
88
89
90class UseTox(TestCommand):
91    RED = 31
92    RESET_SEQ = "\033[0m"
93    BOLD_SEQ = "\033[1m"
94    COLOR_SEQ = "\033[1;%dm"
95
96    def run_tests(self):
97        sys.stderr.write(
98            "%s%spython setup.py test is deprecated by pypa.  Please invoke "
99            "'tox' with no arguments for a basic test run.\n%s"
100            % (self.COLOR_SEQ % self.RED, self.BOLD_SEQ, self.RESET_SEQ)
101        )
102        sys.exit(1)
103
104
105cmdclass["test"] = UseTox
106
107
108def status_msgs(*msgs):
109    print("*" * 75)
110    for msg in msgs:
111        print(msg)
112    print("*" * 75)
113
114
115with open(
116    os.path.join(os.path.dirname(__file__), "lib", "sqlalchemy", "__init__.py")
117) as v_file:
118    VERSION = (
119        re.compile(r""".*__version__ = ["'](.*?)['"]""", re.S)
120        .match(v_file.read())
121        .group(1)
122    )
123
124
125def run_setup(with_cext):
126    kwargs = {}
127    if with_cext:
128        kwargs["ext_modules"] = ext_modules
129    else:
130        if os.environ.get("REQUIRE_SQLALCHEMY_CEXT"):
131            raise AssertionError(
132                "Can't build on this platform with "
133                "REQUIRE_SQLALCHEMY_CEXT set."
134            )
135
136        kwargs["ext_modules"] = []
137
138    setup(version=VERSION, cmdclass=cmdclass, distclass=Distribution, **kwargs)
139
140
141if not cpython:
142    run_setup(False)
143    status_msgs(
144        "WARNING: C extensions are not supported on "
145        + "this Python platform, speedups are not enabled.",
146        "Plain-Python build succeeded.",
147    )
148elif os.environ.get("DISABLE_SQLALCHEMY_CEXT"):
149    run_setup(False)
150    status_msgs(
151        "DISABLE_SQLALCHEMY_CEXT is set; "
152        + "not attempting to build C extensions.",
153        "Plain-Python build succeeded.",
154    )
155
156else:
157    try:
158        run_setup(True)
159    except BuildFailed as exc:
160
161        if os.environ.get("REQUIRE_SQLALCHEMY_CEXT"):
162            status_msgs(
163                "NOTE: C extension build is required because "
164                "REQUIRE_SQLALCHEMY_CEXT is set, and the build has failed; "
165                "will not degrade to non-C extensions"
166            )
167            raise
168
169        status_msgs(
170            exc.cause,
171            "WARNING: The C extension could not be compiled, "
172            + "speedups are not enabled.",
173            "Failure information, if any, is above.",
174            "Retrying the build without the C extension now.",
175        )
176
177        run_setup(False)
178
179        status_msgs(
180            "WARNING: The C extension could not be compiled, "
181            + "speedups are not enabled.",
182            "Plain-Python build succeeded.",
183        )
184