mirror of
https://github.com/funkemunky/KDE-x86_64-v4-Fedora.git
synced 2026-05-31 00:51:56 +00:00
256 lines
8.2 KiB
Python
256 lines
8.2 KiB
Python
#!/usr/bin/env python3
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import re
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(
|
|
description=(
|
|
"Register or update Copr SCM package definitions that use this "
|
|
"repository's .copr/Makefile and optionally submit builds."
|
|
)
|
|
)
|
|
parser.add_argument("--project", required=True, help="Copr project, e.g. user/project")
|
|
parser.add_argument("--clone-url", required=True, help="Git clone URL Copr should use for this repository")
|
|
parser.add_argument("--commit", default="HEAD", help="Git ref Copr should build from")
|
|
parser.add_argument("--chroot", default="fedora-43-x86_64", help="Copr chroot to configure and build for")
|
|
parser.add_argument("--packages-file", default="packages.txt", help="Package list to register")
|
|
parser.add_argument("--specs-dir", default="SPECS", help="Directory containing repo-local package snapshots")
|
|
parser.add_argument(
|
|
"--package-input",
|
|
default="",
|
|
help="Optional comma, space, or newline separated package list. Overrides --packages-file when set.",
|
|
)
|
|
parser.add_argument(
|
|
"--macro-package-name",
|
|
default="copr-rpm-macros-x86-64-v3",
|
|
help="Name of the buildroot macro package in Copr",
|
|
)
|
|
parser.add_argument(
|
|
"--macro-package-spec",
|
|
default="packaging/copr-rpm-macros-x86-64-v3.spec",
|
|
help="Local spec reference for the buildroot macro package",
|
|
)
|
|
parser.add_argument("--webhook-rebuild", choices=("on", "off"), default="off")
|
|
parser.add_argument("--max-builds", type=int, default=0)
|
|
parser.add_argument("--timeout", type=int, default=18000)
|
|
parser.add_argument("--skip-chroot-update", action="store_true")
|
|
parser.add_argument("--submit-macro-build", action="store_true")
|
|
parser.add_argument("--submit-package-builds", action="store_true")
|
|
parser.add_argument("--nowait-package-builds", action="store_true")
|
|
parser.add_argument(
|
|
"--copr-debug",
|
|
action="store_true",
|
|
help="Run copr-cli commands with --debug and include full output on failures.",
|
|
)
|
|
return parser.parse_args()
|
|
|
|
|
|
def run(command: list[str], *, quiet: bool = False) -> subprocess.CompletedProcess[str]:
|
|
kwargs: dict[str, object] = {
|
|
"check": True,
|
|
"text": True,
|
|
}
|
|
if quiet:
|
|
kwargs["stdout"] = subprocess.DEVNULL
|
|
kwargs["stderr"] = subprocess.DEVNULL
|
|
return subprocess.run(command, **kwargs)
|
|
|
|
|
|
def run_copr(args: argparse.Namespace, command: list[str]) -> subprocess.CompletedProcess[str]:
|
|
copr_command = command[:]
|
|
if args.copr_debug and len(copr_command) >= 1 and copr_command[0] == "copr":
|
|
copr_command.insert(1, "--debug")
|
|
|
|
try:
|
|
return subprocess.run(
|
|
copr_command,
|
|
check=True,
|
|
text=True,
|
|
capture_output=True,
|
|
)
|
|
except subprocess.CalledProcessError as exc:
|
|
stdout = (exc.stdout or "").strip()
|
|
stderr = (exc.stderr or "").strip()
|
|
details = []
|
|
if stdout:
|
|
details.append(f"stdout:\n{stdout}")
|
|
if stderr:
|
|
details.append(f"stderr:\n{stderr}")
|
|
output_block = "\n\n".join(details) if details else "(no output captured)"
|
|
raise RuntimeError(
|
|
"Copr command failed:\n"
|
|
f"{' '.join(copr_command)}\n\n"
|
|
f"{output_block}\n\n"
|
|
"If this includes 'Response is not in JSON format', the server likely "
|
|
"returned an HTML error page (auth issue, wrong project path/casing, or "
|
|
"temporary Copr outage). Re-run with --copr-debug for request details."
|
|
) from exc
|
|
|
|
|
|
def package_exists(project: str, package_name: str) -> bool:
|
|
result = subprocess.run(
|
|
["copr", "get-package", project, "--name", package_name],
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
text=True,
|
|
)
|
|
return result.returncode == 0
|
|
|
|
|
|
def load_packages(packages_file: Path, package_input: str) -> list[str]:
|
|
if package_input.strip():
|
|
packages = [entry for entry in re.split(r"[\s,]+", package_input.strip()) if entry]
|
|
else:
|
|
packages = [
|
|
line.strip()
|
|
for line in packages_file.read_text(encoding="utf-8").splitlines()
|
|
if line.strip() and not line.lstrip().startswith("#")
|
|
]
|
|
deduped: list[str] = []
|
|
seen: set[str] = set()
|
|
for package in packages:
|
|
if package not in seen:
|
|
deduped.append(package)
|
|
seen.add(package)
|
|
return deduped
|
|
|
|
|
|
def resolve_spec_ref(package_name: str, specs_dir: Path) -> str:
|
|
package_dir = specs_dir / package_name
|
|
package_named_spec = package_dir / f"{package_name}.spec"
|
|
if package_named_spec.is_file():
|
|
return package_named_spec.as_posix()
|
|
|
|
spec_files = sorted(package_dir.glob("*.spec"))
|
|
if len(spec_files) == 1:
|
|
return spec_files[0].as_posix()
|
|
if len(spec_files) > 1:
|
|
raise RuntimeError(
|
|
f"multiple spec files found for {package_name} under {package_dir}; "
|
|
"set an explicit spec path instead"
|
|
)
|
|
raise RuntimeError(f"no spec file found for {package_name} under {package_dir}")
|
|
|
|
|
|
def upsert_scm_package(
|
|
*,
|
|
args: argparse.Namespace,
|
|
project: str,
|
|
package_name: str,
|
|
spec_ref: str,
|
|
clone_url: str,
|
|
commit: str,
|
|
webhook_rebuild: str,
|
|
max_builds: int,
|
|
timeout: int,
|
|
) -> None:
|
|
action = "edit-package-scm" if package_exists(project, package_name) else "add-package-scm"
|
|
command = [
|
|
"copr",
|
|
action,
|
|
project,
|
|
"--name",
|
|
package_name,
|
|
"--clone-url",
|
|
clone_url,
|
|
"--method",
|
|
"make_srpm",
|
|
"--spec",
|
|
spec_ref,
|
|
]
|
|
if commit != "HEAD":
|
|
command.extend(["--commit", commit])
|
|
if webhook_rebuild != "off":
|
|
command.extend(["--webhook-rebuild", webhook_rebuild])
|
|
if max_builds != 0:
|
|
command.extend(["--max-builds", str(max_builds)])
|
|
if timeout != 18000:
|
|
command.extend(["--timeout", str(timeout)])
|
|
run_copr(args, command)
|
|
|
|
|
|
def submit_build(args: argparse.Namespace, project: str, package_name: str, chroot: str, *, nowait: bool) -> None:
|
|
command = [
|
|
"copr",
|
|
"build-package",
|
|
project,
|
|
"--name",
|
|
package_name,
|
|
"--chroot",
|
|
chroot,
|
|
]
|
|
if nowait:
|
|
command.append("--nowait")
|
|
run_copr(args, command)
|
|
|
|
|
|
def main() -> int:
|
|
args = parse_args()
|
|
packages = load_packages(Path(args.packages_file), args.package_input)
|
|
specs_dir = Path(args.specs_dir)
|
|
|
|
# Preflight to fail fast on auth/project typos before batch operations.
|
|
run_copr(args, ["copr", "get", args.project])
|
|
|
|
upsert_scm_package(
|
|
args=args,
|
|
project=args.project,
|
|
package_name=args.macro_package_name,
|
|
spec_ref=args.macro_package_spec,
|
|
clone_url=args.clone_url,
|
|
commit=args.commit,
|
|
webhook_rebuild=args.webhook_rebuild,
|
|
max_builds=args.max_builds,
|
|
timeout=args.timeout,
|
|
)
|
|
|
|
for package_name in packages:
|
|
upsert_scm_package(
|
|
args=args,
|
|
project=args.project,
|
|
package_name=package_name,
|
|
spec_ref=resolve_spec_ref(package_name, specs_dir),
|
|
clone_url=args.clone_url,
|
|
commit=args.commit,
|
|
webhook_rebuild=args.webhook_rebuild,
|
|
max_builds=args.max_builds,
|
|
timeout=args.timeout,
|
|
)
|
|
|
|
if not args.skip_chroot_update:
|
|
run_copr(
|
|
args,
|
|
[
|
|
"copr",
|
|
"edit-chroot",
|
|
f"{args.project}/{args.chroot}",
|
|
"--packages",
|
|
args.macro_package_name,
|
|
]
|
|
)
|
|
|
|
if args.submit_macro_build:
|
|
submit_build(args, args.project, args.macro_package_name, args.chroot, nowait=False)
|
|
|
|
if args.submit_package_builds:
|
|
for package_name in packages:
|
|
submit_build(
|
|
args,
|
|
args.project,
|
|
package_name,
|
|
args.chroot,
|
|
nowait=args.nowait_package_builds,
|
|
)
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|