diff --git a/pyproject.toml b/pyproject.toml index 67dcf11..37f4367 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,8 +11,14 @@ authors = [ {name = "Kivi Kaitaniemi"} ] dependencies = [ - "requests" + "requests", ] [project.scripts] decman = "decman.app:main" + +[dependency-groups] +dev = [ + "isort>=5.13.2", + "ruff>=0.9.3", +] diff --git a/src/decman/__init__.py b/src/decman/__init__.py index 1af92e5..99ba5dd 100644 --- a/src/decman/__init__.py +++ b/src/decman/__init__.py @@ -2,12 +2,13 @@ Module for writing system configurations for decman. """ -import typing -import pwd import grp -import shutil import os +import pwd +import shutil import subprocess +import typing + import decman.error @@ -20,9 +21,11 @@ class UserRaisedError(Exception): super().__init__(message) -def sh(sh_cmd: str, - user: typing.Optional[str] = None, - env_overrides: typing.Optional[dict[str, str]] = None): +def sh( + sh_cmd: str, + user: typing.Optional[str] = None, + env_overrides: typing.Optional[dict[str, str]] = None, +): """ Shortcut for running a shell command. """ @@ -49,16 +52,20 @@ def sh(sh_cmd: str, f"Running user defined shell command failed because the user {user} doesn't exist." ) from e - with subprocess.Popen(sh_cmd, shell=True, group=gid, user=uid, - env=env) as process: + with subprocess.Popen( + sh_cmd, shell=True, group=gid, user=uid, env=env + ) as process: if process.wait() != 0: raise decman.error.UserFacingError( - f"Running user shell command '{sh_cmd}' as {user} failed.") + f"Running user shell command '{sh_cmd}' as {user} failed." + ) -def prg(command: list[str], - user: typing.Optional[str] = None, - env_overrides: typing.Optional[dict[str, str]] = None): +def prg( + command: list[str], + user: typing.Optional[str] = None, + env_overrides: typing.Optional[dict[str, str]] = None, +): """ Shortcut for running a program. """ @@ -74,7 +81,8 @@ def prg(command: list[str], subprocess.run(command, check=True, env=env) except subprocess.CalledProcessError as e: raise decman.error.UserFacingError( - f"Running user defined program '{command}' failed.") from e + f"Running user defined program '{command}' failed." + ) from e else: try: uid = pwd.getpwnam(user).pw_uid @@ -84,11 +92,11 @@ def prg(command: list[str], f"Running user defined program failed because the user {user} doesn't exist." ) from e - with subprocess.Popen(command, group=gid, user=uid, - env=env) as process: + with subprocess.Popen(command, group=gid, user=uid, env=env) as process: if process.wait() != 0: raise decman.error.UserFacingError( - f"Running user program '{command}' as {user} failed.") + f"Running user program '{command}' as {user} failed." + ) class File: @@ -127,9 +135,7 @@ class File: if group is not None: self.gid = grp.getgrnam(group).gr_gid - def copy_to(self, - target: str, - variables: typing.Optional[dict[str, str]] = None): + def copy_to(self, target: str, variables: typing.Optional[dict[str, str]] = None): """ Copies the contents of this file to the target file. """ @@ -138,8 +144,9 @@ class File: target_directory = os.path.dirname(target) - def create_missing_dirs(dirct: str, uid: typing.Optional[int], - gid: typing.Optional[int]): + def create_missing_dirs( + dirct: str, uid: typing.Optional[int], gid: typing.Optional[int] + ): if not os.path.isdir(dirct): parent_dir = os.path.dirname(dirct) if not os.path.isdir(parent_dir): @@ -161,8 +168,7 @@ class File: os.chmod(target, self.permissions) def _write_content(self, target: str, variables: dict[str, str]): - if self.source_file is not None and (self.bin_file - or len(variables) == 0): + if self.source_file is not None and (self.bin_file or len(variables) == 0): shutil.copy(self.source_file, target) elif self.bin_file and self.content is not None: with open(target, "wb") as file: @@ -177,7 +183,9 @@ class File: with open(target, "wt", encoding=self.encoding) as file: file.write(content) else: - assert self.content is not None, "Content should be set since source_file was not set." + assert self.content is not None, ( + "Content should be set since source_file was not set." + ) content = self.content for var, value in variables.items(): content = content.replace(var, value) @@ -217,10 +225,12 @@ class Directory: if group is not None: self.gid = grp.getgrnam(group).gr_gid - def copy_to(self, - target_directory: str, - variables: typing.Optional[dict[str, str]] = None, - only_print: bool = False) -> list[str]: + def copy_to( + self, + target_directory: str, + variables: typing.Optional[dict[str, str]] = None, + only_print: bool = False, + ) -> list[str]: """ Copies the files in this directory to the target directory. @@ -233,14 +243,15 @@ class Directory: for src_dir, _, src_files in os.walk("."): for src_file in src_files: src_path = os.path.join(src_dir, src_file) - file = File(source_file=src_path, - bin_file=self.bin_files, - encoding=self.encoding, - owner=self.owner, - group=self.group, - permissions=self.permissions) - target = os.path.normpath( - os.path.join(target_directory, src_path)) + file = File( + source_file=src_path, + bin_file=self.bin_files, + encoding=self.encoding, + owner=self.owner, + group=self.group, + permissions=self.permissions, + ) + target = os.path.normpath(os.path.join(target_directory, src_path)) created.append(target) if not only_print: diff --git a/src/decman/app.py b/src/decman/app.py index 5486122..053c51d 100644 --- a/src/decman/app.py +++ b/src/decman/app.py @@ -8,9 +8,9 @@ import sys import traceback import decman +import decman.config as conf import decman.error as err import decman.lib as l -import decman.config as conf from decman.lib import fpm @@ -23,53 +23,62 @@ def main(): parser = argparse.ArgumentParser( prog="decman", - description= - "Declarative package & configuration manager for Arch Linux", - epilog="See more help at: https://github.com/kiviktnm/decman") + description="Declarative package & configuration manager for Arch Linux", + epilog="See more help at: https://github.com/kiviktnm/decman", + ) - parser.add_argument("--source", - action="store", - help="python file containing configuration") + parser.add_argument( + "--source", action="store", help="python file containing configuration" + ) parser.add_argument( "--print", "--dry-run", action="store_true", default=False, - help="print what would happen as a result of running decman") - parser.add_argument("--debug", - action="store_true", - default=False, - help="show debug output") + help="print what would happen as a result of running decman", + ) + parser.add_argument( + "--debug", action="store_true", default=False, help="show debug output" + ) parser.add_argument( "--no-packages", action="store_true", default=False, - help="don't upgrade any packages (including foreign packages)") - parser.add_argument("--no-foreign-packages", - action="store_true", - default=False, - help="don't upgrade foreign packages") - parser.add_argument("--no-files", - action="store_true", - default=False, - help="don't install any files") - parser.add_argument("--no-systemd-units", - action="store_true", - default=False, - help="don't enable/disable systemd units") - parser.add_argument("--no-commands", - action="store_true", - default=False, - help="don't run user specified commands") - parser.add_argument("--upgrade-devel", - action="store_true", - default=False, - help="upgrade devel packages") + help="don't upgrade any packages (including foreign packages)", + ) + parser.add_argument( + "--no-foreign-packages", + action="store_true", + default=False, + help="don't upgrade foreign packages", + ) + parser.add_argument( + "--no-files", action="store_true", default=False, help="don't install any files" + ) + parser.add_argument( + "--no-systemd-units", + action="store_true", + default=False, + help="don't enable/disable systemd units", + ) + parser.add_argument( + "--no-commands", + action="store_true", + default=False, + help="don't run user specified commands", + ) + parser.add_argument( + "--upgrade-devel", + action="store_true", + default=False, + help="upgrade devel packages", + ) parser.add_argument( "--force-build", action="store_true", default=False, - help="force building of packages that are already cached") + help="force building of packages that are already cached", + ) args = parser.parse_args() @@ -105,8 +114,7 @@ def main(): l.print_debug(line) errored = True except decman.UserRaisedError as user_error: - l.print_error( - f"Error encountered while running the source: {user_error}") + l.print_error(f"Error encountered while running the source: {user_error}") errored = True # Save even when an error has occurred, since this avoids repeating steps like building pkgs. @@ -158,13 +166,23 @@ def _set_up(store: l.Store, args): content = file.read() except OSError as e: raise err.UserFacingError( - f"Failed to read source file '{store.source_file}'.") from e + f"Failed to read source file '{store.source_file}'." + ) from e os.chdir(source_dir) sys.path.append(".") exec(content) - return args.print, not args.no_packages, not args.no_foreign_packages, not args.no_files, not args.no_systemd_units, not args.no_commands, args.upgrade_devel, args.force_build + return ( + args.print, + not args.no_packages, + not args.no_foreign_packages, + not args.no_files, + not args.no_systemd_units, + not args.no_commands, + args.upgrade_devel, + args.force_build, + ) class Core: @@ -173,7 +191,16 @@ class Core: """ def __init__(self, store: l.Store, opts): - self.only_print, self.update_packages, self.update_foreign_packages, self.update_files, self.update_units, self.run_commands, self.upgrade_devel, self.force_build = opts + ( + self.only_print, + self.update_packages, + self.update_foreign_packages, + self.update_files, + self.update_units, + self.run_commands, + self.upgrade_devel, + self.force_build, + ) = opts self.store = store self.source = _resolve_source() @@ -183,10 +210,10 @@ class Core: for upkg in self.source.all_user_pkgs(): self.fpkg_search.add_user_pkg( - fpm.PackageInfo.from_user_package(upkg, self.pacman)) + fpm.PackageInfo.from_user_package(upkg, self.pacman) + ) - self.fpm = fpm.ForeignPackageManager(store, self.pacman, - self.fpkg_search) + self.fpm = fpm.ForeignPackageManager(store, self.pacman, self.fpkg_search) def run(self): """ @@ -219,8 +246,7 @@ class Core: to_disable = self.source.units_to_disable(self.store) l.print_list("Disabling systemd units:", to_disable) if to_disable: - l.print_info( - "Disabled systemd units won't be stopped automatically.") + l.print_info("Disabled systemd units won't be stopped automatically.") if not self.only_print: self.systemctl.disable_units(to_disable) @@ -242,15 +268,14 @@ class Core: if not self.only_print: self.pacman.upgrade() if conf.enable_fpm and self.update_foreign_packages: - self.fpm.upgrade(self.upgrade_devel, self.force_build, - self.source.ignored_packages) + self.fpm.upgrade( + self.upgrade_devel, self.force_build, self.source.ignored_packages + ) def _install_pkgs(self): currently_installed = self.pacman.get_installed() - to_install_pacman = self.source.pacman_packages_to_install( - currently_installed) - to_install_fpm = self.source.foreign_packages_to_install( - currently_installed) + to_install_pacman = self.source.pacman_packages_to_install(currently_installed) + to_install_fpm = self.source.foreign_packages_to_install(currently_installed) l.print_list("Installing pacman packages:", to_install_pacman) @@ -269,9 +294,7 @@ class Core: all_created = self.source.create_all_files(self.only_print) to_remove = self.source.files_to_remove(self.store, all_created) - l.print_list("Ensured files are up to date:", - all_created, - elements_per_line=1) + l.print_list("Ensured files are up to date:", all_created, elements_per_line=1) l.print_list("Removing files:", to_remove, elements_per_line=1) if self.only_print: @@ -290,8 +313,7 @@ class Core: to_enable = self.source.units_to_enable(self.store) l.print_list("Enabling systemd units:", to_enable) if to_enable: - l.print_info( - "Enabled systemd units won't be started automatically.") + l.print_info("Enabled systemd units won't be started automatically.") if not self.only_print: self.systemctl.enable_units(to_enable) diff --git a/src/decman/config.py b/src/decman/config.py index 4e217f7..8c85066 100644 --- a/src/decman/config.py +++ b/src/decman/config.py @@ -64,8 +64,7 @@ class Commands: Running this command installs the given packages from pacman repositories. The packages are installed as dependencies. """ - return ["pacman", "-S", "--color=always", "--needed", "--asdeps" - ] + deps + return ["pacman", "-S", "--color=always", "--needed", "--asdeps"] + deps def is_installable(self, pkg: str) -> list[str]: """ @@ -110,8 +109,7 @@ class Commands: """ return ["systemctl", "--user", "-M", f"{user}@", "disable"] + units - def compare_versions(self, installed_version: str, - new_version: str) -> list[str]: + def compare_versions(self, installed_version: str, new_version: str) -> list[str]: """ Running this command outputs -1 when the installed version is older than the new version. """ @@ -160,27 +158,29 @@ class Commands: Running this command installs the given packages to the given chroot. """ return [ - "arch-nspawn", chroot_dir, "pacman", "-S", "--needed", - "--noconfirm" + "arch-nspawn", + chroot_dir, + "pacman", + "-S", + "--needed", + "--noconfirm", ] + packages def remove_chroot_packages(self, chroot_dir: str, packages: list[str]): """ Running this command removes the given packages from the given chroot. """ - return ["arch-nspawn", chroot_dir, "pacman", "-Rsu", "--noconfirm" - ] + packages + return ["arch-nspawn", chroot_dir, "pacman", "-Rsu", "--noconfirm"] + packages - def make_chroot_pkg(self, chroot_wd_dir: str, user: str, - pkgfiles_to_install: list[str]) -> list[str]: + def make_chroot_pkg( + self, chroot_wd_dir: str, user: str, pkgfiles_to_install: list[str] + ) -> list[str]: """ Running this command creates a package file using the given chroot. The package is created as the user and the pkg_files_to_install are installed in the chroot before the package is created. """ - makechrootpkg_cmd = [ - "makechrootpkg", "-c", "-r", chroot_wd_dir, "-U", user - ] + makechrootpkg_cmd = ["makechrootpkg", "-c", "-r", chroot_wd_dir, "-U", user] for pkgfile in pkgfiles_to_install: makechrootpkg_cmd += ["-I", pkgfile] @@ -210,9 +210,9 @@ pacman_output_keywords: list[str] = [ "pacsave", "pacnew", # These cause too many false positives IMO - #"warning", - #"error", - #"note", + # "warning", + # "error", + # "note", ] print_pacman_output_highlights: bool = True diff --git a/src/decman/lib/__init__.py b/src/decman/lib/__init__.py index 8e2e521..03508c2 100644 --- a/src/decman/lib/__init__.py +++ b/src/decman/lib/__init__.py @@ -2,17 +2,18 @@ Library module for decman. """ -import threading -import sys -import shutil -import subprocess import json import os -import typing +import shutil +import subprocess +import sys +import threading import time +import typing + +import decman import decman.config as conf import decman.error as err -import decman _DECMAN_MSG_TAG = "[\033[1;35mDECMAN\033[m]" _RED_PREFIX = "\033[91m" @@ -60,12 +61,14 @@ def print_summary(msg: str): print(f"{_DECMAN_MSG_TAG} {_CYAN_PREFIX}SUMMARY{_RESET_SUFFIX}: {msg}") -def print_list(msg: str, - l: list[str], - elements_per_line: typing.Optional[int] = None, - max_line_width: typing.Optional[int] = None, - limit_to_term_size: bool = True, - level: int = SUMMARY): +def print_list( + msg: str, + l: list[str], + elements_per_line: typing.Optional[int] = None, + max_line_width: typing.Optional[int] = None, + limit_to_term_size: bool = True, + level: int = SUMMARY, +): """ Prints a summary message to the user along with a list of elements. @@ -89,8 +92,11 @@ def print_list(msg: str, max_line_width = 2**32 # Big enough to basically be unlimited if limit_to_term_size: - max_line_width = shutil.get_terminal_size().columns - len( - _SPACING) - len(_CONTINUATION_PREFIX) + max_line_width = ( + shutil.get_terminal_size().columns + - len(_SPACING) + - len(_CONTINUATION_PREFIX) + ) lines = [f"{l.pop(0)}"] index = 0 @@ -131,10 +137,9 @@ def print_debug(msg: str): print(f"{_DECMAN_MSG_TAG} {_GRAY_PREFIX}DEBUG{_RESET_SUFFIX}: {msg}") -def prompt_number(msg: str, - min_num: int, - max_num: int, - default: typing.Optional[int] = None) -> int: +def prompt_number( + msg: str, min_num: int, max_num: int, default: typing.Optional[int] = None +) -> int: """ Prompts the user for a integer. """ @@ -263,11 +268,12 @@ class Store: if latest_path is None: return None - assert latest_version is not None, "If latest_path is set, then latest_version is set." + assert latest_version is not None, ( + "If latest_path is set, then latest_version is set." + ) return (latest_version, latest_path) - def add_package_to_cache(self, package: str, version: str, - path_to_built_pkg: str): + def add_package_to_cache(self, package: str, version: str, path_to_built_pkg: str): """ Adds a built package to the package file cache. Tries to remove excess cached packages. """ @@ -332,14 +338,13 @@ class Store: d = { "source_file": self.source_file, - "allow_running_source_without_prompt": - self.allow_running_source_without_prompt, + "allow_running_source_without_prompt": self.allow_running_source_without_prompt, "enabled_systemd_units": self.enabled_systemd_units, "enabled_user_systemd_units": self._enabled_user_systemd_units, "enabled_modules": self.enabled_modules, "created_files": self.created_files, "package_file_cache": self._package_file_cache, - "pkgbuild_git_commits": self.pkgbuild_latest_reviewed_commits + "pkgbuild_git_commits": self.pkgbuild_latest_reviewed_commits, } try: @@ -370,7 +375,8 @@ class Store: store.source_file = d.get("source_file", None) store.allow_running_source_without_prompt = d.get( - "allow_running_source_without_prompt", False) + "allow_running_source_without_prompt", False + ) store.enabled_systemd_units = d.get( "enabled_systemd_units", [], @@ -390,12 +396,10 @@ class Store: return store except json.JSONDecodeError as e: print_error(f"{e}") - raise err.UserFacingError( - "Failed to parse decman store json.") from e + raise err.UserFacingError("Failed to parse decman store json.") from e except OSError as e: print_error(f"{e}") - raise err.UserFacingError( - "Failed to read saved decman store.") from e + raise err.UserFacingError("Failed to read saved decman store.") from e class Source: @@ -455,7 +459,8 @@ class Source: """ for module in self.modules: if module.enabled and module.version != store.enabled_modules.get( - module.name, module.version): + module.name, module.version + ): module.after_version_change() elif module.enabled and module.name not in store.enabled_modules: module.after_version_change() @@ -467,8 +472,10 @@ class Source: """ created_files = [] - def install_files(files: dict[str, decman.File], - variables: typing.Optional[dict[str, str]] = None): + def install_files( + files: dict[str, decman.File], + variables: typing.Optional[dict[str, str]] = None, + ): for target, file in files.items(): created_files.append(target) @@ -481,19 +488,24 @@ class Source: except OSError as e: print_error(f"{e}") raise err.UserFacingError( - f"Failed to install file to {target}.") from e + f"Failed to install file to {target}." + ) from e - def install_dirs(dirs: dict[str, decman.Directory], - variables: typing.Optional[dict[str, str]] = None): + def install_dirs( + dirs: dict[str, decman.Directory], + variables: typing.Optional[dict[str, str]] = None, + ): for target, directory in dirs.items(): try: print_debug(f"Installing directory to {target}.") created_files.extend( - directory.copy_to(target, variables, only_print)) + directory.copy_to(target, variables, only_print) + ) except OSError as e: print_error(f"{e}") raise err.UserFacingError( - f"Failed to install directory to {target}.") from e + f"Failed to install directory to {target}." + ) from e install_files(self.files) install_dirs(self.directories) @@ -531,8 +543,7 @@ class Source: return all_dirs - def files_to_remove(self, store: Store, - created_files: list[str]) -> list[str]: + def files_to_remove(self, store: Store, created_files: list[str]) -> list[str]: """ Returns all files that should be removed. """ @@ -587,8 +598,7 @@ class Source: result[user] = entry return result - def packages_to_remove( - self, currently_installed_packages: list[str]) -> list[str]: + 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. """ @@ -601,7 +611,8 @@ class Source: return result def pacman_packages_to_install( - self, currently_installed_packages: list[str]) -> list[str]: + self, currently_installed_packages: list[str] + ) -> list[str]: """ Returns all pacman packages that should be installed. """ @@ -614,7 +625,8 @@ class Source: return result def foreign_packages_to_install( - self, currently_installed_packages: list[str]) -> list[str]: + self, currently_installed_packages: list[str] + ) -> list[str]: """ Returns all aur and user packages that should be installed. """ @@ -704,11 +716,16 @@ class Pacman: """ try: - packages = subprocess.run( - conf.commands.list_pkgs(), - check=True, - stdout=subprocess.PIPE, - ).stdout.decode().strip().split('\n') + packages = ( + subprocess.run( + conf.commands.list_pkgs(), + check=True, + stdout=subprocess.PIPE, + ) + .stdout.decode() + .strip() + .split("\n") + ) return packages except subprocess.CalledProcessError as error: raise err.UserFacingError( @@ -722,9 +739,12 @@ class Pacman: if dep in self._installable: return self._installable[dep] - result = subprocess.run(conf.commands.is_installable(dep), - check=False, - capture_output=True).returncode == 0 + result = ( + subprocess.run( + conf.commands.is_installable(dep), check=False, capture_output=True + ).returncode + == 0 + ) self._installable[dep] = result return result @@ -734,18 +754,23 @@ class Pacman: basically AUR packages. """ try: - output = subprocess.run( - conf.commands.list_foreign_pkgs_versioned(), - check=True, - stdout=subprocess.PIPE).stdout.decode().strip().split('\n') + output = ( + subprocess.run( + conf.commands.list_foreign_pkgs_versioned(), + check=True, + stdout=subprocess.PIPE, + ) + .stdout.decode() + .strip() + .split("\n") + ) except subprocess.CalledProcessError as error: raise err.UserFacingError( f"Failed to get foreign packages using '{error.cmd}'. Output: {error.stdout}." ) from error try: - return [(line.split(" ")[0], line.split(" ")[1]) - for line in output] + return [(line.split(" ")[0], line.split(" ")[1]) for line in output] except IndexError as error: raise err.UserFacingError( f"Failed to parse foreign packages from pacman output. Output: {output}" @@ -759,7 +784,8 @@ class Pacman: return returncode, output = echo_and_capture_command( - conf.commands.install_pkgs(packages)) + conf.commands.install_pkgs(packages) + ) if returncode != 0: raise err.UserFacingError( f"Failed to install packages using pacman. Process exited with code {returncode}." @@ -768,9 +794,11 @@ class Pacman: print_highlighted_pacman_messages(output) try: - subprocess.run(conf.commands.set_as_explicitly_installed(packages), - check=True, - capture_output=conf.suppress_command_output) + subprocess.run( + conf.commands.set_as_explicitly_installed(packages), + check=True, + capture_output=conf.suppress_command_output, + ) except subprocess.CalledProcessError as error: raise err.UserFacingError( "Failed to set packages as explicitly installed using pacman." @@ -783,8 +811,7 @@ class Pacman: if not deps: return - returncode, output = echo_and_capture_command( - conf.commands.install_deps(deps)) + returncode, output = echo_and_capture_command(conf.commands.install_deps(deps)) if returncode != 0: raise err.UserFacingError( f"Failed to install packages as dependencies using pacman. Process exited with code {returncode}." @@ -801,7 +828,8 @@ class Pacman: return returncode, output = echo_and_capture_command( - conf.commands.install_files(files)) + conf.commands.install_files(files) + ) if returncode != 0: raise err.UserFacingError( f"Failed to install package files using pacman. Process exited with code {returncode}." @@ -814,7 +842,8 @@ class Pacman: subprocess.run( conf.commands.set_as_explicitly_installed(as_explicit), check=True, - capture_output=conf.suppress_command_output) + capture_output=conf.suppress_command_output, + ) except subprocess.CalledProcessError as error: if conf.suppress_command_output: print_error("Output:") @@ -842,8 +871,7 @@ class Pacman: if not packages: return - returncode, output = echo_and_capture_command( - conf.commands.remove(packages)) + returncode, output = echo_and_capture_command(conf.commands.remove(packages)) if returncode != 0: raise err.UserFacingError( f"Failed to remove packages using pacman. Process exited with code {returncode}." @@ -861,7 +889,7 @@ def print_highlighted_pacman_messages(output: str): for index, line in enumerate(lines): for keyword in conf.pacman_output_keywords: if keyword.lower() in line.lower(): - print_summary(f"lines: {index}-{index+2}") + print_summary(f"lines: {index}-{index + 2}") if index >= 1: print_continuation(lines[index - 1]) print_continuation(line) @@ -879,10 +907,9 @@ def echo_and_capture_command(program: list[str]) -> tuple[int, str]: Returns a tuple containing the return code of the program as well as all output of the program. """ - with subprocess.Popen(program, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) as process: + with subprocess.Popen( + program, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT + ) as process: os.set_blocking(process.stdout.fileno(), False) output_thread = _OutputCapturingThread(process.stdout) @@ -915,7 +942,6 @@ def echo_and_capture_command(program: list[str]) -> tuple[int, str]: class _OutputCapturingThread(threading.Thread): - def __init__(self, stream): super().__init__() self._stream = stream @@ -948,12 +974,15 @@ class Systemd: return try: - subprocess.run(conf.commands.enable_units(units), - check=True, - capture_output=conf.suppress_command_output) + subprocess.run( + conf.commands.enable_units(units), + check=True, + capture_output=conf.suppress_command_output, + ) except subprocess.CalledProcessError as error: raise err.UserFacingError( - f"Failed to enable systemd units: {units}") from error + f"Failed to enable systemd units: {units}" + ) from error self.state.enabled_systemd_units += units def disable_units(self, units: list[str]): @@ -964,12 +993,15 @@ class Systemd: return try: - subprocess.run(conf.commands.disable_units(units), - check=True, - capture_output=conf.suppress_command_output) + subprocess.run( + conf.commands.disable_units(units), + check=True, + capture_output=conf.suppress_command_output, + ) except subprocess.CalledProcessError as error: raise err.UserFacingError( - f"Failed to disable systemd units: {units}") from error + f"Failed to disable systemd units: {units}" + ) from error for unit in units: try: self.state.enabled_systemd_units.remove(unit) @@ -984,9 +1016,11 @@ class Systemd: return try: - subprocess.run(conf.commands.enable_user_units(units, user), - check=True, - capture_output=conf.suppress_command_output) + subprocess.run( + conf.commands.enable_user_units(units, user), + check=True, + capture_output=conf.suppress_command_output, + ) except subprocess.CalledProcessError as error: raise err.UserFacingError( f"Failed to enable systemd units: {units} for {user}." @@ -1003,9 +1037,11 @@ class Systemd: return try: - subprocess.run(conf.commands.disable_user_units(units, user), - check=True, - capture_output=conf.suppress_command_output) + subprocess.run( + conf.commands.disable_user_units(units, user), + check=True, + capture_output=conf.suppress_command_output, + ) except subprocess.CalledProcessError as error: raise err.UserFacingError( f"Failed to disable systemd units: {units} for {user}." diff --git a/src/decman/lib/fpm.py b/src/decman/lib/fpm.py index 5303e06..50798de 100644 --- a/src/decman/lib/fpm.py +++ b/src/decman/lib/fpm.py @@ -11,18 +11,18 @@ Terminology: - all dependencies: normal dependencies and build dependencies combined """ -import shutil -import subprocess import os import re +import shutil +import subprocess import typing import requests import decman import decman.config as conf -import decman.lib as l import decman.error as err +import decman.lib as l def strip_dependency(dep: str) -> str: @@ -58,10 +58,18 @@ class PackageInfo: In case of AUR packages, these are fetched from AUR RPC. """ - def __init__(self, pkgname: str, pkgbase: str, version: str, - provides: list[str], dependencies: list[str], - make_dependencies: list[str], check_dependencies: list[str], - git_url: str, pacman: l.Pacman): + def __init__( + self, + pkgname: str, + pkgbase: str, + version: str, + provides: list[str], + dependencies: list[str], + make_dependencies: list[str], + check_dependencies: list[str], + git_url: str, + pacman: l.Pacman, + ): self.pkgname = pkgname self.pkgbase = pkgbase self.version = version @@ -79,22 +87,23 @@ class PackageInfo: if pacman.is_installable(dep): self.pacman_dependencies.append(dep) else: - self.foreign_dependencies_stripped.append( - strip_dependency(dep)) + self.foreign_dependencies_stripped.append(strip_dependency(dep)) for make_dep in make_dependencies: if pacman.is_installable(make_dep): self.pacman_make_dependencies.append(make_dep) else: self.foreign_make_dependencies_stripped.append( - strip_dependency(make_dep)) + strip_dependency(make_dep) + ) for check_dep in check_dependencies: if pacman.is_installable(check_dep): self.pacman_check_dependencies.append(check_dep) else: self.foreign_check_dependencies_stripped.append( - strip_dependency(check_dep)) + strip_dependency(check_dep) + ) def pkg_file_prefix(self) -> str: """ @@ -103,8 +112,9 @@ class PackageInfo: return f"{self.pkgname}-{self.version}" @staticmethod - def from_user_package(user_package: decman.UserPackage, - pacman: l.Pacman) -> "PackageInfo": + def from_user_package( + user_package: decman.UserPackage, pacman: l.Pacman + ) -> "PackageInfo": """ Converts a UserPackage to PackageInfo """ @@ -132,8 +142,11 @@ class ForeignPackage: def __eq__(self, value: object, /) -> bool: if isinstance(value, self.__class__): - return self.name == value.name \ - and self._all_recursive_foreign_deps == value._all_recursive_foreign_deps + return ( + self.name == value.name + and self._all_recursive_foreign_deps + == value._all_recursive_foreign_deps + ) return False def __hash__(self) -> int: @@ -145,8 +158,7 @@ class ForeignPackage: def __str__(self) -> str: return f"{self.name}" - def add_foreign_dependency_packages(self, - package_names: typing.Iterable[str]): + def add_foreign_dependency_packages(self, package_names: typing.Iterable[str]): """ Adds dependencies to the package. """ @@ -174,8 +186,7 @@ class DepNode: Returns True if the given package name is in the parents of this DepNode. """ for name, parent in self.parents.items(): - if name == pkgname or parent.is_pkgname_in_parents_recursive( - pkgname): + if name == pkgname or parent.is_pkgname_in_parents_recursive(pkgname): return True return False @@ -189,15 +200,15 @@ class DepGraph: self.package_nodes: dict[str, DepNode] = {} self._childless_node_names = set() - def add_requirement(self, child_pkgname: str, - parent_pkgname: typing.Optional[str]): + def add_requirement(self, child_pkgname: str, parent_pkgname: typing.Optional[str]): """ Adds a connection between two packages, creating the child package if it doesn't exist. The parent is the package that requires the child package. """ child_node = self.package_nodes.get( - child_pkgname, DepNode(ForeignPackage(child_pkgname))) + child_pkgname, DepNode(ForeignPackage(child_pkgname)) + ) self.package_nodes[child_pkgname] = child_node if len(child_node.children) == 0: @@ -212,7 +223,8 @@ class DepGraph: raise err.UserFacingError( f"Foreign package dependency cycle detected involving '{child_pkgname}' \ and '{parent_pkgname}'. Foreign package dependencies are also required \ -during package building and therefore dependency cycles cannot be handled.") +during package building and therefore dependency cycles cannot be handled." + ) parent_node.children[child_pkgname] = child_node child_node.parents[parent_pkgname] = parent_node @@ -230,8 +242,7 @@ during package building and therefore dependency cycles cannot be handled.") childless_node = self.package_nodes[childless_node_name] for parent in childless_node.parents.values(): - new_deps = childless_node.pkg.get_all_recursive_foreign_dep_pkgs( - ) + new_deps = childless_node.pkg.get_all_recursive_foreign_dep_pkgs() new_deps.add(childless_node.pkg.name) parent.pkg.add_foreign_dependency_packages(new_deps) del parent.children[childless_node_name] @@ -270,8 +281,7 @@ class ExtendedPackageSearch: times, because then those methods don't have to make new AUR RPC requests. """ - packages = list( - filter(lambda p: p not in self._package_info_cache, packages)) + packages = list(filter(lambda p: p not in self._package_info_cache, packages)) if len(packages) == 0: return @@ -281,8 +291,7 @@ class ExtendedPackageSearch: max_pkgs_per_request = 200 while packages: - to_request = map(lambda p: f"arg[]={p}", - packages[:max_pkgs_per_request]) + to_request = map(lambda p: f"arg[]={p}", packages[:max_pkgs_per_request]) packages = packages[max_pkgs_per_request:] url = f"https://aur.archlinux.org/rpc/v5/info?{'&'.join(to_request)}" @@ -293,8 +302,7 @@ class ExtendedPackageSearch: d = request.json() if d["type"] == "error": - raise err.UserFacingError( - f"AUR RPC returned error: {d['error']}") + raise err.UserFacingError(f"AUR RPC returned error: {d['error']}") for result in d["results"]: pkgname = result["Name"] @@ -304,8 +312,7 @@ class ExtendedPackageSearch: for user_package in self._user_packages: if user_package.pkgname == pkgname: - l.print_debug( - f"'{pkgname}' found in user packages.") + l.print_debug(f"'{pkgname}' found in user packages.") self._package_info_cache[pkgname] = user_package break else: # if not in user_packages then: @@ -317,9 +324,9 @@ class ExtendedPackageSearch: make_dependencies=result.get("MakeDepends", []), check_dependencies=result.get("CheckDepends", []), provides=result.get("Provides", []), - git_url= - f"https://aur.archlinux.org/{result['PackageBase']}.git", - pacman=self._pacman) + git_url=f"https://aur.archlinux.org/{result['PackageBase']}.git", + pacman=self._pacman, + ) self._package_info_cache[pkgname] = info l.print_debug("Request completed.") @@ -355,8 +362,7 @@ class ExtendedPackageSearch: d = request.json() if d["type"] == "error": - raise err.UserFacingError( - f"AUR RPC returned error: {d['error']}") + raise err.UserFacingError(f"AUR RPC returned error: {d['error']}") if d["resultcount"] == 0: l.print_debug(f"'{package}' not found.") @@ -373,9 +379,9 @@ class ExtendedPackageSearch: make_dependencies=result.get("MakeDepends", []), check_dependencies=result.get("CheckDepends", []), provides=result.get("Provides", []), - git_url= - f"https://aur.archlinux.org/{result['PackageBase']}.git", - pacman=self._pacman) + git_url=f"https://aur.archlinux.org/{result['PackageBase']}.git", + pacman=self._pacman, + ) self._package_info_cache[package] = info @@ -386,8 +392,7 @@ class ExtendedPackageSearch: f"Failed to fetch package information for {package} from AUR RPC." ) from e - def find_provider( - self, stripped_dependency: str) -> typing.Optional[PackageInfo]: + def find_provider(self, stripped_dependency: str) -> typing.Optional[PackageInfo]: """ Finds a provider for a dependency. @@ -425,10 +430,13 @@ class ExtendedPackageSearch: return pkg if len(user_pkg_results) > 1: - return self._choose_provider(stripped_dependency, user_pkg_results, - "user packages") + return self._choose_provider( + stripped_dependency, user_pkg_results, "user packages" + ) - url = f"https://aur.archlinux.org/rpc/v5/search/{stripped_dependency}?by=provides" + url = ( + f"https://aur.archlinux.org/rpc/v5/search/{stripped_dependency}?by=provides" + ) l.print_debug( f"Requesting providers for '{stripped_dependency}' from AUR. URL = {url}" ) @@ -437,8 +445,7 @@ class ExtendedPackageSearch: d = request.json() if d["type"] == "error": - raise err.UserFacingError( - f"AUR RPC returned error: {d['error']}") + raise err.UserFacingError(f"AUR RPC returned error: {d['error']}") if d["resultcount"] == 0: l.print_debug(f"'{stripped_dependency}' not found.") @@ -461,8 +468,9 @@ class ExtendedPackageSearch: f"Failed to search for {stripped_dependency} from AUR RPC." ) from e - def _choose_provider(self, dep: str, possible_providers: list[str], - where: str) -> typing.Optional[PackageInfo]: + def _choose_provider( + self, dep: str, possible_providers: list[str], where: str + ) -> typing.Optional[PackageInfo]: min_selection = 1 max_selection = len(possible_providers) l.print_summary( @@ -478,7 +486,8 @@ class ExtendedPackageSearch: f"Select a provider [{min_selection}-{max_selection}] (default: {min_selection}): ", min_selection, max_selection, - default=min_selection) + default=min_selection, + ) info = self.get_package_info(possible_providers[selection - 1]) if info is not None: @@ -541,16 +550,17 @@ class ForeignPackageManager: Class for dealing with foreign packages. """ - def __init__(self, store: l.Store, pacman: l.Pacman, - search: ExtendedPackageSearch): + def __init__(self, store: l.Store, pacman: l.Pacman, search: ExtendedPackageSearch): self._store = store self._pacman = pacman self._search = search - def upgrade(self, - upgrade_devel: bool = False, - force: bool = False, - ignored_pkgs: typing.Optional[set[str]] = None): + def upgrade( + self, + upgrade_devel: bool = False, + force: bool = False, + ignored_pkgs: typing.Optional[set[str]] = None, + ): """ Upgrades all foreign packages. """ @@ -561,11 +571,9 @@ class ForeignPackageManager: all_foreign_pkgs = self._pacman.get_versioned_foreign_packages() all_explicit_pkgs = set(self._pacman.get_installed()) - l.print_debug( - f"Foreign packages to check for upgrades: {all_foreign_pkgs}") + l.print_debug(f"Foreign packages to check for upgrades: {all_foreign_pkgs}") - self._search.try_caching_packages( - list(map(lambda p: p[0], all_foreign_pkgs))) + self._search.try_caching_packages(list(map(lambda p: p[0], all_foreign_pkgs))) as_explicit = [] as_deps = [] @@ -579,8 +587,7 @@ class ForeignPackageManager: f"Failed to find '{pkg}' from AUR or user provided packages." ) - if self.should_upgrade_package(pkg, ver, info.version, - upgrade_devel): + if self.should_upgrade_package(pkg, ver, info.version, upgrade_devel): if pkg in all_explicit_pkgs: as_explicit.append(pkg) else: @@ -592,10 +599,12 @@ class ForeignPackageManager: self.install(as_explicit, as_deps, force) - def install(self, - foreign_pkgs: list[str], - foreign_dep_pkgs: typing.Optional[list[str]] = None, - force: bool = False): + def install( + self, + foreign_pkgs: list[str], + foreign_dep_pkgs: typing.Optional[list[str]] = None, + force: bool = False, + ): """ Installs the given foreign packages and their dependencies (both pacman/AUR). """ @@ -607,39 +616,44 @@ class ForeignPackageManager: return resolved_dependencies = self.resolve_dependencies( - foreign_pkgs, foreign_dep_pkgs) + foreign_pkgs, foreign_dep_pkgs + ) l.print_list( "The following foreign packages will be installed explicitly:", list(resolved_dependencies.foreign_pkgs), - level=l.SUMMARY) + level=l.SUMMARY, + ) l.print_list( "The following foreign packages will be installed as dependencies:", list(resolved_dependencies.foreign_dep_pkgs), - level=l.SUMMARY) + level=l.SUMMARY, + ) l.print_list( "The following foreign packages will be built in order to install other packages. They will not be installed:", list(resolved_dependencies.foreign_build_dep_pkgs), - level=l.SUMMARY) + level=l.SUMMARY, + ) if not l.prompt_confirm("Proceed?", default=True): raise err.UserFacingError("Installing aborted.") l.print_summary("Installing foreign package dependencies from pacman.") - self._pacman.install_dependencies( - list(resolved_dependencies.pacman_deps)) + self._pacman.install_dependencies(list(resolved_dependencies.pacman_deps)) try: - with PackageBuilder(self._search, self._store, - resolved_dependencies) as builder: + with PackageBuilder( + self._search, self._store, resolved_dependencies + ) as builder: while resolved_dependencies.build_order: to_build = resolved_dependencies.build_order.pop(0) pkgbase = resolved_dependencies.get_pkgbase(to_build) package_names = resolved_dependencies.get_pkgs_with_common_pkgbase( - to_build) + to_build + ) packages = [ resolved_dependencies.packages[pkgname] @@ -663,16 +677,17 @@ class ForeignPackageManager: if package_files_to_install or force: l.print_summary("Installing foreign packages.") - self._pacman.install_files(package_files_to_install, - as_explicit=list( - resolved_dependencies.foreign_pkgs)) + self._pacman.install_files( + package_files_to_install, + as_explicit=list(resolved_dependencies.foreign_pkgs), + ) else: l.print_summary("No packages to install.") def resolve_dependencies( self, foreign_pkgs: list[str], - foreign_dep_pkgs: typing.Optional[list[str]] = None + foreign_dep_pkgs: typing.Optional[list[str]] = None, ) -> ResolvedDependencies: """ Resolves foreign dependencies of foreign packages. @@ -690,7 +705,7 @@ class ForeignPackageManager: graph = DepGraph() - for name in (foreign_pkgs + foreign_dep_pkgs): + for name in foreign_pkgs + foreign_dep_pkgs: graph.add_requirement(name, None) seen_packages = set(foreign_pkgs + foreign_dep_pkgs) @@ -709,8 +724,7 @@ class ForeignPackageManager: add_to.add(dep_info.pkgname) - l.print_debug( - f"Adding dependency {dep_info.pkgname} to package {pkgname}.") + l.print_debug(f"Adding dependency {dep_info.pkgname} to package {pkgname}.") graph.add_requirement(dep_info.pkgname, pkgname) if dep_info.pkgname not in seen_packages: to_process.append(dep_info.pkgname) @@ -728,10 +742,14 @@ class ForeignPackageManager: result.pacman_deps.update(info.pacman_dependencies) result.add_pkgbase_info(pkgname, info.pkgbase) - build_deps = info.foreign_make_dependencies_stripped + info.foreign_check_dependencies_stripped + build_deps = ( + info.foreign_make_dependencies_stripped + + info.foreign_check_dependencies_stripped + ) self._search.try_caching_packages( - info.foreign_dependencies_stripped + build_deps) + info.foreign_dependencies_stripped + build_deps + ) for depname in info.foreign_dependencies_stripped: process_dep(pkgname, depname, result.foreign_dep_pkgs) @@ -758,26 +776,29 @@ class ForeignPackageManager: return result - def should_upgrade_package(self, - package: str, - installed_version: str, - fetched_version: str, - upgrade_devel=False) -> bool: + def should_upgrade_package( + self, + package: str, + installed_version: str, + fetched_version: str, + upgrade_devel=False, + ) -> bool: """ Returns True if a package should be upgraded. """ if upgrade_devel and is_devel(package): - l.print_debug( - f"Package {package} is devel package. It should be upgraded.") + l.print_debug(f"Package {package} is devel package. It should be upgraded.") return True try: result = int( - subprocess.run(conf.commands.compare_versions( - installed_version, fetched_version), - check=True, - stdout=subprocess.PIPE).stdout.decode()) + subprocess.run( + conf.commands.compare_versions(installed_version, fetched_version), + check=True, + stdout=subprocess.PIPE, + ).stdout.decode() + ) should_upgrade = result < 0 l.print_debug( f"Installed version is: {installed_version}. Available version is {fetched_version}. Should upgrade: {should_upgrade}" @@ -786,7 +807,8 @@ class ForeignPackageManager: except (ValueError, subprocess.CalledProcessError) as error: l.print_error(f"{error}") raise err.UserFacingError( - "Failed to compare versions using vercmp.") from error + "Failed to compare versions using vercmp." + ) from error class PackageBuilder: @@ -796,8 +818,12 @@ class PackageBuilder: always_included_packages = ["base-devel", "git"] - def __init__(self, search: ExtendedPackageSearch, store: l.Store, - resolved_deps: ResolvedDependencies): + def __init__( + self, + search: ExtendedPackageSearch, + store: l.Store, + resolved_deps: ResolvedDependencies, + ): self._search = search self._store = store self._resolved_deps = resolved_deps @@ -850,7 +876,8 @@ class PackageBuilder: os.chdir(pkgbuild_dir) git_url_info = self._search.get_package_info( - self._resolved_deps.get_some_pkgname(pkgbase)) + self._resolved_deps.get_some_pkgname(pkgbase) + ) # Because all dependencies and packages should be resolved during the creation # of ResolvedDependencies. git_url should not be None. @@ -869,16 +896,16 @@ class PackageBuilder: mkarchroot_env_vars = os.environ.copy() try: del mkarchroot_env_vars["GNUPGHOME"] - l.print_debug( - "Removed GNUPGHOME variable from mkarchroot environment.") + l.print_debug("Removed GNUPGHOME variable from mkarchroot environment.") except KeyError: pass - subprocess.run(conf.commands.make_chroot(self.chroot_dir, - list(self._pkgs_in_chroot)), - env=mkarchroot_env_vars, - check=True, - capture_output=conf.suppress_command_output) + subprocess.run( + conf.commands.make_chroot(self.chroot_dir, list(self._pkgs_in_chroot)), + env=mkarchroot_env_vars, + check=True, + capture_output=conf.suppress_command_output, + ) def remove_build_environment(self): """ @@ -886,8 +913,9 @@ class PackageBuilder: """ shutil.rmtree(conf.build_dir) - def build_packages(self, package_base: str, packages: list[ForeignPackage], - force: bool): + def build_packages( + self, package_base: str, packages: list[ForeignPackage], force: bool + ): """ Builds package(s) with the same package base. @@ -906,8 +934,7 @@ class PackageBuilder: l.print_info(f"Building '{' '.join(package_names)}'.") - chroot_new_pacman_pkgs, chroot_pkg_files = self._get_chroot_packages( - packages) + chroot_new_pacman_pkgs, chroot_pkg_files = self._get_chroot_packages(packages) pkgbuild_dir = self.pkgbase_dir_map[package_base] os.chdir(pkgbuild_dir) @@ -918,19 +945,24 @@ class PackageBuilder: l.print_info("Installing build dependencies to chroot.") - subprocess.run(conf.commands.install_chroot_packages( - self.chroot_dir, - chroot_new_pacman_pkgs + PackageBuilder.always_included_packages), - check=True, - capture_output=conf.suppress_command_output) + subprocess.run( + conf.commands.install_chroot_packages( + self.chroot_dir, + chroot_new_pacman_pkgs + PackageBuilder.always_included_packages, + ), + check=True, + capture_output=conf.suppress_command_output, + ) l.print_info("Making package.") - subprocess.run(conf.commands.make_chroot_pkg(self.chroot_wd_dir, - conf.makepkg_user, - chroot_pkg_files), - check=True, - capture_output=conf.quiet_output) + subprocess.run( + conf.commands.make_chroot_pkg( + self.chroot_wd_dir, conf.makepkg_user, chroot_pkg_files + ), + check=True, + capture_output=conf.quiet_output, + ) for pkgname in package_names: file = self._find_pkgfile(pkgname, pkgbuild_dir) @@ -958,10 +990,11 @@ class PackageBuilder: for p in chroot_new_pacman_pkgs: if p not in self._pkgs_in_chroot: to_remove.append(strip_dependency(p)) - subprocess.run(conf.commands.remove_chroot_packages( - self.chroot_dir, to_remove), - check=True, - capture_output=conf.suppress_command_output) + subprocess.run( + conf.commands.remove_chroot_packages(self.chroot_dir, to_remove), + check=True, + capture_output=conf.suppress_command_output, + ) l.print_info(f"Finished building: '{' '.join(package_names)}'.") @@ -984,7 +1017,7 @@ class PackageBuilder: return True def _get_chroot_packages( - self, pkgs_to_build: list[ForeignPackage] + self, pkgs_to_build: list[ForeignPackage] ) -> tuple[list[str], list[str]]: """ Returns a tuple of pacman build dependencies and built foreign pkgs files that are needed @@ -1030,8 +1063,10 @@ class PackageBuilder: for foreign_pkg in chroot_foreign_pkgs: entry = self._store.get_package(foreign_pkg) - assert entry is not None, "Build order determines that the dependencies are built \ + assert entry is not None, ( + "Build order determines that the dependencies are built \ before and thus are found in the cache." + ) _, file = entry @@ -1070,39 +1105,55 @@ before and thus are found in the cache." The user is prompted to review the PKGBUILD and confirm if the package should be built. """ try: - subprocess.run(conf.commands.git_clone(git_url, "."), - check=True, - capture_output=conf.suppress_command_output) + subprocess.run( + conf.commands.git_clone(git_url, "."), + check=True, + capture_output=conf.suppress_command_output, + ) - if l.prompt_confirm(f"Review PKGBUILD or show diff for {pkgbase}?", - default=True): - latest_reviewed_commit = self._store.pkgbuild_latest_reviewed_commits.get( - pkgbase) + if l.prompt_confirm( + f"Review PKGBUILD or show diff for {pkgbase}?", default=True + ): + latest_reviewed_commit = ( + self._store.pkgbuild_latest_reviewed_commits.get(pkgbase) + ) - git_commit_ids = subprocess.run( - conf.commands.git_log_commit_ids(), - check=True, - stdout=subprocess.PIPE, - ).stdout.decode().strip().split('\n') + git_commit_ids = ( + subprocess.run( + conf.commands.git_log_commit_ids(), + check=True, + stdout=subprocess.PIPE, + ) + .stdout.decode() + .strip() + .split("\n") + ) - if latest_reviewed_commit is None or latest_reviewed_commit not in git_commit_ids: + if ( + latest_reviewed_commit is None + or latest_reviewed_commit not in git_commit_ids + ): for file in os.scandir("."): if file.is_file() and not file.name.startswith("."): - subprocess.run(conf.commands.review_file( - file.path), - check=True) + subprocess.run( + conf.commands.review_file(file.path), check=True + ) else: subprocess.run( - conf.commands.git_diff(latest_reviewed_commit), - check=True) + conf.commands.git_diff(latest_reviewed_commit), check=True + ) if l.prompt_confirm("Build this package?", default=True): - commit_id = subprocess.run( - conf.commands.git_get_commit_id(), - check=True, - capture_output=True).stdout.decode().strip() - self._store.pkgbuild_latest_reviewed_commits[ - pkgbase] = commit_id + commit_id = ( + subprocess.run( + conf.commands.git_get_commit_id(), + check=True, + capture_output=True, + ) + .stdout.decode() + .strip() + ) + self._store.pkgbuild_latest_reviewed_commits[pkgbase] = commit_id else: raise err.UserFacingError("Building aborted.")