diff --git a/src/decman/__init__.py b/src/decman/__init__.py index 1561e24..578c9e8 100644 --- a/src/decman/__init__.py +++ b/src/decman/__init__.py @@ -382,7 +382,14 @@ class Module: def flatpak_packages(self) -> list[str]: """ - Override this method to return flatpak packages that should be installed as a part of this + Override this method to return flatpak packages that should be installed to the system installation as a part of this + Module. + """ + return [] + + def flatpak_user_packages(self) -> list[str]: + """ + Override this method to return flatpak packages that should be installed to the user installation as a part of this Module. """ return [] @@ -420,4 +427,5 @@ files: dict[str, File] = {} directories: dict[str, Directory] = {} modules: list[Module] = [] flatpak_packages: list[str] = [] +flatpak_user_packages: list[str] = [] ignored_flatpak_packages: list[str] = [] diff --git a/src/decman/app.py b/src/decman/app.py index 3f0ebc6..24ea995 100644 --- a/src/decman/app.py +++ b/src/decman/app.py @@ -29,7 +29,6 @@ def main(): epilog="See more help at: https://github.com/kiviktnm/decman", ) - parser.add_argument( "--source", action="store", help="python file containing configuration" ) @@ -186,7 +185,7 @@ def _set_up(store: l.Store, args): args.print, not args.no_packages, not args.no_foreign_packages, - not args.no_flatpaks + not args.no_flatpaks, not args.no_files, not args.no_systemd_units, not args.no_commands, @@ -214,7 +213,9 @@ class Core: ) = opts if conf.enable_flatpak and not shutil.which("flatpak"): - l.print_error("Flatpaks have been enabled in the source file, but the flatpak command could not be found. Either disable flatpaks or make sure that flatpak is installed and can be accessed by decman. Exiting.") + l.print_error( + "Flatpaks have been enabled in the source file, but the flatpak command could not be found. Either disable flatpaks or make sure that flatpak is installed and can be accessed by decman. Exiting." + ) exit() self.store = store @@ -284,9 +285,14 @@ class Core: to_remove_flatpak = self.source.flatpak_packages_to_remove( currently_installed_flatpak ) + currently_installed_user_flatpak = self.flatpak.get_installed(True) + to_remove_user_flatpak = self.source.flatpak_packages_to_remove( + currently_installed_user_flatpak, as_user=True + ) l.print_list("Removing pacman packages:", to_remove) l.print_list("Removing flatpak packages:", to_remove_flatpak) + l.print_list("Removing user flatpak packages:", to_remove_user_flatpak) if self.only_print: return @@ -296,6 +302,7 @@ class Core: # flatpak if conf.enable_flatpak and self.update_flatpaks: self.flatpak.remove(to_remove_flatpak) + self.flatpak.remove(to_remove_user_flatpak, True) def _upgrade_pkgs(self): """ @@ -331,9 +338,14 @@ class Core: to_install_flatpak = self.source.flatpak_packages_to_install( currently_installed_flatpak ) + currently_installed_user_flatpak = self.flatpak.get_installed(True) + to_install_user_flatpak = self.source.flatpak_packages_to_install( + currently_installed_user_flatpak, True + ) l.print_list("Installing pacman packages:", to_install_pacman) l.print_list("Installing flatpak packages:", to_install_flatpak) + l.print_list("Installing user flatpak packages:", to_install_user_flatpak) # fpm prints a summary so no need to print it twice if self.only_print: @@ -346,6 +358,7 @@ class Core: if conf.enable_flatpak and self.update_flatpaks: self.flatpak.install(to_install_flatpak) + self.flatpak.install(to_install_user_flatpak, True) def _create_and_remove_files(self): l.print_summary("Installing files.") @@ -416,6 +429,7 @@ def _resolve_source() -> l.Source: directories=decman.directories, modules=set(decman.modules), flatpak_packages=set(decman.flatpak_packages), + flatpak_user_packages=set(decman.flatpak_user_packages), ignored_flatpak_packages=set(decman.ignored_flatpak_packages), ) diff --git a/src/decman/config.py b/src/decman/config.py index eda3cee..5bd2ca8 100644 --- a/src/decman/config.py +++ b/src/decman/config.py @@ -34,12 +34,19 @@ class Commands: """ return ["pacman", "-Qeq", "--color=never"] - def list_flatpak_pkgs(self) -> list[str]: + def list_flatpak_pkgs(self, as_user: bool = False) -> list[str]: """ Running this command outputs a newline separated list of installed flatpak application ids The first line just says 'Application ID' so this one is ignored. """ - return ["flatpak", "list", "--app", "--columns", "application"] + return [ + "flatpak", + "list", + "--app", + "--user" if as_user else "--system", + "--columns", + "application", + ] def list_foreign_pkgs_versioned(self) -> list[str]: """ @@ -54,11 +61,11 @@ class Commands: """ return ["pacman", "-S", "--color=always", "--needed"] + pkgs - def install_flatpak_pkgs(self, pkgs: list[str]) -> list[str]: + def install_flatpak_pkgs(self, pkgs: list[str], as_user: bool = False) -> list[str]: """ Running this command installs all listed packages, and their dependencies/runtimes automatically. """ - return ["flatpak", "install"] + pkgs + return ["flatpak", "install", "--user" if as_user else "--system"] + pkgs def install_files(self, pkg_files: list[str]) -> list[str]: """ @@ -91,11 +98,11 @@ class Commands: """ return ["pacman", "-Syu", "--color=always"] - def upgrade_flatpak(self) -> list[str]: + def upgrade_flatpak(self, as_user: bool = False) -> list[str]: """ Updates all installed flatpak REFs including runtimes and dependencies. """ - return ["flatpak", "update"] + return ["flatpak", "update", "--user" if as_user else "--system"] def remove(self, pkgs: list[str]) -> list[str]: """ @@ -104,17 +111,17 @@ class Commands: """ return ["pacman", "-Rs", "--color=always"] + pkgs - def remove_flatpak(self, pkgs: list[str]) -> list[str]: + def remove_flatpak(self, pkgs: list[str], as_user: bool = False) -> list[str]: """ Running this command will remove the listed REFs. Unused dependencies might be kept, but to remove them another command needs to be run. """ - return ["flatpak", "remove"] + pkgs + return ["flatpak", "remove", "--user" if as_user else "--system"] + pkgs - def remove_unused_flatpak(self) -> list[str]: + def remove_unused_flatpak(self, as_user: bool = False) -> list[str]: """ This will remove all unused flatpak dependencies and runtimes. """ - return ["flatpak", "remove", "--unused"] + return ["flatpak", "remove", "--unused", "--user" if as_user else "--system"] def enable_units(self, units: list[str]) -> list[str]: """ diff --git a/src/decman/lib/__init__.py b/src/decman/lib/__init__.py index f277a8e..0e52fe6 100644 --- a/src/decman/lib/__init__.py +++ b/src/decman/lib/__init__.py @@ -420,6 +420,7 @@ class Source: directories: dict[str, decman.Directory], modules: set[decman.Module], flatpak_packages: set[str], + flatpak_user_packages: set[str], ignored_flatpak_packages: set[str], ): self.pacman_packages = pacman_packages @@ -432,6 +433,7 @@ class Source: self.directories = directories self.modules = modules self.flatpak_packages = flatpak_packages + self.flatpak_user_packages = flatpak_user_packages self.ignored_flatpak_packages = ignored_flatpak_packages def run_on_enable(self, store: Store): @@ -644,14 +646,14 @@ class Source: return result def flatpak_packages_to_install( - self, currently_installed_packages: list[str] + self, currently_installed_packages: list[str], as_user: bool = False ) -> list[str]: """ Returns all flatpak packages, that are not installed or ignored """ result: list[str] = [] - for pkg in self._all_flatpak_packages(): + for pkg in self._all_flatpak_packages(as_user): if pkg in self.ignored_flatpak_packages: continue if pkg not in currently_installed_packages: @@ -659,16 +661,17 @@ class Source: return result def flatpak_packages_to_remove( - self, currently_installed_packages: list[str] + self, currently_installed_packages: list[str], as_user: bool = False ) -> list[str]: """ - This returns a list of flatpak app ids, that need to be removed since they are installed but not found in either the list of ignored packages or the list of flatpak packages that need to be installed. + This returns a list of flatpak app ids, that need to be removed since they are installed but not found in either the list of ignored packages, + the list of system packages or the list of user packages that need to be installed. """ result: list[str] = [] for package in currently_installed_packages: if package in self.ignored_flatpak_packages: continue - if package not in self._all_flatpak_packages(): + if package not in self._all_flatpak_packages(as_user): result.append(package) return result @@ -702,12 +705,18 @@ class Source: result.update(module.pacman_packages()) return result - def _all_flatpak_packages(self) -> set[str]: + def _all_flatpak_packages(self, as_user: bool = False) -> set[str]: result = set() - result.update(self.flatpak_packages) + result.update( + self.flatpak_packages if not as_user else self.flatpak_user_packages + ) for module in self.modules: if module.enabled: - result.update(module.flatpak_packages()) + result.update( + module.flatpak_packages() + if not as_user + else module.flatpak_user_packages() + ) return result @@ -969,14 +978,14 @@ class Flatpak: def __init__(self) -> None: pass - def get_installed(self) -> list[str]: + def get_installed(self, as_user: bool = False) -> list[str]: """ Return all of the installed applications. Dependencies and runtimes are exluded since they will not be explicitly installed and thus flatpak will manage them. """ try: packages = ( subprocess.run( - conf.commands.list_flatpak_pkgs(), + conf.commands.list_flatpak_pkgs(as_user), check=True, stdout=subprocess.PIPE, ) @@ -995,7 +1004,7 @@ class Flatpak: user_facing_msg=f"Failed to get installed flatpak packages using '{error.cmd}'. Output: {error.stdout}." ) from error - def install(self, packages: list[str]): + def install(self, packages: list[str], as_user: bool = False): """ Install the listed flatpak packages. """ @@ -1003,7 +1012,7 @@ class Flatpak: return returncode, _output = echo_and_capture_command( - conf.commands.install_flatpak_pkgs(packages) + conf.commands.install_flatpak_pkgs(packages, as_user) ) if returncode != 0: raise err.UserFacingError( @@ -1020,7 +1029,7 @@ class Flatpak: f"Failed to upgrade flatpak packages. Process exited with code {returncode}." ) - def remove(self, packages: list[str]): + def remove(self, packages: list[str], as_user: bool = False): """ Remove all the listed packages and their unused dependecies. This has to happen in two steps. """ @@ -1028,7 +1037,7 @@ class Flatpak: return returncode, _output = echo_and_capture_command( - conf.commands.remove_flatpak(packages) + conf.commands.remove_flatpak(packages, as_user) ) if not returncode == 0: diff --git a/tests/test_source_resolution.py b/tests/test_source_resolution.py index 2d9275b..3b42ec5 100644 --- a/tests/test_source_resolution.py +++ b/tests/test_source_resolution.py @@ -141,6 +141,7 @@ class TestSource(unittest.TestCase): files={}, directories={}, flatpak_packages={"f1", "f2", "f3"}, + flatpak_user_packages={"fu1", "fu2", "fu3"}, ignored_flatpak_packages={"i1", "i2"}, ) @@ -290,6 +291,7 @@ class TestModuleUserServices(unittest.TestCase): directories={}, modules={self.ModuleWithUserServiceOne(), self.ModuleWithUserServiceTwo()}, flatpak_packages=set(), + flatpak_user_packages=set(), ignored_flatpak_packages=set(), ) self.store = Store()