adee5dfc78
- FastAPI server with scan/build/repo endpoints - LLM scanner (Ollama) with regex pre-scan - makepkg/devtools builder with chroot isolation - Scan cache with TTL + PKGBUILD hash - Client installer + safe-yay wrapper - Docs + config example
152 lines
3.8 KiB
Python
152 lines
3.8 KiB
Python
"""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) |