teknoraver / rpms / systemd

Forked from rpms/systemd 5 months ago
Clone
ed28c0
#!/usr/bin/env python3
ed28c0
ed28c0
import argparse
ed28c0
import logging
ed28c0
import subprocess
ed28c0
import sys
ed28c0
from datetime import datetime
ed28c0
from pathlib import Path
ed28c0
from typing import Any, NoReturn, Optional, Sequence, Iterator
ed28c0
import enum
ed28c0
import contextlib
ed28c0
import textwrap
ed28c0
ed28c0
SYSTEMD_REPO = "https://github.com/systemd/systemd"
ed28c0
AUTHOR = "CentOS Hyperscale SIG <centos-devel@centos.org>"
ed28c0
ed28c0
ed28c0
class LogFormatter(logging.Formatter):
ed28c0
    def __init__(self, fmt: Optional[str] = None, *args: Any, **kwargs: Any) -> None:
ed28c0
        fmt = fmt or "%(message)s"
ed28c0
ed28c0
        bold = "\033[0;1;39m" if sys.stderr.isatty() else ""
ed28c0
        gray = "\x1b[38;20m" if sys.stderr.isatty() else ""
ed28c0
        red = "\033[31;1m" if sys.stderr.isatty() else ""
ed28c0
        yellow = "\033[33;1m" if sys.stderr.isatty() else ""
ed28c0
        reset = "\033[0m" if sys.stderr.isatty() else ""
ed28c0
ed28c0
        self.formatters = {
ed28c0
            logging.DEBUG: logging.Formatter(f"‣ {gray}{fmt}{reset}"),
ed28c0
            logging.INFO: logging.Formatter(f"‣ {fmt}"),
ed28c0
            logging.WARNING: logging.Formatter(f"‣ {yellow}{fmt}{reset}"),
ed28c0
            logging.ERROR: logging.Formatter(f"‣ {red}{fmt}{reset}"),
ed28c0
            logging.CRITICAL: logging.Formatter(f"‣ {red}{bold}{fmt}{reset}"),
ed28c0
        }
ed28c0
ed28c0
        super().__init__(fmt, *args, **kwargs)
ed28c0
ed28c0
    def format(self, record: logging.LogRecord) -> str:
ed28c0
        return self.formatters[record.levelno].format(record)
ed28c0
ed28c0
ed28c0
def run(cmd: Sequence[str], *args: Any, **kwargs: Any) -> subprocess.CompletedProcess:
ed28c0
    try:
ed28c0
        return subprocess.run(cmd, *args, **kwargs, check=True, text=True)
ed28c0
    except FileNotFoundError:
ed28c0
        die(f"{cmd[0]} not found in PATH.")
ed28c0
    except subprocess.CalledProcessError as e:
ed28c0
        logging.error(
ed28c0
            f'"{" ".join(str(s) for s in cmd)}" returned non-zero exit code {e.returncode}.'
ed28c0
        )
ed28c0
        raise e
ed28c0
ed28c0
ed28c0
def die(message: str) -> NoReturn:
ed28c0
    logging.error(message)
ed28c0
    sys.exit(1)
ed28c0
ed28c0
ed28c0
@contextlib.contextmanager
ed28c0
def restore(path: Path) -> Iterator[None]:
ed28c0
    old = path.read_text()
ed28c0
    try:
ed28c0
        yield
ed28c0
    finally:
ed28c0
        path.write_text(old)
ed28c0
ed28c0
ed28c0
def do_cd(args: argparse.Namespace) -> None:
ed28c0
    if not Path(".git").exists():
ed28c0
        die("The cd verb must be run from the rpm git repository")
ed28c0
ed28c0
    logging.info("Downloading sources")
ed28c0
    run(
ed28c0
        [
ed28c0
            "spectool",
ed28c0
            "--define",
ed28c0
            f"_sourcedir {Path.cwd()}",
ed28c0
            "--define",
ed28c0
            "branch main",
ed28c0
            "--get-files",
ed28c0
            "systemd.spec",
ed28c0
        ],
ed28c0
    )
ed28c0
ed28c0
    # We can't determine the version dynamically in the spec so we retrieve it
ed28c0
    # up front and pass it in via a macro.
ed28c0
    version = run(
ed28c0
        [
ed28c0
            "tar",
ed28c0
            "--gunzip",
ed28c0
            "--extract",
ed28c0
            "--to-stdout",
ed28c0
            "--file=main.tar.gz",
ed28c0
            "systemd-main/meson.version",
ed28c0
        ],
ed28c0
        stdout=subprocess.PIPE,
ed28c0
    ).stdout.strip()
ed28c0
ed28c0
    # The timestamp is to ensure the release is always monotonically increasing
ed28c0
    rpmrelease = datetime.now().strftime(r"%Y%m%d%H%M%S")
ed28c0
ed28c0
    with restore(Path("systemd.spec")):
