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

Skip to content

Commit 2a3edfe

Browse files
committed
Unify querying of executable versions.
1 parent 39575ed commit 2a3edfe

File tree

2 files changed

+138
-134
lines changed

2 files changed

+138
-134
lines changed

lib/matplotlib/__init__.py

Lines changed: 135 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,12 @@
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

107-
from collections import MutableMapping
109+
from collections import MutableMapping, namedtuple
108110
import contextlib
109-
import distutils.version
110-
import distutils.sysconfig
111+
from distutils.version import LooseVersion
111112
import functools
112113
import io
113114
import inspect
@@ -119,6 +120,11 @@
119120
import tempfile
120121
import warnings
121122

123+
try:
124+
from functools import lru_cache
125+
except ImportError:
126+
from backports.functools_lru_cache import lru_cache
127+
122128
# cbook must import matplotlib only within function
123129
# definitions, so it is safe to import from it here.
124130
from . import cbook
@@ -128,8 +134,6 @@
128134
from matplotlib.rcsetup import defaultParams, validate_backend, cycler
129135

130136
import numpy
131-
from six.moves.urllib.request import urlopen
132-
from six.moves import reload_module as reload
133137

134138
# Get the version from the _version.py versioneer file. For a git checkout,
135139
# this is computed based on the number of commits since the last tag.
@@ -169,9 +173,7 @@ def compare_versions(a, b):
169173
a = a.decode('ascii')
170174
if isinstance(b, bytes):
171175
b = b.decode('ascii')
172-
a = distutils.version.LooseVersion(a)
173-
b = distutils.version.LooseVersion(b)
174-
return a >= b
176+
return LooseVersion(a) >= LooseVersion(b)
175177
else:
176178
return False
177179

@@ -314,89 +316,125 @@ def ge(self, level):
314316
verbose = Verbose()
315317

316318

319+
_ExecInfo = namedtuple("_ExecInfo", "executable version")
320+
321+
322+
@lru_cache()
323+
def get_executable_info(name):
324+
"""Get the version of some executables that Matplotlib depends on.
325+
326+
.. warning:
327+
The list of executables that this function supports is set according to
328+
Matplotlib's internal needs, and may change without notice.
329+
330+
Parameters
331+
----------
332+
name : str
333+
The executable to query. The following values are currently supported:
334+
"dvipng", "gs", "inkscape", "pdftops", "tex". This list is subject to
335+
change without notice.
336+
337+
Returns
338+
-------
339+
If the executable is found, a namedtuple with fields ``executable`` (`str`)
340+
and ``version`` (`distutils.version.LooseVersion`, or ``None`` if the
341+
version cannot be determined); ``None`` if the executable is not found.
342+
"""
343+
344+
def impl(args, regex, min_ver=None):
345+
# Execute the subprocess specified by args; capture stdout and stderr.
346+
# Search for a regex match in the output; if the match succeeds, use
347+
# the *first group* of the match as the version.
348+
# If min_ver is not None, emit a warning if the version is less than
349+
# min_ver.
350+
try:
351+
proc = subprocess.Popen(
352+
[str(arg) for arg in args], # str(): Py2 compat.
353+
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
354+
universal_newlines=True)
355+
proc.wait()
356+
except OSError:
357+
return None
358+
match = re.search(regex, proc.stdout.read())
359+
if match:
360+
version = LooseVersion(match.group(1))
361+
if min_ver is not None and version < str(min_ver):
362+
warnings.warn("You have {} version {} but the minimum version "
363+
"supported by Matplotlib is {}."
364+
.format(args[0], version, min_ver))
365+
return None
366+
return _ExecInfo(str(args[0]), version) # str(): Py2 compat.
367+
else:
368+
return None
369+
370+
if name == "dvipng":
371+
info = impl(["dvipng", "-version"], "(?m)^dvipng .* (.+)", "1.6")
372+
elif name == "gs":
373+
execs = (["gswin32c", "gswin64c", "mgs", "gs"] # "mgs" for miktex.
374+
if sys.platform == "win32" else
375+
["gs"])
376+
info = next(filter(None, (impl([e, "--version"], "(.*)", "8.60")
377+
for e in execs)),
378+
None)
379+
elif name == "inkscape":
380+
info = impl(["inkscape", "-V"], "^Inkscape ([^ ]*)")
381+
elif name == "pdftops":
382+
info = impl(["pdftops", "-v"], "^pdftops version (.*)")
383+
if info and not (str("3.0") <= info.version
384+
# poppler version numbers.
385+
or str("0.9") <= info.version <= str("1.0")):
386+
warnings.warn(
387+
"You have pdftops version {} but the minimum version "
388+
"supported by Matplotlib is 3.0.".format(info.version))
389+
return None
390+
elif name == "tex":
391+
info = (_ExecInfo(str("tex"), None) # str(): Py2 compat.
392+
if _backports.which("tex") is not None
393+
else None)
394+
else:
395+
raise ValueError("Unknown executable: {!r}".format(name))
396+
return info
397+
398+
399+
def get_all_executable_infos():
400+
"""Query all executables that Matplotlib may need.
401+
402+
.. warning:
403+
The list of executables that this function queries is set according to
404+
Matplotlib's internal needs, and may change without notice.
405+
406+
Returns
407+
-------
408+
A mapping of the required executable to its corresponding information,
409+
as returned by `get_executable_info`. The keys in the mapping are subject
410+
to change without notice.
411+
"""
412+
return {name: get_executable_info(name)
413+
for name in ["dvipng", "gs", "inkscape", "pdftops", "tex"]}
414+
415+
416+
@cbook.deprecated("2.2")
317417
def checkdep_dvipng():
318-
try:
319-
s = subprocess.Popen([str('dvipng'), '-version'],
320-
stdout=subprocess.PIPE,
321-
stderr=subprocess.PIPE)
322-
stdout, stderr = s.communicate()
323-
line = stdout.decode('ascii').split('\n')[1]
324-
v = line.split()[-1]
325-
return v
326-
except (IndexError, ValueError, OSError):
327-
return None
418+
return str(get_executable_info("dvipng").version)
328419

