Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8b513a5

Browse files
committed
Unify querying of executable versions.
1 parent 6336f2d commit 8b513a5

File tree

2 files changed

+139
-135
lines changed

2 files changed

+139
-135
lines changed

lib/matplotlib/__init__.py

Lines changed: 136 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,13 @@
103103
unicode_literals)
104104

105105
import six
106+
from six.moves.urllib.request import urlopen
107+
from six.moves import reload_module as reload
106108

107109
import atexit
108-
from collections import MutableMapping
110+
from collections import MutableMapping, namedtuple
109111
import contextlib
110-
import distutils.version
111-
import distutils.sysconfig
112+
from distutils.version import LooseVersion
112113
import functools
113114
import io
114115
import inspect
@@ -122,6 +123,11 @@
122123
import tempfile
123124
import warnings
124125

126+
try:
127+
from functools import lru_cache
128+
except ImportError:
129+
from backports.functools_lru_cache import lru_cache
130+
125131
# cbook must import matplotlib only within function
126132
# definitions, so it is safe to import from it here.
127133
from . import cbook
@@ -131,8 +137,6 @@
131137
from matplotlib.rcsetup import defaultParams, validate_backend, cycler
132138

133139
import numpy
134-
from six.moves.urllib.request import urlopen
135-
from six.moves import reload_module as reload
136140

137141
# Get the version from the _version.py versioneer file. For a git checkout,
138142
# this is computed based on the number of commits since the last tag.
@@ -177,9 +181,7 @@ def compare_versions(a, b):
177181
a = a.decode('ascii')
178182
if isinstance(b, bytes):
179183
b = b.decode('ascii')
180-
a = distutils.version.LooseVersion(a)
181-
b = distutils.version.LooseVersion(b)
182-
return a >= b
184+
return LooseVersion(a) >= LooseVersion(b)
183185
else:
184186
return False
185187

@@ -412,89 +414,125 @@ def wrapper(*args, **kwargs):
412414
return wrapper
413415

414416

417+
_ExecInfo = namedtuple("_ExecInfo", "executable version")
418+
419+
420+
@lru_cache()
421+
def get_executable_info(name):
422+
"""Get the version of some executables that Matplotlib depends on.
423+
424+
.. warning:
425+
The list of executables that this function supports is set according to
426+
Matplotlib's internal needs, and may change without notice.
427+
428+
Parameters
429+
----------
430+
name : str
431+
The executable to query. The following values are currently supported:
432+
"dvipng", "gs", "inkscape", "pdftops", "tex". This list is subject to
433+
change without notice.
434+
435+
Returns
436+
-------
437+
If the executable is found, a namedtuple with fields ``executable`` (`str`)
438+
and ``version`` (`distutils.version.LooseVersion`, or ``None`` if the
439+
version cannot be determined); ``None`` if the executable is not found.
440+
"""
441+
442+
def impl(args, regex, min_ver=None):
443+
# Execute the subprocess specified by args; capture stdout and stderr.
444+
# Search for a regex match in the output; if the match succeeds, use
445+
# the *first group* of the match as the version.
446+
# If min_ver is not None, emit a warning if the version is less than
447+
# min_ver.
448+
try:
449+
proc = subprocess.Popen(
450+
[str(arg) for arg in args], # str(): Py2 compat.
451+
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
452+
universal_newlines=True)
453+
proc.wait()
454+
except OSError:
455+
return None
456+
match = re.search(regex, proc.stdout.read())
457+
if match:
458+
version = LooseVersion(match.group(1))
459+
if min_ver is not None and version < str(min_ver):
460+
warnings.warn("You have {} version {} but the minimum version "
461+
"supported by Matplotlib is {}."
462+
.format(args[0], version, min_ver))
463+
return None
464+
return _ExecInfo(str(args[0]), version) # str(): Py2 compat.
465+
else:
466+
return None
467+
468+
if name == "dvipng":
469+
info = impl(["dvipng", "-version"], "(?m)^dvipng .* (.+)", "1.6")
470+
elif name == "gs":
471+
execs = (["gswin32c", "gswin64c", "mgs", "gs"] # "mgs" for miktex.
472+
if sys.platform == "win32" else
473+
["gs"])
474+
info = next(filter(None, (impl([e, "--version"], "(.*)", "8.60")
475+
for e in execs)),
476+
None)
477+
elif name == "inkscape":
478+
info = impl(["inkscape", "-V"], "^Inkscape ([^ ]*)")
479+
elif name == "pdftops":
480+
info = impl(["pdftops", "-v"], "^pdftops version (.*)")
481+
if info and not (str("3.0") <= info.version
482+
# poppler version numbers.
483+
or str("0.9") <= info.version <= str("1.0")):
484+
warnings.warn(
485+
"You have pdftops version {} but the minimum version "
486+
"supported by Matplotlib is 3.0.".format(info.version))
487+
return None
488+
elif name == "tex":
489+
info = (_ExecInfo(str("tex"), None) # str(): Py2 compat.
490+
if _backports.which("tex") is not None
491+
else None)
492+
else:
493+
raise ValueError("Unknown executable: {!r}".format(name))
494+
return info
495+
496+
497+
def get_all_executable_infos():
498+
"""Query all executables that Matplotlib may need.
499+
500+
.. warning:
501+
The list of executables that this function queries is set according to
502+
Matplotlib's internal needs, and may change without notice.
503+
504+
Returns
505+
-------
506+
A mapping of the required executable to its corresponding information,
507+
as returned by `get_executable_info`. The keys in the mapping are subject
508+
to change without notice.
509+
"""
510+
return {name: get_executable_info(name)
511+
for name in ["dvipng", "gs", "inkscape", "pdftops", "tex"]}
512+
513+
514+
@cbook.deprecated("2.2")
415515
def checkdep_dvipng():
416-
try:
417-
s = subprocess.Popen([str('dvipng'), '-version'],
418-
stdout=subprocess.PIPE,
419-
stderr=subprocess.PIPE)
420-
stdout, stderr = s.communicate()
421-
line = stdout.decode('ascii').split('\n')[1]
422-
v = line.split()[-1]
423-
return v
424-
except (IndexError, ValueError, OSError):
425-
return None
516+
return str(get_executable_info("dvipng").version)
426517

