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 70618de

Browse filesBrowse files
authored
Merge pull request #13499 from meeseeksmachine/auto-backport-of-pr-13303-on-v3.1.x
Backport PR #13303 on branch v3.1.x (Unify checking of executable info.)
2 parents f4a699b + 31d7393 commit 70618de
Copy full SHA for 70618de

File tree

Expand file treeCollapse file tree

7 files changed

+198
-121
lines changed
Filter options
Expand file treeCollapse file tree

7 files changed

+198
-121
lines changed
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Deprecations
2+
````````````
3+
4+
``checkdep_dvipng``, ``checkdep_ghostscript``, ``checkdep_pdftops``, and
5+
``checkdep_inkscape`` are deprecated.

‎lib/matplotlib/__init__.py

Copy file name to clipboardExpand all lines: lib/matplotlib/__init__.py
+145-57Lines changed: 145 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,10 @@
115115
""")
116116

117117
import atexit
118+
from collections import namedtuple
118119
from collections.abc import MutableMapping
119120
import contextlib
120-
import distutils.version
121+
from distutils.version import LooseVersion
121122
import functools
122123
import importlib
123124
import inspect
@@ -178,9 +179,7 @@ def compare_versions(a, b):
178179
"3.0", message="compare_versions arguments should be strs.")
179180
b = b.decode('ascii')
180181
if a:
181-
a = distutils.version.LooseVersion(a)
182-
b = distutils.version.LooseVersion(b)
183-
return a >= b
182+
return LooseVersion(a) >= LooseVersion(b)
184183
else:
185184
return False
186185

@@ -194,7 +193,7 @@ def _check_versions():
194193
("pyparsing", "2.0.1"),
195194
]:
196195
module = importlib.import_module(modname)
197-
if distutils.version.LooseVersion(module.__version__) < minver:
196+
if LooseVersion(module.__version__) < minver:
198197
raise ImportError("Matplotlib requires {}>={}; you have {}"
199198
.format(modname, minver, module.__version__))
200199

@@ -276,6 +275,117 @@ def wrapper():
276275
return wrapper
277276

278277

278+
_ExecInfo = namedtuple("_ExecInfo", "executable version")
279+
280+
281+
@functools.lru_cache()
282+
def _get_executable_info(name):
283+
"""
284+
Get the version of some executable that Matplotlib optionally depends on.
285+
286+
.. warning:
287+
The list of executables that this function supports is set according to
288+
Matplotlib's internal needs, and may change without notice.
289+
290+
Parameters
291+
----------
292+
name : str
293+
The executable to query. The following values are currently supported:
294+
"dvipng", "gs", "inkscape", "magick", "pdftops". This list is subject
295+
to change without notice.
296+
297+
Returns
298+
-------
299+
If the executable is found, a namedtuple with fields ``executable`` (`str`)
300+
and ``version`` (`distutils.version.LooseVersion`, or ``None`` if the
301+
version cannot be determined).
302+
303+
Raises
304+
------
305+
FileNotFoundError
306+
If the executable is not found or older than the oldest version
307+
supported by Matplotlib.
308+
ValueError
309+
If the executable is not one that we know how to query.
310+
"""
311+
312+
def impl(args, regex, min_ver=None):
313+
# Execute the subprocess specified by args; capture stdout and stderr.
314+
# Search for a regex match in the output; if the match succeeds, the
315+
# first group of the match is the version.
316+
# Return an _ExecInfo if the executable exists, and has a version of
317+
# at least min_ver (if set); else, raise FileNotFoundError.
318+
output = subprocess.check_output(
319+
args, stderr=subprocess.STDOUT, universal_newlines=True)
320+
match = re.search(regex, output)
321+
if match:
322+
version = LooseVersion(match.group(1))
323+
if min_ver is not None and version < min_ver:
324+
raise FileNotFoundError(
325+
f"You have {args[0]} version {version} but the minimum "
326+
f"version supported by Matplotlib is {min_ver}.")
327+
return _ExecInfo(args[0], version)
328+
else:
329+
raise FileNotFoundError(
330+
f"Failed to determine the version of {args[0]} from "
331+
f"{' '.join(args)}, which output {output}")
332+
333+
if name == "dvipng":
334+
return impl(["dvipng", "-version"], "(?m)^dvipng .* (.+)", "1.6")
335+
elif name == "gs":
336+
execs = (["gswin32c", "gswin64c", "mgs", "gs"] # "mgs" for miktex.
337+
if sys.platform == "win32" else
338+
["gs"])
339+
for e in execs:
340+
try:
341+
return impl([e, "--version"], "(.*)", "9")
342+
except FileNotFoundError:
343+
pass
344+
raise FileNotFoundError("Failed to find a Ghostscript installation")
345+
elif name == "inkscape":
346+
return impl(["inkscape", "-V"], "^Inkscape ([^ ]*)")
347+
elif name == "magick":
348+
path = None
349+
if sys.platform == "win32":
350+
# Check the registry to avoid confusing ImageMagick's convert with
351+
# Windows's builtin convert.exe.
352+
import winreg
353+
binpath = ""
354+
for flag in [0, winreg.KEY_WOW64_32KEY, winreg.KEY_WOW64_64KEY]:
355+
try:
356+
with winreg.OpenKeyEx(
357+
winreg.HKEY_LOCAL_MACHINE,
358+
r"Software\Imagemagick\Current",
359+
0, winreg.KEY_QUERY_VALUE | flag) as hkey:
360+
binpath = winreg.QueryValueEx(hkey, "BinPath")[0]
361+
except OSError:
362+
pass
363+
if binpath:
364+
for name in ["convert.exe", "magick.exe"]:
365+
candidate = Path(binpath, name)
366+
if candidate.exists():
367+
path = candidate
368+
break
369+
else:
370+
path = "convert"
371+
if path is None:
372+
raise FileNotFoundError(
373+
"Failed to find an ImageMagick installation")
374+
return impl([path, "--version"], r"^Version: ImageMagick (\S*)")
375+
elif name == "pdftops":
376+
info = impl(["pdftops", "-v"], "^pdftops version (.*)")
377+
if info and not ("3.0" <= info.version
378+
# poppler version numbers.
379+
or "0.9" <= info.version <= "1.0"):
380+
raise FileNotFoundError(
381+
f"You have pdftops version {info.version} but the minimum "
382+
f"version supported by Matplotlib is 3.0.")
383+
return info
384+
else:
385+
raise ValueError("Unknown executable: {!r}".format(name))
386+
387+
388+
@cbook.deprecated("3.1")
279389
def checkdep_dvipng():
280390
try:
281391
s = subprocess.Popen(['dvipng', '-version'],
@@ -289,6 +399,7 @@ def checkdep_dvipng():
289399
return None
290400

291401

402+
@cbook.deprecated("3.1")
292403
def checkdep_ghostscript():
293404
if checkdep_ghostscript.executable is None:
294405
if sys.platform == 'win32':
@@ -314,6 +425,7 @@ def checkdep_ghostscript():
314425
checkdep_ghostscript.version = None
315426

316427

428+
@cbook.deprecated("3.1")
317429
def checkdep_pdftops():
318430
try:
319431
s = subprocess.Popen(['pdftops', '-v'], stdout=subprocess.PIPE,
@@ -328,6 +440,7 @@ def checkdep_pdftops():
328440
return None
329441

330442

443+
@cbook.deprecated("3.1")
331444
def checkdep_inkscape():
332445
if checkdep_inkscape.version is None:
333446
try:
@@ -350,64 +463,39 @@ def checkdep_inkscape():
350463
def checkdep_ps_distiller(s):
351464
if not s:
352465
return False
353-
354-
flag = True
355-
gs_exec, gs_v = checkdep_ghostscript()
356-
if not gs_exec:
357-
flag = False
358-
_log.warning('matplotlibrc ps.usedistiller option can not be used '
359-
'unless ghostscript 9.0 or later is installed on your '
360-
'system.')
361-
362-
if s == 'xpdf':
363-
pdftops_req = '3.0'
364-
pdftops_req_alt = '0.9' # poppler version numbers, ugh
365-
pdftops_v = checkdep_pdftops()
366-
if compare_versions(pdftops_v, pdftops_req):
367-
pass
368-
elif (compare_versions(pdftops_v, pdftops_req_alt) and not
369-
compare_versions(pdftops_v, '1.0')):
370-
pass
371-
else:
372-
flag = False
373-
_log.warning('matplotlibrc ps.usedistiller can not be set to xpdf '
374-
'unless xpdf-%s or later is installed on your '
375-
'system.', pdftops_req)
376-
377-
if flag:
378-
return s
379-
else:
466+
try:
467+
_get_executable_info("gs")
468+
except FileNotFoundError:
469+
_log.warning(
470+
"Setting rcParams['ps.usedistiller'] requires ghostscript.")
380471
return False
472+
if s == "xpdf":
473+
try:
474+
_get_executable_info("pdftops")
475+
except FileNotFoundError:
476+
_log.warning(
477+
"Setting rcParams['ps.usedistiller'] to 'xpdf' requires xpdf.")
478+
return False
479+
return s
381480

382481

383482
def checkdep_usetex(s):
384483
if not s:
385484
return False
386-
387-
gs_req = '9.00'
388-
dvipng_req = '1.6'
389-
flag = True
390-
391-
if shutil.which("tex") is None:
392-
flag = False
393-
_log.warning('matplotlibrc text.usetex option can not be used unless '
394-
'TeX is installed on your system.')
395-
396-
dvipng_v = checkdep_dvipng()
397-
if not compare_versions(dvipng_v, dvipng_req):
398-
flag = False
399-
_log.warning('matplotlibrc text.usetex can not be used with *Agg '
400-
'backend unless dvipng-%s or later is installed on '
401-
'your system.', dvipng_req)
402-
403-
gs_exec, gs_v = checkdep_ghostscript()
404-
if not compare_versions(gs_v, gs_req):
405-
flag = False
406-
_log.warning('matplotlibrc text.usetex can not be used unless '
407-
'ghostscript-%s or later is installed on your system.',
408-
gs_req)
409-
410-
return flag
485+
if not shutil.which("tex"):
486+
_log.warning("usetex mode requires TeX.")
487+
return False
488+
try:
489+
_get_executable_info("dvipng")
490+
except FileNotFoundError:
491+
_log.warning("usetex mode requires dvipng.")
492+
return False
493+
try:
494+
_get_executable_info("gs")
495+
except FileNotFoundError:
496+
_log.warning("usetex mode requires ghostscript.")
497+
return False
498+
return True
411499

412500

413501
@_logged_cached('$HOME=%s')

‎lib/matplotlib/animation.py

Copy file name to clipboardExpand all lines: lib/matplotlib/animation.py
+10-21Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
import numpy as np
3535

36+
import matplotlib as mpl
3637
from matplotlib._animation_data import (
3738
DISPLAY_TEMPLATE, INCLUDED_FRAMES, JS_INCLUDE, STYLE_INCLUDE)
3839
from matplotlib import cbook, rcParams, rcParamsDefault, rc_context
@@ -709,29 +710,17 @@ def output_args(self):
709710
@classmethod
710711
def bin_path(cls):
711712
binpath = super().bin_path()
712-
if sys.platform == 'win32' and binpath == 'convert':
713-
# Check the registry to avoid confusing ImageMagick's convert with
714-
# Windows's builtin convert.exe.
715-
import winreg
716-
binpath = ''
717-
for flag in (0, winreg.KEY_WOW64_32KEY, winreg.KEY_WOW64_64KEY):
718-
try:
719-
with winreg.OpenKeyEx(
720-
winreg.HKEY_LOCAL_MACHINE,
721-
r'Software\Imagemagick\Current',
722-
0, winreg.KEY_QUERY_VALUE | flag) as hkey:
723-
parent = winreg.QueryValueEx(hkey, 'BinPath')[0]
724-
except OSError:
725-
pass
726-
if binpath:
727-
for exe in ('convert.exe', 'magick.exe'):
728-
candidate = os.path.join(parent, exe)
729-
if os.path.exists(candidate):
730-
binpath = candidate
731-
break
732-
rcParams[cls.exec_key] = rcParamsDefault[cls.exec_key] = binpath
713+
if binpath == 'convert':
714+
binpath = mpl._get_executable_info('magick').executable
733715
return binpath
734716

717+
@classmethod
718+
def isAvailable(cls):
719+
try:
720+
return super().isAvailable()
721+
except FileNotFoundError: # May be raised by get_executable_info.
722+
return False
723+
735724

736725
# Combine ImageMagick options with pipe-based writing
737726
@writers.register('imagemagick')

‎lib/matplotlib/backends/backend_pgf.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_pgf.py
+9-22Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -145,41 +145,28 @@ def _font_properties_str(prop):
145145

146146

147147
def make_pdf_to_png_converter():
148-
"""
149-
Returns a function that converts a pdf file to a png file.
150-
"""
151-
152-
tools_available = []
153-
# check for pdftocairo
154-
try:
155-
subprocess.check_output(["pdftocairo", "-v"], stderr=subprocess.STDOUT)
156-
tools_available.append("pdftocairo")
157-
except OSError:
158-
pass
159-
# check for ghostscript
160-
gs, ver = mpl.checkdep_ghostscript()
161-
if gs:
162-
tools_available.append("gs")
163-
164-
# pick converter
165-
if "pdftocairo" in tools_available:
148+
"""Returns a function that converts a pdf file to a png file."""
149+
if shutil.which("pdftocairo"):
166150
def cairo_convert(pdffile, pngfile, dpi):
167151
cmd = ["pdftocairo", "-singlefile", "-png", "-r", "%d" % dpi,
168152
pdffile, os.path.splitext(pngfile)[0]]
169153
subprocess.check_output(cmd, stderr=subprocess.STDOUT)
170154
return cairo_convert
171-
elif "gs" in tools_available:
155+
try:
156+
gs_info = mpl._get_executable_info("gs")
157+
except FileNotFoundError:
158+
pass
159+
else:
172160
def gs_convert(pdffile, pngfile, dpi):
173-
cmd = [gs,
161+
cmd = [gs_info.executable,
174162
'-dQUIET', '-dSAFER', '-dBATCH', '-dNOPAUSE', '-dNOPROMPT',
175163
'-dUseCIEColor', '-dTextAlphaBits=4',
176164
'-dGraphicsAlphaBits=4', '-dDOINTERPOLATE',
177165
'-sDEVICE=png16m', '-sOutputFile=%s' % pngfile,
178166
'-r%d' % dpi, pdffile]
179167
subprocess.check_output(cmd, stderr=subprocess.STDOUT)
180168
return gs_convert
181-
else:
182-
raise RuntimeError("No suitable pdf to png renderer found.")
169+
raise RuntimeError("No suitable pdf to png renderer found.")
183170

184171

185172
class LatexError(Exception):

‎lib/matplotlib/backends/backend_ps.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_ps.py
+5-4Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import numpy as np
1919

20+
import matplotlib as mpl
2021
from matplotlib import (
2122
cbook, _path, __version__, rcParams, checkdep_ghostscript)
2223
from matplotlib.backend_bases import (
@@ -1359,11 +1360,11 @@ def gs_distill(tmpfile, eps=False, ptype='letter', bbox=None, rotated=False):
13591360
psfile = tmpfile + '.ps'
13601361
dpi = rcParams['ps.distiller.res']
13611362

1362-
gs_exe, gs_version = checkdep_ghostscript()
13631363
cbook._check_and_log_subprocess(
1364-
[gs_exe, "-dBATCH", "-dNOPAUSE", "-r%d" % dpi,
1365-
"-sDEVICE=ps2write", paper_option,
1366-
"-sOutputFile=%s" % psfile, tmpfile], _log)
1364+
[mpl._get_executable_info("gs").executable,
1365+
"-dBATCH", "-dNOPAUSE", "-r%d" % dpi, "-sDEVICE=ps2write",
1366+
paper_option, "-sOutputFile=%s" % psfile, tmpfile],
1367+
_log)
13671368

13681369
os.remove(tmpfile)
13691370
shutil.move(psfile, tmpfile)

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.