1
# Licensed under a 3-clause BSD style license - see LICENSE.rst
2 20
"""
3
This module contains a number of utilities for use during
4
setup/build/packaging that are useful to astropy as a whole.
5
"""
6

7 20
import collections
8 20
import os
9 20
import re
10 20
import subprocess
11 20
import sys
12 20
import traceback
13 20
import warnings
14 20
from configparser import ConfigParser
15 20
import builtins
16

17 20
from distutils import log
18 20
from distutils.errors import DistutilsOptionError, DistutilsModuleError
19 20
from distutils.core import Extension
20 20
from distutils.core import Command
21 20
from distutils.command.sdist import sdist as DistutilsSdist
22

23 20
from setuptools import setup as setuptools_setup
24 20
from setuptools.config import read_configuration
25 20
from setuptools import find_packages as _find_packages
26

27 20
from .distutils_helpers import (add_command_option, get_compiler_option,
28
                                get_dummy_distribution, get_distutils_build_option,
29
                                get_distutils_build_or_install_option)
30 20
from .version_helpers import get_pkg_version_module, generate_version_py
31 20
from .utils import (walk_skip_hidden, import_file, extends_doc,
32
                    resolve_name, AstropyDeprecationWarning)
33

34 20
from .commands.build_ext import AstropyHelpersBuildExt
35 20
from .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
39 20
from .utils import get_numpy_include_path, write_if_different  # noqa
40

41 20
__all__ = ['register_commands', 'get_package_info']
42

43 20
_module_state = {'registered_commands': None,
44
                 'have_sphinx': False,
45
                 'package_cache': None,
46
                 'exclude_packages': set(),
47
                 'excludes_too_late': False}
48

49 20
try:
50 20
    import sphinx  # noqa
51 20
    _module_state['have_sphinx'] = True
52 0
except 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 0
    if 'unknown locale' in e.args[0]:
57 0
        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/")
63 0
except ImportError:
64 0
    pass
65 0
except SyntaxError:
66
    # occurs if markupsafe is recent version, which doesn't support Python 3.2
67 0
    pass
68

69

70 20
def 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 20
    conf = read_configuration('setup.cfg')
82 20
    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 20
    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 20
    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 20
    package_info = get_package_info()
96 20
    package_info['cmdclass'] = cmdclass
97 20
    package_info['version'] = version
98

99
    # Override using any specified keyword arguments
100 20
    package_info.update(kwargs)
101

102 20
    setuptools_setup(**package_info)
103

104

105 20
def adjust_compiler(package):
106 0
    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

112 20
def 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 0
    try:
124 0
        current_debug = get_pkg_version_module(packagename,
125
                                               fromlist=['debug'])[0]
126 0
    except (ImportError, AttributeError):
127 0
        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 0
    dist = get_dummy_distribution()
132 0
    if any(cmd in dist.commands for cmd in ['build', 'build_ext']):
133 0
        debug = bool(get_distutils_build_option('debug'))
134
    else:
135 0
        debug = bool(current_debug)
136

137 0
    if current_debug is not None and current_debug != debug:
138 0
        build_ext_cmd = dist.get_command_class('build_ext')
139 0
        build_ext_cmd._force_rebuild = True
140

141 0
    return debug
142

143

144 20
def add_exclude_packages(excludes):
145

146 20
    if _module_state['excludes_too_late']:
147 20
        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 0
    _module_state['exclude_packages'].update(set(excludes))
152

153

154 20
def 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 20
    if package is not None:
161 20
        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 20
    if version is not None:
166 20
        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 20
    if release is not None:
171 20
        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 20
    conf = ConfigParser()
180 20
    conf.read('setup.cfg')
181

182 20
    if conf.has_option('metadata', 'name'):
183 20
        package = conf.get('metadata', 'name')
184 20
    elif conf.has_option('metadata', 'package_name'):
185
        # The package-template used package_name instead of name for a while
186 0
        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 0
        package = conf.get('metadata', 'package_name')
190 20
    elif package is not None:  # deprecated
191 20
        pass
192
    else:
193 0
        sys.stderr.write('ERROR: Could not read package name from setup.cfg\n')
194 0
        sys.exit(1)
195

196 20
    if _module_state['registered_commands'] is not None:
197 0
        return _module_state['registered_commands']
198

199 20
    if _module_state['have_sphinx']:
200 20
        try:
201 20
            from .commands.build_sphinx import (AstropyBuildSphinx,
202
                                                AstropyBuildDocs)
203 0
        except ImportError:
204 0
            AstropyBuildSphinx = AstropyBuildDocs = FakeBuildSphinx
205
    else:
206 0
        AstropyBuildSphinx = AstropyBuildDocs = FakeBuildSphinx
207

208 20
    _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 20
    for name, cls in registered_commands.items():
227 20
        cls.__name__ = name
228

229
    # Add a few custom options; more of these can be added by specific packages
230
    # later
231 20
    for option in [
232
            ('use-system-libraries',
233
             "Use system libraries whenever possible", True)]:
234 20
        add_command_option('build', *option)
235 20
        add_command_option('install', *option)
