1from __future__ import with_statement 2 3import datetime 4import glob 5import os 6import string 7import sys 8 9from distutils import log 10 11 12try: 13 from packaging.util import split_multiline 14except ImportError: 15 try: 16 from distutils2.util import split_multiline 17 except ImportError: 18 from d2to1.util import split_multiline 19 20 21try: 22 # python 2 23 reload 24except NameError: 25 # python 3 26 from imp import reload 27 28from .svnutils import get_svn_info, get_svn_version 29from .versionutils import (package_uses_version_py, clean_version_py, 30 update_setup_datetime, VERSION_PY_TEMPLATE) 31 32# For each version.py that we create, we will note the version of 33# stsci.distutils (this package) that created it. The problem is 34# when the package being installed is stsci.distutils -- we don't 35# know the version yet. So, if we can't import the version (because 36# it does not exist yet), we declare it to be None and special case 37# it later. 38try : 39 from . import version as my_version 40except ImportError : 41 my_version = None 42 43 44def is_display_option(ignore=None): 45 """A hack to test if one of the arguments passed to setup.py is a display 46 argument that should just display a value and exit. If so, don't bother 47 running this hook (this capability really ought to be included with 48 distutils2). 49 50 Optionally, ignore may contain a list of display options to ignore in this 51 check. Each option in the ignore list must contain the correct number of 52 dashes. 53 """ 54 55 from setuptools.dist import Distribution 56 57 # If there were no arguments in argv (aside from the script name) then this 58 # is an implied display opt 59 if len(sys.argv) < 2: 60 return True 61 62 display_opts = ['--command-packages', '--help', '-h'] 63 64 for opt in Distribution.display_options: 65 display_opts.append('--' + opt[0]) 66 67 for arg in sys.argv: 68 if arg in display_opts and arg not in ignore: 69 return True 70 71 return False 72 73 74# TODO: With luck this can go away soon--packaging now supports adding the cwd 75# to sys.path for running setup_hooks. But it also needs to support adding 76# packages_root. Also, it currently does not support adding cwd/packages_root 77# to sys.path for pre/post-command hooks, so that needs to be fixed. 78def use_packages_root(config): 79 """ 80 Adds the path specified by the 'packages_root' option, or the current path 81 if 'packages_root' is not specified, to sys.path. This is particularly 82 useful, for example, to run setup_hooks or add custom commands that are in 83 your package's source tree. 84 85 Use this when the root of your package source tree is not in 86 the same directory with the setup.py 87 88 Config Usage:: 89 90 [files] 91 packages_root = lib 92 # for installing pkgname from lib/pkgname/*.py 93 94 [global] 95 setup_hooks = stsci.distutils.hooks.use_packages_root 96 97 """ 98 99 if 'files' in config and 'packages_root' in config['files']: 100 root = config['files']['packages_root'] 101 else: 102 root = '' 103 104 if root not in sys.path: 105 if root and sys.path[0] == '': 106 sys.path.insert(1, root) 107 else: 108 sys.path.insert(0, root) 109 110 # Reload the stsci namespace package in case any new paths can be added to 111 # it from the new sys.path entry; depending on how the namespace packages 112 # are installed this may fail--specifically if it's using the old 113 # setuptools-based .pth approach there is not importable package called 114 # 'stsci' that can be imported by itself. 115 if 'stsci' in sys.modules: 116 mod = sys.modules['stsci'] 117 if sys.version_info[:2] >= (3, 3) and not hasattr(mod, '__loader__'): 118 # Workaround for Python bug #17099 on Python 3.3, where reload() 119 # crashes if a module doesn't have an __loader__ attribute 120 del sys.modules['stsci'] 121 try: 122 import stsci 123 except ImportError: 124 pass 125 else: 126 try : 127 reload(sys.modules['stsci']) 128 except ImportError: 129 # doesn't seem to bother anything when this reload() fails 130 pass 131 132 133def tag_svn_revision(config): 134 """ 135 A setup_hook to add the SVN revision of the current working copy path to 136 the package version string, but only if the version ends in .dev. 137 138 For example, ``mypackage-1.0.dev`` becomes ``mypackage-1.0.dev1234``. This 139 is in accordance with the version string format standardized by PEP 386. 140 141 This should be used as a replacement for the ``tag_svn_revision`` option to 142 the egg_info command. This hook is more compatible with 143 packaging/distutils2, which does not include any VCS support. This hook is 144 also more flexible in that it turns the revision number on/off depending on 145 the presence of ``.dev`` in the version string, so that it's not 146 automatically added to the version in final releases. 147 148 This hook does require the ``svnversion`` command to be available in order 149 to work. It does not examine the working copy metadata directly. 150 151 152 Config Usage:: 153 154 [global] 155 setup_hooks = stsci.distutils.hooks.tag_svn_revision 156 157 You should write exactly this in your package's ``__init__.py``:: 158 159 from .version import * 160 161 """ 162 163 if 'metadata' in config and 'version' in config['metadata']: 164 metadata = config['metadata'] 165 version = metadata['version'] 166 167 # Don't add an svn revision unless the version ends with .dev 168 if not version.endswith('.dev'): 169 return 170 171 # First try to get the revision by checking for it in an existing 172 # .version module 173 package_dir = config.get('files', {}).get('packages_root', '') 174 packages = config.get('files', {}).get('packages', '') 175 packages = split_multiline(packages) 176 rev = None 177 for package in packages: 178 version_py = package_uses_version_py(package_dir, package) 179 if not version_py: 180 continue 181 try: 182 mod = __import__(package + '.version', 183 fromlist='__svn_revision__') 184 except ImportError: 185 mod = None 186 if mod is not None and hasattr(mod, '__svn_revision__'): 187 rev = mod.__svn_revision__ 188 break 189 190 # Cleanup 191 names = set([package, package + '.']) 192 for modname in list(sys.modules): 193 if modname == package or modname.startswith(package + '.'): 194 del sys.modules[modname] 195 196 if rev is None: 197 # A .version module didn't exist or was incomplete; try calling 198 # svnversion directly 199 rev = get_svn_version() 200 201 if not rev: 202 return 203 if ':' in rev: 204 rev, _ = rev.split(':', 1) 205 while rev and rev[-1] not in string.digits: 206 rev = rev[:-1] 207 try: 208 rev = int(rev) 209 except (ValueError, TypeError): 210 return 211 212 metadata['version'] ='%s%d' % (version, rev) 213 214 215def _version_hook(function_name, package_dir, packages, name, version, vdate): 216 """This command hook creates an version.py file in each package that 217 requires it. This is by determining if the package's ``__init__.py`` tries 218 to import or import from the version module. 219 220 version.py will not be created in packages that don't use it. It should 221 only be used by the top-level package of the project. 222 223 Don't use this function directly--instead use :func:`version_setup_hook` or 224 :func:`version_pre_command_hook` which know how to retrieve the required 225 metadata depending on the context they are run in. 226 227 Not called directly from the config file. See :func:`version_setup_hook`. 228 229 """ 230 231 # Strip any revision info from version; that will be handled separately 232 if '-' in version: 233 version = version.split('-', 1)[0] 234 235 for package in packages: 236 version_py = package_uses_version_py(package_dir, package) 237 if not version_py: 238 continue 239 240 rev = get_svn_version() 241 if ((not rev or not rev[0] in string.digits) and 242 os.path.exists(version_py)): 243 # If were unable to determine an SVN revision and the version.py 244 # already exists, just update the __setup_datetime__ and leave the 245 # rest of the file untouched 246 update_setup_datetime(version_py) 247 return 248 elif rev is None: 249 rev = 'Unable to determine SVN revision' 250 251 svn_info = get_svn_info() 252 253 # Wrap version, rev, and svn_info in str() to ensure that Python 2 254 # unicode literals aren't written, which will break things in Python 3 255 template_variables = { 256 'hook_function': function_name, 257 'name': name, 258 'version': str(version), 259 'vdate': str(vdate), 260 'svn_revision': str(rev), 261 'svn_full_info': str(svn_info), 262 'setup_datetime': datetime.datetime.now(), 263 } 264 265 # my_version is version.py for the stsci.distutils package. 266 # It can be None if we are called during the install of 267 # stsci.distutils; we are creating the version.py, so it was 268 # not available to import yet. If this is what is happening, 269 # we construct it specially. 270 if my_version is None : 271 if package == 'stsci.distutils' : 272 template_variables['stsci_distutils_version'] = version 273 else: 274 # It should never happen that version.py does not 275 # exist when we are installing any other package. 276 raise RuntimeError('Internal consistency error') 277 else : 278 template_variables['stsci_distutils_version'] = \ 279 my_version.__version__ 280 281 with open(version_py, 'w') as f: 282 f.write(VERSION_PY_TEMPLATE % template_variables) 283 284 285def version_setup_hook(config): 286 """Creates a Python module called version.py which contains these variables: 287 288 * ``__version__`` (the release version) 289 * ``__svn_revision__`` (the SVN revision info as returned by the ``svnversion`` 290 command) 291 * ``__svn_full_info__`` (as returned by the ``svn info`` command) 292 * ``__setup_datetime__`` (the date and time that setup.py was last run). 293 * ``__vdate__`` (the release date) 294 295 These variables can be imported in the package's ``__init__.py`` for 296 degugging purposes. The version.py module will *only* be created in a 297 package that imports from the version module in its ``__init__.py``. It 298 should be noted that this is generally preferable to writing these 299 variables directly into ``__init__.py``, since this provides more control 300 and is less likely to unexpectedly break things in ``__init__.py``. 301 302 Config Usage:: 303 304 [global] 305 setup-hooks = stsci.distutils.hooks.version_setup_hook 306 307 You should write exactly this in your package's ``__init__.py``:: 308 309 from .version import * 310 311 """ 312 313 if is_display_option(ignore=['--version']): 314 return 315 316 name = config['metadata'].get('name') 317 version = config['metadata'].get('version', '0.0.0') 318 vdate = config['metadata'].get('vdate', 'unspecified') 319 package_dir = config.get('files', {}).get('packages_root', '') 320 packages = config.get('files', {}).get('packages', '') 321 322 packages = split_multiline(packages) 323 324 _version_hook(__name__ + '.version_setup_hook', package_dir, packages, 325 name, version, vdate) 326 327 328def version_pre_command_hook(command_obj): 329 """ 330 .. deprecated:: 0.3 331 Use :func:`version_setup_hook` instead; it's generally safer to 332 check/update the version.py module on every setup.py run instead of on 333 specific commands. 334 335 This command hook creates an version.py file in each package that requires 336 it. This is by determining if the package's ``__init__.py`` tries to 337 import or import from the version module. 338 339 version.py will not be created in packages that don't use it. It should 340 only be used by the top-level package of the project. 341 """ 342 343 if is_display_option(): 344 return 345 346 package_dir = command_obj.distribution.package_dir.get('', '.') 347 packages = command_obj.distribution.packages 348 name = command_obj.distribution.metadata.name 349 version = command_obj.distribution.metadata.version 350 351 _version_hook(__name__ + '.version_pre_command_hook',package_dir, packages, 352 name, version, vdate=None) 353 354 355def version_post_command_hook(command_obj): 356 """ 357 .. deprecated:: 0.3 358 This hook was meant to complement :func:`version_pre_command_hook`, 359 also deprecated. 360 361 Cleans up a previously generated version.py in order to avoid 362 clutter. 363 364 Only removes the file if we're in an SVN working copy and the file is not 365 already under version control. 366 """ 367 368 package_dir = command_obj.distribution.package_dir.get('', '.') 369 packages = command_obj.distribution.packages 370 371 for package in packages: 372 clean_version_py(package_dir, package) 373 374 375def numpy_extension_hook(command_obj): 376 """A distutils2 pre-command hook for the build_ext command needed for 377 building extension modules that use NumPy. 378 379 To use this hook, add 'numpy' to the list of include_dirs in setup.cfg 380 section for an extension module. This hook will replace 'numpy' with the 381 necessary numpy header paths in the include_dirs option for that extension. 382 383 Note: Although this function uses numpy, stsci.distutils does not depend on 384 numpy. It is up to the distribution that uses this hook to require numpy 385 as a dependency. 386 387 Config Usage:: 388 389 [extension=mypackage.extmod] 390 sources = 391 foo.c 392 bar.c 393 include_dirs = numpy 394 395 [build_ext] 396 pre-hook.numpy-extension = stsci.distutils.hooks.numpy_extension_hook 397 """ 398 399 command_name = command_obj.get_command_name() 400 if command_name != 'build_ext': 401 log.warn('%s is meant to be used with the build_ext command only; ' 402 'it is not for use with the %s command.' % 403 (__name__, command_name)) 404 try: 405 import numpy 406 except ImportError: 407 # It's virtually impossible to automatically install numpy through 408 # setuptools; I've tried. It's not pretty. 409 # Besides, we don't want users complaining that our software doesn't 410 # work just because numpy didn't build on their system. 411 sys.stderr.write('\n\nNumpy is required to build this package.\n' 412 'Please install Numpy on your system first.\n\n') 413 sys.exit(1) 414 415 includes = [numpy.get_include()] 416 #includes = [numpy.get_numarray_include(), numpy.get_include()] 417 for extension in command_obj.extensions: 418 if 'numpy' not in extension.include_dirs: 419 continue 420 idx = extension.include_dirs.index('numpy') 421 for inc in includes: 422 extension.include_dirs.insert(idx, inc) 423 extension.include_dirs.remove('numpy') 424 425 426def glob_data_files(command_obj): 427 """A pre-command hook for the install_data command allowing wildcard 428 patterns to be used in the data_files option. 429 430 Also ensures that data files with relative paths as their targets are 431 installed relative install_lib. 432 433 Config Usage:: 434 435 [files] 436 data_files = 437 target_directory = source_directory/*.foo 438 other_target_directory = other_source_directory/* 439 440 [install_data] 441 pre-hook.glob-data-files = stsci.distutils.hooks.glob_data_files 442 443 444 """ 445 446 command_name = command_obj.get_command_name() 447 if command_name != 'install_data': 448 log.warn('%s is meant to be used with the install_data command only; ' 449 'it is not for use with the %s command.' % 450 (__name__, command_name)) 451 452 data_files = command_obj.data_files 453 454 for idx, val in enumerate(data_files[:]): 455 if isinstance(val, basestring): 456 # Support the rare, deprecated case where just a filename is given 457 filenames = glob.glob(val) 458 del data_files[idx] 459 data_files.extend(filenames) 460 continue 461 462 dest, filenames = val 463 filenames = sum((glob.glob(item) for item in filenames), []) 464 data_files[idx] = (dest, filenames) 465 466 # Ensure the correct install dir; this is the default behavior for 467 # installing with distribute, but when using 468 # --single-version-externally-managed we need to to tweak this 469 install_cmd = command_obj.get_finalized_command('install') 470 if command_obj.install_dir == install_cmd.install_data: 471 install_lib_cmd = command_obj.get_finalized_command('install_lib') 472 command_obj.install_dir = install_lib_cmd.install_dir 473