Fix CI failures
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 |
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 |
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/") |
|
63 |
except ImportError: |
|
64 |
pass
|
|
65 |
except SyntaxError: |
|
66 |
# occurs if markupsafe is recent version, which doesn't support Python 3.2
|
|
67 |
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 |
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 |
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 |
|
|
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 |
_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 |
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 | 20 |
elif package is not None: # deprecated |
191 | 20 |
pass
|
192 |
else: |
|
193 |
sys.stderr.write('ERROR: Could not read package name from setup.cfg\n') |
|
194 |
sys.exit(1) |
|
195 |
|
|
196 | 20 |
if _module_state['registered_commands'] is not None: |
197 |
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 |
except ImportError: |
|
204 |
AstropyBuildSphinx = AstropyBuildDocs = FakeBuildSphinx |
|
205 |
else: |
|
206 |
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 |
if hasattr(cmdcls, 'command_name'): |
|
261 |
return cmdcls.command_name |
|
262 |
else: |
|
263 |
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 |
return
|
|
318 |
|
|
319 | 20 |
for modname, hook in hooks: |
320 | 20 |
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 | 20 |
hook_obj = hook |
328 |
|
|
329 | 20 |
if not callable(hook_obj): |
330 |
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 |
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 |
|
|
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 |
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 |
|
|
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 |
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 |
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 |
options = setuppkg.get_build_options() |
|
440 |
for option in options: |
|
441 |
add_command_option('build', *option) |
|
442 | 20 |
if hasattr(setuppkg, 'get_external_libraries'): |
443 |
libraries = setuppkg.get_external_libraries() |
|
444 |
for library in libraries: |
|
445 |
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 |
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 |
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 |
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 |
def default_factory(): |
|
590 |
return [] |
|
591 |
|
|
592 |
super(DistutilsExtensionArgs, self).__init__( |
|
593 |
default_factory, *args, **kwargs) |
|
594 |
|
|
595 | 20 |
def update(self, other): |
596 |
for key, val in other.items(): |
|
597 |
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 |
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 |
|
|
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 |
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 |
|
|
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 |
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 |
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) |
Read our documentation on viewing source code .