Co-Study4Grid / expert_backend /install_graphviz.py
github-actions[bot]
Deploy 7688ef1
13d4e44
Raw
History Blame Contribute Delete
4.44 kB
"""Best-effort cross-platform installer for the Graphviz ``dot`` binary.
Co-Study4Grid renders the overflow graph through ``pydot`` /
``networkx.drawing.nx_pydot``, which shells out to Graphviz's ``dot``
executable. ``dot`` is a system-level binary and cannot be shipped as a
pure-Python wheel, so we provide this helper which is invoked from
``setup.py`` after a regular ``pip install``.
The script is intentionally best-effort: if it can't find a supported
package manager or the install fails (no sudo, locked package DB, etc.)
it prints a clear, actionable message rather than aborting the whole
``pip install``. Users can also run it manually:
python -m scripts.install_graphviz
"""
from __future__ import annotations
import os
import platform
import shutil
import subprocess
import sys
MANUAL_HINT = """\
Could not auto-install Graphviz. Please install it manually so the
``dot`` binary is on PATH:
Linux (Debian/Ubuntu): sudo apt-get install graphviz
Linux (RHEL/Fedora): sudo dnf install graphviz (or yum)
Linux (Arch): sudo pacman -S graphviz
Linux (Alpine): sudo apk add graphviz
macOS (Homebrew): brew install graphviz
macOS (MacPorts): sudo port install graphviz
Windows (Chocolatey): choco install graphviz
Windows (winget): winget install Graphviz.Graphviz
Windows (Scoop): scoop install graphviz
After installation, verify with: dot -V
"""
def _run(cmd: list[str]) -> int:
print("+ " + " ".join(cmd), flush=True)
try:
return subprocess.call(cmd)
except FileNotFoundError:
return 127
def _maybe_sudo(cmd: list[str]) -> list[str]:
if os.name == "nt":
return cmd
if os.geteuid() == 0: # type: ignore[attr-defined]
return cmd
if shutil.which("sudo"):
return ["sudo", "-n", *cmd]
return cmd
def _install_linux() -> bool:
candidates = [
("apt-get", ["apt-get", "install", "-y", "graphviz"]),
("dnf", ["dnf", "install", "-y", "graphviz"]),
("yum", ["yum", "install", "-y", "graphviz"]),
("pacman", ["pacman", "-S", "--noconfirm", "graphviz"]),
("apk", ["apk", "add", "--no-cache", "graphviz"]),
("zypper", ["zypper", "install", "-y", "graphviz"]),
]
for tool, cmd in candidates:
if shutil.which(tool):
if _run(_maybe_sudo(cmd)) == 0:
return True
return False
def _install_macos() -> bool:
if shutil.which("brew"):
if _run(["brew", "install", "graphviz"]) == 0:
return True
if shutil.which("port"):
if _run(_maybe_sudo(["port", "install", "graphviz"])) == 0:
return True
return False
def _install_windows() -> bool:
candidates = [
("choco", ["choco", "install", "graphviz", "-y", "--no-progress"]),
("winget", ["winget", "install", "--id", "Graphviz.Graphviz",
"-e", "--accept-source-agreements",
"--accept-package-agreements"]),
("scoop", ["scoop", "install", "graphviz"]),
]
for tool, cmd in candidates:
if shutil.which(tool):
if _run(cmd) == 0:
return True
return False
def ensure_dot() -> bool:
"""Ensure ``dot`` is on PATH; return True if it is (after install)."""
if shutil.which("dot"):
print("Graphviz 'dot' already installed; skipping.", flush=True)
return True
system = platform.system()
print(f"Graphviz 'dot' not found on PATH; attempting install on {system}...",
flush=True)
ok = False
if system == "Linux":
ok = _install_linux()
elif system == "Darwin":
ok = _install_macos()
elif system == "Windows":
ok = _install_windows()
else:
print(f"Unsupported platform: {system}", flush=True)
# Re-check PATH (some installers update PATH only in new shells).
if shutil.which("dot"):
return True
if not ok:
print(MANUAL_HINT, file=sys.stderr, flush=True)
else:
# The package manager succeeded but PATH may not be refreshed
# in this shell. Don't fail loudly.
print(
"Graphviz install command completed but 'dot' is not yet on PATH "
"in this shell. Open a new terminal and run `dot -V` to verify.",
flush=True,
)
return False
if __name__ == "__main__":
sys.exit(0 if ensure_dot() else 1)