102
102
from __future__ import absolute_import , division , print_function
103
103
104
104
import six
105
+ from six .moves .urllib .request import urlopen
106
+ from six .moves import reload_module as reload
105
107
106
108
import atexit
107
- from collections import MutableMapping
109
+ from collections import MutableMapping , namedtuple
108
110
import contextlib
109
- import distutils .version
110
- import distutils .sysconfig
111
+ from distutils .version import LooseVersion
111
112
import functools
112
113
import io
113
114
import inspect
122
123
import tempfile
123
124
import warnings
124
125
126
+ try :
127
+ from functools import lru_cache
128
+ except ImportError :
129
+ from backports .functools_lru_cache import lru_cache
130
+
125
131
# cbook must import matplotlib only within function
126
132
# definitions, so it is safe to import from it here.
127
133
from . import cbook
131
137
from matplotlib .rcsetup import defaultParams , validate_backend , cycler
132
138
133
139
import numpy
134
- from six .moves .urllib .request import urlopen
135
- from six .moves import reload_module as reload
136
140
137
141
# Get the version from the _version.py versioneer file. For a git checkout,
138
142
# this is computed based on the number of commits since the last tag.
@@ -177,9 +181,7 @@ def compare_versions(a, b):
177
181
a = a .decode ('ascii' )
178
182
if isinstance (b , bytes ):
179
183
b = b .decode ('ascii' )
180
- a = distutils .version .LooseVersion (a )
181
- b = distutils .version .LooseVersion (b )
182
- return a >= b
184
+ return LooseVersion (a ) >= LooseVersion (b )
183
185
else :
184
186
return False
185
187
@@ -407,89 +409,125 @@ def wrapper(*args, **kwargs):
407
409
return wrapper
408
410
409
411
412
+ _ExecInfo = namedtuple ("_ExecInfo" , "executable version" )
413
+
414
+
415
+ @lru_cache ()
416
+ def get_executable_info (name ):
417
+ """Get the version of some executables that Matplotlib depends on.
418
+
419
+ .. warning:
420
+ The list of executables that this function supports is set according to
421
+ Matplotlib's internal needs, and may change without notice.
422
+
423
+ Parameters
424
+ ----------
425
+ name : str
426
+ The executable to query. The following values are currently supported:
427
+ "dvipng", "gs", "inkscape", "pdftops", "tex". This list is subject to
428
+ change without notice.
429
+
430
+ Returns
431
+ -------
432
+ If the executable is found, a namedtuple with fields ``executable`` (`str`)
433
+ and ``version`` (`distutils.version.LooseVersion`, or ``None`` if the
434
+ version cannot be determined); ``None`` if the executable is not found.
435
+ """
436
+
437
+ def impl (args , regex , min_ver = None ):
438
+ # Execute the subprocess specified by args; capture stdout and stderr.
439
+ # Search for a regex match in the output; if the match succeeds, use
440
+ # the *first group* of the match as the version.
441
+ # If min_ver is not None, emit a warning if the version is less than
442
+ # min_ver.
443
+ try :
444
+ proc = subprocess .Popen (
445
+ [str (arg ) for arg in args ], # str(): Py2 compat.
446
+ stdout = subprocess .PIPE , stderr = subprocess .STDOUT ,
447
+ universal_newlines = True )
448
+ proc .wait ()
449
+ except OSError :
450
+ return None
451
+ match = re .search (regex , proc .stdout .read ())
452
+ if match :
453
+ version = LooseVersion (match .group (1 ))
454
+ if min_ver is not None and version < str (min_ver ):
455
+ warnings .warn ("You have {} version {} but the minimum version "
456
+ "supported by Matplotlib is {}."
457
+ .format (args [0 ], version , min_ver ))
458
+ return None
459
+ return _ExecInfo (str (args [0 ]), version ) # str(): Py2 compat.
460
+ else :
461
+ return None
462
+
463
+ if name == "dvipng" :
464
+ info = impl (["dvipng" , "-version" ], "(?m)^dvipng .* (.+)" , "1.6" )
465
+ elif name == "gs" :
466
+ execs = (["gswin32c" , "gswin64c" , "mgs" , "gs" ] # "mgs" for miktex.
467
+ if sys .platform == "win32" else
468
+ ["gs" ])
469
+ info = next (filter (None , (impl ([e , "--version" ], "(.*)" , "8.60" )
470
+ for e in execs )),
471
+ None )
472
+ elif name == "inkscape" :
473
+ info = impl (["inkscape" , "-V" ], "^Inkscape ([^ ]*)" )
474
+ elif name == "pdftops" :
475
+ info = impl (["pdftops" , "-v" ], "^pdftops version (.*)" )
476
+ if info and not (str ("3.0" ) <= info .version
477
+ # poppler version numbers.
478
+ or str ("0.9" ) <= info .version <= str ("1.0" )):
479
+ warnings .warn (
480
+ "You have pdftops version {} but the minimum version "
481
+ "supported by Matplotlib is 3.0." .format (info .version ))
482
+ return None
483
+ elif name == "tex" :
484
+ info = (_ExecInfo (str ("tex" ), None ) # str(): Py2 compat.
485
+ if _backports .which ("tex" ) is not None
486
+ else None )
487
+ else :
488
+ raise ValueError ("Unknown executable: {!r}" .format (name ))
489
+ return info
490
+
491
+
492
+ def get_all_executable_infos ():
493
+ """Query all executables that Matplotlib may need.
494
+
495
+ .. warning:
496
+ The list of executables that this function queries is set according to
497
+ Matplotlib's internal needs, and may change without notice.
498
+
499
+ Returns
500
+ -------
501
+ A mapping of the required executable to its corresponding information,
502
+ as returned by `get_executable_info`. The keys in the mapping are subject
503
+ to change without notice.
504
+ """
505
+ return {name : get_executable_info (name )
506
+ for name in ["dvipng" , "gs" , "inkscape" , "pdftops" , "tex" ]}
507
+
508
+
509
+ @cbook .deprecated ("2.2" )
410
510
def checkdep_dvipng ():
411
- try :
412
- s = subprocess .Popen ([str ('dvipng' ), '-version' ],
413
- stdout = subprocess .PIPE ,
414
- stderr = subprocess .PIPE )
415
- stdout , stderr = s .communicate ()
416
- line = stdout .decode ('ascii' ).split ('\n ' )[1 ]
417
- v = line .split ()[- 1 ]
418
- return v
419
- except (IndexError , ValueError , OSError ):
420
- return None
511
+ return str (get_executable_info ("dvipng" ).version )
421
512
422
513
423
514
def checkdep_ghostscript ():
424
- if checkdep_ghostscript .executable is None :
425
- if sys .platform == 'win32' :
426
- # mgs is the name in miktex
427
- gs_execs = ['gswin32c' , 'gswin64c' , 'mgs' , 'gs' ]
428
- else :
429
- gs_execs = ['gs' ]
430
- for gs_exec in gs_execs :
431
- try :
432
- s = subprocess .Popen (
433
- [str (gs_exec ), '--version' ], stdout = subprocess .PIPE ,
434
- stderr = subprocess .PIPE )
435
- stdout , stderr = s .communicate ()
436
- if s .returncode == 0 :
437
- v = stdout [:- 1 ].decode ('ascii' )
438
- checkdep_ghostscript .executable = gs_exec
439
- checkdep_ghostscript .version = v
440
- except (IndexError , ValueError , OSError ):
441
- pass
515
+ info = get_executable_info ("gs" )
516
+ checkdep_ghostscript .executable = info .executable
517
+ checkdep_ghostscript .version = str (info .version )
442
518
return checkdep_ghostscript .executable , checkdep_ghostscript .version
443
519
checkdep_ghostscript .executable = None
444
520
checkdep_ghostscript .version = None
445
521
446
522
447
- # Deprecated, as it is unneeded and some distributions (e.g. MiKTeX 2.9.6350)
448
- # do not actually report the TeX version.
449
- @cbook .deprecated ("2.1" )
450
- def checkdep_tex ():
451
- try :
452
- s = subprocess .Popen ([str ('tex' ), '-version' ], stdout = subprocess .PIPE ,
453
- stderr = subprocess .PIPE )
454
- stdout , stderr = s .communicate ()
455
- line = stdout .decode ('ascii' ).split ('\n ' )[0 ]
456
- pattern = r'3\.1\d+'
457
- match = re .search (pattern , line )
458
- v = match .group (0 )
459
- return v
460
- except (IndexError , ValueError , AttributeError , OSError ):
461
- return None
462
-
463
-
523
+ @cbook .deprecated ("2.2" )
464
524
def checkdep_pdftops ():
465
- try :
466
- s = subprocess .Popen ([str ('pdftops' ), '-v' ], stdout = subprocess .PIPE ,
467
- stderr = subprocess .PIPE )
468
- stdout , stderr = s .communicate ()
469
- lines = stderr .decode ('ascii' ).split ('\n ' )
470
- for line in lines :
471
- if 'version' in line :
472
- v = line .split ()[- 1 ]
473
- return v
474
- except (IndexError , ValueError , UnboundLocalError , OSError ):
475
- return None
525
+ return str (get_executable_info ("pdftops" ).version )
476
526
477
527
528
+ @cbook .deprecated ("2.2" )
478
529
def checkdep_inkscape ():
479
- if checkdep_inkscape .version is None :
480
- try :
481
- s = subprocess .Popen ([str ('inkscape' ), '-V' ],
482
- stdout = subprocess .PIPE ,
483
- stderr = subprocess .PIPE )
484
- stdout , stderr = s .communicate ()
485
- lines = stdout .decode ('ascii' ).split ('\n ' )
486
- for line in lines :
487
- if 'Inkscape' in line :
488
- v = line .split ()[1 ]
489
- break
490
- checkdep_inkscape .version = v
491
- except (IndexError , ValueError , UnboundLocalError , OSError ):
492
- pass
530
+ checkdep_inkscape .version = str (get_executable_info ("inkscape" ).version )
493
531
return checkdep_inkscape .version
494
532
checkdep_inkscape .version = None
495
533
@@ -514,65 +552,31 @@ def checkdep_xmllint():
514
552
def checkdep_ps_distiller (s ):
515
553
if not s :
516
554
return False
517
-
518
- flag = True
519
- gs_req = '8.60'
520
- gs_exec , gs_v = checkdep_ghostscript ()
521
- if not compare_versions (gs_v , gs_req ):
522
- flag = False
523
- warnings .warn (('matplotlibrc ps.usedistiller option can not be used '
524
- 'unless ghostscript-%s or later is installed on your '
525
- 'system' ) % gs_req )
526
-
527
- if s == 'xpdf' :
528
- pdftops_req = '3.0'
529
- pdftops_req_alt = '0.9' # poppler version numbers, ugh
530
- pdftops_v = checkdep_pdftops ()
531
- if compare_versions (pdftops_v , pdftops_req ):
532
- pass
533
- elif (compare_versions (pdftops_v , pdftops_req_alt ) and not
534
- compare_versions (pdftops_v , '1.0' )):
535
- pass
536
- else :
537
- flag = False
538
- warnings .warn (('matplotlibrc ps.usedistiller can not be set to '
539
- 'xpdf unless xpdf-%s or later is installed on '
540
- 'your system' ) % pdftops_req )
541
-
542
- if flag :
543
- return s
544
- else :
555
+ if not get_executable_info ("gs" ):
556
+ warnings .warn (
557
+ "Setting matplotlibrc ps.usedistiller requires ghostscript." )
558
+ return False
559
+ if s == "xpdf" and not get_executable_info ("pdftops" ):
560
+ warnings .warn (
561
+ "setting matplotlibrc ps.usedistiller to 'xpdf' requires xpdf." )
545
562
return False
563
+ return s
546
564
547
565
548
566
def checkdep_usetex (s ):
549
567
if not s :
550
568
return False
551
-
552
- gs_req = '8.60'
553
- dvipng_req = '1.6'
554
- flag = True
555
-
556
- if _backports .which ("tex" ) is None :
557
- flag = False
558
- warnings .warn ('matplotlibrc text.usetex option can not be used unless '
559
- 'TeX is installed on your system' )
560
-
561
- dvipng_v = checkdep_dvipng ()
562
- if not compare_versions (dvipng_v , dvipng_req ):
563
- flag = False
564
- warnings .warn ('matplotlibrc text.usetex can not be used with *Agg '
565
- 'backend unless dvipng-%s or later is installed on '
566
- 'your system' % dvipng_req )
567
-
568
- gs_exec , gs_v = checkdep_ghostscript ()
569
- if not compare_versions (gs_v , gs_req ):
570
- flag = False
571
- warnings .warn ('matplotlibrc text.usetex can not be used unless '
572
- 'ghostscript-%s or later is installed on your system'
573
- % gs_req )
574
-
575
- return flag
569
+ if not get_executable_info ("tex" ):
570
+ warnings .warn ("Setting matplotlibrc text.usetex requires TeX." )
571
+ return False
572
+ if not get_executable_info ("dvipng" ):
573
+ warnings .warn ("Setting matplotlibrc text.usetex requires dvipng." )
574
+ return False
575
+ if not get_executable_info ("gs" ):
576
+ warnings .warn (
577
+ "Setting matplotlibrc text.usetex requires ghostscript." )
578
+ return False
579
+ return True
576
580
577
581
578
582
def _get_home ():
@@ -1361,7 +1365,7 @@ def use(arg, warn=True, force=False):
1361
1365
# Check if we've already set up a backend
1362
1366
if 'matplotlib.backends' in sys .modules :
1363
1367
# Warn only if called with a different name
1364
- if (rcParams ['backend' ] != name ) and warn :
1368
+ if (rcParams ['backend' ] != name ) and warn :
1365
1369
import matplotlib .backends
1366
1370
warnings .warn (
1367
1371
_use_error_msg .format (
0 commit comments