Source code for build_helpers

Build helpers for

The original version of this file was adapted from NiPy project [1].


# Standard library imports
import os
import shutil
import glob
import fnmatch
import logging as log
from setuptools import Command
from skbuild.command.clean import clean
from packaging import version

from install_data import install_data

import sfepy.version as INFO


[docs]class NoOptionsDocs(Command): user_options = [('None', None, 'this command has no options'),]
[docs] def initialize_options(self): pass
[docs] def finalize_options(self): pass
[docs]def get_sphinx_make_command(): if in ['posix']: return 'make' elif in ['nt']: return 'make.bat' else: raise ValueError('unsupported system! (%s)' %
[docs]class SphinxHTMLDocs(NoOptionsDocs): description = """generate html docs by Sphinx"""
[docs] def run(self): os.chdir('doc') try: cmd = get_sphinx_make_command() os.system(cmd + ' html') finally: os.chdir('..')
[docs]class SphinxPDFDocs(NoOptionsDocs): description = """generate pdf docs by Sphinx"""
[docs] def run(self): cwd = os.getcwd() os.chdir('doc') try: cmd = get_sphinx_make_command() os.system(cmd + ' latex') os.chdir('_build/latex') os.system(cmd + ' all-pdf') os.chdir(cwd) try: os.remove('doc/sfepy_manual.pdf') except: pass os.rename('doc/_build/latex/SfePy.pdf', 'doc/sfepy_manual.pdf') finally: os.chdir(cwd)
[docs]class DoxygenDocs(NoOptionsDocs): description = """generate docs by Doxygen"""
[docs] def run(self): try: shutil.rmtree('doc/html') except OSError: pass fd_in = open('doc/doxygen.config', 'r') fd_out = open('doc/doxygenrc', 'w') for line in fd_in: aux = line.split('=') if len(aux) and (aux[0] == 'PROJECT_NUMBER'): line = '='.join([aux[0], INFO.__version__]) fd_out.write(line) fd_out.close() fd_in.close() os.system('doxygen doc/doxygenrc')
[docs]def recursive_glob(top_dir, pattern): """ Utility function working like `glob.glob()`, but working recursively and returning generator. Parameters ---------- topdir : str The top-level directory. pattern : str or list of str The pattern or list of patterns to match. """ if isinstance(pattern, list): for pat in pattern: for fn in recursive_glob(top_dir, pat): yield fn else: for dirpath, dirnames, filenames in os.walk(top_dir): for fn in [fn for fn in filenames if fnmatch.fnmatchcase(fn, pattern)]: yield os.path.join(dirpath, fn)
[docs]class Clean(clean): """ Command class to clean, enhanced to clean also files generated during `python build_ext --inplace`. """
[docs] def run(self): print('extra clean:') suffixes = ['*.pyc', '*.o', '*.so', '*.pyd', '*_wrap.c', '*.bak', '*~', '*%'] for filename in recursive_glob('sfepy', suffixes): print(filename) os.remove(filename) for filename in glob.glob('*.pyc'): print(filename) os.remove(filename) for _filename in recursive_glob('sfepy', ['*.pyx']): filename = _filename.replace('.pyx', '.c') print(filename) try: os.remove(filename) except OSError: pass filename = _filename.replace('.pyx', '.html') print(filename) try: os.remove(filename) except OSError: pass
# The command classes used by cmdclass = { 'htmldocs' : SphinxHTMLDocs, 'pdfdocs' : SphinxPDFDocs, 'doxygendocs' : DoxygenDocs, 'clean': Clean, 'install_data': install_data }
[docs]def have_good_cython(): try: from Cython.Compiler.Version import version as cversion except ImportError: return False return version.parse(cversion) >= version.parse(CYTHON_MIN_VERSION)
[docs]def package_check(pkg_name, version=None, optional=False, checker=version.parse, version_getter=None, messages=None, show_only=False): """ Check if package `pkg_name` is present, and in correct version. Parameters ---------- pkg_name : str or sequence of str The name of the package as imported into python. Alternative names (e.g. for different versions) may be given in a list. version : str, optional The minimum version of the package that is required. If not given, the version is not checked. optional : bool, optional If False, raise error for absent package or wrong version; otherwise warn checker : callable, optional If given, the callable with which to return a comparable thing from a version string. The default is ``packaging.version.parse()``. version_getter : callable, optional: If given, the callable that takes `pkg_name` as argument, and returns the package version string - as in:: ``version = version_getter(pkg_name)`` The default is equivalent to:: mod = __import__(pkg_name); version = mod.__version__`` messages : dict, optional If given, the dictionary providing (some of) output messages. show_only : bool If True, do not raise exceptions, only show the package name and version information. """ if version_getter is None: def version_getter(pkg_name): mod = __import__(pkg_name) return mod.__version__ if messages is None: messages = {} msgs = { 'available' : '%s is available', 'missing' : '%s is missing', 'opt suffix' : '; you may get run-time errors', 'version' : '%s is available in version %s', 'version old' : '%s is available in version %s, but >= %s is needed', 'no version' : '%s is available, cannot determine version', } msgs.update(messages) if isinstance(pkg_name, str): names = [pkg_name] else: names = pkg_name import_ok = False for pkg_name in names: try: __import__(pkg_name) except ImportError: pass else: import_ok = True pkg_info = pkg_name + (' (optional)' if optional else '') if not import_ok: if not (optional or show_only): raise RuntimeError(msgs['missing'] % pkg_name) log.warn(msgs['missing'] % pkg_info + msgs['opt suffix']) return if not version: if show_only:['available'] % pkg_info) return try: have_version = version_getter(pkg_name) except AttributeError: raise RuntimeError(msgs['no version'] % pkg_info) if not have_version: if optional or show_only: log.warn(msgs['no version'] % pkg_info) else: raise RuntimeError(msgs['no version'] % pkg_info) elif checker(have_version) < checker(version): if optional or show_only: log.warn(msgs['version old'] % (pkg_info, have_version, version) + msgs['opt suffix']) else: raise RuntimeError(msgs['version old'] % (pkg_info, have_version, version)) elif show_only:['version'] % (pkg_info, have_version))