Files
markdown-exec/scripts/make.py
T
Timothée Mazzucotelli 103bc1dc5f chore: Template upgrade
2024-12-03 13:56:51 +01:00

194 lines
6.2 KiB
Python
Executable File

#!/usr/bin/env python3
"""Management commands."""
from __future__ import annotations
import os
import shutil
import subprocess
import sys
from contextlib import contextmanager
from pathlib import Path
from textwrap import dedent
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from collections.abc import Iterator
PYTHON_VERSIONS = os.getenv("PYTHON_VERSIONS", "3.9 3.10 3.11 3.12 3.13 3.14").split()
def shell(cmd: str, *, capture_output: bool = False, **kwargs: Any) -> str | None:
"""Run a shell command."""
if capture_output:
return subprocess.check_output(cmd, shell=True, text=True, **kwargs) # noqa: S602
subprocess.run(cmd, shell=True, check=True, stderr=subprocess.STDOUT, **kwargs) # noqa: S602
return None
@contextmanager
def environ(**kwargs: str) -> Iterator[None]:
"""Temporarily set environment variables."""
original = dict(os.environ)
os.environ.update(kwargs)
try:
yield
finally:
os.environ.clear()
os.environ.update(original)
def uv_install(venv: Path) -> None:
"""Install dependencies using uv."""
with environ(UV_PROJECT_ENVIRONMENT=str(venv), PYO3_USE_ABI3_FORWARD_COMPATIBILITY="1"):
if "CI" in os.environ:
shell("uv sync --all-extras --no-editable")
else:
shell("uv sync --all-extras")
def setup() -> None:
"""Setup the project."""
if not shutil.which("uv"):
raise ValueError("make: setup: uv must be installed, see https://github.com/astral-sh/uv")
print("Installing dependencies (default environment)")
default_venv = Path(".venv")
if not default_venv.exists():
shell("uv venv")
uv_install(default_venv)
if PYTHON_VERSIONS:
for version in PYTHON_VERSIONS:
print(f"\nInstalling dependencies (python{version})")
venv_path = Path(f".venvs/{version}")
if not venv_path.exists():
shell(f"uv venv --python {version} {venv_path}")
with environ(UV_PROJECT_ENVIRONMENT=str(venv_path.resolve())):
uv_install(venv_path)
def run(version: str, cmd: str, *args: str, no_sync: bool = False, **kwargs: Any) -> None:
"""Run a command in a virtual environment."""
kwargs = {"check": True, **kwargs}
uv_run = ["uv", "run"]
if no_sync:
uv_run.append("--no-sync")
if version == "default":
with environ(UV_PROJECT_ENVIRONMENT=".venv"):
subprocess.run([*uv_run, cmd, *args], **kwargs) # noqa: S603, PLW1510
else:
with environ(UV_PROJECT_ENVIRONMENT=f".venvs/{version}", MULTIRUN="1"):
subprocess.run([*uv_run, cmd, *args], **kwargs) # noqa: S603, PLW1510
def multirun(cmd: str, *args: str, **kwargs: Any) -> None:
"""Run a command for all configured Python versions."""
if PYTHON_VERSIONS:
for version in PYTHON_VERSIONS:
run(version, cmd, *args, **kwargs)
else:
run("default", cmd, *args, **kwargs)
def allrun(cmd: str, *args: str, **kwargs: Any) -> None:
"""Run a command in all virtual environments."""
run("default", cmd, *args, **kwargs)
if PYTHON_VERSIONS:
multirun(cmd, *args, **kwargs)
def clean() -> None:
"""Delete build artifacts and cache files."""
paths_to_clean = ["build", "dist", "htmlcov", "site", ".coverage*", ".pdm-build"]
for path in paths_to_clean:
shutil.rmtree(path, ignore_errors=True)
cache_dirs = {".cache", ".pytest_cache", ".mypy_cache", ".ruff_cache", "__pycache__"}
for dirpath in Path(".").rglob("*/"):
if dirpath.parts[0] not in (".venv", ".venvs") and dirpath.name in cache_dirs:
shutil.rmtree(dirpath, ignore_errors=True)
def vscode() -> None:
"""Configure VSCode to work on this project."""
shutil.copytree("config/vscode", ".vscode", dirs_exist_ok=True)
def main() -> int:
"""Main entry point."""
args = list(sys.argv[1:])
if not args or args[0] == "help":
if len(args) > 1:
run("default", "duty", "--help", args[1])
else:
print(
dedent(
"""
Available commands
help Print this help. Add task name to print help.
setup Setup all virtual environments (install dependencies).
run Run a command in the default virtual environment.
multirun Run a command for all configured Python versions.
allrun Run a command in all virtual environments.
3.x Run a command in the virtual environment for Python 3.x.
clean Delete build artifacts and cache files.
vscode Configure VSCode to work on this project.
""",
),
flush=True,
)
if os.path.exists(".venv"):
print("\nAvailable tasks", flush=True)
run("default", "duty", "--list", no_sync=True)
return 0
while args:
cmd = args.pop(0)
if cmd == "run":
run("default", *args)
return 0
if cmd == "multirun":
multirun(*args)
return 0
if cmd == "allrun":
allrun(*args)
return 0
if cmd.startswith("3."):
run(cmd, *args)
return 0
opts = []
while args and (args[0].startswith("-") or "=" in args[0]):
opts.append(args.pop(0))
if cmd == "clean":
clean()
elif cmd == "setup":
setup()
elif cmd == "vscode":
vscode()
elif cmd == "check":
multirun("duty", "check-quality", "check-types", "check-docs")
run("default", "duty", "check-api")
elif cmd in {"check-quality", "check-docs", "check-types", "test"}:
multirun("duty", cmd, *opts)
else:
run("default", "duty", cmd, *opts)
return 0
if __name__ == "__main__":
try:
sys.exit(main())
except subprocess.CalledProcessError as process:
if process.output:
print(process.output, file=sys.stderr)
sys.exit(process.returncode)