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
@@ -178,9 +179,7 @@ def compare_versions(a, b):
178
179
"3.0" , message = "compare_versions arguments should be strs." )
179
180
b = b .decode ('ascii' )
180
181
if a :
181
- a = distutils .version .LooseVersion (a )
182
- b = distutils .version .LooseVersion (b )
183
- return a >= b
182
+ return LooseVersion (a ) >= LooseVersion (b )
184
183
else :
185
184
return False
186
185
@@ -194,7 +193,7 @@ def _check_versions():
194
193
("pyparsing" , "2.0.1" ),
195
194
]:
196
195
module = importlib .import_module (modname )
197
- if distutils . version . LooseVersion (module .__version__ ) < minver :
196
+ if LooseVersion (module .__version__ ) < minver :
198
197
raise ImportError ("Matplotlib requires {}>={}; you have {}"
199
198
.format (modname , minver , module .__version__ ))
200
199
@@ -276,6 +275,117 @@ def wrapper():
276
275
return wrapper
277
276
278
277
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" )
279
389
def checkdep_dvipng ():
280
390
try :
281
391
s = subprocess .Popen (['dvipng' , '-version' ],
@@ -289,6 +399,7 @@ def checkdep_dvipng():
289
399
return None
290
400
291
401
402
+ @cbook .deprecated ("3.1" )
292
403
def checkdep_ghostscript ():
293
404
if checkdep_ghostscript .executable is None :
294
405
if sys .platform == 'win32' :
@@ -314,6 +425,7 @@ def checkdep_ghostscript():
314
425
checkdep_ghostscript .version = None
315
426
316
427
428
+ @cbook .deprecated ("3.1" )
317
429
def checkdep_pdftops ():
318
430
try :
319
431
s = subprocess .Popen (['pdftops' , '-v' ], stdout = subprocess .PIPE ,
@@ -328,6 +440,7 @@ def checkdep_pdftops():
328
440
return None
329
441
330
442
443
+ @cbook .deprecated ("3.1" )
331
444
def checkdep_inkscape ():
332
445
if checkdep_inkscape .version is None :
333
446
try :
@@ -350,64 +463,39 @@ def checkdep_inkscape():
350
463
def checkdep_ps_distiller (s ):
351
464
if not s :
352
465
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." )
380
471
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
381
480
382
481
383
482
def checkdep_usetex (s ):
384
483
if not s :
385
484
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
411
499
412
500
413
501
@_logged_cached ('$HOME=%s' )
0 commit comments