|
| 1 | +"""Auxiliary module for the checkhealth subcommand, contains |
| 2 | +the actual check implementations.""" |
| 3 | + |
| 4 | +from __future__ import annotations |
| 5 | + |
| 6 | +import os |
| 7 | +import shutil |
| 8 | +import subprocess |
| 9 | +from typing import Callable |
| 10 | + |
| 11 | +from ..._config import config |
| 12 | + |
| 13 | +HEALTH_CHECKS = [] |
| 14 | + |
| 15 | + |
| 16 | +def healthcheck( |
| 17 | + description: str, |
| 18 | + recommendation: str, |
| 19 | + skip_on_failed: list[Callable | str] | None = None, |
| 20 | + post_fail_fix_hook: Callable | None = None, |
| 21 | +): |
| 22 | + """Decorator used for declaring health checks. |
| 23 | +
|
| 24 | + This decorator attaches some data to a function, |
| 25 | + which is then added to a list containing all checks. |
| 26 | +
|
| 27 | + Parameters |
| 28 | + ---------- |
| 29 | + description |
| 30 | + A brief description of this check, displayed when |
| 31 | + the checkhealth subcommand is run. |
| 32 | + recommendation |
| 33 | + Help text which is displayed in case the check fails. |
| 34 | + skip_on_failed |
| 35 | + A list of check functions which, if they fail, cause |
| 36 | + the current check to be skipped. |
| 37 | + post_fail_fix_hook |
| 38 | + A function that is supposed to (interactively) help |
| 39 | + to fix the detected problem, if possible. This is |
| 40 | + only called upon explicit confirmation of the user. |
| 41 | +
|
| 42 | + Returns |
| 43 | + ------- |
| 44 | + A check function, as required by the checkhealth subcommand. |
| 45 | + """ |
| 46 | + if skip_on_failed is None: |
| 47 | + skip_on_failed = [] |
| 48 | + skip_on_failed = [ |
| 49 | + skip.__name__ if callable(skip) else skip for skip in skip_on_failed |
| 50 | + ] |
| 51 | + |
| 52 | + def decorator(func): |
| 53 | + func.description = description |
| 54 | + func.recommendation = recommendation |
| 55 | + func.skip_on_failed = skip_on_failed |
| 56 | + func.post_fail_fix_hook = post_fail_fix_hook |
| 57 | + HEALTH_CHECKS.append(func) |
| 58 | + return func |
| 59 | + |
| 60 | + return decorator |
| 61 | + |
| 62 | + |
| 63 | +@healthcheck( |
| 64 | + description="Checking whether manim is on your PATH", |
| 65 | + recommendation=( |
| 66 | + "The command <manim> is currently not on your system's PATH.\n\n" |
| 67 | + "You can work around this by calling the manim module directly " |
| 68 | + "via <python -m manim> instead of just <manim>.\n\n" |
| 69 | + "To fix the PATH issue properly: " |
| 70 | + "Usually, the Python package installer pip issues a warning " |
| 71 | + "during the installation which contains more information. " |
| 72 | + "Consider reinstalling manim via <pip uninstall manim> " |
| 73 | + "followed by <pip install manim> to see the warning again, " |
| 74 | + "then consult the internet on how to modify your system's " |
| 75 | + "PATH variable." |
| 76 | + ), |
| 77 | +) |
| 78 | +def is_manim_on_path(): |
| 79 | + path_to_manim = shutil.which("manim") |
| 80 | + return path_to_manim is not None |
| 81 | + |
| 82 | + |
| 83 | +@healthcheck( |
| 84 | + description="Checking whether the executable belongs to manim", |
| 85 | + recommendation=( |
| 86 | + "The command <manim> does not belong to your installed version " |
| 87 | + "of this library, it likely belongs to manimgl / manimlib.\n\n" |
| 88 | + "Run manim via <python -m manim> or via <manimce>, or uninstall " |
| 89 | + "and reinstall manim via <pip install --upgrade " |
| 90 | + "--force-reinstall manim> to fix this." |
| 91 | + ), |
| 92 | + skip_on_failed=[is_manim_on_path], |
| 93 | +) |
| 94 | +def is_manim_executable_associated_to_this_library(): |
| 95 | + path_to_manim = shutil.which("manim") |
| 96 | + with open(path_to_manim, "rb") as f: |
| 97 | + manim_exec = f.read() |
| 98 | + |
| 99 | + # first condition below corresponds to the executable being |
| 100 | + # some sort of python script. second condition happens when |
| 101 | + # the executable is actually a Windows batch file. |
| 102 | + return b"manim.__main__" in manim_exec or b'"%~dp0\\manim"' in manim_exec |
| 103 | + |
| 104 | + |
| 105 | +@healthcheck( |
| 106 | + description="Checking whether ffmpeg is available", |
| 107 | + recommendation=( |
| 108 | + "Manim does not work without ffmpeg. Please follow our " |
| 109 | + "installation instructions " |
| 110 | + "at https://docs.manim.community/en/stable/installation.html " |
| 111 | + "to download ffmpeg. Then, either ...\n\n" |
| 112 | + "(a) ... make the ffmpeg executable available to your system's PATH,\n" |
| 113 | + "(b) or, alternatively, use <manim cfg write --open> to create a " |
| 114 | + "custom configuration and set the ffmpeg_executable variable to the " |
| 115 | + "full absolute path to the ffmpeg executable." |
| 116 | + ), |
| 117 | +) |
| 118 | +def is_ffmpeg_available(): |
| 119 | + path_to_ffmpeg = shutil.which(config.ffmpeg_executable) |
| 120 | + return path_to_ffmpeg is not None and os.access(path_to_ffmpeg, os.X_OK) |
| 121 | + |
| 122 | + |
| 123 | +@healthcheck( |
| 124 | + description="Checking whether ffmpeg is working", |
| 125 | + recommendation=( |
| 126 | + "Your installed version of ffmpeg does not support x264 encoding, " |
| 127 | + "which manim requires. Please follow our installation instructions " |
| 128 | + "at https://docs.manim.community/en/stable/installation.html " |
| 129 | + "to download and install a newer version of ffmpeg." |
| 130 | + ), |
| 131 | + skip_on_failed=[is_ffmpeg_available], |
| 132 | +) |
| 133 | +def is_ffmpeg_working(): |
| 134 | + ffmpeg_version = subprocess.run( |
| 135 | + [config.ffmpeg_executable, "-version"], |
| 136 | + stdout=subprocess.PIPE, |
| 137 | + ).stdout.decode() |
| 138 | + return ( |
| 139 | + ffmpeg_version.startswith("ffmpeg version") |
| 140 | + and "--enable-libx264" in ffmpeg_version |
| 141 | + ) |
| 142 | + |
| 143 | + |
| 144 | +@healthcheck( |
| 145 | + description="Checking whether latex is available", |
| 146 | + recommendation=( |
| 147 | + "Manim cannot find <latex> on your system's PATH. " |
| 148 | + "You will not be able to use Tex and MathTex mobjects " |
| 149 | + "in your scenes.\n\n" |
| 150 | + "Consult our installation instructions " |
| 151 | + "at https://docs.manim.community/en/stable/installation.html " |
| 152 | + "or search the web for instructions on how to install a " |
| 153 | + "LaTeX distribution on your operating system." |
| 154 | + ), |
| 155 | +) |
| 156 | +def is_latex_available(): |
| 157 | + path_to_latex = shutil.which("latex") |
| 158 | + return path_to_latex is not None and os.access(path_to_latex, os.X_OK) |
| 159 | + |
| 160 | + |
| 161 | +@healthcheck( |
| 162 | + description="Checking whether dvisvgm is available", |
| 163 | + recommendation=( |
| 164 | + "Manim could find <latex>, but not <dvisvgm> on your system's " |
| 165 | + "PATH. Make sure your installed LaTeX distribution comes with " |
| 166 | + "dvisvgm and consider installing a larger distribution if it " |
| 167 | + "does not." |
| 168 | + ), |
| 169 | + skip_on_failed=[is_latex_available], |
| 170 | +) |
| 171 | +def is_dvisvgm_available(): |
| 172 | + path_to_dvisvgm = shutil.which("dvisvgm") |
| 173 | + return path_to_dvisvgm is not None and os.access(path_to_dvisvgm, os.X_OK) |
0 commit comments