115115""" )
116116
117117import atexit
118+ from collections import namedtuple
118119from collections .abc import MutableMapping
119120import contextlib
120- import distutils .version
121+ from distutils .version import LooseVersion
121122import functools
122123import importlib
123124import 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" )
279389def 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" )
292403def checkdep_ghostscript ():
293404 if checkdep_ghostscript .executable is None :
294405 if sys .platform == 'win32' :
@@ -314,6 +425,7 @@ def checkdep_ghostscript():
314425checkdep_ghostscript .version = None
315426
316427
428+ @cbook .deprecated ("3.1" )
317429def 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" )
331444def checkdep_inkscape ():
332445 if checkdep_inkscape .version is None :
333446 try :
@@ -350,64 +463,39 @@ def checkdep_inkscape():
350463def 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
383482def 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' )
0 commit comments