1 # -*- coding: ascii -*- 2 # 3 # Copyright 2007 - 2013 4 # Andr\xe9 Malo or his licensors, as applicable 5 # 6 # Licensed under the Apache License, Version 2.0 (the "License"); 7 # you may not use this file except in compliance with the License. 8 # You may obtain a copy of the License at 9 # 10 # http://www.apache.org/licenses/LICENSE-2.0 11 # 12 # Unless required by applicable law or agreed to in writing, software 13 # distributed under the License is distributed on an "AS IS" BASIS, 14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 # See the License for the specific language governing permissions and 16 # limitations under the License. 17 """ 18 =================== 19 Main setup runner 20 =================== 21 22 This module provides a wrapper around the distutils core setup. 23 """ 24 __author__ = "Andr\xe9 Malo" 25 __docformat__ = "restructuredtext en" 26 27 import configparser as _config_parser 28 from distutils import core as _core 29 import os as _os 30 import posixpath as _posixpath 31 import sys as _sys 32 33 from _setup import commands as _commands 34 from _setup import data as _data 35 from _setup import ext as _ext 36 from _setup import util as _util 37 from _setup import shell as _shell 38 39 40 def check_python_version(impl, version_min, version_max): 41 """ Check python version """ 42 if impl == 'python': 43 version_info = _sys.version_info 44 elif impl == 'pypy': 45 version_info = getattr(_sys, 'pypy_version_info', None) 46 if not version_info: 47 return 48 elif impl == 'jython': 49 if not 'java' in _sys.platform.lower(): 50 return 51 version_info = _sys.version_info 52 else: 53 raise AssertionError("impl not in ('python', 'pypy', 'jython')") 54 55 pyversion = list(map(int, version_info[:3])) 56 if version_min: 57 min_required = list( 58 map(int, '.'.join((version_min, '0.0.0')).split('.')[:3]) 59 ) 60 if pyversion < min_required: 61 raise EnvironmentError("Need at least %s %s (vs. %s)" % ( 62 impl, version_min, '.'.join(map(str, pyversion)) 63 )) 64 if version_max: 65 max_required = list(map(int, version_max.split('.'))) 66 max_required[-1] += 1 67 if pyversion >= max_required: 68 raise EnvironmentError("Need at max %s %s (vs. %s)" % ( 69 impl, 70 version_max, 71 '.'.join(map(str, pyversion)) 72 )) 73 74 75 def find_description(docs): 76 """ 77 Determine the package description from DESCRIPTION 78 79 :Parameters: 80 `docs` : ``dict`` 81 Docs config section 82 83 :Return: Tuple of summary, description and license 84 (``('summary', 'description', 'license')``) 85 (all may be ``None``) 86 :Rtype: ``tuple`` 87 """ 88 summary = None 89 filename = docs.get('meta.summary', 'SUMMARY').strip() 90 if filename and _os.path.isfile(filename): 91 fp = open(filename, encoding='utf-8') 92 try: 93 try: 94 summary = fp.read().strip().splitlines()[0].rstrip() 95 except IndexError: 96 summary = '' 97 finally: 98 fp.close() 99 100 description = None 101 filename = docs.get('meta.description', 'DESCRIPTION').strip() 102 if filename and _os.path.isfile(filename): 103 fp = open(filename, encoding='utf-8') 104 try: 105 description = fp.read().rstrip() 106 finally: 107 fp.close() 108 109 if summary is None and description: 110 from docutils import core 111 summary = core.publish_parts( 112 source=description, 113 source_path=filename, 114 writer_name='html', 115 )['title'].encode('utf-8') 116 117 return summary, description 118 119 120 def find_classifiers(docs): 121 """ 122 Determine classifiers from CLASSIFIERS 123 124 :return: List of classifiers (``['classifier', ...]``) 125 :rtype: ``list`` 126 """ 127 filename = docs.get('meta.classifiers', 'CLASSIFIERS').strip() 128 if filename and _os.path.isfile(filename): 129 fp = open(filename, encoding='utf-8') 130 try: 131 content = fp.read() 132 finally: 133 fp.close() 134 content = [item.strip() for item in content.splitlines()] 135 return [item for item in content if item and not item.startswith('#')] 136 return [] 137 138 139 def find_provides(docs): 140 """ 141 Determine provides from PROVIDES 142 143 :return: List of provides (``['provides', ...]``) 144 :rtype: ``list`` 145 """ 146 filename = docs.get('meta.provides', 'PROVIDES').strip() 147 if filename and _os.path.isfile(filename): 148 fp = open(filename, encoding='utf-8') 149 try: 150 content = fp.read() 151 finally: 152 fp.close() 153 content = [item.strip() for item in content.splitlines()] 154 return [item for item in content if item and not item.startswith('#')] 155 return [] 156 157 158 def find_license(docs): 159 """ 160 Determine license from LICENSE 161 162 :return: License text 163 :rtype: ``str`` 164 """ 165 filename = docs.get('meta.license', 'LICENSE').strip() 166 if filename and _os.path.isfile(filename): 167 fp = open(filename, encoding='utf-8') 168 try: 169 return fp.read().rstrip() 170 finally: 171 fp.close() 172 return None 173 174 175 def find_packages(manifest): 176 """ Determine packages and subpackages """ 177 packages = {} 178 collect = manifest.get('packages.collect', '').split() 179 lib = manifest.get('packages.lib', '.') 180 try: 181 sep = _os.path.sep 182 except AttributeError: 183 sep = _os.path.join('1', '2')[1:-1] 184 for root in collect: 185 for dirpath, _, filenames in _shell.walk(_os.path.join(lib, root)): 186 if dirpath.find('.svn') >= 0 or dirpath.find('.git') >= 0: 187 continue 188 if '__init__.py' in filenames: 189 packages[ 190 _os.path.normpath(dirpath).replace(sep, '.') 191 ] = None 192 packages = list(packages.keys()) 193 packages.sort() 194 return packages 195 196 197 def find_data(name, docs): 198 """ Determine data files """ 199 result = [] 200 if docs.get('extra', '').strip(): 201 result.append(_data.Documentation(docs['extra'].split(), 202 prefix='share/doc/%s' % name, 203 )) 204 if docs.get('examples.dir', '').strip(): 205 tpl = ['recursive-include %s *' % docs['examples.dir']] 206 if docs.get('examples.ignore', '').strip(): 207 tpl.extend(["global-exclude %s" % item 208 for item in docs['examples.ignore'].split() 209 ]) 210 strip = int(docs.get('examples.strip', '') or 0) 211 result.append(_data.Documentation.from_templates(*tpl, **{ 212 'strip': strip, 213 'prefix': 'share/doc/%s' % name, 214 'preserve': 1, 215 })) 216 if docs.get('userdoc.dir', '').strip(): 217 tpl = ['recursive-include %s *' % docs['userdoc.dir']] 218 if docs.get('userdoc.ignore', '').strip(): 219 tpl.extend(["global-exclude %s" % item 220 for item in docs['userdoc.ignore'].split() 221 ]) 222 strip = int(docs.get('userdoc.strip', '') or 0) 223 result.append(_data.Documentation.from_templates(*tpl, **{ 224 'strip': strip, 225 'prefix': 'share/doc/%s' % name, 226 'preserve': 1, 227 })) 228 if docs.get('apidoc.dir', '').strip(): 229 tpl = ['recursive-include %s *' % docs['apidoc.dir']] 230 if docs.get('apidoc.ignore', '').strip(): 231 tpl.extend(["global-exclude %s" % item 232 for item in docs['apidoc.ignore'].split() 233 ]) 234 strip = int(docs.get('apidoc.strip', '') or 0) 235 result.append(_data.Documentation.from_templates(*tpl, **{ 236 'strip': strip, 237 'prefix': 'share/doc/%s' % name, 238 'preserve': 1, 239 })) 240 if docs.get('man', '').strip(): 241 result.extend(_data.Manpages.dispatch(docs['man'].split())) 242 return result 243 244 245 def make_manifest(manifest, config, docs, kwargs): 246 """ Create file list to pack up """ 247 # pylint: disable = R0912 248 kwargs = kwargs.copy() 249 kwargs['script_args'] = ['install'] 250 kwargs['packages'] = list(kwargs.get('packages') or ()) + [ 251 '_setup', '_setup.py2', '_setup.py3', 252 ] + list(manifest.get('packages.extra', '').split() or ()) 253 _core._setup_stop_after = "commandline" 254 try: 255 dist = _core.setup(**kwargs) 256 finally: 257 _core._setup_stop_after = None 258 259 result = ['MANIFEST', 'PKG-INFO', 'setup.py'] + list(config) 260 # TODO: work with default values: 261 for key in ('classifiers', 'description', 'summary', 'provides', 262 'license'): 263 filename = docs.get('meta.' + key, '').strip() 264 if filename and _os.path.isfile(filename): 265 result.append(filename) 266 267 cmd = dist.get_command_obj("build_py") 268 cmd.ensure_finalized() 269 #from pprint import pprint; pprint(("build_py", cmd.get_source_files())) 270 for item in cmd.get_source_files(): 271 result.append(_posixpath.sep.join( 272 _os.path.normpath(item).split(_os.path.sep) 273 )) 274 275 cmd = dist.get_command_obj("build_ext") 276 cmd.ensure_finalized() 277 #from pprint import pprint; pprint(("build_ext", cmd.get_source_files())) 278 for item in cmd.get_source_files(): 279 result.append(_posixpath.sep.join( 280 _os.path.normpath(item).split(_os.path.sep) 281 )) 282 for ext in cmd.extensions: 283 if ext.depends: 284 result.extend([_posixpath.sep.join( 285 _os.path.normpath(item).split(_os.path.sep) 286 ) for item in ext.depends]) 287 288 cmd = dist.get_command_obj("build_clib") 289 cmd.ensure_finalized() 290 if cmd.libraries: 291 #import pprint; pprint.pprint(("build_clib", cmd.get_source_files())) 292 for item in cmd.get_source_files(): 293 result.append(_posixpath.sep.join( 294 _os.path.normpath(item).split(_os.path.sep) 295 )) 296 for lib in cmd.libraries: 297 if lib[1].get('depends'): 298 result.extend([_posixpath.sep.join( 299 _os.path.normpath(item).split(_os.path.sep) 300 ) for item in lib[1]['depends']]) 301 302 cmd = dist.get_command_obj("build_scripts") 303 cmd.ensure_finalized() 304 #import pprint; pprint.pprint(("build_scripts", cmd.get_source_files())) 305 if cmd.get_source_files(): 306 for item in cmd.get_source_files(): 307 result.append(_posixpath.sep.join( 308 _os.path.normpath(item).split(_os.path.sep) 309 )) 310 311 cmd = dist.get_command_obj("install_data") 312 cmd.ensure_finalized() 313 #from pprint import pprint; pprint(("install_data", cmd.get_inputs())) 314 try: 315 strings = str 316 except NameError: 317 strings = (str, str) 318 319 for item in cmd.get_inputs(): 320 if isinstance(item, strings): 321 result.append(item) 322 else: 323 result.extend(item[1]) 324 325 for item in manifest.get('dist', '').split(): 326 result.append(item) 327 if _os.path.isdir(item): 328 for filename in _shell.files(item): 329 result.append(filename) 330 331 result = list(dict([(item, None) for item in result]).keys()) 332 result.sort() 333 return result 334 335 336 def run(config=('package.cfg',), ext=None, script_args=None, manifest_only=0): 337 """ Main runner """ 338 if ext is None: 339 ext = [] 340 341 cfg = _util.SafeConfigParser() 342 cfg.read(config, encoding='utf-8') 343 pkg = dict(cfg.items('package')) 344 python_min = pkg.get('python.min') or None 345 python_max = pkg.get('python.max') or None 346 check_python_version('python', python_min, python_max) 347 pypy_min = pkg.get('pypy.min') or None 348 pypy_max = pkg.get('pypy.max') or None 349 check_python_version('pypy', pypy_min, pypy_max) 350 jython_min = pkg.get('jython.min') or None 351 jython_max = pkg.get('jython.max') or None 352 check_python_version('jython', jython_min, jython_max) 353 354 manifest = dict(cfg.items('manifest')) 355 try: 356 docs = dict(cfg.items('docs')) 357 except _config_parser.NoSectionError: 358 docs = {} 359 360 summary, description = find_description(docs) 361 scripts = manifest.get('scripts', '').strip() or None 362 if scripts: 363 scripts = scripts.split() 364 modules = manifest.get('modules', '').strip() or None 365 if modules: 366 modules = modules.split() 367 keywords = docs.get('meta.keywords', '').strip() or None 368 if keywords: 369 keywords = keywords.split() 370 revision = pkg.get('version.revision', '').strip() 371 if revision: 372 revision = "-r%s" % (revision,) 373 374 kwargs = { 375 'name': pkg['name'], 376 'version': "%s%s" % ( 377 pkg['version.number'], 378 ["", "-dev%s" % (revision,)][_util.humanbool( 379 'version.dev', pkg.get('version.dev', 'false') 380 )], 381 ), 382 'provides': find_provides(docs), 383 'description': summary, 384 'long_description': description, 385 'classifiers': find_classifiers(docs), 386 'keywords': keywords, 387 'author': pkg['author.name'], 388 'author_email': pkg['author.email'], 389 'maintainer': pkg.get('maintainer.name'), 390 'maintainer_email': pkg.get('maintainer.email'), 391 'url': pkg.get('url.homepage'), 392 'download_url': pkg.get('url.download'), 393 'license': find_license(docs), 394 'package_dir': {'': manifest.get('packages.lib', '.')}, 395 'packages': find_packages(manifest), 396 'py_modules': modules, 397 'ext_modules': ext, 398 'scripts': scripts, 399 'script_args': script_args, 400 'data_files': find_data(pkg['name'], docs), 401 'cmdclass': { 402 'build' : _commands.Build, 403 'build_ext' : _commands.BuildExt, 404 'install' : _commands.Install, 405 'install_data': _commands.InstallData, 406 'install_lib' : _commands.InstallLib, 407 } 408 } 409 for key in ('provides',): 410 if key not in _core.setup_keywords: 411 del kwargs[key] 412 413 if manifest_only: 414 return make_manifest(manifest, config, docs, kwargs) 415 416 # monkey-patch crappy manifest writer away. 417 from distutils.command import sdist 418 sdist.sdist.get_file_list = sdist.sdist.read_manifest 419 420 return _core.setup(**kwargs) 421