|
117 | 117 | """)
|
118 | 118 |
|
119 | 119 | import atexit
|
120 |
| -from collections import MutableMapping |
| 120 | +from collections import MutableMapping, namedtuple |
121 | 121 | import contextlib
|
122 |
| -import distutils.version |
| 122 | +from distutils.version import LooseVersion |
123 | 123 | import functools
|
124 | 124 | import io
|
125 | 125 | import importlib
|
@@ -184,9 +184,7 @@ def compare_versions(a, b):
|
184 | 184 | "3.0", "compare_version arguments should be strs.")
|
185 | 185 | b = b.decode('ascii')
|
186 | 186 | if a:
|
187 |
| - a = distutils.version.LooseVersion(a) |
188 |
| - b = distutils.version.LooseVersion(b) |
189 |
| - return a >= b |
| 187 | + return LooseVersion(a) >= LooseVersion(b) |
190 | 188 | else:
|
191 | 189 | return False
|
192 | 190 |
|
@@ -424,138 +422,158 @@ def wrapper(*args, **kwargs):
|
424 | 422 | return wrapper
|
425 | 423 |
|
426 | 424 |
|
| 425 | +_ExecInfo = namedtuple("_ExecInfo", "executable version") |
| 426 | + |
| 427 | + |
| 428 | +@functools.lru_cache() |
| 429 | +def get_executable_info(name): |
| 430 | + """Get the version of some executables that Matplotlib depends on. |
| 431 | +
|
| 432 | + .. warning: |
| 433 | + The list of executables that this function supports is set according to |
| 434 | + Matplotlib's internal needs, and may change without notice. |
| 435 | +
|
| 436 | + Parameters |
| 437 | + ---------- |
| 438 | + name : str |
| 439 | + The executable to query. The following values are currently supported: |
| 440 | + "dvipng", "gs", "inkscape", "pdftops", "tex". This list is subject to |
| 441 | + change without notice. |
| 442 | +
|
| 443 | + Returns |
| 444 | + ------- |
| 445 | + If the executable is found, a namedtuple with fields ``executable`` (`str`) |
| 446 | + and ``version`` (`distutils.version.LooseVersion`, or ``None`` if the |
| 447 | + version cannot be determined); ``None`` if the executable is not found. |
| 448 | + """ |
| 449 | + |
| 450 | + def impl(args, regex, min_ver=None): |
| 451 | + # Execute the subprocess specified by args; capture stdout and stderr. |
| 452 | + # Search for a regex match in the output; if the match succeeds, use |
| 453 | + # the *first group* of the match as the version. |
| 454 | + # If min_ver is not None, emit a warning if the version is less than |
| 455 | + # min_ver. |
| 456 | + try: |
| 457 | + proc = subprocess.Popen( |
| 458 | + args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, |
| 459 | + universal_newlines=True) |
| 460 | + proc.wait() |
| 461 | + except OSError: |
| 462 | + return None |
| 463 | + match = re.search(regex, proc.stdout.read()) |
| 464 | + if match: |
| 465 | + version = LooseVersion(match.group(1)) |
| 466 | + if min_ver is not None and version < min_ver: |
| 467 | + warnings.warn("You have {} version {} but the minimum version " |
| 468 | + "supported by Matplotlib is {}." |
| 469 | + .format(args[0], version, min_ver)) |
| 470 | + return None |
| 471 | + return _ExecInfo(args[0], version) |
| 472 | + else: |
| 473 | + return None |
| 474 | + |
| 475 | + if name == "dvipng": |
| 476 | + info = impl(["dvipng", "-version"], "(?m)^dvipng .* (.+)", "1.6") |
| 477 | + elif name == "gs": |
| 478 | + execs = (["gswin32c", "gswin64c", "mgs", "gs"] # "mgs" for miktex. |
| 479 | + if sys.platform == "win32" else |
| 480 | + ["gs"]) |
| 481 | + info = next((info for info in (impl([e, "--version"], "(.*)", "9") |
| 482 | + for e in execs) |
| 483 | + if info), |
| 484 | + None) |
| 485 | + elif name == "inkscape": |
| 486 | + info = impl(["inkscape", "-V"], "^Inkscape ([^ ]*)") |
| 487 | + elif name == "pdftops": |
| 488 | + info = impl(["pdftops", "-v"], "^pdftops version (.*)") |
| 489 | + if info and not ("3.0" <= info.version |
| 490 | + # poppler version numbers. |
| 491 | + or "0.9" <= info.version <= "1.0"): |
| 492 | + warnings.warn( |
| 493 | + "You have pdftops version {} but the minimum version " |
| 494 | + "supported by Matplotlib is 3.0.".format(info.version)) |
| 495 | + return None |
| 496 | + elif name == "tex": |
| 497 | + info = (_ExecInfo("tex", None) if shutil.which("tex") is not None |
| 498 | + else None) |
| 499 | + else: |
| 500 | + raise ValueError("Unknown executable: {!r}".format(name)) |
| 501 | + return info |
| 502 | + |
| 503 | + |
| 504 | +def get_all_executable_infos(): |
| 505 | + """Query all executables that Matplotlib may need. |
| 506 | +
|
| 507 | + .. warning: |
| 508 | + The list of executables that this function queries is set according to |
| 509 | + Matplotlib's internal needs, and may change without notice. |
| 510 | +
|
| 511 | + Returns |
| 512 | + ------- |
| 513 | + A mapping of the required executable to its corresponding information, |
| 514 | + as returned by `get_executable_info`. The keys in the mapping are subject |
| 515 | + to change without notice. |
| 516 | + """ |
| 517 | + return {name: get_executable_info(name) |
| 518 | + for name in ["dvipng", "gs", "inkscape", "pdftops", "tex"]} |
| 519 | + |
| 520 | + |
| 521 | +@cbook.deprecated("3.0") |
427 | 522 | def checkdep_dvipng():
|
428 |
| - try: |
429 |
| - s = subprocess.Popen([str('dvipng'), '-version'], |
430 |
| - stdout=subprocess.PIPE, |
431 |
| - stderr=subprocess.PIPE) |
432 |
| - stdout, stderr = s.communicate() |
433 |
| - line = stdout.decode('ascii').split('\n')[1] |
434 |
| - v = line.split()[-1] |
435 |
| - return v |
436 |
| - except (IndexError, ValueError, OSError): |
437 |
| - return None |
| 523 | + return str(get_executable_info("dvipng").version) |
438 | 524 |
|
439 | 525 |
|
| 526 | +@cbook.deprecated("3.0") |
440 | 527 | def checkdep_ghostscript():
|
441 |
| - if checkdep_ghostscript.executable is None: |
442 |
| - if sys.platform == 'win32': |
443 |
| - # mgs is the name in miktex |
444 |
| - gs_execs = ['gswin32c', 'gswin64c', 'mgs', 'gs'] |
445 |
| - else: |
446 |
| - gs_execs = ['gs'] |
447 |
| - for gs_exec in gs_execs: |
448 |
| - try: |
449 |
| - s = subprocess.Popen( |
450 |
| - [gs_exec, '--version'], stdout=subprocess.PIPE, |
451 |
| - stderr=subprocess.PIPE) |
452 |
| - stdout, stderr = s.communicate() |
453 |
| - if s.returncode == 0: |
454 |
| - v = stdout[:-1].decode('ascii') |
455 |
| - checkdep_ghostscript.executable = gs_exec |
456 |
| - checkdep_ghostscript.version = v |
457 |
| - except (IndexError, ValueError, OSError): |
458 |
| - pass |
| 528 | + info = get_executable_info("gs") |
| 529 | + checkdep_ghostscript.executable = info.executable |
| 530 | + checkdep_ghostscript.version = str(info.version) |
459 | 531 | return checkdep_ghostscript.executable, checkdep_ghostscript.version
|
460 | 532 | checkdep_ghostscript.executable = None
|
461 | 533 | checkdep_ghostscript.version = None
|
462 | 534 |
|
463 | 535 |
|
| 536 | +@cbook.deprecated("3.0") |
464 | 537 | 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 |
| 538 | + return str(get_executable_info("pdftops").version) |
476 | 539 |
|
477 | 540 |
|
| 541 | +@cbook.deprecated("3.0") |
478 | 542 | 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 |
| 543 | + checkdep_inkscape.version = str(get_executable_info("inkscape").version) |
493 | 544 | return checkdep_inkscape.version
|
494 | 545 | checkdep_inkscape.version = None
|
495 | 546 |
|
496 | 547 |
|
| 548 | +@cbook.deprecated("3.0") |
497 | 549 | def checkdep_ps_distiller(s):
|
498 | 550 | if not s:
|
499 | 551 | return False
|
500 |
| - |
501 |
| - flag = True |
502 |
| - gs_req = '8.60' |
503 |
| - gs_exec, gs_v = checkdep_ghostscript() |
504 |
| - if not compare_versions(gs_v, gs_req): |
505 |
| - flag = False |
506 |
| - warnings.warn(('matplotlibrc ps.usedistiller option can not be used ' |
507 |
| - 'unless ghostscript-%s or later is installed on your ' |
508 |
| - 'system') % gs_req) |
509 |
| - |
510 |
| - if s == 'xpdf': |
511 |
| - pdftops_req = '3.0' |
512 |
| - pdftops_req_alt = '0.9' # poppler version numbers, ugh |
513 |
| - pdftops_v = checkdep_pdftops() |
514 |
| - if compare_versions(pdftops_v, pdftops_req): |
515 |
| - pass |
516 |
| - elif (compare_versions(pdftops_v, pdftops_req_alt) and not |
517 |
| - compare_versions(pdftops_v, '1.0')): |
518 |
| - pass |
519 |
| - else: |
520 |
| - flag = False |
521 |
| - warnings.warn(('matplotlibrc ps.usedistiller can not be set to ' |
522 |
| - 'xpdf unless xpdf-%s or later is installed on ' |
523 |
| - 'your system') % pdftops_req) |
524 |
| - |
525 |
| - if flag: |
526 |
| - return s |
527 |
| - else: |
| 552 | + if not get_executable_info("gs"): |
| 553 | + warnings.warn( |
| 554 | + "Setting matplotlibrc ps.usedistiller requires ghostscript.") |
| 555 | + return False |
| 556 | + if s == "xpdf" and not get_executable_info("pdftops"): |
| 557 | + warnings.warn( |
| 558 | + "Setting matplotlibrc ps.usedistiller to 'xpdf' requires xpdf.") |
528 | 559 | return False
|
| 560 | + return s |
529 | 561 |
|
530 | 562 |
|
531 | 563 | def checkdep_usetex(s):
|
532 | 564 | if not s:
|
533 | 565 | return False
|
534 |
| - |
535 |
| - gs_req = '8.60' |
536 |
| - dvipng_req = '1.6' |
537 |
| - flag = True |
538 |
| - |
539 |
| - if shutil.which("tex") is None: |
540 |
| - flag = False |
541 |
| - warnings.warn('matplotlibrc text.usetex option can not be used unless ' |
542 |
| - 'TeX is installed on your system') |
543 |
| - |
544 |
| - dvipng_v = checkdep_dvipng() |
545 |
| - if not compare_versions(dvipng_v, dvipng_req): |
546 |
| - flag = False |
547 |
| - warnings.warn('matplotlibrc text.usetex can not be used with *Agg ' |
548 |
| - 'backend unless dvipng-%s or later is installed on ' |
549 |
| - 'your system' % dvipng_req) |
550 |
| - |
551 |
| - gs_exec, gs_v = checkdep_ghostscript() |
552 |
| - if not compare_versions(gs_v, gs_req): |
553 |
| - flag = False |
554 |
| - warnings.warn('matplotlibrc text.usetex can not be used unless ' |
555 |
| - 'ghostscript-%s or later is installed on your system' |
556 |
| - % gs_req) |
557 |
| - |
558 |
| - return flag |
| 566 | + if not get_executable_info("tex"): |
| 567 | + warnings.warn("Setting matplotlibrc text.usetex requires TeX.") |
| 568 | + return False |
| 569 | + if not get_executable_info("dvipng"): |
| 570 | + warnings.warn("Setting matplotlibrc text.usetex requires dvipng.") |
| 571 | + return False |
| 572 | + if not get_executable_info("gs"): |
| 573 | + warnings.warn( |
| 574 | + "Setting matplotlibrc text.usetex requires ghostscript.") |
| 575 | + return False |
| 576 | + return True |
559 | 577 |
|
560 | 578 |
|
561 | 579 | def _get_home():
|
@@ -1133,9 +1151,6 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True):
|
1133 | 1151 | defaultParams.items()
|
1134 | 1152 | if key not in _all_deprecated])
|
1135 | 1153 |
|
1136 |
| -rcParams['ps.usedistiller'] = checkdep_ps_distiller( |
1137 |
| - rcParams['ps.usedistiller']) |
1138 |
| - |
1139 | 1154 | rcParams['text.usetex'] = checkdep_usetex(rcParams['text.usetex'])
|
1140 | 1155 |
|
1141 | 1156 | if rcParams['axes.formatter.use_locale']:
|
|
0 commit comments