Skip to content

arch_yay

Arch Linux yay package manager backend.

This backend supports Arch and Arch-based distros by: - bootstrapping yay if missing (requires non-interactive sudo) - installing packages via yay -S --needed --noconfirm

ArchYayManager

yay manager for Arch Linux and Arch-based distros.

Source code in src/awesome_os/tasks/managers/arch_yay.py
class ArchYayManager:
    """`yay` manager for Arch Linux and Arch-based distros."""

    name = "yay"

    def _yay(self) -> str | None:
        return shutil.which("yay")

    def _ensure_yay(self) -> TaskResult:
        yay = self._yay()
        if yay is not None:
            return TaskResult(ok=True, summary="yay: found")

        if not _sudo_non_interactive_ok():
            return TaskResult(
                ok=False,
                summary="yay: missing (sudo password required)",
                details=(
                    "This app runs non-interactively; it cannot prompt for your sudo password.\n"
                    "Install yay manually, then re-run:\n"
                    "  sudo pacman -Sy --needed base-devel git\n"
                    "  cd /tmp && git clone https://aur.archlinux.org/yay.git\n"
                    "  cd yay && makepkg -si\n"
                ),
            )

        # Ensure build tooling exists.
        res = run(
            ["sudo", "-n", "pacman", "-Sy", "--needed", "--noconfirm", "base-devel", "git"],
            check=False,
        )
        if res.returncode != 0:
            return TaskResult(
                ok=False,
                summary="yay: bootstrap failed (pacman deps)",
                details=_format_failed(res.argv, res.stdout, res.stderr),
            )

        # Build and install yay from AUR.
        with tempfile.TemporaryDirectory(prefix="awesome-os-yay-") as td:
            tdir = Path(td)
            clone = run(
                ["git", "clone", "https://aur.archlinux.org/yay.git", str(tdir / "yay")],
                check=False,
            )
            if clone.returncode != 0:
                return TaskResult(
                    ok=False,
                    summary="yay: bootstrap failed (git clone)",
                    details=_format_failed(clone.argv, clone.stdout, clone.stderr),
                )

            yay_dir = tdir / "yay"
            # Note: `makepkg -si` will install the built package via pacman (sudo).
            makepkg = run(
                ["bash", "-lc", f"cd {str(yay_dir)!r} && makepkg -si --noconfirm"],
                check=False,
                capture_output=True,
                text=True,
            )
            if makepkg.returncode != 0:
                return TaskResult(
                    ok=False,
                    summary="yay: bootstrap failed (makepkg)",
                    details=_format_failed(makepkg.argv, makepkg.stdout, makepkg.stderr),
                )

        yay = self._yay()
        if yay is None:
            return TaskResult(
                ok=False,
                summary="yay: bootstrap finished but yay not on PATH",
                details="`yay` was built/installed but is still not found on PATH. Restart your shell and try again.",
            )
        return TaskResult(ok=True, summary="yay: installed")

    def is_installed(self, package: str) -> bool:
        # Both repo and AUR packages end up in pacman's local DB.
        pacman = shutil.which("pacman")
        if pacman is None:
            return False
        res = run([pacman, "-Q", package], check=False)
        return res.returncode == 0

    def install(self, package: str) -> InstallResult:
        ensure = self._ensure_yay()
        if not ensure.ok:
            return InstallResult(
                ok=False, summary=f"{package}: install failed (yay missing)", details=ensure.details
            )

        yay = self._yay()
        if yay is None:
            return InstallResult(
                ok=False, summary="yay not found on PATH", details="Ensure `yay` is installed."
            )

        logger.info(f"Installing {package} via yay...")
        res = run([yay, "-S", "--needed", "--noconfirm", package], check=False)
        if res.returncode == 0:
            return InstallResult(ok=True, summary=f"{package}: installed (yay)")
        return InstallResult(
            ok=False,
            summary=f"{package}: install failed (yay)",
            details=_format_failed(res.argv, res.stdout, res.stderr),
        )

    def update(self) -> TaskResult:
        ensure = self._ensure_yay()
        if not ensure.ok:
            return TaskResult(ok=False, summary="yay update: failed", details=ensure.details)
        yay = self._yay()
        if yay is None:
            return TaskResult(
                ok=False, summary="yay update: failed", details="yay not found on PATH"
            )
        res = run([yay, "-Sy", "--noconfirm"], check=False)
        if res.returncode == 0:
            return TaskResult(ok=True, summary="yay sync: done")
        return TaskResult(
            ok=False,
            summary="yay sync: failed",
            details=_format_failed(res.argv, res.stdout, res.stderr),
        )

    def upgrade(self) -> TaskResult:
        ensure = self._ensure_yay()
        if not ensure.ok:
            return TaskResult(ok=False, summary="yay upgrade: failed", details=ensure.details)
        yay = self._yay()
        if yay is None:
            return TaskResult(
                ok=False, summary="yay upgrade: failed", details="yay not found on PATH"
            )
        res = run([yay, "-Syu", "--noconfirm"], check=False)
        if res.returncode == 0:
            return TaskResult(ok=True, summary="yay -Syu: done")
        return TaskResult(
            ok=False,
            summary="yay -Syu: failed",
            details=_format_failed(res.argv, res.stdout, res.stderr),
        )

    def cleanup(self) -> TaskResult:
        ensure = self._ensure_yay()
        if not ensure.ok:
            return TaskResult(ok=False, summary="yay cleanup: failed", details=ensure.details)
        yay = self._yay()
        if yay is None:
            return TaskResult(
                ok=False, summary="yay cleanup: failed", details="yay not found on PATH"
            )
        # `-Sc` removes unused packages from cache. `--noconfirm` to avoid prompts.
        res = run([yay, "-Sc", "--noconfirm"], check=False)
        if res.returncode == 0:
            return TaskResult(ok=True, summary="yay cache cleanup: done")
        return TaskResult(
            ok=False,
            summary="yay cache cleanup: failed",
            details=_format_failed(res.argv, res.stdout, res.stderr),
        )