427518

428519
def checkdep_ghostscript():
429-
if checkdep_ghostscript.executable is None:
430-
if sys.platform == 'win32':
431-
# mgs is the name in miktex
432-
gs_execs = ['gswin32c', 'gswin64c', 'mgs', 'gs']
433-
else:
434-
gs_execs = ['gs']
435-
for gs_exec in gs_execs:
436-
try:
437-
s = subprocess.Popen(
438-
[str(gs_exec), '--version'], stdout=subprocess.PIPE,
439-
stderr=subprocess.PIPE)
440-
stdout, stderr = s.communicate()
441-
if s.returncode == 0:
442-
v = stdout[:-1].decode('ascii')
443-
checkdep_ghostscript.executable = gs_exec
444-
checkdep_ghostscript.version = v
445-
except (IndexError, ValueError, OSError):
446-
pass
520+
info = get_executable_info("gs")
521+
checkdep_ghostscript.executable = info.executable
522+
checkdep_ghostscript.version = str(info.version)
447523
return checkdep_ghostscript.executable, checkdep_ghostscript.version
448524
checkdep_ghostscript.executable = None
449525
checkdep_ghostscript.version = None
450526

451527

452-
# Deprecated, as it is unneeded and some distributions (e.g. MiKTeX 2.9.6350)
453-
# do not actually report the TeX version.
454-
@cbook.deprecated("2.1")
455-
def checkdep_tex():
456-
try:
457-
s = subprocess.Popen([str('tex'), '-version'], stdout=subprocess.PIPE,
458-
stderr=subprocess.PIPE)
459-
stdout, stderr = s.communicate()
460-
line = stdout.decode('ascii').split('\n')[0]
461-
pattern = r'3\.1\d+'
462-
match = re.search(pattern, line)
463-
v = match.group(0)
464-
return v
465-
except (IndexError, ValueError, AttributeError, OSError):
466-
return None
467-
468-
528+
@cbook.deprecated("2.2")
469529
def checkdep_pdftops():
470-
try:
471-
s = subprocess.Popen([str('pdftops'), '-v'], stdout=subprocess.PIPE,
472-
stderr=subprocess.PIPE)
473-
stdout, stderr = s.communicate()
474-
lines = stderr.decode('ascii').split('\n')
475-
for line in lines:
476-
if 'version' in line:
477-
v = line.split()[-1]
478-
return v
479-
except (IndexError, ValueError, UnboundLocalError, OSError):
480-
return None
530+
return str(get_executable_info("pdftops").version)
481531

482532

533+
@cbook.deprecated("2.2")
483534
def checkdep_inkscape():
484-
if checkdep_inkscape.version is None:
485-
try:
486-
s = subprocess.Popen([str('inkscape'), '-V'],
487-
stdout=subprocess.PIPE,
488-
stderr=subprocess.PIPE)
489-
stdout, stderr = s.communicate()
490-
lines = stdout.decode('ascii').split('\n')
491-
for line in lines:
492-
if 'Inkscape' in line:
493-
v = line.split()[1]
494-
break
495-
checkdep_inkscape.version = v
496-
except (IndexError, ValueError, UnboundLocalError, OSError):
497-
pass
535+
checkdep_inkscape.version = str(get_executable_info("inkscape").version)
498536
return checkdep_inkscape.version
499537
checkdep_inkscape.version = None
500538

