Public Access
Post-merge / detect-type (push) Successful in 9s
Post-merge / validate-commit-msg (push) Successful in 10s
Post-merge / configure-repo (push) Successful in 13s
Post-merge / release (push) Successful in 45s
Post-merge / vikunja (push) Successful in 11s
Post-merge / sync-wiki (push) Successful in 39s
Post-merge / badges (push) Successful in 42s
184 lines
6.1 KiB
Python
184 lines
6.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Project setup: install Python deps, Ansible collections, pre-commit hooks, and tea CLI login.
|
|
|
|
Usage::
|
|
|
|
python3 -m devx.tools.setup --bin .venv/bin
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import shutil
|
|
import subprocess # nosec B404
|
|
from pathlib import Path
|
|
|
|
import click
|
|
from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnknownVariableType]
|
|
|
|
load_dotenv()
|
|
|
|
|
|
def _run(cmd: list[str]) -> None:
|
|
"""Run a command, streaming output to stdout/stderr."""
|
|
click.echo(f" $ {' '.join(cmd)}")
|
|
subprocess.run(cmd, check=True) # nosec B603
|
|
|
|
|
|
def _install_python_deps(bin_dir: str, extras: str = "dev") -> None:
|
|
"""Install the project with the specified extras in editable mode."""
|
|
pip = str(Path(bin_dir) / "pip")
|
|
cmd = [pip, "install", "-e", f".[{extras}]"]
|
|
# In CI (system Python), --break-system-packages allows installing to
|
|
# system site-packages, and --ignore-installed avoids uninstall failures
|
|
# for debian-installed packages (e.g. platformdirs) that lack RECORD files.
|
|
if os.environ.get("PIP_BREAK_SYSTEM_PACKAGES") == "1":
|
|
cmd.extend(["--break-system-packages", "--ignore-installed"])
|
|
_run(cmd)
|
|
|
|
|
|
def _install_pre_commit_hooks(bin_dir: str) -> None:
|
|
"""Install pre-commit hooks for commit-msg, pre-commit, and pre-push."""
|
|
pre_commit = str(Path(bin_dir) / "pre-commit")
|
|
for hook_type in ["pre-commit", "commit-msg", "pre-push"]:
|
|
_run([pre_commit, "install", "--hook-type", hook_type])
|
|
|
|
|
|
def _install_ansible_collections(bin_dir: str) -> None:
|
|
"""Install required Ansible Galaxy collections if requirements exist."""
|
|
galaxy = str(Path(bin_dir) / "ansible-galaxy")
|
|
requirements = Path("ansible/requirements.yml")
|
|
if not requirements.exists():
|
|
click.echo(" ansible/requirements.yml not found — skipping collections.")
|
|
return
|
|
_run([galaxy, "collection", "install", "-r", str(requirements)])
|
|
|
|
|
|
def _configure_tea_login() -> None:
|
|
"""Configure tea CLI login from .env if REPO_TOKEN is set.
|
|
|
|
Idempotent: if a login with the same name already exists, it is not re-added.
|
|
Skips if tea is not installed or REPO_TOKEN is not set.
|
|
"""
|
|
tea_bin = shutil.which("tea")
|
|
if tea_bin is None:
|
|
click.echo("tea: not installed — run 'make install-tools' to install it.")
|
|
return
|
|
|
|
token = os.environ.get("REPO_TOKEN", "")
|
|
if not token:
|
|
click.echo("tea: REPO_TOKEN not set — skipping login configuration.")
|
|
return
|
|
|
|
api_url = os.environ.get("DEVX_GITEA_API_URL", "https://git.oblachno.oblachno.fyi/api/v1")
|
|
gitea_url = api_url.replace("/api/v1", "")
|
|
login_name = "devx"
|
|
|
|
result = subprocess.run( # nosec B603
|
|
[tea_bin, "login", "list", "--output", "simple"],
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
if result.returncode == 0 and login_name in result.stdout:
|
|
click.echo(f"tea: login '{login_name}' already configured.")
|
|
return
|
|
|
|
click.echo(f"tea: configuring login '{login_name}' for {gitea_url}...")
|
|
add_result = subprocess.run( # nosec B603
|
|
[tea_bin, "login", "add", "--name", login_name, "--url", gitea_url, "--token", token],
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
if add_result.returncode != 0:
|
|
click.echo(f"tea: login configuration failed: {add_result.stderr.strip()}", err=True)
|
|
else:
|
|
subprocess.run( # nosec B603
|
|
[tea_bin, "login", "default", login_name],
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
click.echo(f"tea: login '{login_name}' configured and set as default.")
|
|
|
|
|
|
def _verify(bin_dir: str) -> None:
|
|
"""Print versions of installed tools for verification."""
|
|
devx = str(Path(bin_dir) / "devx")
|
|
pre_commit = str(Path(bin_dir) / "pre-commit")
|
|
for tool in [devx, pre_commit]:
|
|
try:
|
|
result = subprocess.run([tool, "--version"], capture_output=True, text=True, timeout=10) # nosec B603
|
|
if result.returncode == 0:
|
|
click.echo(f" {result.stdout.strip()}")
|
|
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
pass
|
|
|
|
|
|
@click.command()
|
|
@click.option("--bin", "bin_dir", default=".venv/bin", help="Path to the virtualenv bin directory.")
|
|
@click.option(
|
|
"--extras",
|
|
default="dev",
|
|
help="Dependency group to install: ci, lint, or dev (default: dev).",
|
|
)
|
|
@click.option(
|
|
"--no-pre-commit",
|
|
is_flag=True,
|
|
default=False,
|
|
help="Skip pre-commit hook installation.",
|
|
)
|
|
@click.option(
|
|
"--no-tea-login",
|
|
is_flag=True,
|
|
default=False,
|
|
help="Skip tea CLI login configuration.",
|
|
)
|
|
@click.option(
|
|
"--no-ansible-collections",
|
|
is_flag=True,
|
|
default=False,
|
|
help="Skip Ansible Galaxy collection installation.",
|
|
)
|
|
def main(
|
|
bin_dir: str,
|
|
extras: str,
|
|
no_pre_commit: bool,
|
|
no_tea_login: bool,
|
|
no_ansible_collections: bool,
|
|
) -> None:
|
|
"""Install Python deps, pre-commit hooks, and configure tea CLI."""
|
|
if not Path(bin_dir).exists():
|
|
raise click.ClickException(f"Bin directory not found: {bin_dir}. Run 'python3 -m venv .venv' first.")
|
|
|
|
click.echo(f"Installing Python dependencies (extras: {extras})...")
|
|
_install_python_deps(bin_dir, extras)
|
|
|
|
if not no_ansible_collections:
|
|
click.echo("Installing Ansible Galaxy collections...")
|
|
_install_ansible_collections(bin_dir)
|
|
|
|
if not no_pre_commit:
|
|
click.echo("Installing pre-commit hooks...")
|
|
_install_pre_commit_hooks(bin_dir)
|
|
|
|
if not no_tea_login:
|
|
click.echo("Configuring tea CLI login...")
|
|
_configure_tea_login()
|
|
|
|
click.echo("")
|
|
click.echo("Setup complete.")
|
|
click.echo("Activate the virtual environment with one of:")
|
|
click.echo(" source .venv/bin/activate (generic)")
|
|
click.echo(" source activate.sh (bash)")
|
|
click.echo(" source activate.fish (fish)")
|
|
click.echo(" source activate.zsh (zsh)")
|
|
click.echo("")
|
|
|
|
_verify(bin_dir)
|
|
|
|
|
|
if __name__ == "__main__": # pragma: no cover
|
|
main() # pragma: no cover
|