fix: catch errors with package installation

This commit is contained in:
2 * r + 2 * t
2026-06-18 22:36:59 +10:00
parent f53b3d036f
commit 6c3b69cb84
3 changed files with 59 additions and 28 deletions
+6 -4
View File
@@ -1,5 +1,4 @@
import shutil
import subprocess
import textwrap
from argparse import Namespace
from pathlib import Path
@@ -8,7 +7,7 @@ from caelestia.utils.dots.deployer import Deployer
from caelestia.utils.dots.legacy import LEGACY_META_PKG, detect_legacy_repo, legacy_to_delete
from caelestia.utils.dots.manifest import ComponentError, Manifest, ManifestError
from caelestia.utils.dots.misc import build_local_packages, run_hooks
from caelestia.utils.dots.packages import DEFAULT_AUR_HELPER, PackageInstaller
from caelestia.utils.dots.packages import DEFAULT_AUR_HELPER, PackageError, PackageInstaller
from caelestia.utils.dots.source import DotsSource, SourceError
from caelestia.utils.dots.state import DotsState
from caelestia.utils.io import confirm, disable_input, fatal, info, log, pause, prompt_selection, warn
@@ -40,7 +39,10 @@ class Command:
source, tip, manifest = self.fetch_manifest()
deployed = self.deploy_configs(source, manifest)
installer, packages, local_packages = self.install_packages(source, manifest)
try:
installer, packages, local_packages = self.install_packages(source, manifest)
except PackageError as e:
fatal(e)
run_hooks(manifest, "post_install")
DotsState(
@@ -190,7 +192,7 @@ class Command:
if meta_installed:
log("Removing legacy meta package...")
installer.remove([LEGACY_META_PKG])
except (OSError, subprocess.CalledProcessError) as e:
except (OSError, PackageError) as e:
warn(f"could not fully clear the legacy installation: {e}")
def print_done(self) -> None:
+13 -9
View File
@@ -6,7 +6,7 @@ from caelestia.utils.dots.deployer import Deployer
from caelestia.utils.dots.diff import Changeset
from caelestia.utils.dots.manifest import ComponentError, Manifest, ManifestError
from caelestia.utils.dots.misc import build_local_packages, run_hooks
from caelestia.utils.dots.packages import PackageInstaller
from caelestia.utils.dots.packages import PackageError, PackageInstaller
from caelestia.utils.dots.source import DotsSource, SourceError
from caelestia.utils.dots.state import DotsState
from caelestia.utils.io import disable_input, fatal, info, log, prompt_selection, warn
@@ -27,8 +27,11 @@ class Command:
fatal("dots not installed yet. Run `caelestia install` first.")
# Run system update
installer = PackageInstaller.get(self.args.aur_helper or state.aur_helper, self.args.noconfirm)
installer.system_update()
try:
installer = PackageInstaller.get(self.args.aur_helper or state.aur_helper, self.args.noconfirm)
installer.system_update()
except PackageError as e:
fatal(e)
# Get manifest or exit if up to date
source, tip, manifest = self.fetch_manifest(state, state.applied_rev)
@@ -54,13 +57,14 @@ class Command:
# Install new/remove old packages
desired = manifest.enabled_packages()
state.packages = self.sync_packages(installer, state.packages, desired)
state.save()
# Install new/remove old local PKGBUILD packages
desired_local = manifest.enabled_local_packages()
state.local_packages = self.sync_local_packages(installer, source, state.local_packages, desired_local)
state.save()
try:
state.packages = self.sync_packages(installer, state.packages, desired)
state.save()
state.local_packages = self.sync_local_packages(installer, source, state.local_packages, desired_local)
state.save()
except PackageError as e:
fatal(e)
# Run hooks
run_hooks(manifest, "post_update")
+40 -15
View File
@@ -5,32 +5,48 @@ import tempfile
from abc import ABC, abstractmethod
from pathlib import Path
from caelestia.utils.io import fatal, info
from caelestia.utils.io import fatal, info, warn
DEFAULT_AUR_HELPER = "paru"
AUR_HELPERS = DEFAULT_AUR_HELPER, "yay"
class PackageError(Exception):
"""Raised when a package operation (install/remove/build/update) fails."""
def _try_run(cmd: list[str], error_msg: str, **kwargs) -> None:
"""Run a subprocess, raising `PackageError` if it fails."""
try:
subprocess.run(cmd, check=True, **kwargs)
except (subprocess.CalledProcessError, FileNotFoundError) as e:
raise PackageError(error_msg) from e
def _install_aur_helper(helper: str, noconfirm: bool = False) -> None:
pacman_cmd = ["sudo", "pacman", "-S", "--needed", "git", "base-devel"]
if noconfirm:
pacman_cmd.append("--noconfirm")
subprocess.run(pacman_cmd, check=True)
_try_run(pacman_cmd, "failed to install AUR helper build dependencies")
repo_url = f"https://aur.archlinux.org/{helper}.git"
with tempfile.TemporaryDirectory() as repo_dir:
subprocess.run(["git", "clone", repo_url, repo_dir], check=True)
_try_run(["git", "clone", repo_url, repo_dir], f"failed to clone {helper} from the AUR")
makepkg_cmd = ["makepkg", "-si"]
if noconfirm:
makepkg_cmd.append("--noconfirm")
subprocess.run(makepkg_cmd, cwd=repo_dir, check=True)
_try_run(makepkg_cmd, f"failed to build and install {helper}", cwd=repo_dir)
if helper == "yay":
subprocess.run(["yay", "-Y", "--gendb"], check=True)
subprocess.run(["yay", "-Y", "--devel", "--save"], check=True)
elif helper == "paru":
subprocess.run(["paru", "--gendb"], check=True)
try:
if helper == "yay":
subprocess.run(["yay", "-Y", "--gendb"], check=True)
subprocess.run(["yay", "-Y", "--devel", "--save"], check=True)
elif helper == "paru":
subprocess.run(["paru", "--gendb"], check=True)
except (subprocess.CalledProcessError, FileNotFoundError) as e:
warn(f"failed to run AUR helper post install actions: {e}")
class PackageInstaller(ABC):
@@ -114,19 +130,26 @@ class ArchInstaller(PackageInstaller):
cmd = [self.helper, "-S", "--needed", *self.flags]
if not explicit:
cmd.append("--asdeps") # Set install reason to dep (does not affect already installed packages)
subprocess.run(cmd + packages, check=True)
_try_run(cmd + packages, f"failed to install packages: {', '.join(packages)}")
# Force install reason to explicit install
if explicit:
subprocess.run([self.helper, "-D", "--asexplicit", *self.flags, *packages], check=True)
try:
subprocess.run([self.helper, "-D", "--asexplicit", *self.flags, *packages], check=True)
except (subprocess.CalledProcessError, FileNotFoundError):
warn(f"failed to mark packages as explicitly installed: {', '.join(packages)}")
def remove(self, packages: list[str]) -> None:
if not packages:
return
subprocess.run([self.helper, "-Rns", *self.flags, *packages], check=True)
_try_run([self.helper, "-Rns", *self.flags, *packages], f"failed to remove packages: {', '.join(packages)}")
def build_install(self, directory: Path) -> list[str]:
srcinfo = subprocess.check_output(["makepkg", "--printsrcinfo"], cwd=directory, text=True)
try:
srcinfo = subprocess.check_output(["makepkg", "--printsrcinfo"], cwd=directory, text=True)
except (subprocess.CalledProcessError, FileNotFoundError) as e:
raise PackageError(f"failed to read package metadata in {directory}") from e
names = []
depends = []
for line in srcinfo.splitlines():
@@ -145,7 +168,9 @@ class ArchInstaller(PackageInstaller):
# Stop makepkg from resetting sudo
env = {**os.environ, "PACMAN_AUTH": "sudo"}
# -f = force, -s = sync deps, -i = install
subprocess.run(["makepkg", "-fsi", *self.flags], cwd=directory, env=env, check=True)
_try_run(
["makepkg", "-fsi", *self.flags], f"failed to build local package in {directory}", cwd=directory, env=env
)
return names
@@ -160,4 +185,4 @@ class ArchInstaller(PackageInstaller):
)
def system_update(self) -> None:
subprocess.run([self.helper, "-Syu", *self.flags], check=True)
_try_run([self.helper, "-Syu", *self.flags], "failed to perform system update")