329420

330421
def checkdep_ghostscript():
331-
if checkdep_ghostscript.executable is None:
332-
if sys.platform == 'win32':
333-
# mgs is the name in miktex
334-
gs_execs = ['gswin32c', 'gswin64c', 'mgs', 'gs']
335-
else:
336-
gs_execs = ['gs']
337-
for gs_exec in gs_execs:
338-
try:
339-
s = subprocess.Popen(
340-
[str(gs_exec), '--version'], stdout=subprocess.PIPE,
341-
stderr=subprocess.PIPE)
342-
stdout, stderr = s.communicate()
343-
if s.returncode == 0:
344-
v = stdout[:-1].decode('ascii')
345-
checkdep_ghostscript.executable = gs_exec
346-
checkdep_ghostscript.version = v
347-
except (IndexError, ValueError, OSError):
348-
pass
422+
info = get_executable_info("gs")
423+
checkdep_ghostscript.executable = info.executable
424+
checkdep_ghostscript.version = str(info.version)
349425
return checkdep_ghostscript.executable, checkdep_ghostscript.version
350426
checkdep_ghostscript.executable = None
351427
checkdep_ghostscript.version = None
352428

353429

354-
# Deprecated, as it is unneeded and some distributions (e.g. MiKTeX 2.9.6350)
355-
# do not actually report the TeX version.
356-
@cbook.deprecated("2.1")
357-
def checkdep_tex():
358-
try:
359-
s = subprocess.Popen([str('tex'), '-version'], stdout=subprocess.PIPE,
360-
stderr=subprocess.PIPE)
361-
stdout, stderr = s.communicate()
362-
line = stdout.decode('ascii').split('\n')[0]
363-
pattern = r'3\.1\d+'
364-
match = re.search(pattern, line)
365-
v = match.group(0)
366-
return v
367-
except (IndexError, ValueError, AttributeError, OSError):
368-
return None
369-
370-
430+
@cbook.deprecated("2.2")
371431
def checkdep_pdftops():
372-
try:
373-
s = subprocess.Popen([str('pdftops'), '-v'], stdout=subprocess.PIPE,
374-
stderr=subprocess.PIPE)
375-
stdout, stderr = s.communicate()
376-
lines = stderr.decode('ascii').split('\n')
377-
for line in lines:
378-
if 'version' in line:
379-
v = line.split()[-1]
380-
return v
381-
except (IndexError, ValueError, UnboundLocalError, OSError):
382-
return None
432+
return str(get_executable_info("pdftops").version)
383433

