1#!/usr/bin/env python
2# -*- coding: ascii -*-
3u"""
4:Copyright:
5
6 Copyright 2011 - 2019
7 Andr\xe9 Malo or his licensors, as applicable
8
9:License:
10
11 Licensed under the Apache License, Version 2.0 (the "License");
12 you may not use this file except in compliance with the License.
13 You may obtain a copy of the License at
14
15     http://www.apache.org/licenses/LICENSE-2.0
16
17 Unless required by applicable law or agreed to in writing, software
18 distributed under the License is distributed on an "AS IS" BASIS,
19 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 See the License for the specific language governing permissions and
21 limitations under the License.
22
23===========================================
24 rJSmin - A Javascript Minifier For Python
25===========================================
26
27rJSmin - A Javascript Minifier For Python.
28"""
29from __future__ import print_function
30__author__ = u"Andr\xe9 Malo"
31__docformat__ = "restructuredtext en"
32
33import os as _os
34import posixpath as _posixpath
35import sys as _sys
36
37# pylint: disable = no-name-in-module, import-error
38from distutils import core as _core
39import setuptools as _setuptools
40
41# pylint: disable = invalid-name
42
43
44def _doc(filename):
45    """ Read docs file """
46    args = {} if str is bytes else dict(encoding='utf-8')
47    try:
48        with open(_os.path.join('docs', filename), **args) as fp:
49            return fp.read()
50    except IOError:
51        return None
52
53
54def _lines(multiline):
55    """ Split multiline string into single line % empty and comments """
56    return [line for line in (
57        line.strip() for line in multiline.splitlines(False)
58    ) if line and not line.startswith('#')]
59
60
61package = dict(
62    name='rjsmin',
63    top='.',
64    pathname='.',
65    provides=_doc('PROVIDES'),
66    desc=_doc('SUMMARY').strip(),
67    longdesc=_doc('DESCRIPTION'),
68    author=__author__,
69    email='nd@perlig.de',
70    license="Apache License, Version 2.0",
71    keywords=_lines(_doc('KEYWORDS')),
72    url='http://opensource.perlig.de/rjsmin/',
73    classifiers=_lines(_doc('CLASSIFIERS') or ''),
74
75    packages=False,
76    py_modules=['rjsmin'],
77    version_file='rjsmin.py',
78    install_requires=[],
79)
80
81
82class BuildFailed(Exception):
83    """ The build has failed """
84
85
86from distutils.command import build_ext as _build_ext  # pylint: disable = wrong-import-order
87from distutils import errors as _errors  # pylint: disable = wrong-import-order
88class build_ext(_build_ext.build_ext):  # pylint: disable = no-init
89    """ Improved extension building code """
90
91    def run(self):
92        """ Unify exception """
93        try:
94            _build_ext.build_ext.run(self)
95        except _errors.DistutilsPlatformError:
96            raise BuildFailed()
97
98
99    def build_extension(self, ext):
100        """
101        Build C extension - with extended functionality
102
103        The following features are added here:
104
105        - The macros ``EXT_PACKAGE`` and ``EXT_MODULE`` will be filled (or
106          unset) depending on the extensions name, but only if they are not
107          already defined.
108
109        - "." is added to the include directories (for cext.h)
110
111        :Parameters:
112          `ext` : `Extension`
113            The extension to build
114
115        :Return: whatever ``distutils.command.build_ext.build_ext`` returns
116        :Rtype: any
117        """
118        # handle name macros
119        macros = dict(ext.define_macros or ())
120        tup = ext.name.split('.')
121        if len(tup) == 1:
122            pkg, mod = None, tup[0]
123        else:
124            pkg, mod = '.'.join(tup[:-1]), tup[-1]
125        if pkg is not None and 'EXT_PACKAGE' not in macros:
126            ext.define_macros.append(('EXT_PACKAGE', pkg))
127        if 'EXT_MODULE' not in macros:
128            ext.define_macros.append(('EXT_MODULE', mod))
129        if pkg is None:
130            macros = dict(ext.undef_macros or ())
131            if 'EXT_PACKAGE' not in macros:
132                ext.undef_macros.append('EXT_PACKAGE')
133
134        try:
135            return _build_ext.build_ext.build_extension(self, ext)
136        except (_errors.CCompilerError, _errors.DistutilsExecError,
137                _errors.DistutilsPlatformError, IOError, ValueError):
138            raise BuildFailed()
139
140
141class Extension(_core.Extension):
142    """ improved functionality """
143
144    def __init__(self, *args, **kwargs):
145        """ Initialization """
146        version = kwargs.pop('version')
147        self.depends = []
148        if 'depends' in kwargs:
149            self.depends = kwargs['depends']
150        _core.Extension.__init__(self, *args, **kwargs)
151        self.define_macros.append(('EXT_VERSION', version))
152
153        # add include path
154        included = '.'
155        if included not in self.include_dirs:
156            self.include_dirs.append(included)
157
158        # add cext.h to the dependencies
159        cext_h = _posixpath.normpath(_posixpath.join(included, 'cext.h'))
160        for item in self.depends:
161            if _posixpath.normpath(item) == cext_h:
162                break
163        else:
164            self.depends.append(cext_h)
165
166
167EXTENSIONS = lambda v: [Extension('_rjsmin', ["rjsmin.c"], version=v)]
168
169
170def do_setup(cext):
171    """ Main """
172    # pylint: disable = too-many-branches
173
174    version_file = '%s/%s' % (package['pathname'],
175                              package.get('version_file', '__init__.py'))
176    with open(version_file) as fp:
177        for line in fp:  # pylint: disable = redefined-outer-name
178            if line.startswith('__version__'):
179                version = line.split('=', 1)[1].strip()
180                if version.startswith(("'", '"')):
181                    version = version[1:-1].strip()
182                break
183        else:
184            raise RuntimeError("Version not found")
185
186    kwargs = {}
187
188    if not cext or 'java' in _sys.platform.lower():
189        extensions = []
190    else:
191        extensions = EXTENSIONS(version)
192
193    if extensions:
194        if 'build_ext' in globals():
195            kwargs.setdefault('cmdclass', {})['build_ext'] = build_ext
196        kwargs['ext_modules'] = extensions
197
198        gcov = False
199        if _os.environ.get('CFLAGS') is None:
200            from distutils import ccompiler as _ccompiler
201            from distutils import log as _log
202
203            compiler = _ccompiler.get_default_compiler()
204            try:
205                with open("debug.%s.cflags" % compiler) as fp:
206                    cflags = ' '.join([
207                        line for line in (line.strip() for line in fp)
208                        if line and not line.startswith('#')
209                    ]) or None
210            except IOError:
211                pass
212            else:
213                if cflags is not None:
214                    # pylint: disable = unsupported-membership-test
215                    if 'coverage' in cflags:
216                        gcov = True
217                    _log.info("Setting CFLAGS to %r", cflags)
218                    _os.environ['CFLAGS'] = cflags
219
220        if gcov:
221            for ext in extensions:
222                ext.libraries.append('gcov')
223
224    if package.get('packages', True):
225        kwargs['packages'] = [package['top']] + [
226            '%s.%s' % (package['top'], item)
227            for item in
228            _setuptools.find_packages(package['pathname'])
229        ]
230    if package.get('py_modules'):
231        kwargs['py_modules'] = package['py_modules']
232
233    _core.setup(
234        name=package['name'],
235        author=package['author'],
236        author_email=package['email'],
237        license=package['license'],
238        classifiers=package['classifiers'],
239        description=package['desc'],
240        long_description=package['longdesc'],
241        url=package['url'],
242        install_requires=package['install_requires'],
243        version=version,
244        zip_safe=False,
245        **kwargs
246    )
247
248
249def setup():
250    """ Run setup """
251    try:
252        do_setup(True)
253    except BuildFailed:
254        if _os.environ.get('SETUP_CEXT_REQUIRED', '') not in ('', '0'):
255            raise
256        print("C extension build failed - building python only version now. "
257              "Set 'RJSMIN_CEXT_REQUIRED' environment variable to '1' to "
258              "make it fail.", file=_sys.stderr)
259        do_setup(False)
260
261
262if __name__ == '__main__':
263    setup()
264