mirror of
https://github.com/caelestia-dots/cli.git
synced 2026-06-19 07:20:01 -05:00
fix: catch errors with package installation
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
Reference in New Issue
Block a user