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

Skip to content

Commit 61983bb

Browse files
committed
Unify querying of executable versions.
1 parent 9ec4b95 commit 61983bb

File tree

6 files changed

+173
-178
lines changed

6 files changed

+173
-178
lines changed

lib/matplotlib/__init__.py

Lines changed: 128 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,9 @@
117117
""")
118118

119119
import atexit
120-
from collections import MutableMapping
120+
from collections import MutableMapping, namedtuple
121121
import contextlib
122-
import distutils.version
122+
from distutils.version import LooseVersion
123123
import functools
124124
import io
125125
import importlib
@@ -184,9 +184,7 @@ def compare_versions(a, b):
184184
"3.0", "compare_version arguments should be strs.")
185185
b = b.decode('ascii')
186186
if a:
187-
a = distutils.version.LooseVersion(a)
188-
b = distutils.version.LooseVersion(b)
189-
return a >= b
187+
return LooseVersion(a) >= LooseVersion(b)
190188
else:
191189
return False
192190

@@ -424,138 +422,157 @@ def wrapper(*args, **kwargs):
424422
return wrapper
425423

426424

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

439524

525+
@cbook.deprecated("3.0")
440526
def checkdep_ghostscript():
441-
if checkdep_ghostscript.executable is None:
442-
if sys.platform == 'win32':
443-
# mgs is the name in miktex
444-
gs_execs = ['gswin32c', 'gswin64c', 'mgs', 'gs']
445-
else:
446-
gs_execs = ['gs']
447-
for gs_exec in gs_execs:
448-
try:
449-
s = subprocess.Popen(
450-
[gs_exec, '--version'], stdout=subprocess.PIPE,
451-
stderr=subprocess.PIPE)
452-
stdout, stderr = s.communicate()
453-
if s.returncode == 0:
454-
v = stdout[:-1].decode('ascii')
455-
checkdep_ghostscript.executable = gs_exec
456-
checkdep_ghostscript.version = v
457-
except (IndexError, ValueError, OSError):
458-
pass
527+
info = get_executable_info("gs")
528+
checkdep_ghostscript.executable = info.executable
529+
checkdep_ghostscript.version = str(info.version)
459530
return checkdep_ghostscript.executable, checkdep_ghostscript.version
460531
checkdep_ghostscript.executable = None
461532
checkdep_ghostscript.version = None
462533

463534

535+
@cbook.deprecated("3.0")
464536
def checkdep_pdftops():
465-
try:
466-
s = subprocess.Popen([str('pdftops'), '-v'], stdout=subprocess.PIPE,
467-
stderr=subprocess.PIPE)
468-
stdout, stderr = s.communicate()
469-
lines = stderr.decode('ascii').split('\n')
470-
for line in lines:
471-
if 'version' in line:
472-
v = line.split()[-1]
473-
return v
474-
except (IndexError, ValueError, UnboundLocalError, OSError):
475-
return None
537+
return str(get_executable_info("pdftops").version)
476538

477539

540+
@cbook.deprecated("3.0")
478541
def checkdep_inkscape():
479-
if checkdep_inkscape.version is None:
480-
try:
481-
s = subprocess.Popen([str('inkscape'), '-V'],
482-
stdout=subprocess.PIPE,
483-
stderr=subprocess.PIPE)
484-
stdout, stderr = s.communicate()
485-
lines = stdout.decode('ascii').split('\n')
486-
for line in lines:
487-
if 'Inkscape' in line:
488-
v = line.split()[1]
489-
break
490-
checkdep_inkscape.version = v
491-
except (IndexError, ValueError, UnboundLocalError, OSError):
492-
pass
542+
checkdep_inkscape.version = str(get_executable_info("inkscape").version)
493543
return checkdep_inkscape.version
494544
checkdep_inkscape.version = None
495545

496546

547+
@cbook.deprecated("3.0")
497548
def checkdep_ps_distiller(s):
498549
if not s:
499550
return False
500-
501-
flag = True
502-
gs_req = '8.60'
503-
gs_exec, gs_v = checkdep_ghostscript()
504-
if not compare_versions(gs_v, gs_req):
505-
flag = False
506-
warnings.warn(('matplotlibrc ps.usedistiller option can not be used '
507-
'unless ghostscript-%s or later is installed on your '
508-
'system') % gs_req)
509-
510-
if s == 'xpdf':
511-
pdftops_req = '3.0'
512-
pdftops_req_alt = '0.9' # poppler version numbers, ugh
513-
pdftops_v = checkdep_pdftops()
514-
if compare_versions(pdftops_v, pdftops_req):
515-
pass
516-
elif (compare_versions(pdftops_v, pdftops_req_alt) and not
517-
compare_versions(pdftops_v, '1.0')):
518-
pass
519-
else:
520-
flag = False
521-
warnings.warn(('matplotlibrc ps.usedistiller can not be set to '
522-
'xpdf unless xpdf-%s or later is installed on '
523-
'your system') % pdftops_req)
524-
525-
if flag:
526-
return s
527-
else:
551+
if not get_executable_info("gs"):
552+
warnings.warn(
553+
"Setting matplotlibrc ps.usedistiller requires ghostscript.")
554+
return False
555+
if s == "xpdf" and not get_executable_info("pdftops"):
556+
warnings.warn(
557+
"Setting matplotlibrc ps.usedistiller to 'xpdf' requires xpdf.")
528558
return False
559+
return s
529560

530561

531562
def checkdep_usetex(s):
532563
if not s:
533564
return False
534-
535-
gs_req = '8.60'
536-
dvipng_req = '1.6'
537-
flag = True
538-
539-
if shutil.which("tex") is None:
540-
flag = False
541-
warnings.warn('matplotlibrc text.usetex option can not be used unless '
542-
'TeX is installed on your system')
543-
544-
dvipng_v = checkdep_dvipng()
545-
if not compare_versions(dvipng_v, dvipng_req):
546-
flag = False
547-
warnings.warn('matplotlibrc text.usetex can not be used with *Agg '
548-
'backend unless dvipng-%s or later is installed on '
549-
'your system' % dvipng_req)
550-
551-
gs_exec, gs_v = checkdep_ghostscript()
552-
if not compare_versions(gs_v, gs_req):
553-
flag = False
554-
warnings.warn('matplotlibrc text.usetex can not be used unless '
555-
'ghostscript-%s or later is installed on your system'
556-
% gs_req)
557-
558-
return flag
565+
if not shutil.which("tex"):
566+
warnings.warn("Setting matplotlibrc text.usetex requires TeX.")
567+
return False
568+
if not get_executable_info("dvipng"):
569+
warnings.warn("Setting matplotlibrc text.usetex requires dvipng.")
570+
return False
571+
if not get_executable_info("gs"):
572+
warnings.warn(
573+
"Setting matplotlibrc text.usetex requires ghostscript.")
574+
return False
575+
return True
559576

560577

561578
def _get_home():
@@ -1133,9 +1150,6 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True):
11331150
defaultParams.items()
11341151
if key not in _all_deprecated])
11351152

1136-
rcParams['ps.usedistiller'] = checkdep_ps_distiller(
1137-
rcParams['ps.usedistiller'])
1138-
11391153
rcParams['text.usetex'] = checkdep_usetex(rcParams['text.usetex'])
11401154

11411155
if rcParams['axes.formatter.use_locale']:

lib/matplotlib/backends/backend_pgf.py

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -163,41 +163,24 @@ def _font_properties_str(prop):
163163

164164

165165
def make_pdf_to_png_converter():
166-
"""
167-
Returns a function that converts a pdf file to a png file.
168-
"""
169-
170-
tools_available = []
171-
# check for pdftocairo
172-
try:
173-
subprocess.check_output(["pdftocairo", "-v"], stderr=subprocess.STDOUT)
174-
tools_available.append("pdftocairo")
175-
except OSError:
176-
pass
177-
# check for ghostscript
178-
gs, ver = mpl.checkdep_ghostscript()
179-
if gs:
180-
tools_available.append("gs")
181-
182-
# pick converter
183-
if "pdftocairo" in tools_available:
166+
"""Returns a function that converts a pdf file to a png file."""
167+
if shutil.which("pdftocairo"):
184168
def cairo_convert(pdffile, pngfile, dpi):
185169
cmd = ["pdftocairo", "-singlefile", "-png", "-r", "%d" % dpi,
186170
pdffile, os.path.splitext(pngfile)[0]]
187171
subprocess.check_output(cmd, stderr=subprocess.STDOUT)
188172
return cairo_convert
189-
elif "gs" in tools_available:
173+
if mpl.get_executable_info("gs"):
190174
def gs_convert(pdffile, pngfile, dpi):
191-
cmd = [gs,
175+
cmd = [mpl.get_executable_info("gs").executable,
192176
'-dQUIET', '-dSAFER', '-dBATCH', '-dNOPAUSE', '-dNOPROMPT',
193177
'-dUseCIEColor', '-dTextAlphaBits=4',
194178
'-dGraphicsAlphaBits=4', '-dDOINTERPOLATE',
195179
'-sDEVICE=png16m', '-sOutputFile=%s' % pngfile,
196180
'-r%d' % dpi, pdffile]
197181
subprocess.check_output(cmd, stderr=subprocess.STDOUT)
198182
return gs_convert
199-
else:
200-
raise RuntimeError("No suitable pdf to png renderer found.")
183+
raise RuntimeError("No suitable pdf to png renderer found")
201184

202185

203186
class LatexError(Exception):

0 commit comments

Comments
 (0)