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