115
115
""" )
116
116
117
117
import atexit
118
+ from collections import namedtuple
118
119
from collections .abc import MutableMapping
119
120
import contextlib
120
- import distutils .version
121
+ from distutils .version import LooseVersion
121
122
import functools
122
123
import importlib
123
124
import inspect
@@ -179,9 +180,7 @@ def compare_versions(a, b):
179
180
"3.0" , message = "compare_versions arguments should be strs." )
180
181
b = b .decode ('ascii' )
181
182
if a :
182
- a = distutils .version .LooseVersion (a )
183
- b = distutils .version .LooseVersion (b )
184
- return a >= b
183
+ return LooseVersion (a ) >= LooseVersion (b )
185
184
else :
186
185
return False
187
186
@@ -195,7 +194,7 @@ def _check_versions():
195
194
("pyparsing" , "2.0.1" ),
196
195
]:
197
196
module = importlib .import_module (modname )
198
- if distutils . version . LooseVersion (module .__version__ ) < minver :
197
+ if LooseVersion (module .__version__ ) < minver :
199
198
raise ImportError ("Matplotlib requires {}>={}; you have {}"
200
199
.format (modname , minver , module .__version__ ))
201
200
@@ -282,6 +281,117 @@ def wrapper():
282
281
return wrapper
283
282
284
283
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 that 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" , alternative = "get_executable_info('dvipng')" )
285
395
def checkdep_dvipng ():
286
396
try :
287
397
s = subprocess .Popen (['dvipng' , '-version' ],
@@ -295,6 +405,7 @@ def checkdep_dvipng():
295
405
return None
296
406
297
407
408
+ @cbook .deprecated ("3.1" , alternative = "get_executable_info('gs')" )
298
409
def checkdep_ghostscript ():
299
410
if checkdep_ghostscript .executable is None :
300
411
if sys .platform == 'win32' :
@@ -320,6 +431,7 @@ def checkdep_ghostscript():
320
431
checkdep_ghostscript .version = None
321
432
322
433
434
+ @cbook .deprecated ("3.1" , alternative = "get_executable_info('pdftops')" )
323
435
def checkdep_pdftops ():
324
436
try :
325
437
s = subprocess .Popen (['pdftops' , '-v' ], stdout = subprocess .PIPE ,
@@ -334,6 +446,7 @@ def checkdep_pdftops():
334
446
return None
335
447
336
448
449
+ @cbook .deprecated ("3.1" , alternative = "get_executable_info('inkscape')" )
337
450
def checkdep_inkscape ():
338
451
if checkdep_inkscape .version is None :
339
452
try :
@@ -356,64 +469,39 @@ def checkdep_inkscape():
356
469
def checkdep_ps_distiller (s ):
357
470
if not s :
358
471
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." )
386
477
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
387
486
388
487
389
488
def checkdep_usetex (s ):
390
489
if not s :
391
490
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
417
505
418
506
419
507
@_logged_cached ('$HOME=%s' )
0 commit comments