Skip to content

Propagate changes to backend loading to setup/setupext. #11962

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ exclude =

per-file-ignores =
setup.py: E402
setupext.py: E302, E501
setupext.py: E501

tools/compare_backend_driver_results.py: E501
tools/subset.py: E221, E231, E251, E261, E302, E501, E701
Expand Down
9 changes: 1 addition & 8 deletions setup.cfg.template
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,9 @@
# behavior
#
#agg = auto
#cairo = auto
#gtk3agg = auto
#gtk3cairo = auto
#macosx = auto
#pyside = auto
#qt4agg = auto
#tkagg = auto
#windowing = auto
#wxagg = auto

[rc_options]
# User-configurable options
Expand All @@ -81,10 +75,9 @@
#
# The Agg, Ps, Pdf and SVG backends do not require external dependencies. Do
# not choose MacOSX, or TkAgg if you have disabled the relevant extension
# modules. Agg will be used by default.
# modules. The default is determined by fallback.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this change. Isn't the fallback approach being removed by this PR? (Or is this referring to something else recent that I probably missed while away from the project for the last couple of years...)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback has been moved to runtime (not build time).

#
#backend = Agg
#

[package_data]
# Package additional files found in the lib/matplotlib directories.
Expand Down
32 changes: 10 additions & 22 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,18 +70,9 @@
setupext.Tests(),
setupext.Toolkits_Tests(),
'Optional backend extensions',
# These backends are listed in order of preference, the first
# being the most preferred. The first one that looks like it will
# work will be selected as the default backend.
setupext.BackendMacOSX(),
setupext.BackendQt5(),
setupext.BackendQt4(),
setupext.BackendGtk3Agg(),
setupext.BackendGtk3Cairo(),
setupext.BackendTkAgg(),
setupext.BackendWxAgg(),
setupext.BackendAgg(),
setupext.BackendCairo(),
setupext.BackendTkAgg(),
setupext.BackendMacOSX(),
setupext.Windowing(),
'Optional package data',
setupext.Dlls(),
Expand Down Expand Up @@ -133,7 +124,6 @@ def run(self):
package_dir = {'': 'lib'}
install_requires = []
setup_requires = []
default_backend = None

# If the user just queries for information, don't bother figuring out which
# packages to build or install.
Expand Down Expand Up @@ -169,10 +159,6 @@ def run(self):
required_failed.append(package)
else:
good_packages.append(package)
if (isinstance(package, setupext.OptionalBackendPackage)
and package.runtime_check()
and default_backend is None):
default_backend = package.name
print_raw('')

# Abort if any of the required packages can not be built.
Expand Down Expand Up @@ -203,14 +189,16 @@ def run(self):
setup_requires.extend(package.get_setup_requires())

# Write the default matplotlibrc file
if default_backend is None:
default_backend = 'svg'
if setupext.options['backend']:
default_backend = setupext.options['backend']
with open('matplotlibrc.template') as fd:
template = fd.read()
template_lines = fd.read().splitlines(True)
backend_line_idx, = [ # Also asserts that there is a single such line.
idx for idx, line in enumerate(template_lines)
if line.startswith('#backend ')]
if setupext.options['backend']:
template_lines[backend_line_idx] = (
'backend: {}'.format(setupext.options['backend']))
with open('lib/matplotlib/mpl-data/matplotlibrc', 'w') as fd:
fd.write(template)
fd.write(''.join(template_lines))

# Finalize the extension modules so they can get the Numpy include
# dirs
Expand Down
230 changes: 0 additions & 230 deletions setupext.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,13 +384,6 @@ def check(self):
"""
pass

def runtime_check(self):
"""
True if the runtime dependencies of the backend are met. Assumes that
the build-time dependencies are met.
"""
return True

def get_packages(self):
"""
Get a list of package names to add to the configuration.
Expand Down Expand Up @@ -1368,10 +1361,6 @@ class BackendTkAgg(OptionalBackendPackage):
def check(self):
return "installing; run-time loading from Python Tcl / Tk"

def runtime_check(self):
"""Checks whether TkAgg runtime dependencies are met."""
return importlib.util.find_spec("tkinter") is not None

def get_extension(self):
sources = [
'src/_tkagg.cpp'
Expand All @@ -1391,57 +1380,6 @@ def add_flags(self, ext):
ext.libraries.extend(['dl'])


class BackendGtk3Agg(OptionalBackendPackage):
name = "gtk3agg"

def check_requirements(self):
if not any(map(importlib.util.find_spec, ["cairocffi", "cairo"])):
raise CheckFailed("Requires cairocffi or pycairo to be installed.")

try:
import gi
except ImportError:
raise CheckFailed("Requires pygobject to be installed.")

try:
gi.require_version("Gtk", "3.0")
except ValueError:
raise CheckFailed(
"Requires gtk3 development files to be installed.")
except AttributeError:
raise CheckFailed("pygobject version too old.")

try:
from gi.repository import Gtk, Gdk, GObject
except (ImportError, RuntimeError):
raise CheckFailed("Requires pygobject to be installed.")

return "version {}.{}.{}".format(
Gtk.get_major_version(),
Gtk.get_minor_version(),
Gtk.get_micro_version())

def get_package_data(self):
return {'matplotlib': ['mpl-data/*.glade']}


class BackendGtk3Cairo(BackendGtk3Agg):
name = "gtk3cairo"


class BackendWxAgg(OptionalBackendPackage):
name = "wxagg"

def check_requirements(self):
try:
import wx
backend_version = wx.VERSION_STRING
except ImportError:
raise CheckFailed("requires wxPython")

return "version %s" % backend_version


class BackendMacOSX(OptionalBackendPackage):
name = 'macosx'

Expand Down Expand Up @@ -1487,174 +1425,6 @@ def get_extension(self):
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?")

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. Probably 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.PYQT_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_pyside2_internal_check(self):
try:
from PySide2 import __version__
from PySide2 import QtCore
except ImportError:
raise CheckFailed("PySide2 not found")
else:
return ("Qt: %s, PySide2: %s" %
(QtCore.__version__, __version__))

def backend_pyqt5_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.PYQT_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))

def backend_qt5_internal_check(self):
successes = []
failures = []
try:
successes.append(backend_pyside2_internal_check(self))
except CheckFailed as e:
failures.append(str(e))

try:
successes.append(backend_pyqt5_internal_check(self))
except CheckFailed as e:
failures.append(str(e))

if len(successes) == 0:
raise CheckFailed('; '.join(failures))
return '; '.join(successes + failures)

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 OptionalPackageData(OptionalPackage):
config_category = "package_data"

Expand Down