Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8721bf8
added new checkhealth subcommand
behackl Jul 18, 2023
4769f58
basic checkhealth tests
behackl Jul 18, 2023
3599055
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 18, 2023
e1a888c
check -> healthcheck
behackl Jul 18, 2023
5cc66ef
more helpful test output on checkhealth fail
behackl Jul 18, 2023
47d7047
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 18, 2023
61fdf05
callable -> Callable
behackl Jul 18, 2023
10cbf35
Merge branch 'cli-checkhealth' of github.com:behackl/manim into cli-c…
behackl Jul 18, 2023
814f723
fix executable check for windows
behackl Jul 19, 2023
57af9da
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 19, 2023
1dd987f
fixed type
behackl Jul 19, 2023
9b2a533
shutil already returns .exe in case it is there
behackl Jul 19, 2023
f06d6b4
debug commit ...
behackl Jul 19, 2023
3746b25
Merge branch 'cli-checkhealth' of github.com:behackl/manim into cli-c…
behackl Jul 19, 2023
d3cad81
do proper debug commit for windows ...
behackl Jul 19, 2023
c5996a0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 19, 2023
eeb1df3
fix failing test if executable is batch file
behackl Jul 28, 2023
3f4c97b
added more helpful (?) recommendations on failed tests
behackl Jul 28, 2023
4c3f5a4
allow rendering ManimBanner from prerendered SVG path
behackl Jul 29, 2023
85ab0dd
improved test scene, actually test text / latex
behackl Jul 29, 2023
2af2fb8
added debug info on python executable
behackl Jul 29, 2023
fc7b76e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 29, 2023
eafc57c
remove debug test again
behackl Jul 29, 2023
027a756
move SCALE_FACTOR_PER_FONT_POINT to constants
behackl Aug 5, 2023
238746d
access constants via module in logo.py
behackl Aug 5, 2023
08ed37c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 5, 2023
558dfd8
replaced other occurrence of 48 / 960
behackl Aug 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions manim/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from . import __version__, cli_ctx_settings, console
from .cli.cfg.group import cfg
from .cli.checkhealth.commands import checkhealth
from .cli.default_group import DefaultGroup
from .cli.init.commands import init
from .cli.new.group import new
Expand Down Expand Up @@ -48,6 +49,7 @@ def main(ctx):
pass


main.add_command(checkhealth)
main.add_command(cfg)
main.add_command(plugins)
main.add_command(init)
Expand Down
Empty file.
173 changes: 173 additions & 0 deletions manim/cli/checkhealth/checks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
"""Auxiliary module for the checkhealth subcommand, contains
the actual check implementations."""

from __future__ import annotations

import os
import shutil
import subprocess
from typing import Callable

from ..._config import config

HEALTH_CHECKS = []


def healthcheck(
description: str,
recommendation: str,
skip_on_failed: list[Callable | str] | None = None,
post_fail_fix_hook: Callable | None = None,
):
"""Decorator used for declaring health checks.

This decorator attaches some data to a function,
which is then added to a list containing all checks.

Parameters
----------
description
A brief description of this check, displayed when
the checkhealth subcommand is run.
recommendation
Help text which is displayed in case the check fails.
skip_on_failed
A list of check functions which, if they fail, cause
the current check to be skipped.
post_fail_fix_hook
A function that is supposed to (interactively) help
to fix the detected problem, if possible. This is
only called upon explicit confirmation of the user.

Returns
-------
A check function, as required by the checkhealth subcommand.
"""
if skip_on_failed is None:
skip_on_failed = []
skip_on_failed = [
skip.__name__ if callable(skip) else skip for skip in skip_on_failed
]

def decorator(func):
func.description = description
func.recommendation = recommendation
func.skip_on_failed = skip_on_failed
func.post_fail_fix_hook = post_fail_fix_hook
HEALTH_CHECKS.append(func)
return func

return decorator


@healthcheck(
description="Checking whether manim is on your PATH",
recommendation=(
"The command <manim> is currently not on your system's PATH.\n\n"
"You can work around this by calling the manim module directly "
"via <python -m manim> instead of just <manim>.\n\n"
"To fix the PATH issue properly: "
"Usually, the Python package installer pip issues a warning "
"during the installation which contains more information. "
"Consider reinstalling manim via <pip uninstall manim> "
"followed by <pip install manim> to see the warning again, "
"then consult the internet on how to modify your system's "
"PATH variable."
),
)
def is_manim_on_path():
path_to_manim = shutil.which("manim")
return path_to_manim is not None


@healthcheck(
description="Checking whether the executable belongs to manim",
recommendation=(
"The command <manim> does not belong to your installed version "
"of this library, it likely belongs to manimgl / manimlib.\n\n"
"Run manim via <python -m manim> or via <manimce>, or uninstall "
"and reinstall manim via <pip install --upgrade "
"--force-reinstall manim> to fix this."
),
skip_on_failed=[is_manim_on_path],
)
def is_manim_executable_associated_to_this_library():
path_to_manim = shutil.which("manim")
with open(path_to_manim, "rb") as f:
manim_exec = f.read()