@@ -519,65 +557,31 @@ def checkdep_xmllint():
519557
def checkdep_ps_distiller(s):
520558
if not s:
521559
return False
522-
523-
flag = True
524-
gs_req = '8.60'
525-
gs_exec, gs_v = checkdep_ghostscript()
526-
if not compare_versions(gs_v, gs_req):
527-
flag = False
528-
warnings.warn(('matplotlibrc ps.usedistiller option can not be used '
529-
'unless ghostscript-%s or later is installed on your '
530-
'system') % gs_req)
531-
532-
if s == 'xpdf':
533-
pdftops_req = '3.0'
534-
pdftops_req_alt = '0.9' # poppler version numbers, ugh
535-
pdftops_v = checkdep_pdftops()
536-
if compare_versions(pdftops_v, pdftops_req):
537-
pass
538-
elif (compare_versions(pdftops_v, pdftops_req_alt) and not
539-
compare_versions(pdftops_v, '1.0')):
540-
pass
541-
else:
542-
flag = False
543-
warnings.warn(('matplotlibrc ps.usedistiller can not be set to '
544-
'xpdf unless xpdf-%s or later is installed on '
545-
'your system') % pdftops_req)
546-
547-
if flag:
548-
return s
549-
else:
560+
if not get_executable_info("gs"):
561+
warnings.warn(
562+
"Setting matplotlibrc ps.usedistiller requires ghostscript.")
563+
return False
564+
if s == "xpdf" and not get_executable_info("pdftops"):
565+
warnings.warn(
566+
"setting matplotlibrc ps.usedistiller to 'xpdf' requires xpdf.")
550567
return False
568+
return s
551569

552570

553571
def checkdep_usetex(s):
554572
if not s:
555573
return False
556-
557-
gs_req = '8.60'
558-
dvipng_req = '1.6'
559-
flag = True
560-
561-
if _backports.which("tex") is None:
562-
flag = False
563-
warnings.warn('matplotlibrc text.usetex option can not be used unless '
564-
'TeX is installed on your system')
565-
566-
dvipng_v = checkdep_dvipng()
567-
if not compare_versions(dvipng_v, dvipng_req):
568-
flag = False
569-
warnings.warn('matplotlibrc text.usetex can not be used with *Agg '
570-
'backend unless dvipng-%s or later is installed on '
571-
'your system' % dvipng_req)
572-
573-
gs_exec, gs_v = checkdep_ghostscript()
574-
if not compare_versions(gs_v, gs_req):
575-
flag = False
576-
warnings.warn('matplotlibrc text.usetex can not be used unless '
577-
'ghostscript-%s or later is installed on your system'
578-
% gs_req)
579-
580-
return flag
574+
if not get_executable_info("tex"):
575+
warnings.warn("Setting matplotlibrc text.usetex requires TeX.")
576+
return False
577+
if not get_executable_info("dvipng"):
578+
warnings.warn("Setting matplotlibrc text.usetex requires dvipng.")
579+
return False
580+
if not get_executable_info("gs"):
581+
warnings.warn(
582+
"Setting matplotlibrc text.usetex requires ghostscript.")
583+
return False
584+
return True
581585

582586

583587
def _get_home():
@@ -1375,7 +1379,7 @@ def use(arg, warn=True, force=False):
13751379
# Check if we've already set up a backend
13761380
if 'matplotlib.backends' in sys.modules:
13771381
# Warn only if called with a different name
1378-
if (rcParams['backend'] != name) and warn:
1382+
if (rcParams['backend'] != name) and warn:
13791383
import matplotlib.backends
13801384
warnings.warn(
13811385
_use_error_msg.format(

lib/matplotlib/testing/compare.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,8 @@ def get_file_hash(path, block_size=2 ** 20):
109109
from matplotlib import checkdep_ghostscript
110110
md5.update(checkdep_ghostscript()[1].encode('utf-8'))
111111
elif path.endswith('.svg'):
112-
from matplotlib import checkdep_inkscape
113-
md5.update(checkdep_inkscape().encode('utf-8'))
112+
md5.update(str(matplotlib.get_executable_info("inkscape").version)
113+
.encode('utf-8'))
114114

115115
return md5.hexdigest()
116116

@@ -246,7 +246,7 @@ def cmd(old, new):
246246
converter['pdf'] = make_external_conversion_command(cmd)
247247
converter['eps'] = make_external_conversion_command(cmd)
248248

249-
if matplotlib.checkdep_inkscape() is not None:
249+
if matplotlib.get_executable_info("inkscape"):
250250
converter['svg'] = _SVGConverter()
251251

252252

0 commit comments

Comments
 (0)