1# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
3
4"""Code coverage measurement for Python"""
5
6# Distutils setup for coverage.py
7# This file is used unchanged under all versions of Python, 2.x and 3.x.
8
9import os
10import sys
11
12from setuptools import setup
13from distutils.core import Extension                # pylint: disable=no-name-in-module, import-error
14from distutils.command.build_ext import build_ext   # pylint: disable=no-name-in-module, import-error
15from distutils import errors                        # pylint: disable=no-name-in-module
16
17# Get or massage our metadata.  We exec coverage/version.py so we can avoid
18# importing the product code into setup.py.
19
20classifiers = """\
21Environment :: Console
22Intended Audience :: Developers
23License :: OSI Approved :: Apache Software License
24Operating System :: OS Independent
25Programming Language :: Python :: 2.6
26Programming Language :: Python :: 2.7
27Programming Language :: Python :: 3.3
28Programming Language :: Python :: 3.4
29Programming Language :: Python :: 3.5
30Programming Language :: Python :: Implementation :: CPython
31Programming Language :: Python :: Implementation :: PyPy
32Topic :: Software Development :: Quality Assurance
33Topic :: Software Development :: Testing
34"""
35
36cov_ver_py = os.path.join(os.path.split(__file__)[0], "coverage/version.py")
37with open(cov_ver_py) as version_file:
38    # __doc__ will be overwritten by version.py.
39    doc = __doc__
40    # Keep pylint happy.
41    __version__ = __url__ = version_info = ""
42    # Execute the code in version.py.
43    exec(compile(version_file.read(), cov_ver_py, 'exec'))
44
45with open("README.rst") as readme:
46    long_description = readme.read().replace("http://coverage.readthedocs.org", __url__)
47
48classifier_list = classifiers.splitlines()
49
50if version_info[3] == 'alpha':
51    devstat = "3 - Alpha"
52elif version_info[3] in ['beta', 'candidate']:
53    devstat = "4 - Beta"
54else:
55    assert version_info[3] == 'final'
56    devstat = "5 - Production/Stable"
57classifier_list.append("Development Status :: " + devstat)
58
59# Create the keyword arguments for setup()
60
61setup_args = dict(
62    name='coverage',
63    version=__version__,
64
65    packages=[
66        'coverage',
67    ],
68
69    package_data={
70        'coverage': [
71            'htmlfiles/*.*',
72        ]
73    },
74
75    entry_points={
76        # Install a script as "coverage", and as "coverage[23]", and as
77        # "coverage-2.7" (or whatever).
78        'console_scripts': [
79            'coverage = coverage.cmdline:main',
80            'coverage%d = coverage.cmdline:main' % sys.version_info[:1],
81            'coverage-%d.%d = coverage.cmdline:main' % sys.version_info[:2],
82        ],
83    },
84
85    # We need to get HTML assets from our htmlfiles directory.
86    zip_safe=False,
87
88    author='Ned Batchelder and others',
89    author_email='ned@nedbatchelder.com',
90    description=doc,
91    long_description=long_description,
92    keywords='code coverage testing',
93    license='Apache 2.0',
94    classifiers=classifier_list,
95    url=__url__,
96)
97
98# A replacement for the build_ext command which raises a single exception
99# if the build fails, so we can fallback nicely.
100
101ext_errors = (
102    errors.CCompilerError,
103    errors.DistutilsExecError,
104    errors.DistutilsPlatformError,
105)
106if sys.platform == 'win32':
107    # distutils.msvc9compiler can raise an IOError when failing to
108    # find the compiler
109    ext_errors += (IOError,)
110
111
112class BuildFailed(Exception):
113    """Raise this to indicate the C extension wouldn't build."""
114    def __init__(self):
115        Exception.__init__(self)
116        self.cause = sys.exc_info()[1]      # work around py 2/3 different syntax
117
118
119class ve_build_ext(build_ext):
120    """Build C extensions, but fail with a straightforward exception."""
121
122    def run(self):
123        """Wrap `run` with `BuildFailed`."""
124        try:
125            build_ext.run(self)
126        except errors.DistutilsPlatformError:
127            raise BuildFailed()
128
129    def build_extension(self, ext):
130        """Wrap `build_extension` with `BuildFailed`."""
131        try:
132            # Uncomment to test compile failure handling:
133            #   raise errors.CCompilerError("OOPS")
134            build_ext.build_extension(self, ext)
135        except ext_errors:
136            raise BuildFailed()
137        except ValueError as err:
138            # this can happen on Windows 64 bit, see Python issue 7511
139            if "'path'" in str(err):    # works with both py 2/3
140                raise BuildFailed()
141            raise
142
143# There are a few reasons we might not be able to compile the C extension.
144# Figure out if we should attempt the C extension or not.
145
146compile_extension = True
147
148if sys.platform.startswith('java'):
149    # Jython can't compile C extensions
150    compile_extension = False
151
152if '__pypy__' in sys.builtin_module_names:
153    # Pypy can't compile C extensions
154    compile_extension = False
155
156if compile_extension:
157    setup_args.update(dict(
158        ext_modules=[
159            Extension(
160                "coverage.tracer",
161                sources=[
162                    "coverage/ctracer/datastack.c",
163                    "coverage/ctracer/filedisp.c",
164                    "coverage/ctracer/module.c",
165                    "coverage/ctracer/tracer.c",
166                ],
167            ),
168        ],
169        cmdclass={
170            'build_ext': ve_build_ext,
171        },
172    ))
173
174# Py3.x-specific details.
175
176if sys.version_info >= (3, 0):
177    setup_args.update(dict(
178        use_2to3=False,
179    ))
180
181
182def main():
183    """Actually invoke setup() with the arguments we built above."""
184    # For a variety of reasons, it might not be possible to install the C
185    # extension.  Try it with, and if it fails, try it without.
186    try:
187        setup(**setup_args)
188    except BuildFailed as exc:
189        msg = "Couldn't install with extension module, trying without it..."
190        exc_msg = "%s: %s" % (exc.__class__.__name__, exc.cause)
191        print("**\n** %s\n** %s\n**" % (msg, exc_msg))
192
193        del setup_args['ext_modules']
194        setup(**setup_args)
195
196if __name__ == '__main__':
197    main()
198