From bc87165af7c751fd37069185fab38c43f9cc772c Mon Sep 17 00:00:00 2001 From: Serban Udrea Date: Sun, 30 Apr 2017 09:36:49 +0700 Subject: development/python3-matplotlib: Added (Python 2D plotting library). Signed-off-by: Willy Sudiarto Raharjo --- development/python3-matplotlib/setupext.py | 1921 ++++++++++++++++++++++++++++ 1 file changed, 1921 insertions(+) create mode 100644 development/python3-matplotlib/setupext.py (limited to 'development/python3-matplotlib/setupext.py') diff --git a/development/python3-matplotlib/setupext.py b/development/python3-matplotlib/setupext.py new file mode 100644 index 0000000000..5a1939fa12 --- /dev/null +++ b/development/python3-matplotlib/setupext.py @@ -0,0 +1,1921 @@ +from __future__ import print_function, absolute_import + +from distutils import sysconfig +from distutils import version +from distutils.core import Extension +import glob +import io +import multiprocessing +import os +import re +import subprocess +import sys +import warnings +from textwrap import fill + +import versioneer + + +PY3min = (sys.version_info[0] >= 3) +PY32min = (PY3min and sys.version_info[1] >= 2 or sys.version_info[0] > 3) + + +try: + from subprocess import check_output +except ImportError: + # check_output is not available in Python 2.6 + def check_output(*popenargs, **kwargs): + """ + Run command with arguments and return its output as a byte + string. + + Backported from Python 2.7 as it's implemented as pure python + on stdlib. + """ + process = subprocess.Popen( + stdout=subprocess.PIPE, *popenargs, **kwargs) + output, unused_err = process.communicate() + retcode = process.poll() + if retcode: + cmd = kwargs.get("args") + if cmd is None: + cmd = popenargs[0] + error = subprocess.CalledProcessError(retcode, cmd) + error.output = output + raise error + return output + + +if sys.platform != 'win32': + if not PY3min: + from commands import getstatusoutput + else: + from subprocess import getstatusoutput + + +if PY3min: + import configparser +else: + import ConfigParser as configparser + + +# matplotlib build options, which can be altered using setup.cfg +options = { + 'display_status': True, + 'verbose': False, + 'backend': None, + 'basedirlist': None + } + + +setup_cfg = os.environ.get('MPLSETUPCFG', 'setup.cfg') +if os.path.exists(setup_cfg): + if PY32min: + config = configparser.ConfigParser() + else: + config = configparser.SafeConfigParser() + config.read(setup_cfg) + + try: + options['display_status'] = not config.getboolean("status", "suppress") + except: + pass + + try: + options['backend'] = config.get("rc_options", "backend") + except: + pass + + try: + options['basedirlist'] = [ + x.strip() for x in + config.get("directories", "basedirlist").split(',')] + except: + pass +else: + config = None + + +def get_win32_compiler(): + """ + Determine the compiler being used on win32. + """ + # Used to determine mingw32 or msvc + # This is pretty bad logic, someone know a better way? + for v in sys.argv: + if 'mingw32' in v: + return 'mingw32' + return 'msvc' +win32_compiler = get_win32_compiler() + + +def extract_versions(): + """ + Extracts version values from the main matplotlib __init__.py and + returns them as a dictionary. + """ + with open('lib/matplotlib/__init__.py') as fd: + for line in fd.readlines(): + if (line.startswith('__version__numpy__')): + exec(line.strip()) + return locals() + + +def has_include_file(include_dirs, filename): + """ + Returns `True` if `filename` can be found in one of the + directories in `include_dirs`. + """ + if sys.platform == 'win32': + include_dirs += os.environ.get('INCLUDE', '.').split(';') + for dir in include_dirs: + if os.path.exists(os.path.join(dir, filename)): + return True + return False + + +def check_include_file(include_dirs, filename, package): + """ + Raises an exception if the given include file can not be found. + """ + if not has_include_file(include_dirs, filename): + raise CheckFailed( + "The C/C++ header for %s (%s) could not be found. You " + "may need to install the development package." % + (package, filename)) + + +def get_base_dirs(): + """ + Returns a list of standard base directories on this platform. + """ + if options['basedirlist']: + return options['basedirlist'] + + basedir_map = { + 'win32': ['win32_static', ], + 'darwin': ['/usr/local/', '/usr', '/usr/X11', + '/opt/X11', '/opt/local'], + 'sunos5': [os.getenv('MPLIB_BASE') or '/usr/local', ], + 'gnu0': ['/usr'], + 'aix5': ['/usr/local'], + } + return basedir_map.get(sys.platform, ['/usr/local', '/usr']) + + +def get_include_dirs(): + """ + Returns a list of standard include directories on this platform. + """ + include_dirs = [os.path.join(d, 'include') for d in get_base_dirs()] + include_dirs.extend( + os.environ.get('CPLUS_INCLUDE_PATH', '').split(os.pathsep)) + return include_dirs + + +def is_min_version(found, minversion): + """ + Returns `True` if `found` is at least as high a version as + `minversion`. + """ + expected_version = version.LooseVersion(minversion) + found_version = version.LooseVersion(found) + return found_version >= expected_version + + +# Define the display functions only if display_status is True. +if options['display_status']: + def print_line(char='='): + print(char * 76) + + def print_status(package, status): + initial_indent = "%22s: " % package + indent = ' ' * 24 + print(fill(str(status), width=76, + initial_indent=initial_indent, + subsequent_indent=indent)) + + def print_message(message): + indent = ' ' * 24 + "* " + print(fill(str(message), width=76, + initial_indent=indent, + subsequent_indent=indent)) + + def print_raw(section): + print(section) +else: + def print_line(*args, **kwargs): + pass + print_status = print_message = print_raw = print_line + + +# Remove the -Wstrict-prototypesoption, is it's not valid for C++ +customize_compiler = sysconfig.customize_compiler + + +def my_customize_compiler(compiler): + retval = customize_compiler(compiler) + try: + compiler.compiler_so.remove('-Wstrict-prototypes') + except (ValueError, AttributeError): + pass + return retval + +sysconfig.customize_compiler = my_customize_compiler + + +def make_extension(name, files, *args, **kwargs): + """ + Make a new extension. Automatically sets include_dirs and + library_dirs to the base directories appropriate for this + platform. + + `name` is the name of the extension. + + `files` is a list of source files. + + Any additional arguments are passed to the + `distutils.core.Extension` constructor. + """ + ext = DelayedExtension(name, files, *args, **kwargs) + for dir in get_base_dirs(): + include_dir = os.path.join(dir, 'include') + if os.path.exists(include_dir): + ext.include_dirs.append(include_dir) + for lib in ('lib', 'lib64'): + lib_dir = os.path.join(dir, lib) + if os.path.exists(lib_dir): + ext.library_dirs.append(lib_dir) + ext.include_dirs.append('.') + + return ext + + +class PkgConfig(object): + """ + This is a class for communicating with pkg-config. + """ + def __init__(self): + """ + Determines whether pkg-config exists on this machine. + """ + if sys.platform == 'win32': + self.has_pkgconfig = False + else: + try: + self.pkg_config = os.environ['PKG_CONFIG'] + except KeyError: + self.pkg_config = 'pkg-config' + + self.set_pkgconfig_path() + status, output = getstatusoutput(self.pkg_config + " --help") + self.has_pkgconfig = (status == 0) + if not self.has_pkgconfig: + print("IMPORTANT WARNING:") + print( + " pkg-config is not installed.\n" + " matplotlib may not be able to find some of its dependencies") + + def set_pkgconfig_path(self): + pkgconfig_path = sysconfig.get_config_var('LIBDIR') + if pkgconfig_path is None: + return + + pkgconfig_path = os.path.join(pkgconfig_path, 'pkgconfig') + if not os.path.isdir(pkgconfig_path): + return + + try: + os.environ['PKG_CONFIG_PATH'] += ':' + pkgconfig_path + except KeyError: + os.environ['PKG_CONFIG_PATH'] = pkgconfig_path + + def setup_extension(self, ext, package, default_include_dirs=[], + default_library_dirs=[], default_libraries=[], + alt_exec=None): + """ + Add parameters to the given `ext` for the given `package`. + """ + flag_map = { + '-I': 'include_dirs', '-L': 'library_dirs', '-l': 'libraries'} + + executable = alt_exec + if self.has_pkgconfig: + executable = (self.pkg_config + ' {0}').format(package) + + use_defaults = True + + if executable is not None: + command = "{0} --libs --cflags ".format(executable) + + try: + output = check_output(command, shell=True, + stderr=subprocess.STDOUT) + except subprocess.CalledProcessError: + pass + else: + output = output.decode(sys.getfilesystemencoding()) + use_defaults = False + for token in output.split(): + attr = flag_map.get(token[:2]) + if attr is not None: + getattr(ext, attr).insert(0, token[2:]) + + if use_defaults: + basedirs = get_base_dirs() + for base in basedirs: + for include in default_include_dirs: + dir = os.path.join(base, include) + if os.path.exists(dir): + ext.include_dirs.append(dir) + for lib in default_library_dirs: + dir = os.path.join(base, lib) + if os.path.exists(dir): + ext.library_dirs.append(dir) + ext.libraries.extend(default_libraries) + return True + + return False + + def get_version(self, package): + """ + Get the version of the package from pkg-config. + """ + if not self.has_pkgconfig: + return None + + status, output = getstatusoutput( + self.pkg_config + " %s --modversion" % (package)) + if status == 0: + return output + return None + + +# The PkgConfig class should be used through this singleton +pkg_config = PkgConfig() + + +class CheckFailed(Exception): + """ + Exception thrown when a `SetupPackage.check` method fails. + """ + pass + + +class SetupPackage(object): + optional = False + + def check(self): + """ + Checks whether the dependencies are met. Should raise a + `CheckFailed` exception if the dependency could not be met, + otherwise return a string indicating a version number or some + other message indicating what was found. + """ + pass + + def get_packages(self): + """ + Get a list of package names to add to the configuration. + These are added to the `packages` list passed to + `distutils.setup`. + """ + return [] + + def get_namespace_packages(self): + """ + Get a list of namespace package names to add to the configuration. + These are added to the `namespace_packages` list passed to + `distutils.setup`. + """ + return [] + + def get_py_modules(self): + """ + Get a list of top-level modules to add to the configuration. + These are added to the `py_modules` list passed to + `distutils.setup`. + """ + return [] + + def get_package_data(self): + """ + Get a package data dictionary to add to the configuration. + These are merged into to the `package_data` list passed to + `distutils.setup`. + """ + return {} + + def get_extension(self): + """ + Get a list of C extensions (`distutils.core.Extension` + objects) to add to the configuration. These are added to the + `extensions` list passed to `distutils.setup`. + """ + return None + + def get_install_requires(self): + """ + Get a list of Python packages that we require. + pip/easy_install will attempt to download and install this + package if it is not installed. + """ + return [] + + def get_setup_requires(self): + """ + Get a list of Python packages that we require at build time. + pip/easy_install will attempt to download and install this + package if it is not installed. + """ + return [] + + def _check_for_pkg_config(self, package, include_file, min_version=None, + version=None): + """ + A convenience function for writing checks for a + pkg_config-defined dependency. + + `package` is the pkg_config package name. + + `include_file` is a top-level include file we expect to find. + + `min_version` is the minimum version required. + + `version` will override the found version if this package + requires an alternate method for that. Set version='unknown' + if the version is not known but you still want to disabled + pkg_config version check. + """ + if version is None: + version = pkg_config.get_version(package) + + if version is None: + raise CheckFailed( + "pkg-config information for '%s' could not be found." % + package) + + if min_version == 'PATCH': + raise CheckFailed( + "Requires patches that have not been merged upstream.") + + if min_version and version != 'unknown': + if (not is_min_version(version, min_version)): + raise CheckFailed( + "Requires %s %s or later. Found %s." % + (package, min_version, version)) + + ext = self.get_extension() + if ext is None: + ext = make_extension('test', []) + pkg_config.setup_extension(ext, package) + + check_include_file( + ext.include_dirs + get_include_dirs(), include_file, package) + + return 'version %s' % version + + +class OptionalPackage(SetupPackage): + optional = True + force = False + config_category = "packages" + + @classmethod + def get_config(cls): + """ + Look at `setup.cfg` and return one of ["auto", True, False] indicating + if the package is at default state ("auto"), forced by the user (case + insensitively defined as 1, true, yes, on for True) or opted-out (case + insensitively defined as 0, false, no, off for False). + """ + conf = "auto" + if config is not None and config.has_option(cls.config_category, cls.name): + try: + conf = config.getboolean(cls.config_category, cls.name) + except ValueError: + conf = config.get(cls.config_category, cls.name) + return conf + + def check(self): + """ + Do not override this method! + + For custom dependency checks override self.check_requirements(). + Two things are checked: Configuration file and requirements. + """ + # Check configuration file + conf = self.get_config() + # Default "auto" state or install forced by user + if conf in [True, 'auto']: + message = "installing" + # Set non-optional if user sets `True` in config + if conf is True: + self.optional = False + # Configuration opt-out by user + else: + # Some backend extensions (e.g. Agg) need to be built for certain + # other GUI backends (e.g. TkAgg) even when manually disabled + if self.force is True: + message = "installing forced (config override)" + else: + raise CheckFailed("skipping due to configuration") + + # Check requirements and add extra information (if any) to message. + # If requirements are not met a CheckFailed should be raised in there. + additional_info = self.check_requirements() + if additional_info: + message += ", " + additional_info + + # No CheckFailed raised until now, return install message. + return message + + def check_requirements(self): + """ + Override this method to do custom dependency checks. + + - Raise CheckFailed() if requirements are not met. + - Return message with additional information, or an empty string + (or None) for no additional information. + """ + return "" + + +class OptionalBackendPackage(OptionalPackage): + config_category = "gui_support" + + +class Platform(SetupPackage): + name = "platform" + + def check(self): + return sys.platform + + +class Python(SetupPackage): + name = "python" + + def check(self): + major, minor1, minor2, s, tmp = sys.version_info + + if major < 2: + raise CheckFailed( + "Requires Python 2.6 or later") + elif major == 2 and minor1 < 6: + raise CheckFailed( + "Requires Python 2.6 or later (in the 2.x series)") + elif major == 3 and minor1 < 1: + raise CheckFailed( + "Requires Python 3.1 or later (in the 3.x series)") + + return sys.version + + +class Matplotlib(SetupPackage): + name = "matplotlib" + + def check(self): + return versioneer.get_version() + + def get_packages(self): + return [ + 'matplotlib', + 'matplotlib.backends', + 'matplotlib.backends.qt_editor', + 'matplotlib.compat', + 'matplotlib.projections', + 'matplotlib.axes', + 'matplotlib.sphinxext', + 'matplotlib.style', + 'matplotlib.testing', + 'matplotlib.testing.jpl_units', + 'matplotlib.tri', + ] + + def get_py_modules(self): + return ['pylab'] + + def get_package_data(self): + return { + 'matplotlib': + [ + 'mpl-data/fonts/afm/*.afm', + 'mpl-data/fonts/pdfcorefonts/*.afm', + 'mpl-data/fonts/pdfcorefonts/*.txt', + 'mpl-data/fonts/ttf/*.ttf', + 'mpl-data/fonts/ttf/LICENSE_STIX', + 'mpl-data/fonts/ttf/COPYRIGHT.TXT', + 'mpl-data/fonts/ttf/README.TXT', + 'mpl-data/fonts/ttf/RELEASENOTES.TXT', + 'mpl-data/images/*.xpm', + 'mpl-data/images/*.svg', + 'mpl-data/images/*.gif', + 'mpl-data/images/*.pdf', + 'mpl-data/images/*.png', + 'mpl-data/images/*.ppm', + 'mpl-data/example/*.npy', + 'mpl-data/matplotlibrc', + 'backends/web_backend/*.*', + 'backends/web_backend/jquery/js/*.min.js', + 'backends/web_backend/jquery/css/themes/base/*.min.css', + 'backends/web_backend/jquery/css/themes/base/images/*', + 'backends/web_backend/css/*.*', + 'backends/Matplotlib.nib/*', + 'mpl-data/stylelib/*.mplstyle', + ]} + + +class SampleData(OptionalPackage): + """ + This handles the sample data that ships with matplotlib. It is + technically optional, though most often will be desired. + """ + name = "sample_data" + + def get_package_data(self): + return { + 'matplotlib': + [ + 'mpl-data/sample_data/*.*', + 'mpl-data/sample_data/axes_grid/*.*', + ]} + + +class Toolkits(OptionalPackage): + name = "toolkits" + + def get_packages(self): + return [ + 'mpl_toolkits', + 'mpl_toolkits.mplot3d', + 'mpl_toolkits.axes_grid', + 'mpl_toolkits.axes_grid1', + 'mpl_toolkits.axisartist', + ] + + def get_namespace_packages(self): + return ['mpl_toolkits'] + + +class Tests(OptionalPackage): + name = "tests" + nose_min_version = '0.11.1' + + def check(self): + super(Tests, self).check() + + msgs = [] + msg_template = ('{package} is required to run the matplotlib test ' + 'suite. Please install it with pip or your preferred' + ' tool to run the test suite') + + bad_nose = msg_template.format( + package='nose %s or later' % self.nose_min_version + ) + try: + import nose + if is_min_version(nose.__version__, self.nose_min_version): + msgs += ['using nose version %s' % nose.__version__] + else: + msgs += [bad_nose] + except ImportError: + msgs += [bad_nose] + + if sys.version_info >= (3, 3): + msgs += ['using unittest.mock'] + else: + try: + import mock + msgs += ['using mock %s' % mock.__version__] + except ImportError: + msgs += [msg_template.format(package='mock')] + + return ' / '.join(msgs) + + def get_packages(self): + return [ + 'matplotlib.tests', + 'matplotlib.sphinxext.tests', + ] + + def get_package_data(self): + baseline_images = [ + 'tests/baseline_images/%s/*' % x + for x in os.listdir('lib/matplotlib/tests/baseline_images')] + + return { + 'matplotlib': + baseline_images + + [ + 'tests/mpltest.ttf', + 'tests/test_rcparams.rc', + 'tests/test_utf32_be_rcparams.rc', + 'sphinxext/tests/tinypages/*.rst', + 'sphinxext/tests/tinypages/*.py', + 'sphinxext/tests/tinypages/_static/*', + ]} + + +class Toolkits_Tests(Tests): + name = "toolkits_tests" + + def check_requirements(self): + conf = self.get_config() + toolkits_conf = Toolkits.get_config() + tests_conf = Tests.get_config() + + if conf is True: + Tests.force = True + Toolkits.force = True + elif conf == "auto" and not (toolkits_conf and tests_conf): + # Only auto-install if both toolkits and tests are set + # to be installed + raise CheckFailed("toolkits_tests needs 'toolkits' and 'tests'") + return "" + + def get_packages(self): + return [ + 'mpl_toolkits.tests', + ] + + def get_package_data(self): + baseline_images = [ + 'tests/baseline_images/%s/*' % x + for x in os.listdir('lib/mpl_toolkits/tests/baseline_images')] + + return {'mpl_toolkits': baseline_images} + + def get_namespace_packages(self): + return ['mpl_toolkits'] + + +class DelayedExtension(Extension, object): + """ + A distutils Extension subclass where some of its members + may have delayed computation until reaching the build phase. + + This is so we can, for example, get the Numpy include dirs + after pip has installed Numpy for us if it wasn't already + on the system. + """ + def __init__(self, *args, **kwargs): + super(DelayedExtension, self).__init__(*args, **kwargs) + self._finalized = False + self._hooks = {} + + def add_hook(self, member, func): + """ + Add a hook to dynamically compute a member. + + Parameters + ---------- + member : string + The name of the member + + func : callable + The function to call to get dynamically-computed values + for the member. + """ + self._hooks[member] = func + + def finalize(self): + self._finalized = True + + class DelayedMember(property): + def __init__(self, name): + self._name = name + + def __get__(self, obj, objtype=None): + result = getattr(obj, '_' + self._name, []) + + if obj._finalized: + if self._name in obj._hooks: + result = obj._hooks[self._name]() + result + + return result + + def __set__(self, obj, value): + setattr(obj, '_' + self._name, value) + + include_dirs = DelayedMember('include_dirs') + + +class Numpy(SetupPackage): + name = "numpy" + + @staticmethod + def include_dirs_hook(): + if sys.version_info[0] >= 3: + import builtins + if hasattr(builtins, '__NUMPY_SETUP__'): + del builtins.__NUMPY_SETUP__ + import imp + import numpy + imp.reload(numpy) + else: + import __builtin__ + if hasattr(__builtin__, '__NUMPY_SETUP__'): + del __builtin__.__NUMPY_SETUP__ + import numpy + reload(numpy) + + ext = Extension('test', []) + ext.include_dirs.append(numpy.get_include()) + if not has_include_file( + ext.include_dirs, os.path.join("numpy", "arrayobject.h")): + warnings.warn( + "The C headers for numpy could not be found. " + "You may need to install the development package") + + return [numpy.get_include()] + + def check(self): + min_version = extract_versions()['__version__numpy__'] + try: + import numpy + except ImportError: + raise CheckFailed( + "could not be found" ) + if not is_min_version(numpy.__version__, min_version): + raise CheckFailed( + "requires numpy %s or later to build. (Found %s)" % + (min_version, numpy.__version__)) + + return 'version %s' % numpy.__version__ + + def add_flags(self, ext): + # Ensure that PY_ARRAY_UNIQUE_SYMBOL is uniquely defined for + # each extension + array_api_name = 'MPL_' + ext.name.replace('.', '_') + '_ARRAY_API' + + ext.define_macros.append(('PY_ARRAY_UNIQUE_SYMBOL', array_api_name)) + ext.add_hook('include_dirs', self.include_dirs_hook) + + ext.define_macros.append(('NPY_NO_DEPRECATED_API', + 'NPY_1_7_API_VERSION')) + + def get_setup_requires(self): + return ['numpy>=1.6'] + + def get_install_requires(self): + return ['numpy>=1.6'] + + +class LibAgg(SetupPackage): + name = 'libagg' + + def check(self): + self.__class__.found_external = True + try: + return self._check_for_pkg_config( + 'libagg', 'agg2/agg_basics.h', min_version='PATCH') + except CheckFailed as e: + self.__class__.found_external = False + return str(e) + ' Using local copy.' + + def add_flags(self, ext, add_sources=True): + if self.found_external: + pkg_config.setup_extension(ext, 'libagg') + else: + ext.include_dirs.append('extern/agg24-svn/include') + if add_sources: + agg_sources = [ + 'agg_bezier_arc.cpp', + 'agg_curves.cpp', + 'agg_image_filters.cpp', + 'agg_trans_affine.cpp', + 'agg_vcgen_contour.cpp', + 'agg_vcgen_dash.cpp', + 'agg_vcgen_stroke.cpp', + 'agg_vpgen_segmentator.cpp' + ] + ext.sources.extend( + os.path.join('extern', 'agg24-svn', 'src', x) for x in agg_sources) + + +class FreeType(SetupPackage): + name = "freetype" + + def check(self): + if sys.platform == 'win32': + check_include_file(get_include_dirs(), 'ft2build.h', 'freetype') + return 'Using unknown version found on system.' + + status, output = getstatusoutput("freetype-config --ftversion") + if status == 0: + version = output + else: + version = None + + # Early versions of freetype grep badly inside freetype-config, + # so catch those cases. (tested with 2.5.3). + if version is None or 'No such file or directory\ngrep:' in version: + version = self.version_from_header() + + # pkg_config returns the libtool version rather than the + # freetype version so we need to explicitly pass the version + # to _check_for_pkg_config + return self._check_for_pkg_config( + 'freetype2', 'ft2build.h', + min_version='2.3', version=version) + + def version_from_header(self): + version = 'unknown' + ext = self.get_extension() + if ext is None: + return version + # Return the first version found in the include dirs. + for include_dir in ext.include_dirs: + header_fname = os.path.join(include_dir, 'freetype.h') + if os.path.exists(header_fname): + major, minor, patch = 0, 0, 0 + with open(header_fname, 'r') as fh: + for line in fh: + if line.startswith('#define FREETYPE_'): + value = line.rsplit(' ', 1)[1].strip() + if 'MAJOR' in line: + major = value + elif 'MINOR' in line: + minor = value + else: + patch = value + return '.'.join([major, minor, patch]) + + def add_flags(self, ext): + pkg_config.setup_extension( + ext, 'freetype2', + default_include_dirs=[ + 'include/freetype2', 'freetype2', + 'lib/freetype2/include', + 'lib/freetype2/include/freetype2'], + default_library_dirs=[ + 'freetype2/lib'], + default_libraries=['freetype', 'z']) + + +class FT2Font(SetupPackage): + name = 'ft2font' + + def get_extension(self): + sources = [ + 'src/ft2font.cpp', + 'src/ft2font_wrapper.cpp', + 'src/mplutils.cpp' + ] + ext = make_extension('matplotlib.ft2font', sources) + FreeType().add_flags(ext) + Numpy().add_flags(ext) + return ext + + +class Png(SetupPackage): + name = "png" + + def check(self): + if sys.platform == 'win32': + check_include_file(get_include_dirs(), 'png.h', 'png') + return 'Using unknown version found on system.' + + status, output = getstatusoutput("libpng-config --version") + if status == 0: + version = output + else: + version = None + + try: + return self._check_for_pkg_config( + 'libpng', 'png.h', + min_version='1.2', version=version) + except CheckFailed as e: + if has_include_file(get_include_dirs(), 'png.h'): + return str(e) + ' Using unknown version found on system.' + raise + + def get_extension(self): + sources = [ + 'src/_png.cpp', + 'src/mplutils.cpp' + ] + ext = make_extension('matplotlib._png', sources) + pkg_config.setup_extension( + ext, 'libpng', default_libraries=['png', 'z'], + alt_exec='libpng-config --ldflags') + Numpy().add_flags(ext) + return ext + + +class Qhull(SetupPackage): + name = "qhull" + + def check(self): + self.__class__.found_external = True + try: + return self._check_for_pkg_config( + 'qhull', 'qhull/qhull_a.h', min_version='2003.1') + except CheckFailed as e: + self.__class__.found_pkgconfig = False + # Qhull may not be in the pkg-config system but may still be + # present on this system, so check if the header files can be + # found. + include_dirs = [ + os.path.join(x, 'qhull') for x in get_include_dirs()] + if has_include_file(include_dirs, 'qhull_a.h'): + return 'Using system Qhull (version unknown, no pkg-config info)' + else: + self.__class__.found_external = False + return str(e) + ' Using local copy.' + + def add_flags(self, ext): + if self.found_external: + pkg_config.setup_extension(ext, 'qhull', + default_libraries=['qhull']) + else: + ext.include_dirs.append('extern') + ext.sources.extend(glob.glob('extern/qhull/*.c')) + + +class TTConv(SetupPackage): + name = "ttconv" + + def get_extension(self): + sources = [ + 'src/_ttconv.cpp', + 'extern/ttconv/pprdrv_tt.cpp', + 'extern/ttconv/pprdrv_tt2.cpp', + 'extern/ttconv/ttutil.cpp' + ] + ext = make_extension('matplotlib.ttconv', sources) + Numpy().add_flags(ext) + ext.include_dirs.append('extern') + return ext + + +class Path(SetupPackage): + name = "path" + + def get_extension(self): + sources = [ + 'src/py_converters.cpp', + 'src/_path_wrapper.cpp' + ] + + ext = make_extension('matplotlib._path', sources) + Numpy().add_flags(ext) + LibAgg().add_flags(ext) + return ext + + +class Image(SetupPackage): + name = "image" + + def get_extension(self): + sources = [ + 'src/_image.cpp', + 'src/mplutils.cpp', + 'src/_image_wrapper.cpp' + ] + ext = make_extension('matplotlib._image', sources) + Numpy().add_flags(ext) + LibAgg().add_flags(ext) + return ext + + +class ContourLegacy(SetupPackage): + name = "contour_legacy" + + def get_extension(self): + sources = [ + "src/cntr.c" + ] + ext = make_extension('matplotlib._cntr', sources) + Numpy().add_flags(ext) + return ext + + +class Contour(SetupPackage): + name = "contour" + + def get_extension(self): + sources = [ + "src/_contour.cpp", + "src/_contour_wrapper.cpp", + ] + ext = make_extension('matplotlib._contour', sources) + Numpy().add_flags(ext) + return ext + + +class Delaunay(SetupPackage): + name = "delaunay" + + def get_packages(self): + return ['matplotlib.delaunay'] + + def get_extension(self): + sources = ["_delaunay.cpp", "VoronoiDiagramGenerator.cpp", + "delaunay_utils.cpp", "natneighbors.cpp"] + sources = [os.path.join('lib/matplotlib/delaunay', s) for s in sources] + ext = make_extension('matplotlib._delaunay', sources) + Numpy().add_flags(ext) + return ext + + +class QhullWrap(SetupPackage): + name = "qhull_wrap" + + def get_extension(self): + sources = ['src/qhull_wrap.c'] + ext = make_extension('matplotlib._qhull', sources, + define_macros=[('MPL_DEVNULL', os.devnull)]) + Numpy().add_flags(ext) + Qhull().add_flags(ext) + return ext + + +class Tri(SetupPackage): + name = "tri" + + def get_extension(self): + sources = [ + "lib/matplotlib/tri/_tri.cpp", + "lib/matplotlib/tri/_tri_wrapper.cpp", + "src/mplutils.cpp" + ] + ext = make_extension('matplotlib._tri', sources) + Numpy().add_flags(ext) + return ext + + +class Externals(SetupPackage): + name = "externals" + + def get_packages(self): + return ['matplotlib.externals'] + + +class Pytz(SetupPackage): + name = "pytz" + + def check(self): + try: + import pytz + except ImportError: + raise CheckFailed( + "could not be found") + + return "using pytz version %s" % pytz.__version__ + + def get_install_requires(self): + return ['pytz'] + + +class Cycler(SetupPackage): + name = "cycler" + + def check(self): + try: + import cycler + except ImportError: + raise CheckFailed( + "could not be found") + + return "using cycler version %s" % cycler.__version__ + + def get_install_requires(self): + return ['cycler'] + + +class Dateutil(SetupPackage): + name = "dateutil" + + def __init__(self, version=None): + self.version = version + + def check(self): + try: + import dateutil + except ImportError: + # dateutil 2.1 has a file encoding bug that breaks installation on + # python 3.3 + # https://github.com/matplotlib/matplotlib/issues/2373 + # hack around the problem by installing the (working) v2.0 + #major, minor1, _, _, _ = sys.version_info + #if self.version is None and (major, minor1) == (3, 3): + #self.version = '!=2.1' + + raise CheckFailed ( + "could not be found") + + major, minor1, _, _, _ = sys.version_info + if dateutil.__version__ == '2.1' and (major, minor1) == (3, 3): + raise CheckFailed ( + "dateutil v. 2.1 has a bug that breaks installation" + "on python 3.3.x, use another dateutil version") + return "using dateutil version %s" % dateutil.__version__ + + def get_install_requires(self): + dateutil = 'python-dateutil' + if self.version is not None: + dateutil += self.version + return [dateutil] + + +class Tornado(SetupPackage): + name = "tornado" + + def check(self): + try: + import tornado + except ImportError: + raise CheckFailed ( + "could not be found") + + return "using tornado version %s" % tornado.version + + +class Pyparsing(SetupPackage): + name = "pyparsing" + # pyparsing 2.0.4 has broken python 3 support. + # pyparsing 2.1.2 is broken in python3.4/3.3. + def is_ok(self): + # pyparsing 2.0.0 bug, but it may be patched in distributions + try: + import pyparsing + f = pyparsing.Forward() + f <<= pyparsing.Literal('a') + return f is not None + except (ImportError, TypeError): + return False + + def check(self): + try: + import pyparsing + except ImportError: + raise CheckFailed( + "could not be found") + + required = [1, 5, 6] + if [int(x) for x in pyparsing.__version__.split('.')] < required: + raise CheckFailed( + "matplotlib requires pyparsing >= {0}".format( + '.'.join(str(x) for x in required))) + + if not self.is_ok(): + return ( + "Your pyparsing contains a bug that will be monkey-patched by " + "matplotlib. For best results, upgrade to pyparsing 2.0.1 or " + "later.") + + return "using pyparsing version %s" % pyparsing.__version__ + + def get_install_requires(self): + versionstring = 'pyparsing>=1.5.6,!=2.0.4,!=2.1.2' + if self.is_ok(): + return [versionstring] + else: + return [versionstring + ',!=2.0.0'] + + +class BackendAgg(OptionalBackendPackage): + name = "agg" + force = True + + def get_extension(self): + sources = [ + "src/mplutils.cpp", + "src/py_converters.cpp", + "src/_backend_agg.cpp", + "src/_backend_agg_wrapper.cpp" + ] + ext = make_extension('matplotlib.backends._backend_agg', sources) + Numpy().add_flags(ext) + LibAgg().add_flags(ext) + FreeType().add_flags(ext) + return ext + + +class BackendTkAgg(OptionalBackendPackage): + name = "tkagg" + force = True + + def check(self): + return "installing; run-time loading from Python Tcl / Tk" + + def get_extension(self): + sources = [ + 'src/py_converters.cpp', + 'src/_tkagg.cpp' + ] + + ext = make_extension('matplotlib.backends._tkagg', sources) + self.add_flags(ext) + Numpy().add_flags(ext) + LibAgg().add_flags(ext, add_sources=False) + return ext + + def add_flags(self, ext): + ext.include_dirs.extend(['src']) + if sys.platform == 'win32': + # PSAPI library needed for finding Tcl / Tk at run time + ext.libraries.extend(['psapi']) + + +class BackendGtk(OptionalBackendPackage): + name = "gtk" + + def check_requirements(self): + try: + import gtk + except ImportError: + raise CheckFailed("Requires pygtk") + except RuntimeError: + raise CheckFailed('pygtk present, but import failed.') + else: + version = (2, 2, 0) + if gtk.pygtk_version < version: + raise CheckFailed( + "Requires pygtk %d.%d.%d or later. " + "Found %d.%d.%d" % (version + gtk.pygtk_version)) + + ext = self.get_extension() + self.add_flags(ext) + check_include_file(ext.include_dirs, + os.path.join("gtk", "gtk.h"), + 'gtk') + check_include_file(ext.include_dirs, + os.path.join("pygtk", "pygtk.h"), + 'pygtk') + + return 'Gtk: %s pygtk: %s' % ( + ".".join(str(x) for x in gtk.gtk_version), + ".".join(str(x) for x in gtk.pygtk_version)) + + def get_package_data(self): + return {'matplotlib': ['mpl-data/*.glade']} + + def get_extension(self): + sources = [ + 'src/_backend_gdk.c' + ] + ext = make_extension('matplotlib.backends._backend_gdk', sources) + self.add_flags(ext) + Numpy().add_flags(ext) + return ext + + def add_flags(self, ext): + if sys.platform == 'win32': + def getoutput(s): + ret = os.popen(s).read().strip() + return ret + + if 'PKG_CONFIG_PATH' not in os.environ: + # If Gtk+ is installed, pkg-config is required to be installed + os.environ['PKG_CONFIG_PATH'] = 'C:\\GTK\\lib\\pkgconfig' + + # popen broken on my win32 plaform so I can't use pkgconfig + ext.library_dirs.extend( + ['C:/GTK/bin', 'C:/GTK/lib']) + + ext.include_dirs.extend( + ['win32_static/include/pygtk-2.0', + 'C:/GTK/include', + 'C:/GTK/include/gobject', + 'C:/GTK/include/gext', + 'C:/GTK/include/glib', + 'C:/GTK/include/pango', + 'C:/GTK/include/atk', + 'C:/GTK/include/X11', + 'C:/GTK/include/cairo', + 'C:/GTK/include/gdk', + 'C:/GTK/include/gdk-pixbuf', + 'C:/GTK/include/gtk', + ]) + + pygtkIncludes = getoutput( + 'pkg-config --cflags-only-I pygtk-2.0').split() + gtkIncludes = getoutput( + 'pkg-config --cflags-only-I gtk+-2.0').split() + includes = pygtkIncludes + gtkIncludes + ext.include_dirs.extend([include[2:] for include in includes]) + + pygtkLinker = getoutput('pkg-config --libs pygtk-2.0').split() + gtkLinker = getoutput('pkg-config --libs gtk+-2.0').split() + linkerFlags = pygtkLinker + gtkLinker + + ext.libraries.extend( + [flag[2:] for flag in linkerFlags if flag.startswith('-l')]) + + ext.library_dirs.extend( + [flag[2:] for flag in linkerFlags if flag.startswith('-L')]) + + ext.extra_link_args.extend( + [flag for flag in linkerFlags if not + (flag.startswith('-l') or flag.startswith('-L'))]) + + # visual studio doesn't need the math library + if (sys.platform == 'win32' and + win32_compiler == 'msvc' and + 'm' in ext.libraries): + ext.libraries.remove('m') + + elif sys.platform != 'win32': + pkg_config.setup_extension(ext, 'pygtk-2.0') + pkg_config.setup_extension(ext, 'gtk+-2.0') + + +class BackendGtkAgg(BackendGtk): + name = "gtkagg" + + def check(self): + try: + return super(BackendGtkAgg, self).check() + except: + raise + + def get_package_data(self): + return {'matplotlib': ['mpl-data/*.glade']} + + def get_extension(self): + sources = [ + 'src/py_converters.cpp', + 'src/_gtkagg.cpp', + 'src/mplutils.cpp' + ] + ext = make_extension('matplotlib.backends._gtkagg', sources) + self.add_flags(ext) + LibAgg().add_flags(ext) + Numpy().add_flags(ext) + return ext + + +def backend_gtk3agg_internal_check(x): + try: + import gi + except ImportError: + return (False, "Requires pygobject to be installed.") + + try: + gi.require_version("Gtk", "3.0") + except ValueError: + return (False, "Requires gtk3 development files to be installed.") + except AttributeError: + return (False, "pygobject version too old.") + + try: + from gi.repository import Gtk, Gdk, GObject + except (ImportError, RuntimeError): + return (False, "Requires pygobject to be installed.") + + return (True, "version %s.%s.%s" % ( + Gtk.get_major_version(), + Gtk.get_micro_version(), + Gtk.get_minor_version())) + + +class BackendGtk3Agg(OptionalBackendPackage): + name = "gtk3agg" + + def check_requirements(self): + if 'TRAVIS' in os.environ: + raise CheckFailed("Can't build with Travis") + + # This check needs to be performed out-of-process, because + # importing gi and then importing regular old pygtk afterward + # segfaults the interpreter. + try: + p = multiprocessing.Pool() + except: + return "unknown (can not use multiprocessing to determine)" + try: + res = p.map_async(backend_gtk3agg_internal_check, [0]) + success, msg = res.get(timeout=10)[0] + except multiprocessing.TimeoutError: + p.terminate() + # No result returned. Probaly hanging, terminate the process. + success = False + raise CheckFailed("Check timed out") + except: + p.close() + # Some other error. + success = False + msg = "Could not determine" + raise + else: + p.close() + finally: + p.join() + + if success: + return msg + else: + raise CheckFailed(msg) + + def get_package_data(self): + return {'matplotlib': ['mpl-data/*.glade']} + + +def backend_gtk3cairo_internal_check(x): + try: + import cairocffi + except ImportError: + try: + import cairo + except ImportError: + return (False, "Requires cairocffi or pycairo to be installed.") + + try: + import gi + except ImportError: + return (False, "Requires pygobject to be installed.") + + try: + gi.require_version("Gtk", "3.0") + except ValueError: + return (False, "Requires gtk3 development files to be installed.") + except AttributeError: + return (False, "pygobject version too old.") + + try: + from gi.repository import Gtk, Gdk, GObject + except (RuntimeError, ImportError): + return (False, "Requires pygobject to be installed.") + + return (True, "version %s.%s.%s" % ( + Gtk.get_major_version(), + Gtk.get_micro_version(), + Gtk.get_minor_version())) + + +class BackendGtk3Cairo(OptionalBackendPackage): + name = "gtk3cairo" + + def check_requirements(self): + if 'TRAVIS' in os.environ: + raise CheckFailed("Can't build with Travis") + + # This check needs to be performed out-of-process, because + # importing gi and then importing regular old pygtk afterward + # segfaults the interpreter. + try: + p = multiprocessing.Pool() + except: + return "unknown (can not use multiprocessing to determine)" + try: + res = p.map_async(backend_gtk3cairo_internal_check, [0]) + success, msg = res.get(timeout=10)[0] + except multiprocessing.TimeoutError: + p.terminate() + # No result returned. Probaly hanging, terminate the process. + success = False + raise CheckFailed("Check timed out") + except: + p.close() + success = False + raise + else: + p.close() + finally: + p.join() + + if success: + return msg + else: + raise CheckFailed(msg) + + def get_package_data(self): + return {'matplotlib': ['mpl-data/*.glade']} + + +class BackendWxAgg(OptionalBackendPackage): + name = "wxagg" + + def check_requirements(self): + wxversioninstalled = True + try: + import wxversion + except ImportError: + wxversioninstalled = False + + if wxversioninstalled: + try: + _wx_ensure_failed = wxversion.AlreadyImportedError + except AttributeError: + _wx_ensure_failed = wxversion.VersionError + + try: + wxversion.ensureMinimal('2.8') + except _wx_ensure_failed: + pass + + try: + import wx + backend_version = wx.VERSION_STRING + except ImportError: + raise CheckFailed("requires wxPython") + + # Extra version check in case wxversion lacks AlreadyImportedError; + # then VersionError might have been raised and ignored when + # there really *is* a problem with the version. + major, minor = [int(n) for n in backend_version.split('.')[:2]] + if major < 2 or (major < 3 and minor < 8): + raise CheckFailed( + "Requires wxPython 2.8, found %s" % backend_version) + + return "version %s" % backend_version + + +class BackendMacOSX(OptionalBackendPackage): + name = 'macosx' + + def check_requirements(self): + if sys.platform != 'darwin': + raise CheckFailed("Mac OS-X only") + + return 'darwin' + + def get_extension(self): + sources = [ + 'src/_macosx.m', + 'src/py_converters.cpp', + 'src/path_cleanup.cpp' + ] + + ext = make_extension('matplotlib.backends._macosx', sources) + Numpy().add_flags(ext) + LibAgg().add_flags(ext) + ext.extra_link_args.extend(['-framework', 'Cocoa']) + return ext + + +class Windowing(OptionalBackendPackage): + """ + Builds the windowing extension. + """ + name = "windowing" + + def check_requirements(self): + if sys.platform != 'win32': + raise CheckFailed("Microsoft Windows only") + config = self.get_config() + if config is False: + raise CheckFailed("skipping due to configuration") + return "" + + def get_extension(self): + sources = [ + "src/_windowing.cpp" + ] + ext = make_extension('matplotlib._windowing', sources) + ext.include_dirs.extend(['C:/include']) + ext.libraries.extend(['user32']) + ext.library_dirs.extend(['C:/lib']) + ext.extra_link_args.append("-mwindows") + return ext + + +class BackendQtBase(OptionalBackendPackage): + + def convert_qt_version(self, version): + version = '%x' % version + temp = [] + while len(version) > 0: + version, chunk = version[:-2], version[-2:] + temp.insert(0, str(int(chunk, 16))) + return '.'.join(temp) + + def check_requirements(self): + ''' + If PyQt4/PyQt5 is already imported, importing PyQt5/PyQt4 will fail + so we need to test in a subprocess (as for Gtk3). + ''' + try: + p = multiprocessing.Pool() + + except: + # Can't do multiprocessing, fall back to normal approach ( this will fail if importing both PyQt4 and PyQt5 ) + try: + # Try in-process + msg = self.callback(self) + + except RuntimeError: + raise CheckFailed("Could not import: are PyQt4 & PyQt5 both installed?") + + except: + # Raise any other exceptions + raise + + else: + # Multiprocessing OK + try: + res = p.map_async(self.callback, [self]) + msg = res.get(timeout=10)[0] + except multiprocessing.TimeoutError: + p.terminate() + # No result returned. Probaly hanging, terminate the process. + raise CheckFailed("Check timed out") + except: + # Some other error. + p.close() + raise + else: + # Clean exit + p.close() + finally: + # Tidy up multiprocessing + p.join() + + return msg + + +def backend_pyside_internal_check(self): + try: + from PySide import __version__ + from PySide import QtCore + except ImportError: + raise CheckFailed("PySide not found") + else: + return ("Qt: %s, PySide: %s" % + (QtCore.__version__, __version__)) + + +def backend_pyqt4_internal_check(self): + try: + from PyQt4 import QtCore + except ImportError: + raise CheckFailed("PyQt4 not found") + + try: + qt_version = QtCore.QT_VERSION + pyqt_version_str = QtCore.QT_VERSION_STR + except AttributeError: + raise CheckFailed('PyQt4 not correctly imported') + else: + return ("Qt: %s, PyQt: %s" % (self.convert_qt_version(qt_version), pyqt_version_str)) + + +def backend_qt4_internal_check(self): + successes = [] + failures = [] + try: + successes.append(backend_pyside_internal_check(self)) + except CheckFailed as e: + failures.append(str(e)) + + try: + successes.append(backend_pyqt4_internal_check(self)) + except CheckFailed as e: + failures.append(str(e)) + + if len(successes) == 0: + raise CheckFailed('; '.join(failures)) + return '; '.join(successes + failures) + + +class BackendQt4(BackendQtBase): + name = "qt4agg" + + def __init__(self, *args, **kwargs): + BackendQtBase.__init__(self, *args, **kwargs) + self.callback = backend_qt4_internal_check + + +def backend_qt5_internal_check(self): + try: + from PyQt5 import QtCore + except ImportError: + raise CheckFailed("PyQt5 not found") + + try: + qt_version = QtCore.QT_VERSION + pyqt_version_str = QtCore.QT_VERSION_STR + except AttributeError: + raise CheckFailed('PyQt5 not correctly imported') + else: + return ("Qt: %s, PyQt: %s" % (self.convert_qt_version(qt_version), pyqt_version_str)) + + +class BackendQt5(BackendQtBase): + name = "qt5agg" + + def __init__(self, *args, **kwargs): + BackendQtBase.__init__(self, *args, **kwargs) + self.callback = backend_qt5_internal_check + + +class BackendCairo(OptionalBackendPackage): + name = "cairo" + + def check_requirements(self): + try: + import cairocffi + except ImportError: + try: + import cairo + except ImportError: + raise CheckFailed("cairocffi or pycairo not found") + else: + return "pycairo version %s" % cairo.version + else: + return "cairocffi version %s" % cairocffi.version + + +class DviPng(SetupPackage): + name = "dvipng" + optional = True + + def check(self): + try: + output = check_output('dvipng -version', shell=True, + stderr=subprocess.STDOUT) + return "version %s" % output.splitlines()[1].decode().split()[-1] + except (IndexError, ValueError, subprocess.CalledProcessError): + raise CheckFailed() + + +class Ghostscript(SetupPackage): + name = "ghostscript" + optional = True + + def check(self): + try: + if sys.platform == 'win32': + command = 'gswin32c --version' + try: + output = check_output(command, shell=True, + stderr=subprocess.STDOUT) + except subprocess.CalledProcessError: + command = 'gswin64c --version' + output = check_output(command, shell=True, + stderr=subprocess.STDOUT) + else: + command = 'gs --version' + output = check_output(command, shell=True, + stderr=subprocess.STDOUT) + return "version %s" % output.decode()[:-1] + except (IndexError, ValueError, subprocess.CalledProcessError): + raise CheckFailed() + + +class LaTeX(SetupPackage): + name = "latex" + optional = True + + def check(self): + try: + output = check_output('latex -version', shell=True, + stderr=subprocess.STDOUT) + line = output.splitlines()[0].decode() + pattern = '(3\.1\d+)|(MiKTeX \d+.\d+)' + match = re.search(pattern, line) + return "version %s" % match.group(0) + except (IndexError, ValueError, AttributeError, subprocess.CalledProcessError): + raise CheckFailed() + + +class PdfToPs(SetupPackage): + name = "pdftops" + optional = True + + def check(self): + try: + output = check_output('pdftops -v', shell=True, + stderr=subprocess.STDOUT) + for line in output.splitlines(): + line = line.decode() + if 'version' in line: + return "version %s" % line.split()[2] + except (IndexError, ValueError, subprocess.CalledProcessError): + pass + + raise CheckFailed() + + +class OptionalPackageData(OptionalPackage): + config_category = "package_data" + + +class Dlls(OptionalPackageData): + """ + On Windows, this packages any DLL files that can be found in the + lib/matplotlib/* directories. + """ + name = "dlls" + + def check_requirements(self): + if sys.platform != 'win32': + raise CheckFailed("Microsoft Windows only") + + def get_package_data(self): + return {'': ['*.dll']} + + @classmethod + def get_config(cls): + """ + Look at `setup.cfg` and return one of ["auto", True, False] indicating + if the package is at default state ("auto"), forced by the user (True) + or opted-out (False). + """ + try: + return config.getboolean(cls.config_category, cls.name) + except: + return False # <-- default -- cgit v1.2.3