ed28c0
        Path("systemd.spec").write_text(
ed28c0
            textwrap.dedent(
ed28c0
                f"""\
ed28c0
                %bcond upstream 1
ed28c0
                %define version_override {version}
ed28c0
                %define release_override {rpmrelease}
ed28c0
                %define branch main
ed28c0
                """
ed28c0
            )
ed28c0
            + Path("systemd.spec").read_text()
ed28c0
        )
ed28c0
ed28c0
        if args.repo == "main":
ed28c0
            root = f"centos-stream-hyperscale-{args.release}-x86_64"
ed28c0
        else:
ed28c0
            root = f"centos-stream-hyperscale-{args.repo}-{args.release}-x86_64"
ed28c0
ed28c0
        logging.info("Building src.rpm")
ed28c0
        run(
ed28c0
            [
ed28c0
                "mock",
ed28c0
                "--root",
ed28c0
                root,
ed28c0
                "--sources=.",
ed28c0
                "--spec=systemd.spec",
ed28c0
                "--enable-network",
ed28c0
                "--define",
ed28c0
                "%_disable_source_fetch 0",
ed28c0
                "--buildsrpm",
ed28c0
                "--resultdir=.",
ed28c0
            ],
ed28c0
        )
ed28c0
ed28c0
    srcrpm = next(Path.cwd().glob("*.src.rpm"))
ed28c0
    logging.info(f"Wrote: {srcrpm}")
ed28c0
ed28c0
    run(
ed28c0
        [
ed28c0
            "cbs",
ed28c0
            *(["--cert", args.cert] if args.cert else []),
ed28c0
            "build",
ed28c0
            "--wait",
ed28c0
            "--fail-fast",
ed28c0
            "--skip-tag",
ed28c0
            f"hyperscale{args.release}s-packages-{args.repo}-el{args.release}s",
ed28c0
            str(srcrpm),
ed28c0
        ],
ed28c0
    )
ed28c0
ed28c0
    if not args.publish:
ed28c0
        logging.info("Publishing not requested, not tagging builds in testing")
ed28c0
        return
ed28c0
ed28c0
    prefix = "hs+fb" if args.repo == "facebook" else "hs"
ed28c0
ed28c0
    run(
ed28c0
        [
ed28c0
            "cbs",
ed28c0
            *(["--cert", args.cert] if args.cert else []),
ed28c0
            "tag-build",
ed28c0
            f"hyperscale{args.release}s-packages-{args.repo}-testing",
ed28c0
            f"systemd-{version}-{rpmrelease}.{prefix}.el{args.release}",
ed28c0
        ]
ed28c0
    )
ed28c0
ed28c0
ed28c0
class Verb(enum.Enum):
ed28c0
    cd = "cd"
ed28c0
ed28c0
    def __str__(self) -> str:
ed28c0
        return self.value
ed28c0
ed28c0
    def run(self, args: argparse.Namespace) -> None:
ed28c0
        {Verb.cd: do_cd}[self](args)
ed28c0
ed28c0
ed28c0
def main() -> None:
ed28c0
    handler = logging.StreamHandler(stream=sys.stderr)
ed28c0
    handler.setFormatter(LogFormatter())
ed28c0
    logging.getLogger().addHandler(handler)
ed28c0
    logging.getLogger().setLevel("INFO")
ed28c0
ed28c0
    parser = argparse.ArgumentParser()
ed28c0
ed28c0
    parser.add_argument(
ed28c0
        "--release",
ed28c0
        help="CentOS Stream release to use (e.g 9)",
ed28c0
        metavar="RELEASE",
ed28c0
        default=9,
ed28c0
        choices=[9, 10],
ed28c0
        type=int,
ed28c0
    )
ed28c0
    parser.add_argument(
ed28c0
        "--repo",
ed28c0
        help="Hyperscale repository to build against",
ed28c0
        choices=["main", "facebook"],
ed28c0
        default="main",
ed28c0
    )
ed28c0
    parser.add_argument(
ed28c0
        "--cert",
ed28c0
        help="Path to the CentOS certificate to use",
ed28c0
        metavar="PATH",
ed28c0
        type=Path,
ed28c0
        default=None,
ed28c0
    )
ed28c0
    parser.add_argument(
ed28c0
        "--publish",
ed28c0
        action="store_true",
ed28c0
        help="Publish results of operation (by default only a dry-run is done)",
ed28c0
    )
ed28c0
    parser.add_argument(
ed28c0
        "verb",
ed28c0
        type=Verb,
ed28c0
        choices=list(Verb),
ed28c0
        help=argparse.SUPPRESS,
ed28c0
    )
ed28c0
ed28c0
    args = parser.parse_args()
ed28c0
ed28c0
    if args.cert:
ed28c0
        args.cert = args.cert.absolute()
ed28c0
ed28c0
    try:
ed28c0
        args.verb.run(args)
ed28c0
    except SystemExit as e:
ed28c0
        sys.exit(e.code)
ed28c0
    except KeyboardInterrupt:
ed28c0
        logging.error("Interrupted")
ed28c0
        sys.exit(1)
ed28c0
    except subprocess.CalledProcessError as e:
ed28c0
        sys.exit(e.returncode)
ed28c0
ed28c0
ed28c0
if __name__ == "__main__":
ed28c0
    main()