"""Package builder — runs makepkg in a clean environment.""" from __future__ import annotations import os import shutil import subprocess from dataclasses import dataclass from pathlib import Path from .config import ServerConfig, BuildConfig @dataclass class BuildResult: success: bool pkg_files: list[str] # paths to .pkg.tar.zst log: str error: str = "" def build_package( pkgbuild: str, package_name: str, server_cfg: ServerConfig, build_cfg: BuildConfig, ) -> BuildResult: """Build a package from a PKGBUILD string.""" work_dir = Path(server_cfg.work_dir) / package_name work_dir.mkdir(parents=True, exist_ok=True) # Write PKGBUILD pkgbuild_path = work_dir / "PKGBUILD" pkgbuild_path.write_text(pkgbuild) # Clean previous build artifacts for f in work_dir.glob("*.pkg.tar.*"): f.unlink() log_lines: list[str] = [] try: if build_cfg.use_devtools and shutil.which("extra-x86_64-build"): # Use devtools chroot for isolation cmd = [ "extra-x86_64-build", "--", "-cC", ] cwd = work_dir else: # Fallback: makepkg directly cmd = [ "makepkg", "-sf", "--noconfirm", "--noprogressbar", ] cwd = work_dir log_lines.append(f"Building {package_name} with: {' '.join(cmd)}") result = subprocess.run( cmd, cwd=cwd, capture_output=True, text=True, timeout=build_cfg.timeout, env={**os.environ, "MAKEFLAGS": "-j$(nproc)"}, ) log_lines.append(result.stdout) if result.stderr: log_lines.append("--- stderr ---") log_lines.append(result.stderr) if result.returncode != 0: return BuildResult( success=False, pkg_files=[], log="\n".join(log_lines), error=f"Build failed (exit {result.returncode})", ) # Find built packages pkg_files = [ str(f) for f in work_dir.glob("*.pkg.tar.zst") ] if not pkg_files: return BuildResult( success=False, pkg_files=[], log="\n".join(log_lines), error="Build succeeded but no .pkg.tar.zst found", ) log_lines.append(f"Built: {pkg_files}") return BuildResult( success=True, pkg_files=pkg_files, log="\n".join(log_lines), ) except subprocess.TimeoutExpired: return BuildResult( success=False, pkg_files=[], log="\n".join(log_lines), error=f"Build timed out after {build_cfg.timeout}s", ) except Exception as e: return BuildResult( success=False, pkg_files=[], log="\n".join(log_lines), error=str(e), ) def add_to_repo( pkg_files: list[str], repo_dir: str, ) -> str: """Add built packages to the local pacman repo using repo-add.""" repo_path = Path(repo_dir) repo_path.mkdir(parents=True, exist_ok=True) db_name = "aur-shield.db" db_file = repo_path / f"{db_name}.tar.gz" # Copy packages to repo dir copied = [] for pkg in pkg_files: src = Path(pkg) dst = repo_path / src.name shutil.copy2(src, dst) copied.append(str(dst)) if not copied: return str(db_file) # Run repo-add cmd = ["repo-add", str(db_file)] + copied result = subprocess.run( cmd, capture_output=True, text=True, timeout=60, ) if result.returncode != 0: raise RuntimeError(f"repo-add failed: {result.stderr}") return str(db_file)