aboutsummaryrefslogtreecommitdiff
path: root/.run/src/bin/ci.py
blob: 6234a1946374cb4b8d7559df25e00ce3d4d0f3ea (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
"""Full CI orchestration for the mingling project.

Runs every `cargo ci` step in order: lock the workspace, run all checks,
refresh the generated artifacts, then unlock. The final `git-unlock` doubles
as the idempotency check: it fails with a non-zero exit code when the run
left the working tree dirty.

The script locates the git repository root and runs with it as the working
directory, so it can be invoked from anywhere inside the repo.
"""

import os
import subprocess
import sys
from pathlib import Path

# The pipeline steps, in execution order, as (command, args) pairs.
STEPS: list[tuple[str, list[str]]] = [
    ("git-lock", []),
    ("report-clean", []),
    ("build-check", []),
    ("clippy-check", []),
    ("test-all", []),
    ("example-check", []),
    ("docs-check", []),
    ("example-refresh", []),
    ("docsify-refresh", []),
    ("features-refresh", []),
    # Idempotency check: exits non-zero if CI contaminated the workspace, and
    # prints the diff of the contamination before restoring.
    ("git-unlock", ["--show-diff"]),
]


def find_repo_root() -> Path:
    """Return the nearest ancestor directory containing `.git`."""
    current = Path.cwd()
    for directory in (current, *current.parents):
        if (directory / ".git").is_dir():
            return directory
    raise SystemExit("error: not inside a git repository")


def main() -> int:
    root = find_repo_root()
    os.chdir(root)

    # Signature banner: docs/res/ci_banner.txt, relative to this script
    # (.run/src/bin -> four levels up is the repo root).
    banner = (
        Path(__file__).resolve().parent.parent.parent.parent
        / "docs"
        / "res"
        / "ci_banner.txt"
    )
    try:
        print(banner.read_text(encoding="utf-8"), end="")
    except OSError:
        pass

    for command, args in STEPS:
        print(f"==> cargo ci {' '.join([command, *args])}")
        result = subprocess.run(["cargo", "ci", command, *args], check=False)
        if result.returncode != 0:
            print(
                f"error: step `{command}` failed with exit code {result.returncode}",
                file=sys.stderr,
            )
            return result.returncode

    return 0


if __name__ == "__main__":
    sys.exit(main())