diff --git a/src/decman/__init__.py b/src/decman/__init__.py index a8c9c20..eb3ae5b 100644 --- a/src/decman/__init__.py +++ b/src/decman/__init__.py @@ -1,3 +1,40 @@ """ Module for writing system configurations for decman. """ + +import typing + + +class UserPackage: + """ + Defines a custom package. + """ + + def __init__( + self, + pkgname: str, + version: str, + dependencies: list[str], + git_url: str, + pkgbase: typing.Optional[str] = None, + provides: typing.Optional[list[str]] = None, + make_dependencies: typing.Optional[list[str]] = None, + check_dependencies: typing.Optional[list[str]] = None, + ): + if pkgbase is None: + pkgbase = pkgname + if provides is None: + provides = [] + if make_dependencies is None: + make_dependencies = [] + if check_dependencies is None: + check_dependencies = [] + + self.pkgname = pkgname + self.pkgbase = pkgbase + self.version = version + self.provides = provides + self.dependencies = dependencies + self.make_dependencies = make_dependencies + self.check_dependencies = check_dependencies + self.git_url = git_url diff --git a/src/decman/lib/__init__.py b/src/decman/lib/__init__.py index f8725b9..71a553e 100644 --- a/src/decman/lib/__init__.py +++ b/src/decman/lib/__init__.py @@ -8,6 +8,7 @@ import json import os import typing import decman.config as conf +import decman _DECMAN_MSG_TAG = "[\033[1;35mDECMAN\033[m]" _RED_PREFIX = "\033[91m" @@ -220,6 +221,112 @@ class UserFacingError(Exception): self.user_facing_msg = user_facing_msg +class Source: + """ + Configuration that describes a system. + """ + + def __init__(self, pacman_packages: list[str], aur_packages: list[str], + user_packages: list[decman.UserPackage], + ignored_packages: list[str], systemd_units: list[str], + systemd_user_units: dict[str, list[str]]): + self.pacman_packages = pacman_packages + self.aur_packages = aur_packages + self.user_packages = user_packages + self.ignored_packages = ignored_packages + self.systemd_units = systemd_units + self.systemd_user_units = systemd_user_units + + def units_to_enable(self, store: Store) -> list[str]: + """ + Returns all systemd units that should be enabled. + """ + result = [] + for unit in self.systemd_units: + if unit not in store.enabled_systemd_units: + result.append(unit) + return result + + def units_to_disable(self, store: Store) -> list[str]: + """ + Returns all systemd units that should be disabled. + """ + result = [] + for unit in store.enabled_systemd_units: + if unit not in self.systemd_units: + result.append(unit) + return result + + def user_units_to_enable(self, store: Store) -> dict[str, list[str]]: + """ + Returns all user systemd units that should be enabled. + """ + result = {} + for user, units in self.systemd_user_units.items(): + for unit in units: + stored = f"{user}: {unit}" + if stored not in store.enabled_user_systemd_units: + entry = result.get(user, []) + entry.append(unit) + result[user] = entry + return result + + def user_units_to_disable(self, store: Store) -> dict[str, list[str]]: + """ + Returns all user systemd units that should be disabled. + """ + result = {} + for stored in store.enabled_user_systemd_units: + s = stored.split(": ") + user = s[0] + unit = s[1] + + if unit not in self.systemd_user_units.get(user, []): + entry = result.get(user, []) + entry.append(unit) + result[user] = entry + return result + + def packages_to_remove( + self, currently_installed_packages: list[str]) -> list[str]: + """ + Returns all packages that should be removed. This includes pacman, aur and user packages. + """ + result = [] + all_pkgs = self.pacman_packages + self.aur_packages + list( + map(lambda p: p.pkgname, self.user_packages)) + for pkg in currently_installed_packages: + if pkg not in all_pkgs and pkg not in self.ignored_packages: + result.append(pkg) + return result + + def pacman_packages_to_install( + self, currently_installed_packages: list[str]) -> list[str]: + """ + Returns all pacman packages that should be installed. + """ + result = [] + for pkg in self.pacman_packages: + if pkg not in currently_installed_packages and pkg not in self.ignored_packages: + result.append(pkg) + return result + + def foreign_packages_to_install( + self, currently_installed_packages: list[str]) -> list[str]: + """ + Returns all aur and user packages that should be installed. + """ + result = [] + for pkg in self.aur_packages: + if pkg not in currently_installed_packages and pkg not in self.ignored_packages: + result.append(pkg) + + for pkg in self.user_packages: + if pkg.pkgname not in currently_installed_packages and pkg.pkgname not in self.ignored_packages: + result.append(pkg.pkgname) + return result + + class Pacman: """ Interface for interacting with pacman. diff --git a/src/decman/lib/aur.py b/src/decman/lib/aur.py index 9f2db49..35254c6 100644 --- a/src/decman/lib/aur.py +++ b/src/decman/lib/aur.py @@ -19,6 +19,7 @@ import typing import requests +import decman import decman.config as conf import decman.lib as l @@ -100,6 +101,24 @@ class PackageInfo: """ return f"{self.pkgname}-{self.version}" + @staticmethod + def from_user_package(user_package: decman.UserPackage, + pacman: l.Pacman) -> "PackageInfo": + """ + Converts a UserPackage to PackageInfo + """ + return PackageInfo( + pkgname=user_package.pkgname, + pkgbase=user_package.pkgbase, + version=user_package.version, + provides=user_package.provides, + dependencies=user_package.dependencies, + make_dependencies=user_package.make_dependencies, + check_dependencies=user_package.check_dependencies, + git_url=user_package.git_url, + pacman=pacman, + ) + class ForeignPackage: """ @@ -521,10 +540,16 @@ class ForeignPackageManager: self._pacman = pacman self._search = search - def upgrade(self, upgrade_devel: bool = False, force: bool = False): + def upgrade(self, + upgrade_devel: bool = False, + force: bool = False, + ignored_pkgs: typing.Optional[list[str]] = None): """ Upgrades all foreign packages. """ + if ignored_pkgs is None: + ignored_pkgs = [] + l.print_summary("Determining packages to upgrade.") all_foreign_pkgs = self._pacman.get_versioned_foreign_packages() @@ -538,6 +563,9 @@ class ForeignPackageManager: as_explicit = [] as_deps = [] for pkg, ver in all_foreign_pkgs: + if pkg in ignored_pkgs: + continue + info = self._search.get_package_info(pkg) if info is None: raise l.UserFacingError(f"Failed to find package: {pkg}.") diff --git a/tests/test_package_management.py b/tests/test_package_management.py index 6f33ec2..8b1b439 100644 --- a/tests/test_package_management.py +++ b/tests/test_package_management.py @@ -5,9 +5,9 @@ from decman.lib import UserFacingError, Pacman, Store from decman.lib.aur import ForeignPackageManager, DepGraph, ForeignPackage, ExtendedPackageSearch -class TestAUR(unittest.TestCase): +class TestVersionComparisons(unittest.TestCase): - def setUp(self) -> None: + def setUp(self): pacman = Pacman() self.aur = ForeignPackageManager(Store(), pacman, ExtendedPackageSearch(pacman)) diff --git a/tests/test_source_resolution.py b/tests/test_source_resolution.py new file mode 100644 index 0000000..41a4f06 --- /dev/null +++ b/tests/test_source_resolution.py @@ -0,0 +1,95 @@ +# pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring + +import unittest +from decman.lib import Source, Store +from decman import UserPackage + + +class TestSource(unittest.TestCase): + + def setUp(self): + source = Source( + pacman_packages=["p1", "p2", "p3"], + aur_packages=["A1", "A2", "A3"], + user_packages=[ + UserPackage( + pkgname="U1", + version="1", + dependencies=["d1"], + git_url="/am/url/yes", + ), + UserPackage( + pkgname="U2", + version="1", + dependencies=["d2"], + git_url="/am/url/yes", + ) + ], + ignored_packages=["i1", "i2"], + systemd_units=["1.service", "2.timer"], + systemd_user_units={"user": ["u1.service", "u2.timer"]}, + ) + + store = Store() + store.enabled_systemd_units.extend(["1.service", "3.service"]) + store.enabled_user_systemd_units.extend( + ["user: u1.service", "user: u3.service"]) + + currently_installed_packages = [ + "p1", + "p2", + "p4", + "A2", + "A3", + "A4", + "U1", + "i1", + ] + + self.source = source + self.store = store + self.currently_installed_packages = currently_installed_packages + + def test_units_to_enable(self): + self.assertCountEqual( + self.source.units_to_enable(self.store), + ["2.timer"], + ) + + def test_units_to_disable(self): + self.assertCountEqual( + self.source.units_to_disable(self.store), + ["3.service"], + ) + + def test_user_units_to_enable(self): + self.assertDictEqual( + self.source.user_units_to_enable(self.store), + {"user": ["u2.timer"]}, + ) + + def test_user_units_to_disable(self): + self.assertDictEqual( + self.source.user_units_to_disable(self.store), + {"user": ["u3.service"]}, + ) + + def test_pacman_packages_to_install(self): + self.assertCountEqual( + self.source.pacman_packages_to_install( + self.currently_installed_packages), + ["p3"], + ) + + def test_foreign_packages_to_install(self): + self.assertCountEqual( + self.source.foreign_packages_to_install( + self.currently_installed_packages), + ["A1", "U2"], + ) + + def test_packages_to_remove(self): + self.assertCountEqual( + self.source.packages_to_remove(self.currently_installed_packages), + ["p4", "A4"], + )