236

237 20
    add_command_hooks(registered_commands, srcdir=srcdir)
238

239 20
    return registered_commands
240

241

242 20
def 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 20
    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 20
    def get_command_name(cmdcls):
260 0
        if hasattr(cmdcls, 'command_name'):
261 0
            return cmdcls.command_name
262
        else:
263 0
            return cmdcls.__name__
264

265 20
    packages = find_packages(srcdir)
266 20
    dist = get_dummy_distribution()
267

268 20
    hooks = collections.defaultdict(dict)
269

270 20
    for setuppkg in iter_setup_packages(srcdir, packages):
271 20
        for name, obj in vars(setuppkg).items():
272 20
            match = hook_re.match(name)
273 20
            if not match:
274 20
                continue
275

276 20
            hook_type = match.group(1)
277 20
            cmd_name = match.group(2)
278

279 20
            if hook_type not in hooks[cmd_name]:
280 20
                hooks[cmd_name][hook_type] = []
281

282 20
            hooks[cmd_name][hook_type].append((setuppkg.__name__, obj))
283

284 20
    for cmd_name, cmd_hooks in hooks.items():
285 20
        commands[cmd_name] = generate_hooked_command(
286
            cmd_name, dist.get_command_class(cmd_name), cmd_hooks)
287

288

289 20
def 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 20
    def run(self, orig_run=cmd_cls.run):
297 20
        self.run_command_hooks('pre_hooks')
298 20
        orig_run(self)
299 20
        self.run_command_hooks('post_hooks')
300

301 20
    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

307 20
def 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 20
    hooks = getattr(cmd_obj, hook_kind, None)
315

316 20
    if not hooks:
317 0
        return
318

319 20
    for modname, hook in hooks:
320 20
        if isinstance(hook, str):
321 0
            try:
322 0
                hook_obj = resolve_name(hook)
323 0
            except ImportError as exc:
324 0
                raise DistutilsModuleError(
325
                    'cannot find hook {0}: {1}'.format(hook, exc))
326
        else:
327 20
            hook_obj = hook
328

329 20
        if not callable(hook_obj):
330 0
            raise DistutilsOptionError('hook {0!r} is not callable' % hook)
331

332 20
        log.info('running {0} from {1} for {2} command'.format(
333
                 hook_kind.rstrip('s'), modname, cmd_obj.get_command_name()))
334

335 20
        try:
336 20
            hook_obj(cmd_obj)
337 0
        except Exception:
338 0
            log.error('{0} command hook {1} raised an exception: %s\n'.format(
339
                hook_obj.__name__, cmd_obj.get_command_name()))
340 0
            log.error(traceback.format_exc())
341 0
            sys.exit(1)
342

343

344 20
def 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 20
    return type(package_name.title() + 'Test', (AstropyTest,),
352
                {'package_name': package_name})
353

354

355 20
def 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 0
    info = get_package_info(srcdir)
364 0
    extensions.extend(info['ext_modules'])
365 0
    package_data.update(info['package_data'])
366 0
    packagenames = list(set(packagenames + info['packages']))
367 0
    package_dirs.update(info['package_dir'])
368

369

370 20
def 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 20
    ext_modules = []
405 20
    packages = []
406 20
    package_dir = {}
407

408
    # Read in existing package data, and add to it below
409 20
    setup_cfg = os.path.join(srcdir, 'setup.cfg')
410 20
    if os.path.exists(setup_cfg):
411 20
        conf = read_configuration(setup_cfg)
412 20
        if 'options' in conf and 'package_data' in conf['options']:
413 0
            package_data = conf['options']['package_data']
414
        else:
415 20
            package_data = {}
416
    else:
417 20
        package_data = {}
418

419 20
    if exclude:
420 20
        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 20
    packages = find_packages(srcdir, exclude=exclude)
428

429
    # Update package_dir if the package lies in a subdirectory
430 20
    if srcdir != '.':
431 0
        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 20
    for setuppkg in iter_setup_packages(srcdir, packages):
438 20
        if hasattr(setuppkg, 'get_build_options'):
439 0
            options = setuppkg.get_build_options()
440 0
            for option in options:
441 0
                add_command_option('build', *option)
442 20
        if hasattr(setuppkg, 'get_external_libraries'):
443 0
            libraries = setuppkg.get_external_libraries()
444 0
            for library in libraries:
445 0
                add_external_library(library)
446

447 20
    for setuppkg in iter_setup_packages(srcdir, packages):
448
        # get_extensions must include any Cython extensions by their .pyx
449
        # filename.
450 20
        if hasattr(setuppkg, 'get_extensions'):
451 20
            ext_modules.extend(setuppkg.get_extensions())
452 20
        if hasattr(setuppkg, 'get_package_data'):
453 0
            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 20
    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 20
    for i, ext in reversed(list(enumerate(ext_modules))):
463 20
        if ext.name == 'skip_cython':
464 0
            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 20
    if get_compiler_option() == 'msvc':
471 2
        for ext in ext_modules:
472 2
            ext.extra_link_args.append('/MANIFEST')
473

