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