1# Licensed under a 3-clause BSD style license - see LICENSE.rst 2""" 3This module contains a number of utilities for use during 4setup/build/packaging that are useful to astropy as a whole. 5""" 6 7import collections 8import os 9import re 10import subprocess 11import sys 12import traceback 13import warnings 14from configparser import ConfigParser 15import builtins 16 17from distutils import log 18from distutils.errors import DistutilsOptionError, DistutilsModuleError 19from distutils.core import Extension 20from distutils.core import Command 21from distutils.command.sdist import sdist as DistutilsSdist 22 23from setuptools import setup as setuptools_setup 24from setuptools.config import read_configuration 25from setuptools import find_packages as _find_packages 26 27from .distutils_helpers import (add_command_option, get_compiler_option, 28 get_dummy_distribution, get_distutils_build_option, 29 get_distutils_build_or_install_option) 30from .version_helpers import get_pkg_version_module, generate_version_py 31from .utils import (walk_skip_hidden, import_file, extends_doc, 32 resolve_name, AstropyDeprecationWarning) 33 34from .commands.build_ext import AstropyHelpersBuildExt 35from .commands.test import AstropyTest 36 37# These imports are not used in this module, but are included for backwards 38# compat with older versions of this module 39from .utils import get_numpy_include_path, write_if_different # noqa 40 41__all__ = ['register_commands', 'get_package_info'] 42 43_module_state = {'registered_commands': None, 44 'have_sphinx': False, 45 'package_cache': None, 46 'exclude_packages': set(), 47 'excludes_too_late': False} 48 49try: 50 import sphinx # noqa 51 _module_state['have_sphinx'] = True 52except ValueError as e: 53 # This can occur deep in the bowels of Sphinx's imports by way of docutils 54 # and an occurrence of this bug: http://bugs.python.org/issue18378 55 # In this case sphinx is effectively unusable 56 if 'unknown locale' in e.args[0]: 57 log.warn( 58 "Possible misconfiguration of one of the environment variables " 59 "LC_ALL, LC_CTYPES, LANG, or LANGUAGE. For an example of how to " 60 "configure your system's language environment on OSX see " 61 "http://blog.remibergsma.com/2012/07/10/" 62 "setting-locales-correctly-on-mac-osx-terminal-application/") 63except ImportError: 64 pass 65except SyntaxError: 66 # occurs if markupsafe is recent version, which doesn't support Python 3.2 67 pass 68 69 70def setup(**kwargs): 71 """ 72 A wrapper around setuptools' setup() function that automatically sets up 73 custom commands, generates a version file, and customizes the setup process 74 via the ``setup_package.py`` files. 75 """ 76 77 # DEPRECATED: store the package name in a built-in variable so it's easy 78 # to get from other parts of the setup infrastructure. We should phase this 79 # out in packages that use it - the cookiecutter template should now be 80 # able to put the right package name where needed. 81 conf = read_configuration('setup.cfg') 82 builtins._ASTROPY_PACKAGE_NAME_ = conf['metadata']['name'] 83 84 # Create a dictionary with setup command overrides. Note that this gets 85 # information about the package (name and version) from the setup.cfg file. 86 cmdclass = register_commands() 87 88 # Freeze build information in version.py. Note that this gets information 89 # about the package (name and version) from the setup.cfg file. 90 version = generate_version_py() 91 92 # Get configuration information from all of the various subpackages. 93 # See the docstring for setup_helpers.update_package_files for more 94 # details. 95 package_info = get_package_info() 96 package_info['cmdclass'] = cmdclass 97 package_info['version'] = version 98 99 # Override using any specified keyword arguments 100 package_info.update(kwargs) 101 102 setuptools_setup(**package_info) 103 104 105def adjust_compiler(package): 106 warnings.warn( 107 'The adjust_compiler function in setup.py is ' 108 'deprecated and can be removed from your setup.py.', 109 AstropyDeprecationWarning) 110 111 112def get_debug_option(packagename): 113 """ Determines if the build is in debug mode. 114 115 Returns 116 ------- 117 debug : bool 118 True if the current build was started with the debug option, False 119 otherwise. 120 121 """ 122 123 try: 124 current_debug = get_pkg_version_module(packagename, 125 fromlist=['debug'])[0] 126 except (ImportError, AttributeError): 127 current_debug = None 128 129 # Only modify the debug flag if one of the build commands was explicitly 130 # run (i.e. not as a sub-command of something else) 131 dist = get_dummy_distribution() 132 if any(cmd in dist.commands for cmd in ['build', 'build_ext']): 133 debug = bool(get_distutils_build_option('debug')) 134 else: 135 debug = bool(current_debug) 136 137 if current_debug is not None and current_debug != debug: 138 build_ext_cmd = dist.get_command_class('build_ext') 139 build_ext_cmd._force_rebuild = True 140 141 return debug 142 143 144def add_exclude_packages(excludes): 145 146 if _module_state['excludes_too_late']: 147 raise RuntimeError( 148 "add_package_excludes must be called before all other setup helper " 149 "functions in order to properly handle excluded packages") 150 151 _module_state['exclude_packages'].update(set(excludes)) 152 153 154def register_commands(package=None, version=None, release=None, srcdir='.'): 155 """ 156 This function generates a dictionary containing customized commands that 157 can then be passed to the ``cmdclass`` argument in ``setup()``. 158 """ 159 160 if package is not None: 161 warnings.warn('The package argument to generate_version_py has ' 162 'been deprecated and will be removed in future. Specify ' 163 'the package name in setup.cfg instead', AstropyDeprecationWarning) 164 165 if version is not None: 166 warnings.warn('The version argument to generate_version_py has ' 167 'been deprecated and will be removed in future. Specify ' 168 'the version number in setup.cfg instead', AstropyDeprecationWarning) 169 170 if release is not None: 171 warnings.warn('The release argument to generate_version_py has ' 172 'been deprecated and will be removed in future. We now ' 173 'use the presence of the "dev" string in the version to ' 174 'determine whether this is a release', AstropyDeprecationWarning) 175 176 # We use ConfigParser instead of read_configuration here because the latter 177 # only reads in keys recognized by setuptools, but we need to access 178 # package_name below. 179 conf = ConfigParser() 180 conf.read('setup.cfg') 181 182 if conf.has_option('metadata', 'name'): 183 package = conf.get('metadata', 'name') 184 elif conf.has_option('metadata', 'package_name'): 185 # The package-template used package_name instead of name for a while 186 warnings.warn('Specifying the package name using the "package_name" ' 187 'option in setup.cfg is deprecated - use the "name" ' 188 'option instead.', AstropyDeprecationWarning) 189 package = conf.get('metadata', 'package_name') 190 elif package is not None: # deprecated 191 pass 192 else: 193 sys.stderr.write('ERROR: Could not read package name from setup.cfg\n') 194 sys.exit(1) 195 196 if _module_state['registered_commands'] is not None: 197 return _module_state['registered_commands'] 198 199 if _module_state['have_sphinx']: 200 try: 201 from .commands.build_sphinx import (AstropyBuildSphinx, 202 AstropyBuildDocs) 203 except ImportError: 204 AstropyBuildSphinx = AstropyBuildDocs = FakeBuildSphinx 205 else: 206 AstropyBuildSphinx = AstropyBuildDocs = FakeBuildSphinx 207 208 _module_state['registered_commands'] = registered_commands = { 209 'test': generate_test_command(package), 210 211 # Use distutils' sdist because it respects package_data. 212 # setuptools/distributes sdist requires duplication of information in 213 # MANIFEST.in 214 'sdist': DistutilsSdist, 215 216 'build_ext': AstropyHelpersBuildExt, 217 'build_sphinx': AstropyBuildSphinx, 218 'build_docs': AstropyBuildDocs 219 } 220 221 # Need to override the __name__ here so that the commandline options are 222 # presented as being related to the "build" command, for example; normally 223 # this wouldn't be necessary since commands also have a command_name 224 # attribute, but there is a bug in distutils' help display code that it 225 # uses __name__ instead of command_name. Yay distutils! 226 for name, cls in registered_commands.items(): 227 cls.__name__ = name 228 229 # Add a few custom options; more of these can be added by specific packages 230 # later 231 for option in [ 232 ('use-system-libraries', 233 "Use system libraries whenever possible", True)]: 234 add_command_option('build', *option) 235 add_command_option('install', *option) 236 237 add_command_hooks(registered_commands, srcdir=srcdir) 238 239 return registered_commands 240 241 242def add_command_hooks(commands, srcdir='.'): 243 """ 244 Look through setup_package.py modules for functions with names like 245 ``pre_<command_name>_hook`` and ``post_<command_name>_hook`` where 246 ``<command_name>`` is the name of a ``setup.py`` command (e.g. build_ext). 247 248 If either hook is present this adds a wrapped version of that command to 249 the passed in ``commands`` `dict`. ``commands`` may be pre-populated with 250 other custom distutils command classes that should be wrapped if there are 251 hooks for them (e.g. `AstropyBuildPy`). 252 """ 253 254 hook_re = re.compile(r'^(pre|post)_(.+)_hook$') 255 256 # Distutils commands have a method of the same name, but it is not a 257 # *classmethod* (which probably didn't exist when distutils was first 258 # written) 259 def get_command_name(cmdcls): 260 if hasattr(cmdcls, 'command_name'): 261 return cmdcls.command_name 262 else: 263 return cmdcls.__name__ 264 265 packages = find_packages(srcdir) 266 dist = get_dummy_distribution() 267 268 hooks = collections.defaultdict(dict) 269 270 for setuppkg in iter_setup_packages(srcdir, packages): 271 for name, obj in vars(setuppkg).items(): 272 match = hook_re.match(name) 273 if not match: 274 continue 275 276 hook_type = match.group(1) 277 cmd_name = match.group(2) 278 279 if hook_type not in hooks[cmd_name]: 280 hooks[cmd_name][hook_type] = [] 281 282 hooks[cmd_name][hook_type].append((setuppkg.__name__, obj)) 283 284 for cmd_name, cmd_hooks in hooks.items(): 285 commands[cmd_name] = generate_hooked_command( 286 cmd_name, dist.get_command_class(cmd_name), cmd_hooks) 287 288 289def generate_hooked_command(cmd_name, cmd_cls, hooks): 290 """ 291 Returns a generated subclass of ``cmd_cls`` that runs the pre- and 292 post-command hooks for that command before and after the ``cmd_cls.run`` 293 method. 294 """ 295 296 def run(self, orig_run=cmd_cls.run): 297 self.run_command_hooks('pre_hooks') 298 orig_run(self) 299 self.run_command_hooks('post_hooks') 300 301 return type(cmd_name, (cmd_cls, object), 302 {'run': run, 'run_command_hooks': run_command_hooks, 303 'pre_hooks': hooks.get('pre', []), 304 'post_hooks': hooks.get('post', [])}) 305 306 307def run_command_hooks(cmd_obj, hook_kind): 308 """Run hooks registered for that command and phase. 309 310 *cmd_obj* is a finalized command object; *hook_kind* is either 311 'pre_hook' or 'post_hook'. 312 """ 313 314 hooks = getattr(cmd_obj, hook_kind, None) 315 316 if not hooks: 317 return 318 319 for modname, hook in hooks: 320 if isinstance(hook, str): 321 try: 322 hook_obj = resolve_name(hook) 323 except ImportError as exc: 324 raise DistutilsModuleError( 325 'cannot find hook {0}: {1}'.format(hook, exc)) 326 else: 327 hook_obj = hook 328 329 if not callable(hook_obj): 330 raise DistutilsOptionError('hook {0!r} is not callable' % hook) 331 332 log.info('running {0} from {1} for {2} command'.format( 333 hook_kind.rstrip('s'), modname, cmd_obj.get_command_name())) 334 335 try: 336 hook_obj(cmd_obj) 337 except Exception: 338 log.error('{0} command hook {1} raised an exception: %s\n'.format( 339 hook_obj.__name__, cmd_obj.get_command_name())) 340 log.error(traceback.format_exc()) 341 sys.exit(1) 342 343 344def generate_test_command(package_name): 345 """ 346 Creates a custom 'test' command for the given package which sets the 347 command's ``package_name`` class attribute to the name of the package being 348 tested. 349 """ 350 351 return type(package_name.title() + 'Test', (AstropyTest,), 352 {'package_name': package_name}) 353 354 355def update_package_files(srcdir, extensions, package_data, packagenames, 356 package_dirs): 357 """ 358 This function is deprecated and maintained for backward compatibility 359 with affiliated packages. Affiliated packages should update their 360 setup.py to use `get_package_info` instead. 361 """ 362 363 info = get_package_info(srcdir) 364 extensions.extend(info['ext_modules']) 365 package_data.update(info['package_data']) 366 packagenames = list(set(packagenames + info['packages'])) 367 package_dirs.update(info['package_dir']) 368 369 370def get_package_info(srcdir='.', exclude=()): 371 """ 372 Collates all of the information for building all subpackages 373 and returns a dictionary of keyword arguments that can 374 be passed directly to `distutils.setup`. 375 376 The purpose of this function is to allow subpackages to update the 377 arguments to the package's ``setup()`` function in its setup.py 378 script, rather than having to specify all extensions/package data 379 directly in the ``setup.py``. See Astropy's own 380 ``setup.py`` for example usage and the Astropy development docs 381 for more details. 382 383 This function obtains that information by iterating through all 384 packages in ``srcdir`` and locating a ``setup_package.py`` module. 385 This module can contain the following functions: 386 ``get_extensions()``, ``get_package_data()``, 387 ``get_build_options()``, and ``get_external_libraries()``. 388 389 Each of those functions take no arguments. 390 391 - ``get_extensions`` returns a list of 392 `distutils.extension.Extension` objects. 393 394 - ``get_package_data()`` returns a dict formatted as required by 395 the ``package_data`` argument to ``setup()``. 396 397 - ``get_build_options()`` returns a list of tuples describing the 398 extra build options to add. 399 400 - ``get_external_libraries()`` returns 401 a list of libraries that can optionally be built using external 402 dependencies. 403 """ 404 ext_modules = [] 405 packages = [] 406 package_dir = {} 407 408 # Read in existing package data, and add to it below 409 setup_cfg = os.path.join(srcdir, 'setup.cfg') 410 if os.path.exists(setup_cfg): 411 conf = read_configuration(setup_cfg) 412 if 'options' in conf and 'package_data' in conf['options']: 413 package_data = conf['options']['package_data'] 414 else: 415 package_data = {} 416 else: 417 package_data = {} 418 419 if exclude: 420 warnings.warn( 421 "Use of the exclude parameter is no longer supported since it does " 422 "not work as expected. Use add_exclude_packages instead. Note that " 423 "it must be called prior to any other calls from setup helpers.", 424 AstropyDeprecationWarning) 425 426 # Use the find_packages tool to locate all packages and modules 427 packages = find_packages(srcdir, exclude=exclude) 428 429 # Update package_dir if the package lies in a subdirectory 430 if srcdir != '.': 431 package_dir[''] = srcdir 432 433 # For each of the setup_package.py modules, extract any 434 # information that is needed to install them. The build options 435 # are extracted first, so that their values will be available in 436 # subsequent calls to `get_extensions`, etc. 437 for setuppkg in iter_setup_packages(srcdir, packages): 438 if hasattr(setuppkg, 'get_build_options'): 439 options = setuppkg.get_build_options() 440 for option in options: 441 add_command_option('build', *option) 442 if hasattr(setuppkg, 'get_external_libraries'): 443 libraries = setuppkg.get_external_libraries() 444 for library in libraries: 445 add_external_library(library) 446 447 for setuppkg in iter_setup_packages(srcdir, packages): 448 # get_extensions must include any Cython extensions by their .pyx 449 # filename. 450 if hasattr(setuppkg, 'get_extensions'): 451 ext_modules.extend(setuppkg.get_extensions()) 452 if hasattr(setuppkg, 'get_package_data'): 453 package_data.update(setuppkg.get_package_data()) 454 455 # Locate any .pyx files not already specified, and add their extensions in. 456 # The default include dirs include numpy to facilitate numerical work. 457 ext_modules.extend(get_cython_extensions(srcdir, packages, ext_modules, 458 ['numpy'])) 459 460 # Now remove extensions that have the special name 'skip_cython', as they 461 # exist Only to indicate that the cython extensions shouldn't be built 462 for i, ext in reversed(list(enumerate(ext_modules))): 463 if ext.name == 'skip_cython': 464 del ext_modules[i] 465 466 # On Microsoft compilers, we need to pass the '/MANIFEST' 467 # commandline argument. This was the default on MSVC 9.0, but is 468 # now required on MSVC 10.0, but it doesn't seem to hurt to add 469 # it unconditionally. 470 if get_compiler_option() == 'msvc': 471 for ext in ext_modules: 472 ext.extra_link_args.append('/MANIFEST') 473 474 return { 475 'ext_modules': ext_modules, 476 'packages': packages, 477 'package_dir': package_dir, 478 'package_data': package_data, 479 } 480 481 482def iter_setup_packages(srcdir, packages): 483 """ A generator that finds and imports all of the ``setup_package.py`` 484 modules in the source packages. 485 486 Returns 487 ------- 488 modgen : generator 489 A generator that yields (modname, mod), where `mod` is the module and 490 `modname` is the module name for the ``setup_package.py`` modules. 491 492 """ 493 494 for packagename in packages: 495 package_parts = packagename.split('.') 496 package_path = os.path.join(srcdir, *package_parts) 497 setup_package = os.path.relpath( 498 os.path.join(package_path, 'setup_package.py')) 499 500 if os.path.isfile(setup_package): 501 module = import_file(setup_package, 502 name=packagename + '.setup_package') 503 yield module 504 505 506def iter_pyx_files(package_dir, package_name): 507 """ 508 A generator that yields Cython source files (ending in '.pyx') in the 509 source packages. 510 511 Returns 512 ------- 513 pyxgen : generator 514 A generator that yields (extmod, fullfn) where `extmod` is the 515 full name of the module that the .pyx file would live in based 516 on the source directory structure, and `fullfn` is the path to 517 the .pyx file. 518 """ 519 for dirpath, dirnames, filenames in walk_skip_hidden(package_dir): 520 for fn in filenames: 521 if fn.endswith('.pyx'): 522 fullfn = os.path.relpath(os.path.join(dirpath, fn)) 523 # Package must match file name 524 extmod = '.'.join([package_name, fn[:-4]]) 525 yield (extmod, fullfn) 526 527 break # Don't recurse into subdirectories 528 529 530def get_cython_extensions(srcdir, packages, prevextensions=tuple(), 531 extincludedirs=None): 532 """ 533 Looks for Cython files and generates Extensions if needed. 534 535 Parameters 536 ---------- 537 srcdir : str 538 Path to the root of the source directory to search. 539 prevextensions : list of `~distutils.core.Extension` objects 540 The extensions that are already defined. Any .pyx files already here 541 will be ignored. 542 extincludedirs : list of str or None 543 Directories to include as the `include_dirs` argument to the generated 544 `~distutils.core.Extension` objects. 545 546 Returns 547 ------- 548 exts : list of `~distutils.core.Extension` objects 549 The new extensions that are needed to compile all .pyx files (does not 550 include any already in `prevextensions`). 551 """ 552 553 # Vanilla setuptools and old versions of distribute include Cython files 554 # as .c files in the sources, not .pyx, so we cannot simply look for 555 # existing .pyx sources in the previous sources, but we should also check 556 # for .c files with the same remaining filename. So we look for .pyx and 557 # .c files, and we strip the extension. 558 prevsourcepaths = [] 559 ext_modules = [] 560 561 for ext in prevextensions: 562 for s in ext.sources: 563 if s.endswith(('.pyx', '.c', '.cpp')): 564 sourcepath = os.path.realpath(os.path.splitext(s)[0]) 565 prevsourcepaths.append(sourcepath) 566 567 for package_name in packages: 568 package_parts = package_name.split('.') 569 package_path = os.path.join(srcdir, *package_parts) 570 571 for extmod, pyxfn in iter_pyx_files(package_path, package_name): 572 sourcepath = os.path.realpath(os.path.splitext(pyxfn)[0]) 573 if sourcepath not in prevsourcepaths: 574 ext_modules.append(Extension(extmod, [pyxfn], 575 include_dirs=extincludedirs)) 576 577 return ext_modules 578 579 580class DistutilsExtensionArgs(collections.defaultdict): 581 """ 582 A special dictionary whose default values are the empty list. 583 584 This is useful for building up a set of arguments for 585 `distutils.Extension` without worrying whether the entry is 586 already present. 587 """ 588 def __init__(self, *args, **kwargs): 589 def default_factory(): 590 return [] 591 592 super(DistutilsExtensionArgs, self).__init__( 593 default_factory, *args, **kwargs) 594 595 def update(self, other): 596 for key, val in other.items(): 597 self[key].extend(val) 598 599 600def pkg_config(packages, default_libraries, executable='pkg-config'): 601 """ 602 Uses pkg-config to update a set of distutils Extension arguments 603 to include the flags necessary to link against the given packages. 604 605 If the pkg-config lookup fails, default_libraries is applied to 606 libraries. 607 608 Parameters 609 ---------- 610 packages : list of str 611 A list of pkg-config packages to look up. 612 613 default_libraries : list of str 614 A list of library names to use if the pkg-config lookup fails. 615 616 Returns 617 ------- 618 config : dict 619 A dictionary containing keyword arguments to 620 `distutils.Extension`. These entries include: 621 622 - ``include_dirs``: A list of include directories 623 - ``library_dirs``: A list of library directories 624 - ``libraries``: A list of libraries 625 - ``define_macros``: A list of macro defines 626 - ``undef_macros``: A list of macros to undefine 627 - ``extra_compile_args``: A list of extra arguments to pass to 628 the compiler 629 """ 630 631 flag_map = {'-I': 'include_dirs', '-L': 'library_dirs', '-l': 'libraries', 632 '-D': 'define_macros', '-U': 'undef_macros'} 633 command = "{0} --libs --cflags {1}".format(executable, ' '.join(packages)), 634 635 result = DistutilsExtensionArgs() 636 637 try: 638 pipe = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) 639 output = pipe.communicate()[0].strip() 640 except subprocess.CalledProcessError as e: 641 lines = [ 642 ("{0} failed. This may cause the build to fail below." 643 .format(executable)), 644 " command: {0}".format(e.cmd), 645 " returncode: {0}".format(e.returncode), 646 " output: {0}".format(e.output) 647 ] 648 log.warn('\n'.join(lines)) 649 result['libraries'].extend(default_libraries) 650 else: 651 if pipe.returncode != 0: 652 lines = [ 653 "pkg-config could not lookup up package(s) {0}.".format( 654 ", ".join(packages)), 655 "This may cause the build to fail below." 656 ] 657 log.warn('\n'.join(lines)) 658 result['libraries'].extend(default_libraries) 659 else: 660 for token in output.split(): 661 # It's not clear what encoding the output of 662 # pkg-config will come to us in. It will probably be 663 # some combination of pure ASCII (for the compiler 664 # flags) and the filesystem encoding (for any argument 665 # that includes directories or filenames), but this is 666 # just conjecture, as the pkg-config documentation 667 # doesn't seem to address it. 668 arg = token[:2].decode('ascii') 669 value = token[2:].decode(sys.getfilesystemencoding()) 670 if arg in flag_map: 671 if arg == '-D': 672 value = tuple(value.split('=', 1)) 673 result[flag_map[arg]].append(value) 674 else: 675 result['extra_compile_args'].append(value) 676 677 return result 678 679 680def add_external_library(library): 681 """ 682 Add a build option for selecting the internal or system copy of a library. 683 684 Parameters 685 ---------- 686 library : str 687 The name of the library. If the library is `foo`, the build 688 option will be called `--use-system-foo`. 689 """ 690 691 for command in ['build', 'build_ext', 'install']: 692 add_command_option(command, str('use-system-' + library), 693 'Use the system {0} library'.format(library), 694 is_bool=True) 695 696 697def use_system_library(library): 698 """ 699 Returns `True` if the build configuration indicates that the given 700 library should use the system copy of the library rather than the 701 internal one. 702 703 For the given library `foo`, this will be `True` if 704 `--use-system-foo` or `--use-system-libraries` was provided at the 705 commandline or in `setup.cfg`. 706 707 Parameters 708 ---------- 709 library : str 710 The name of the library 711 712 Returns 713 ------- 714 use_system : bool 715 `True` if the build should use the system copy of the library. 716 """ 717 return ( 718 get_distutils_build_or_install_option('use_system_{0}'.format(library)) or 719 get_distutils_build_or_install_option('use_system_libraries')) 720 721 722@extends_doc(_find_packages) 723def find_packages(where='.', exclude=(), invalidate_cache=False): 724 """ 725 This version of ``find_packages`` caches previous results to speed up 726 subsequent calls. Use ``invalide_cache=True`` to ignore cached results 727 from previous ``find_packages`` calls, and repeat the package search. 728 """ 729 730 if exclude: 731 warnings.warn( 732 "Use of the exclude parameter is no longer supported since it does " 733 "not work as expected. Use add_exclude_packages instead. Note that " 734 "it must be called prior to any other calls from setup helpers.", 735 AstropyDeprecationWarning) 736 737 # Calling add_exclude_packages after this point will have no effect 738 _module_state['excludes_too_late'] = True 739 740 if not invalidate_cache and _module_state['package_cache'] is not None: 741 return _module_state['package_cache'] 742 743 packages = _find_packages( 744 where=where, exclude=list(_module_state['exclude_packages'])) 745 _module_state['package_cache'] = packages 746 747 return packages 748 749 750class FakeBuildSphinx(Command): 751 """ 752 A dummy build_sphinx command that is called if Sphinx is not 753 installed and displays a relevant error message 754 """ 755 756 # user options inherited from sphinx.setup_command.BuildDoc 757 user_options = [ 758 ('fresh-env', 'E', ''), 759 ('all-files', 'a', ''), 760 ('source-dir=', 's', ''), 761 ('build-dir=', None, ''), 762 ('config-dir=', 'c', ''), 763 ('builder=', 'b', ''), 764 ('project=', None, ''), 765 ('version=', None, ''), 766 ('release=', None, ''), 767 ('today=', None, ''), 768 ('link-index', 'i', '')] 769 770 # user options appended in astropy.setup_helpers.AstropyBuildSphinx 771 user_options.append(('warnings-returncode', 'w', '')) 772 user_options.append(('clean-docs', 'l', '')) 773 user_options.append(('no-intersphinx', 'n', '')) 774 user_options.append(('open-docs-in-browser', 'o', '')) 775 776 def initialize_options(self): 777 try: 778 raise RuntimeError("Sphinx and its dependencies must be installed " 779 "for build_docs.") 780 except: 781 log.error('error: Sphinx and its dependencies must be installed ' 782 'for build_docs.') 783 sys.exit(1) 784