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 shutil
import subprocess
import textwrap import textwrap
from argparse import Namespace from argparse import Namespace
from pathlib import Path 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.legacy import LEGACY_META_PKG, detect_legacy_repo, legacy_to_delete
from caelestia.utils.dots.manifest import ComponentError, Manifest, ManifestError from caelestia.utils.dots.manifest import ComponentError, Manifest, ManifestError
from caelestia.utils.dots.misc import build_local_packages, run_hooks 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.source import DotsSource, SourceError
from caelestia.utils.dots.state import DotsState from caelestia.utils.dots.state import DotsState
from caelestia.utils.io import confirm, disable_input, fatal, info, log, pause, prompt_selection, warn 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() source, tip, manifest = self.fetch_manifest()
deployed = self.deploy_configs(source, 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") run_hooks(manifest, "post_install")
DotsState( DotsState(
@@ -190,7 +192,7 @@ class Command:
if meta_installed: if meta_installed:
log("Removing legacy meta package...") log("Removing legacy meta package...")
installer.remove([LEGACY_META_PKG]) 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}") warn(f"could not fully clear the legacy installation: {e}")
def print_done(self) -> None: 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.diff import Changeset
from caelestia.utils.dots.manifest import ComponentError, Manifest, ManifestError from caelestia.utils.dots.manifest import ComponentError, Manifest, ManifestError
from caelestia.utils.dots.misc import build_local_packages, run_hooks 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.source import DotsSource, SourceError
from caelestia.utils.dots.state import DotsState from caelestia.utils.dots.state import DotsState
from caelestia.utils.io import disable_input, fatal, info, log, prompt_selection, warn 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.") fatal("dots not installed yet. Run `caelestia install` first.")
# Run system update # Run system update
installer = PackageInstaller.get(self.args.aur_helper or state.aur_helper, self.args.noconfirm) try:
installer.system_update() 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 # Get manifest or exit if up to date
source, tip, manifest = self.fetch_manifest(state, state.applied_rev) source, tip, manifest = self.fetch_manifest(state, state.applied_rev)
@@ -54,13 +57,14 @@ class Command:
# Install new/remove old packages # Install new/remove old packages
desired = manifest.enabled_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() desired_local = manifest.enabled_local_packages()
state.local_packages = self.sync_local_packages(installer, source, state.local_packages, desired_local) try:
state.save() 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
run_hooks(manifest, "post_update") run_hooks(manifest, "post_update")
+40 -15
View File
@@ -5,32 +5,48 @@ import tempfile
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from pathlib import Path from pathlib import Path
from caelestia.utils.io import fatal, info from caelestia.utils.io import fatal, info, warn
DEFAULT_AUR_HELPER = "paru" DEFAULT_AUR_HELPER = "paru"
AUR_HELPERS = DEFAULT_AUR_HELPER, "yay" 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: def _install_aur_helper(helper: str, noconfirm: bool = False) -> None:
pacman_cmd = ["sudo", "pacman", "-S", "--needed", "git", "base-devel"] pacman_cmd = ["sudo", "pacman", "-S", "--needed", "git", "base-devel"]
if noconfirm: if noconfirm:
pacman_cmd.append("--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" repo_url = f"https://aur.archlinux.org/{helper}.git"
with tempfile.TemporaryDirectory() as repo_dir: 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"] makepkg_cmd = ["makepkg", "-si"]
if noconfirm: if noconfirm:
makepkg_cmd.append("--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": try:
subprocess.run(["yay", "-Y", "--gendb"], check=True) if helper == "yay":
subprocess.run(["yay", "-Y", "--devel", "--save"], check=True) subprocess.run(["yay", "-Y", "--gendb"], check=True)
elif helper == "paru": subprocess.run(["yay", "-Y", "--devel", "--save"], check=True)
subprocess.run(["paru", "--gendb"], 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): class PackageInstaller(ABC):
@@ -114,19 +130,26 @@ class ArchInstaller(PackageInstaller):
cmd = [self.helper, "-S", "--needed", *self.flags] cmd = [self.helper, "-S", "--needed", *self.flags]
if not explicit: if not explicit:
cmd.append("--asdeps") # Set install reason to dep (does not affect already installed packages) 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 # Force install reason to explicit install
if explicit: 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: def remove(self, packages: list[str]) -> None:
if not packages: if not packages:
return 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]: 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 = [] names = []
depends = [] depends = []
for line in srcinfo.splitlines(): for line in srcinfo.splitlines():
@@ -145,7 +168,9 @@ class ArchInstaller(PackageInstaller):
# Stop makepkg from resetting sudo # Stop makepkg from resetting sudo
env = {**os.environ, "PACMAN_AUTH": "sudo"} env = {**os.environ, "PACMAN_AUTH": "sudo"}
# -f = force, -s = sync deps, -i = install # -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 return names
@@ -160,4 +185,4 @@ class ArchInstaller(PackageInstaller):
) )
def system_update(self) -> None: 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")