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
@@ -179,9 +180,7 @@ def compare_versions(a, b):
179180 "3.0" , message = "compare_versions arguments should be strs." )
180181 b = b .decode ('ascii' )
181182 if a :
182- a = distutils .version .LooseVersion (a )
183- b = distutils .version .LooseVersion (b )
184- return a >= b
183+ return LooseVersion (a ) >= LooseVersion (b )
185184 else :
186185 return False
187186
@@ -195,7 +194,7 @@ def _check_versions():
195194 ("pyparsing" , "2.0.1" ),
196195 ]:
197196 module = importlib .import_module (modname )
198- if distutils . version . LooseVersion (module .__version__ ) < minver :
197+ if LooseVersion (module .__version__ ) < minver :
199198 raise ImportError ("Matplotlib requires {}>={}; you have {}"
200199 .format (modname , minver , module .__version__ ))
201200
@@ -282,6 +281,117 @@ def wrapper():
282281 return wrapper
283282
284283
284+ _ExecInfo = namedtuple ("_ExecInfo" , "executable version" )
285+
286+
287+ @functools .lru_cache ()
288+ def _get_executable_info (name ):
289+ """
290+ Get the version of some executable that Matplotlib optionally depends on.
291+
292+ .. warning:
293+ The list of executables that this function supports is set according to
294+ Matplotlib's internal needs, and may change without notice.
295+
296+ Parameters
297+ ----------
298+ name : str
299+ The executable to query. The following values are currently supported:
300+ "dvipng", "gs", "inkscape", "magick", "pdftops". This list is subject
301+ to change without notice.
302+
303+ Returns
304+ -------
305+ If the executable is found, a namedtuple with fields ``executable`` (`str`)
306+ and ``version`` (`distutils.version.LooseVersion`, or ``None`` if the
307+ version cannot be determined).
308+
309+ Raises
310+ ------
311+ FileNotFoundError
312+ If the executable is not found or older than the oldest version
313+ supported by Matplotlib.
314+ ValueError
315+ If the executable is not one that we know how to query.
316+ """
317+
318+ def impl (args , regex , min_ver = None ):
319+ # Execute the subprocess specified by args; capture stdout and stderr.
320+ # Search for a regex match in the output; if the match succeeds, the
321+ # first group of the match is the version.
322+ # Return an _ExecInfo if the executable exists, and has a version of
323+ # at least min_ver (if set); else, raise FileNotFoundError.
324+ output = subprocess .check_output (
325+ args , stderr = subprocess .STDOUT , universal_newlines = True )
326+ match = re .search (regex , output )
327+ if match :
328+ version = LooseVersion (match .group (1 ))
329+ if min_ver is not None and version < min_ver :
330+ raise FileNotFoundError (
331+ f"You have { args [0 ]} version { version } but the minimum "
332+ f"version supported by Matplotlib is { min_ver } ." )
333+ return _ExecInfo (args [0 ], version )
334+ else :
335+ raise FileNotFoundError (
336+ f"Failed to determine the version of { args [0 ]} from "
337+ f"{ ' ' .join (args )} , which output { output } " )
338+
339+ if name == "dvipng" :
340+ return impl (["dvipng" , "-version" ], "(?m)^dvipng .* (.+)" , "1.6" )
341+ elif name == "gs" :
342+ execs = (["gswin32c" , "gswin64c" , "mgs" , "gs" ] # "mgs" for miktex.
343+ if sys .platform == "win32" else
344+ ["gs" ])
345+ for e in execs :
346+ try :
347+ return impl ([e , "--version" ], "(.*)" , "9" )
348+ except FileNotFoundError :
349+ pass
350+ raise FileNotFoundError ("Failed to find a Ghostscript installation" )
351+ elif name == "inkscape" :
352+ return impl (["inkscape" , "-V" ], "^Inkscape ([^ ]*)" )
353+ elif name == "magick" :
354+ path = None
355+ if sys .platform == "win32" :
356+ # Check the registry to avoid confusing ImageMagick's convert with
357+ # Windows's builtin convert.exe.
358+ import winreg
359+ binpath = ""
360+ for flag in [0 , winreg .KEY_WOW64_32KEY , winreg .KEY_WOW64_64KEY ]:
361+ try :
362+ with winreg .OpenKeyEx (
363+ winreg .HKEY_LOCAL_MACHINE ,
364+ r"Software\Imagemagick\Current" ,
365+ 0 , winreg .KEY_QUERY_VALUE | flag ) as hkey :
366+ binpath = winreg .QueryValueEx (hkey , "BinPath" )[0 ]
367+ except OSError :
368+ pass
369+ if binpath :
370+ for name in ["convert.exe" , "magick.exe" ]:
371+ candidate = Path (binpath , name )
372+ if candidate .exists ():
373+ path = candidate
374+ break
375+ else :
376+ path = "convert"
377+ if path is None :
378+ raise FileNotFoundError (
379+ "Failed to find an ImageMagick installation" )
380+ return impl ([path , "--version" ], r"^Version: ImageMagick (\S*)" )
381+ elif name == "pdftops" :
382+ info = impl (["pdftops" , "-v" ], "^pdftops version (.*)" )
383+ if info and not ("3.0" <= info .version
384+ # poppler version numbers.
385+ or "0.9" <= info .version <= "1.0" ):
386+ raise FileNotFoundError (
387+ f"You have pdftops version { info .version } but the minimum "
388+ f"version supported by Matplotlib is 3.0." )
389+ return info
390+ else :
391+ raise ValueError ("Unknown executable: {!r}" .format (name ))
392+
393+
394+ @cbook .deprecated ("3.1" )
285395def checkdep_dvipng ():
286396 try :
287397 s = subprocess .Popen (['dvipng' , '-version' ],
@@ -295,6 +405,7 @@ def checkdep_dvipng():
295405 return None
296406
297407
408+ @cbook .deprecated ("3.1" )
298409def checkdep_ghostscript ():
299410 if checkdep_ghostscript .executable is None :
300411 if sys .platform == 'win32' :
@@ -320,6 +431,7 @@ def checkdep_ghostscript():
320431checkdep_ghostscript .version = None
321432
322433
434+ @cbook .deprecated ("3.1" )
323435def checkdep_pdftops ():
324436 try :
325437 s = subprocess .Popen (['pdftops' , '-v' ], stdout = subprocess .PIPE ,
@@ -334,6 +446,7 @@ def checkdep_pdftops():
334446 return None
335447
336448
449+ @cbook .deprecated ("3.1" )
337450def checkdep_inkscape ():
338451 if checkdep_inkscape .version is None :
339452 try :
@@ -356,64 +469,39 @@ def checkdep_inkscape():
356469def checkdep_ps_distiller (s ):
357470 if not s :
358471 return False
359-
360- flag = True
361- gs_exec , gs_v = checkdep_ghostscript ()
362- if not gs_exec :
363- flag = False
364- _log .warning ('matplotlibrc ps.usedistiller option can not be used '
365- 'unless ghostscript 9.0 or later is installed on your '
366- 'system.' )
367-
368- if s == 'xpdf' :
369- pdftops_req = '3.0'
370- pdftops_req_alt = '0.9' # poppler version numbers, ugh
371- pdftops_v = checkdep_pdftops ()
372- if compare_versions (pdftops_v , pdftops_req ):
373- pass
374- elif (compare_versions (pdftops_v , pdftops_req_alt ) and not
375- compare_versions (pdftops_v , '1.0' )):
376- pass
377- else :
378- flag = False
379- _log .warning ('matplotlibrc ps.usedistiller can not be set to xpdf '
380- 'unless xpdf-%s or later is installed on your '
381- 'system.' , pdftops_req )
382-
383- if flag :
384- return s
385- else :
472+ try :
473+ _get_executable_info ("gs" )
474+ except FileNotFoundError :
475+ _log .warning (
476+ "Setting rcParams['ps.usedistiller'] requires ghostscript." )
386477 return False
478+ if s == "xpdf" :
479+ try :
480+ _get_executable_info ("pdftops" )
481+ except FileNotFoundError :
482+ _log .warning (
483+ "Setting rcParams['ps.usedistiller'] to 'xpdf' requires xpdf." )
484+ return False
485+ return s
387486
388487
389488def checkdep_usetex (s ):
390489 if not s :
391490 return False
392-
393- gs_req = '9.00'
394- dvipng_req = '1.6'
395- flag = True
396-
397- if shutil .which ("tex" ) is None :
398- flag = False
399- _log .warning ('matplotlibrc text.usetex option can not be used unless '
400- 'TeX is installed on your system.' )
401-
402- dvipng_v = checkdep_dvipng ()
403- if not compare_versions (dvipng_v , dvipng_req ):
404- flag = False
405- _log .warning ('matplotlibrc text.usetex can not be used with *Agg '
406- 'backend unless dvipng-%s or later is installed on '
407- 'your system.' , dvipng_req )
408-
409- gs_exec , gs_v = checkdep_ghostscript ()
410- if not compare_versions (gs_v , gs_req ):
411- flag = False
412- _log .warning ('matplotlibrc text.usetex can not be used unless '
413- 'ghostscript-%s or later is installed on your system.' ,
414- gs_req )
415-
416- return flag
491+ if not shutil .which ("tex" ):
492+ _log .warning ("usetex mode requires TeX." )
493+ return False
494+ try :
495+ _get_executable_info ("dvipng" )
496+ except FileNotFoundError :
497+ _log .warning ("usetex mode requires dvipng." )
498+ return False
499+ try :
500+ _get_executable_info ("gs" )
501+ except FileNotFoundError :
502+ _log .warning ("usetex mode requires ghostscript." )
503+ return False
504+ return True
417505
418506
419507@_logged_cached ('$HOME=%s' )
0 commit comments