1
# Licensed under a 3-clause BSD style license - see LICENSE.rst
2 11
"""
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 11
import collections
8 11
import os
9 11
import re
10 11
import subprocess
11 11
import sys
12 11
import traceback
13 11
import warnings
14 11
from configparser import ConfigParser
15 11
import builtins
16

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

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

27 11
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 11
from .version_helpers import get_pkg_version_module, generate_version_py
31 11
from .utils import (walk_skip_hidden, import_file, extends_doc,
32
                    resolve_name, AstropyDeprecationWarning)
33

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

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

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

49 11
try:
50 11
    import sphinx  # noqa
51 11
    _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 11
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 11
    conf = read_configuration('setup.cfg')
82 11
    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 11
    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 11
    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 11
    package_info = get_package_info()
96 11
    package_info['cmdclass'] = cmdclass
97 11
    package_info['version'] = version
98

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

102 11
    setuptools_setup(**package_info)
103

104

105 11
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 11
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 11
def add_exclude_packages(excludes):
145

146 11
    if _module_state['excludes_too_late']:
147 11
        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 11
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 11
    if package is not None:
161 11
        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 11
    if version is not None:
166 11
        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 11
    if release is not None:
171 11
        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 11
    conf = ConfigParser()
180 11
    conf.read('setup.cfg')
181

182 11
    if conf.has_option('metadata', 'name'):
183 11
        package = conf.get('metadata', 'name')
184 11
    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 11
    elif package is not None:  # deprecated
191 11
        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 11
    if _module_state['registered_commands'] is not None:
197 0
        return _module_state['registered_commands']
198

199 11
    if _module_state['have_sphinx']:
200 11
        try:
201 11
            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 11
    _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 11
    for name, cls in registered_commands.items():
227 11
        cls.__name__ = name
228

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

237 11
    add_command_hooks(registered_commands, srcdir=srcdir)
238

239 11
    return registered_commands
240

241

242 11
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 11
    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 11
    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 11
    packages = find_packages(srcdir)
266 11
    dist = get_dummy_distribution()
267

268 11
    hooks = collections.defaultdict(dict)
269

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

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

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

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

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

288

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

301 11
    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 11
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 11
    hooks = getattr(cmd_obj, hook_kind, None)
315

316 11
    if not hooks:
317 0
        return
318

319 11
    for modname, hook in hooks:
320 11
        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 11
            hook_obj = hook
328

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

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

335 11
        try:
336 11
            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 11
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 11
    return type(package_name.title() + 'Test', (AstropyTest,),
352
                {'package_name': package_name})
353

354

355 11
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 11
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 11
    ext_modules = []
405 11
    packages = []
406 11
    package_dir = {}
407

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

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

429
    # Update package_dir if the package lies in a subdirectory
430 11
    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 11
    for setuppkg in iter_setup_packages(srcdir, packages):
438 11
        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 11
        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 11
    for setuppkg in iter_setup_packages(srcdir, packages):
448
        # get_extensions must include any Cython extensions by their .pyx
449
        # filename.
450 11
        if hasattr(setuppkg, 'get_extensions'):
451 11
            ext_modules.extend(setuppkg.get_extensions())
452 11
        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 11
    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 11
    for i, ext in reversed(list(enumerate(ext_modules))):
463 11
        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 11
    if get_compiler_option() == 'msvc':
471 2
        for ext in ext_modules:
472 2
            ext.extra_link_args.append('/MANIFEST')
473

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

481

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

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

505

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

527 11
        break  # Don't recurse into subdirectories
528

529

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

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

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

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

577 11
    return ext_modules
578

579

580 11
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 11
    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 11
    def update(self, other):
596 0
        for key, val in other.items():
597 0
            self[key].extend(val)
598

599

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

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

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

747 11
    return packages
748

749

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

776 11
    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