# first condition below corresponds to the executable being
# some sort of python script. second condition happens when
# the executable is actually a Windows batch file.
return b"manim.__main__" in manim_exec or b'"%~dp0\\manim"' in manim_exec


@healthcheck(
description="Checking whether ffmpeg is available",
recommendation=(
"Manim does not work without ffmpeg. Please follow our "
"installation instructions "
"at https://docs.manim.community/en/stable/installation.html "
"to download ffmpeg. Then, either ...\n\n"
"(a) ... make the ffmpeg executable available to your system's PATH,\n"
"(b) or, alternatively, use <manim cfg write --open> to create a "
"custom configuration and set the ffmpeg_executable variable to the "
"full absolute path to the ffmpeg executable."
),
)
def is_ffmpeg_available():
path_to_ffmpeg = shutil.which(config.ffmpeg_executable)
return path_to_ffmpeg is not None and os.access(path_to_ffmpeg, os.X_OK)


@healthcheck(
description="Checking whether ffmpeg is working",
recommendation=(
"Your installed version of ffmpeg does not support x264 encoding, "
"which manim requires. Please follow our installation instructions "
"at https://docs.manim.community/en/stable/installation.html "
"to download and install a newer version of ffmpeg."
),
skip_on_failed=[is_ffmpeg_available],
)
def is_ffmpeg_working():
ffmpeg_version = subprocess.run(
[config.ffmpeg_executable, "-version"],
stdout=subprocess.PIPE,
).stdout.decode()
return (
ffmpeg_version.startswith("ffmpeg version")
and "--enable-libx264" in ffmpeg_version
)


@healthcheck(
description="Checking whether latex is available",
recommendation=(
"Manim cannot find <latex> on your system's PATH. "
"You will not be able to use Tex and MathTex mobjects "
"in your scenes.\n\n"
"Consult our installation instructions "
"at https://docs.manim.community/en/stable/installation.html "
"or search the web for instructions on how to install a "
"LaTeX distribution on your operating system."
),
)
def is_latex_available():
path_to_latex = shutil.which("latex")
return path_to_latex is not None and os.access(path_to_latex, os.X_OK)


@healthcheck(
description="Checking whether dvisvgm is available",
recommendation=(
"Manim could find <latex>, but not <dvisvgm> on your system's "
"PATH. Make sure your installed LaTeX distribution comes with "
"dvisvgm and consider installing a larger distribution if it "
"does not."
),
skip_on_failed=[is_latex_available],
)
def is_dvisvgm_available():
path_to_dvisvgm = shutil.which("dvisvgm")
return path_to_dvisvgm is not None and os.access(path_to_dvisvgm, os.X_OK)
81 changes: 81 additions & 0 deletions manim/cli/checkhealth/commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""A CLI utility helping to diagnose problems with
your Manim installation.

"""

from __future__ import annotations

import sys

import click
import cloup

from .checks import HEALTH_CHECKS


@cloup.command(
context_settings=None,
)
def checkhealth():
"""This subcommand checks whether Manim is installed correctly
and has access to its required (and optional) system dependencies.
"""
click.echo(f"Python executable: {sys.executable}\n")
click.echo("Checking whether your installation of Manim Community is healthy...")
failed_checks = []

for check in HEALTH_CHECKS:
click.echo(f"- {check.description} ... ", nl=False)
if any(
failed_check.__name__ in check.skip_on_failed
for failed_check in failed_checks
):
click.secho("SKIPPED", fg="blue")
continue
check_result = check()
if check_result:
click.secho("PASSED", fg="green")
else:
click.secho("FAILED", fg="red")
failed_checks.append(check)

click.echo()

if failed_checks:
click.echo(
"There are problems with your installation, "
"here are some recommendations to fix them:"
)
for ind, failed_check in enumerate(failed_checks):
click.echo(failed_check.recommendation)
if ind + 1 < len(failed_checks):
click.confirm("Continue with next recommendation?")

else: # no problems detected!
click.echo("No problems detected, your installation seems healthy!")
render_test_scene = click.confirm(
"Would you like to render and preview a test scene?"
)
if render_test_scene:
import manim as mn

class CheckHealthDemo(mn.Scene):
def construct(self):
banner = mn.ManimBanner().shift(mn.UP * 0.5)
self.play(banner.create())
self.wait(0.5)
self.play(banner.expand())
self.wait(0.5)
text_left = mn.Text("All systems operational!")
formula_right = mn.MathTex(r"\oint_{\gamma} f(z)~dz = 0")
text_tex_group = mn.VGroup(text_left, formula_right)
text_tex_group.arrange(mn.RIGHT, buff=1).next_to(banner, mn.DOWN)
self.play(mn.Write(text_tex_group))
self.wait(0.5)
self.play(
mn.FadeOut(banner, shift=mn.UP),
mn.FadeOut(text_tex_group, shift=mn.DOWN),
)

with mn.tempconfig({"preview": True, "disable_caching": True}):
CheckHealthDemo().render()
Loading