384434

435+
@cbook.deprecated("2.2")
385436
def checkdep_inkscape():
386-
if checkdep_inkscape.version is None:
387-
try:
388-
s = subprocess.Popen([str('inkscape'), '-V'],
389-
stdout=subprocess.PIPE,
390-
stderr=subprocess.PIPE)
391-
stdout, stderr = s.communicate()
392-
lines = stdout.decode('ascii').split('\n')
393-
for line in lines:
394-
if 'Inkscape' in line:
395-
v = line.split()[1]
396-
break
397-
checkdep_inkscape.version = v
398-
except (IndexError, ValueError, UnboundLocalError, OSError):
399-
pass
437+
checkdep_inkscape.version = str(get_executable_info("inkscape").version)
400438
return checkdep_inkscape.version
401439
checkdep_inkscape.version = None
402440

@@ -421,65 +459,31 @@ def checkdep_xmllint():
421459
def checkdep_ps_distiller(s):
422460
if not s:
423461
return False
424-
425-
flag = True
426-
gs_req = '8.60'
427-
gs_exec, gs_v = checkdep_ghostscript()
428-
if not compare_versions(gs_v, gs_req):
429-
flag = False
430-
warnings.warn(('matplotlibrc ps.usedistiller option can not be used '
431-
'unless ghostscript-%s or later is installed on your '
432-
'system') % gs_req)
433-
434-
if s == 'xpdf':
435-
pdftops_req = '3.0'
436-
pdftops_req_alt = '0.9' # poppler version numbers, ugh
437-
pdftops_v = checkdep_pdftops()
438-
if compare_versions(pdftops_v, pdftops_req):
439-
pass
440-
elif (compare_versions(pdftops_v, pdftops_req_alt) and not
441-
compare_versions(pdftops_v, '1.0')):
442-
pass
443-
else:
444-
flag = False
445-
warnings.warn(('matplotlibrc ps.usedistiller can not be set to '
446-
'xpdf unless xpdf-%s or later is installed on '
447-
'your system') % pdftops_req)
448-
449-
if flag:
450-
return s
451-
else:
462+
if not get_executable_info("gs"):
463+
warnings.warn(
464+
"Setting matplotlibrc ps.usedistiller requires ghostscript.")
452465
return False
466+
if s == "xpdf" and not get_executable_info("pdftops"):
467+
warnings.warn(
468+
"setting matplotlibrc ps.usedistiller to 'xpdf' requires xpdf.")
469+
return False
470+
return s
453471

454472

455473
def checkdep_usetex(s):
456474
if not s:
457475
return False
458-
459-
gs_req = '8.60'
460-
dvipng_req = '1.6'
461-
flag = True
462-
463-
if _backports.which("tex") is None:
464-
flag = False
465-
warnings.warn('matplotlibrc text.usetex option can not be used unless '
466-
'TeX is installed on your system')
467-
468-
dvipng_v = checkdep_dvipng()
469-
if not compare_versions(dvipng_v, dvipng_req):
470-
flag = False
471-
warnings.warn('matplotlibrc text.usetex can not be used with *Agg '
472-
'backend unless dvipng-%s or later is installed on '
473-
'your system' % dvipng_req)
474-
475-
gs_exec, gs_v = checkdep_ghostscript()
476-
if not compare_versions(gs_v, gs_req):
477-
flag = False
478-
warnings.warn('matplotlibrc text.usetex can not be used unless '
479-
'ghostscript-%s or later is installed on your system'
480-
% gs_req)
481-
482-
return flag
476+
if not get_executable_info("tex"):
477+
warnings.warn("Setting matplotlibrc text.usetex requires TeX.")
478+
return False
479+
if not get_executable_info("dvipng"):
480+
warnings.warn("Setting matplotlibrc text.usetex requires dvipng.")
481+
return False
482+
if not get_executable_info("gs"):
483+
warnings.warn(
484+
"Setting matplotlibrc text.usetex requires ghostscript.")
485+
return False
486+
return True
483487

484488

485489
def _get_home():

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)