474 20
    return {
475
        'ext_modules': ext_modules,
476
        'packages': packages,
477
        'package_dir': package_dir,
478
        'package_data': package_data,
479
        }
480

481

482 20
def 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 20
    for packagename in packages:
495 20
        package_parts = packagename.split('.')
496 20
        package_path = os.path.join(srcdir, *package_parts)
497 20
        setup_package = os.path.relpath(
498
            os.path.join(package_path, 'setup_package.py'))
499

500 20
        if os.path.isfile(setup_package):
501 20
            module = import_file(setup_package,
502
                                 name=packagename + '.setup_package')
503 20
            yield module
504

505

506 20
def 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 20
    for dirpath, dirnames, filenames in walk_skip_hidden(package_dir):
520 20
        for fn in filenames:
521 20
            if fn.endswith('.pyx'):
522 20
                fullfn = os.path.relpath(os.path.join(dirpath, fn))
523
                # Package must match file name
524 20
                extmod = '.'.join([package_name, fn[:-4]])
525 20
                yield (extmod, fullfn)
526

527 20
        break  # Don't recurse into subdirectories
528

529

530 20
def 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 20
    prevsourcepaths = []
559 20
    ext_modules = []
560

561 20
    for ext in prevextensions:
562 20
        for s in ext.sources:
563 20
            if s.endswith(('.pyx', '.c', '.cpp')):
564 20
                sourcepath = os.path.realpath(os.path.splitext(s)[0])
565 20
                prevsourcepaths.append(sourcepath)
566

567 20
    for package_name in packages:
568 20
        package_parts = package_name.split('.')
569 20
        package_path = os.path.join(srcdir, *package_parts)
570

571 20
        for extmod, pyxfn in iter_pyx_files(package_path, package_name):
572 20
            sourcepath = os.path.realpath(os.path.splitext(pyxfn)[0])
573 20
            if sourcepath not in prevsourcepaths:
574 0
                ext_modules.append(Extension(extmod, [pyxfn],
575
                                             include_dirs=extincludedirs))
576

577 20
    return ext_modules
578

579

580 20
class 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 20
    def __init__(self, *args, **kwargs):
589 0
        def default_factory():
590 0
            return []
591

592 0
        super(DistutilsExtensionArgs, self).__init__(
593
            default_factory, *args, **kwargs)
594

595 20
    def update(self, other):
596 0
        for key, val in other.items():
597 0
            self[key].extend(val)
598

599

600 20
def 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 0
    flag_map = {'-I': 'include_dirs', '-L': 'library_dirs', '-l': 'libraries',
632
                '-D': 'define_macros', '-U': 'undef_macros'}
633 0
    command = "{0} --libs --cflags {1}".format(executable, ' '.join(packages)),
634

635 0
    result = DistutilsExtensionArgs()
636

637 0
    try:
638 0
        pipe = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
639 0
        output = pipe.communicate()[0].strip()
640 0
    except subprocess.CalledProcessError as e:
641 0
        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 0
        log.warn('\n'.join(lines))
649 0
        result['libraries'].extend(default_libraries)
650
    else:
651 0
        if pipe.returncode != 0:
652 0
            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 0
            log.warn('\n'.join(lines))
658 0
            result['libraries'].extend(default_libraries)
659
        else:
660 0
            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 0
                arg = token[:2].decode('ascii')
669 0
                value = token[2:].decode(sys.getfilesystemencoding())
670 0
                if arg in flag_map:
671 0
                    if arg == '-D':
672 0
                        value = tuple(value.split('=', 1))
673 0
                    result[flag_map[arg]].append(value)
674
                else:
675 0
                    result['extra_compile_args'].append(value)
676

677 0
    return result
678

679

680 20
def 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 0
    for command in ['build', 'build_ext', 'install']:
692 0
        add_command_option(command, str('use-system-' + library),
693
                           'Use the system {0} library'.format(library),
694
                           is_bool=True)
695

696

697 20
def 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 0
    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 20
@extends_doc(_find_packages)
723 20
def 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 20
    if exclude:
731 20
        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 20
    _module_state['excludes_too_late'] = True
739

740 20
    if not invalidate_cache and _module_state['package_cache'] is not None:
741 20
        return _module_state['package_cache']
742

743 20
    packages = _find_packages(
744
        where=where, exclude=list(_module_state['exclude_packages']))
745 20
    _module_state['package_cache'] = packages
746

747 20
    return packages
748

749

750 20
class 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 20
    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 20
    user_options.append(('warnings-returncode', 'w', ''))
772 20
    user_options.append(('clean-docs', 'l', ''))
773 20
    user_options.append(('no-intersphinx', 'n', ''))
774 20
    user_options.append(('open-docs-in-browser', 'o', ''))
775

776 20
    def initialize_options(self):
777 0
        try:
778 0
            raise RuntimeError("Sphinx and its dependencies must be installed "
779
                               "for build_docs.")
780 0
        except:
781 0
            log.error('error: Sphinx and its dependencies must be installed '
782
                      'for build_docs.')
783 0
            sys.exit(1)

Read our documentation on viewing source code .

Loading