8000 Unify querying of executable versions. · matplotlib/matplotlib@33f199e · GitHub
[go: up one dir, main page]

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 33f199e

Browse files
committed
Unify querying of executable versions.
1 parent 7673002 commit 33f199e

File tree

2 files changed

+133
-134
lines changed

2 files changed

+133
-134
lines changed

lib/matplotlib/__init__.py

Lines changed: 130 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,13 @@
104104
from __future__ import absolute_import, division, print_function
105105

106106
import six
107+
from six.moves.urllib.request import urlopen
108+
from six.moves import reload_module as reload
107109

108110
import atexit
109-
from collections import MutableMapping
111+
from collections import MutableMapping, namedtuple
110112
import contextlib
111-
import distutils.version
112-
import distutils.sysconfig
113+
from distutils.version import LooseVersion
113114
import functools
114115
import io
115116
import inspect
@@ -144,8 +145,6 @@
144145
from matplotlib.rcsetup import defaultParams, validate_backend, cycler
145146

146147
import numpy
147-
from six.moves.urllib.request import urlopen
148-
from six.moves import reload_module as reload
149148

150149
# Get the version from the _version.py versioneer file. For a git checkout,
151150
# this is computed based on the number of commits since the last tag.
@@ -190,9 +189,7 @@ def compare_versions(a, b):
190189
a = a.decode('ascii')
191190
if isinstance(b, bytes):
192191
b = b.decode('ascii')
193-
a = distutils.version.LooseVersion(a)
194-
b = distutils.version.LooseVersion(b)
195-
return a >= b
192+
return LooseVersion(a) >= LooseVersion(b)
196193
else:
197194
return False
198195

@@ -420,89 +417,125 @@ def wrapper(*args, **kwargs):
420417
return wrapper
421418

422419

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

435521

436522
def checkdep_ghostscript():
437-
if checkdep_ghostscript.executable is None:
438-
if sys.platform == 'win32':
439-
# mgs is the name in miktex
440-
gs_execs = ['gswin32c', 'gswin64c', 'mgs', 'gs']
441-
else:
442-
gs_execs = ['gs']
443-
for gs_exec in gs_execs:
444-
try:
445-
s = subprocess.Popen(
446-
[str(gs_exec), '--version'], stdout=subprocess.PIPE,
447-
stderr=subprocess.PIPE)
448-
stdout, stderr = s.communicate()
449-
if s.returncode == 0:
450-
v = stdout[:-1].decode('ascii')
451-
checkdep_ghostscript.executable = gs_exec
452-
checkdep_ghostscript.version = v
453-
except (IndexError, ValueError, OSError):
454-
pass
523+
info = get_executable_info("gs")
524+
checkdep_ghostscript.executable = info.executable
525+
checkdep_ghostscript.version = str(info.version)
455526
return checkdep_ghostscript.executable, checkdep_ghostscript.version
456527
checkdep_ghostscript.executable = None
457528
checkdep_ghostscript.version = None
458529

459530

460-
# Deprecated, as it is unneeded and some distributions (e.g. MiKTeX 2.9.6350)
461-
# do not actually report the TeX version.
462-
@cbook.deprecated("2.1")
463-
def checkdep_tex():
464-
try:
465-
s = subprocess.Popen([str('tex'), '-version'], stdout=subprocess.PIPE,
466-
stderr=subprocess.PIPE)
467-
stdout, stderr = s.communicate()
468-
line = stdout.decode('ascii').split('\n')[0]
469-
pattern = r'3\.1\d+'
470-
match = re.search(pattern, line)
471-
v = match.group(0)
472-
return v
473-
except (IndexError, ValueError, AttributeError, OSError):
474-
return None
475-
476-
531+
@cbook.deprecated("2.2")
477532
def checkdep_pdftops():
478-
try:
479-
s = subprocess.Popen([str('pdftops'), '-v'], stdout=subprocess.PIPE,
480-
stderr=subprocess.PIPE)
481-
stdout, stderr = s.communicate()
482-
lines = stderr.decode('ascii').split('\n')
483-
for line in lines:
484-
if 'version' in line:
485-
v = line.split()[-1]
486-
return v
487-
except (IndexError, ValueError, UnboundLocalError, OSError):
488-
return None
533+
return str(get_executable_info("pdftops").version)
489534

490535

536+
@cbook.deprecated("2.2")
491537
def checkdep_inkscape():
492-
if checkdep_inkscape.version is None:
493-
try:
494-
s = subprocess.Popen([str('inkscape'), '-V'],
495-
stdout=subprocess.PIPE,
496-
stderr=subprocess.PIPE)
497-
stdout, stderr = s.communicate()
498-
lines = stdout.decode('ascii').split('\n')
499-
for line in lines:
500-
if 'Inkscape' in line:
501-
v = line.split()[1]
502-
break
503-
checkdep_inkscape.version = v
504-
except (IndexError, ValueError, UnboundLocalError, OSError):
505-
pass
538+
checkdep_inkscape.version = str(get_executable_info("inkscape").version)
506539
return checkdep_inkscape.version
507540
checkdep_inkscape.version = None
508541

@@ -527,65 +560,31 @@ def checkdep_xmllint():
527560
def checkdep_ps_distiller(s):
528561
if not s:
529562
return False
530-
531-
flag = True
532-
gs_req = '8.60'
533-
gs_exec, gs_v = checkdep_ghostscript()
534-
if not compare_versions(gs_v, gs_req):
535-
flag = False
536-
warnings.warn(('matplotlibrc ps.usedistiller option can not be used '
537-
'unless ghostscript-%s or later is installed on your '
538-
'system') % gs_req)
539-
540-
if s == 'xpdf':
541-
pdftops_req = < 10000 span class=pl-s>'3.0'
542-
pdftops_req_alt = '0.9' # poppler version numbers, ugh
543-
pdftops_v = checkdep_pdftops()
544-
if compare_versions(pdftops_v, pdftops_req):
545-
pass
546-
elif (compare_versions(pdftops_v, pdftops_req_alt) and not
547-
compare_versions(pdftops_v, '1.0')):
548-
pass
549-
else:
550-
flag = False
551-
warnings.warn(('matplotlibrc ps.usedistiller can not be set to '
552-
'xpdf unless xpdf-%s or later is installed on '
553-
'your system') % pdftops_req)
554-
555-
if flag:
556-
return s
557-
else:
563+
if not get_executable_info("gs"):
564+
warnings.warn(
565+
"Setting matplotlibrc ps.usedistiller requires ghostscript.")
558566
return False
567+
if s == "xpdf" and not get_executable_info("pdftops"):
568+
warnings.warn(
569+
"setting matplotlibrc ps.usedistiller to 'xpdf' requires xpdf.")
570+
return False
571+
return s
559572

560573

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

590589

591590
def _get_home():

lib/matplotlib/testing/compare.py

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

114114
return md5.hexdigest()
115115

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

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

251251

0 commit comments

Comments
 (0)
0