From 74eea36f34073114041f5d4d522ca0b7c393ea5e Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 5 Mar 2025 20:32:58 +0100 Subject: [PATCH] Move package installation and removal to PackageManager interface There's no need for these to be implemented by the Distribution interface as they don't need distribution specific knowledge so let's move them to the PackageManager interface instead. --- mkosi/__init__.py | 20 +++++++++++++++----- mkosi/distributions/__init__.py | 15 --------------- mkosi/distributions/arch.py | 17 ++--------------- mkosi/distributions/azure.py | 2 +- mkosi/distributions/centos.py | 12 ++---------- mkosi/distributions/custom.py | 12 ------------ mkosi/distributions/debian.py | 22 +++++----------------- mkosi/distributions/fedora.py | 12 ++---------- mkosi/distributions/mageia.py | 3 ++- mkosi/distributions/openmandriva.py | 3 ++- mkosi/distributions/opensuse.py | 27 ++------------------------- mkosi/installer/__init__.py | 14 ++++++++++++++ mkosi/installer/apt.py | 19 +++++++++++++++++++ mkosi/installer/dnf.py | 14 ++++++++++++++ mkosi/installer/pacman.py | 15 +++++++++++++++ mkosi/installer/zypper.py | 20 ++++++++++++++++++++ 16 files changed, 115 insertions(+), 112 deletions(-) diff --git a/mkosi/__init__.py b/mkosi/__init__.py index ecef22759..6ab11017a 100644 --- a/mkosi/__init__.py +++ b/mkosi/__init__.py @@ -239,7 +239,9 @@ def install_distribution(context: Context) -> None: return with complete_step(f"Installing extra packages for {context.config.distribution.pretty_name()}"): - context.config.distribution.install_packages(context, context.config.packages) + context.config.distribution.package_manager(context.config).install( + context, context.config.packages + ) else: if context.config.overlay or context.config.output_format.is_extension_image(): if context.config.packages: @@ -278,7 +280,9 @@ def install_distribution(context: Context) -> None: (context.root / "boot/loader/entries.srel").write_text("type1\n") if context.config.packages: - context.config.distribution.install_packages(context, context.config.packages) + context.config.distribution.package_manager(context.config).install( + context, context.config.packages + ) for f in ( "var/lib/systemd/random-seed", @@ -300,7 +304,9 @@ def install_build_packages(context: Context) -> None: complete_step(f"Installing build packages for {context.config.distribution.pretty_name()}"), setup_build_overlay(context), ): - context.config.distribution.install_packages(context, context.config.build_packages) + context.config.distribution.package_manager(context.config).install( + context, context.config.build_packages + ) def install_volatile_packages(context: Context) -> None: @@ -308,7 +314,9 @@ def install_volatile_packages(context: Context) -> None: return with complete_step(f"Installing volatile packages for {context.config.distribution.pretty_name()}"): - context.config.distribution.install_packages(context, context.config.volatile_packages) + context.config.distribution.package_manager(context.config).install( + context, context.config.volatile_packages + ) def remove_packages(context: Context) -> None: @@ -319,7 +327,9 @@ def remove_packages(context: Context) -> None: with complete_step(f"Removing {len(context.config.remove_packages)} packages…"): try: - context.config.distribution.remove_packages(context, context.config.remove_packages) + context.config.distribution.package_manager(context.config).remove( + context, context.config.remove_packages + ) except NotImplementedError: die(f"Removing packages is not supported for {context.config.distribution}") diff --git a/mkosi/distributions/__init__.py b/mkosi/distributions/__init__.py index 0800bafc0..381becc29 100644 --- a/mkosi/distributions/__init__.py +++ b/mkosi/distributions/__init__.py @@ -3,7 +3,6 @@ import enum import importlib import urllib.parse -from collections.abc import Sequence from pathlib import Path from typing import TYPE_CHECKING, Optional, cast @@ -43,14 +42,6 @@ class DistributionInstaller: def install(cls, context: "Context") -> None: raise NotImplementedError - @classmethod - def install_packages(cls, context: "Context", packages: Sequence[str]) -> None: - raise NotImplementedError - - @classmethod - def remove_packages(cls, context: "Context", packages: Sequence[str]) -> None: - raise NotImplementedError - @classmethod def filesystem(cls) -> str: return "ext4" @@ -136,12 +127,6 @@ class Distribution(StrEnum): def install(self, context: "Context") -> None: return self.installer().install(context) - def install_packages(self, context: "Context", packages: Sequence[str]) -> None: - return self.installer().install_packages(context, packages) - - def remove_packages(self, context: "Context", packages: Sequence[str]) -> None: - return self.installer().remove_packages(context, packages) - def filesystem(self) -> str: return self.installer().filesystem() diff --git a/mkosi/distributions/arch.py b/mkosi/distributions/arch.py index 382742c25..65910801e 100644 --- a/mkosi/distributions/arch.py +++ b/mkosi/distributions/arch.py @@ -1,7 +1,7 @@ # SPDX-License-Identifier: LGPL-2.1-or-later import tempfile -from collections.abc import Iterable, Sequence +from collections.abc import Iterable from pathlib import Path from mkosi.archive import extract_tar @@ -62,20 +62,7 @@ class Installer(DistributionInstaller): @classmethod def install(cls, context: Context) -> None: - cls.install_packages(context, ["filesystem"], apivfs=False) - - @classmethod - def install_packages(cls, context: Context, packages: Sequence[str], apivfs: bool = True) -> None: - Pacman.invoke( - context, - "--sync", - ["--needed", "--assume-installed", "initramfs", *packages], - apivfs=apivfs, - ) - - @classmethod - def remove_packages(cls, context: Context, packages: Sequence[str]) -> None: - Pacman.invoke(context, "--remove", ["--nosave", "--recursive", *packages], apivfs=True) + Pacman.install(context, ["filesystem"], apivfs=False) @classmethod def repositories(cls, context: Context) -> Iterable[PacmanRepository]: diff --git a/mkosi/distributions/azure.py b/mkosi/distributions/azure.py index 84df21e1c..f66d8a450 100644 --- a/mkosi/distributions/azure.py +++ b/mkosi/distributions/azure.py @@ -33,7 +33,7 @@ class Installer(fedora.Installer): @classmethod def install(cls, context: Context) -> None: - cls.install_packages(context, ["filesystem", "azurelinux-release"], apivfs=False) + Dnf.install(context, ["filesystem", "azurelinux-release"], apivfs=False) @classmethod def repositories(cls, context: Context) -> Iterable[RpmRepository]: diff --git a/mkosi/distributions/centos.py b/mkosi/distributions/centos.py index c57b8945c..29f03c15c 100644 --- a/mkosi/distributions/centos.py +++ b/mkosi/distributions/centos.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: LGPL-2.1-or-later -from collections.abc import Iterable, Sequence +from collections.abc import Iterable from mkosi.config import Architecture, Config from mkosi.context import Context @@ -78,15 +78,7 @@ class Installer(DistributionInstaller): @classmethod def install(cls, context: Context) -> None: - cls.install_packages(context, ["basesystem"], apivfs=False) - - @classmethod - def install_packages(cls, context: Context, packages: Sequence[str], apivfs: bool = True) -> None: - Dnf.invoke(context, "install", packages, apivfs=apivfs) - - @classmethod - def remove_packages(cls, context: Context, packages: Sequence[str]) -> None: - Dnf.invoke(context, "remove", packages, apivfs=True) + Dnf.install(context, ["basesystem"], apivfs=False) @classmethod def architecture(cls, arch: Architecture) -> str: diff --git a/mkosi/distributions/custom.py b/mkosi/distributions/custom.py index fd31f5c08..8c36cfdec 100644 --- a/mkosi/distributions/custom.py +++ b/mkosi/distributions/custom.py @@ -1,12 +1,10 @@ # SPDX-License-Identifier: LGPL-2.1-or-later -from collections.abc import Sequence from mkosi.config import Architecture, Config from mkosi.context import Context from mkosi.distributions import DistributionInstaller from mkosi.installer import PackageManager -from mkosi.log import die class Installer(DistributionInstaller): @@ -29,13 +27,3 @@ class Installer(DistributionInstaller): @classmethod def install(cls, context: Context) -> None: pass - - @classmethod - def install_packages(cls, context: Context, packages: Sequence[str]) -> None: - if packages: - die("Installing packages is not supported for custom distributions'") - - @classmethod - def remove_packages(cls, context: Context, packages: Sequence[str]) -> None: - if packages: - die("Removing packages is not supported for custom distributions") diff --git a/mkosi/distributions/debian.py b/mkosi/distributions/debian.py index b6730d3f6..dfa4413eb 100644 --- a/mkosi/distributions/debian.py +++ b/mkosi/distributions/debian.py @@ -1,7 +1,8 @@ # SPDX-License-Identifier: LGPL-2.1-or-later +import itertools import tempfile -from collections.abc import Iterable, Sequence +from collections.abc import Iterable from pathlib import Path from mkosi.archive import extract_tar @@ -192,25 +193,12 @@ class Installer(DistributionInstaller): # Finally, run apt to properly install packages in the chroot without having to worry that maintainer # scripts won't find basic tools that they depend on. - cls.install_packages( - context, [Path(deb).name.partition("_")[0].removesuffix(".deb") for deb in essential] - ) + Apt.install(context, [Path(deb).name.partition("_")[0].removesuffix(".deb") for deb in essential]) fixup_os_release(context) - @classmethod - def install_packages(cls, context: Context, packages: Sequence[str], apivfs: bool = True) -> None: - Apt.invoke(context, "install", packages, apivfs=apivfs) - install_apt_sources(context, cls.repositories(context, local=False)) - - # systemd-gpt-auto-generator is disabled by default in Ubuntu: - # https://git.launchpad.net/ubuntu/+source/systemd/tree/debian/systemd.links?h=ubuntu/noble-proposed. - # Let's make sure it is enabled by default in our images. - (context.root / "etc/systemd/system-generators/systemd-gpt-auto-generator").unlink(missing_ok=True) - - @classmethod - def remove_packages(cls, context: Context, packages: Sequence[str]) -> None: - Apt.invoke(context, "purge", packages, apivfs=True) + if "apt" in itertools.chain(context.config.packages, context.config.volatile_packages): + install_apt_sources(context, cls.repositories(context, local=False)) @classmethod def architecture(cls, arch: Architecture) -> str: diff --git a/mkosi/distributions/fedora.py b/mkosi/distributions/fedora.py index 5e8af9c5f..88b94c9f7 100644 --- a/mkosi/distributions/fedora.py +++ b/mkosi/distributions/fedora.py @@ -3,7 +3,7 @@ import re import subprocess import tempfile -from collections.abc import Iterable, Sequence +from collections.abc import Iterable from pathlib import Path from mkosi.config import Architecture, Config @@ -129,15 +129,7 @@ class Installer(DistributionInstaller): @classmethod def install(cls, context: Context) -> None: - cls.install_packages(context, ["basesystem"], apivfs=False) - - @classmethod - def install_packages(cls, context: Context, packages: Sequence[str], apivfs: bool = True) -> None: - Dnf.invoke(context, "install", packages, apivfs=apivfs) - - @classmethod - def remove_packages(cls, context: Context, packages: Sequence[str]) -> None: - Dnf.invoke(context, "remove", packages, apivfs=True) + Dnf.install(context, ["basesystem"], apivfs=False) @classmethod def repositories(cls, context: Context) -> Iterable[RpmRepository]: diff --git a/mkosi/distributions/mageia.py b/mkosi/distributions/mageia.py index ce8317dc6..65224f69e 100644 --- a/mkosi/distributions/mageia.py +++ b/mkosi/distributions/mageia.py @@ -5,6 +5,7 @@ from collections.abc import Iterable from mkosi.config import Architecture from mkosi.context import Context from mkosi.distributions import fedora, join_mirror +from mkosi.installer.dnf import Dnf from mkosi.installer.rpm import RpmRepository, find_rpm_gpgkey from mkosi.log import die @@ -24,7 +25,7 @@ class Installer(fedora.Installer): @classmethod def install(cls, context: Context) -> None: - cls.install_packages(context, ["filesystem"], apivfs=False) + Dnf.install(context, ["filesystem"], apivfs=False) @classmethod def repositories(cls, context: Context) -> Iterable[RpmRepository]: diff --git a/mkosi/distributions/openmandriva.py b/mkosi/distributions/openmandriva.py index 1e0de8b54..2bfc2a225 100644 --- a/mkosi/distributions/openmandriva.py +++ b/mkosi/distributions/openmandriva.py @@ -5,6 +5,7 @@ from collections.abc import Iterable from mkosi.config import Architecture from mkosi.context import Context from mkosi.distributions import fedora, join_mirror +from mkosi.installer.dnf import Dnf from mkosi.installer.rpm import RpmRepository, find_rpm_gpgkey from mkosi.log import die @@ -24,7 +25,7 @@ class Installer(fedora.Installer): @classmethod def install(cls, context: Context) -> None: - cls.install_packages(context, ["filesystem"], apivfs=False) + Dnf.install(context, ["filesystem"], apivfs=False) @classmethod def repositories(cls, context: Context) -> Iterable[RpmRepository]: diff --git a/mkosi/distributions/opensuse.py b/mkosi/distributions/opensuse.py index 7ecc38020..ff9360c0c 100644 --- a/mkosi/distributions/opensuse.py +++ b/mkosi/distributions/opensuse.py @@ -1,7 +1,7 @@ # SPDX-License-Identifier: LGPL-2.1-or-later import tempfile -from collections.abc import Iterable, Sequence +from collections.abc import Iterable from pathlib import Path from xml.etree import ElementTree @@ -58,30 +58,7 @@ class Installer(DistributionInstaller): @classmethod def install(cls, context: Context) -> None: - cls.install_packages(context, ["filesystem"], apivfs=False) - - @classmethod - def install_packages(cls, context: Context, packages: Sequence[str], apivfs: bool = True) -> None: - if context.config.find_binary("zypper"): - Zypper.invoke( - context, - "install", - [ - "--download", "in-advance", - "--recommends" if context.config.with_recommends else "--no-recommends", - *packages, - ], - apivfs=apivfs, - ) # fmt: skip - else: - Dnf.invoke(context, "install", packages, apivfs=apivfs) - - @classmethod - def remove_packages(cls, context: Context, packages: Sequence[str]) -> None: - if context.config.find_binary("zypper"): - Zypper.invoke(context, "remove", ["--clean-deps", *packages], apivfs=True) - else: - Dnf.invoke(context, "remove", packages, apivfs=True) + cls.package_manager(context.config).install(context, ["filesystem"], apivfs=False) @classmethod def repositories(cls, context: Context) -> Iterable[RpmRepository]: diff --git a/mkosi/installer/__init__.py b/mkosi/installer/__init__.py index b92773966..5a49da680 100644 --- a/mkosi/installer/__init__.py +++ b/mkosi/installer/__init__.py @@ -145,6 +145,20 @@ class PackageManager: ], ) # fmt: skip + @classmethod + def install( + cls, + context: Context, + packages: Sequence[str], + *, + apivfs: bool = True, + ) -> None: + pass + + @classmethod + def remove(cls, context: Context, packages: Sequence[str]) -> None: + pass + @classmethod def sync(cls, context: Context, force: bool) -> None: pass diff --git a/mkosi/installer/apt.py b/mkosi/installer/apt.py index ec89a4d82..d53bb83bf 100644 --- a/mkosi/installer/apt.py +++ b/mkosi/installer/apt.py @@ -229,6 +229,25 @@ class Apt(PackageManager): stdout=stdout, ) + @classmethod + def install( + cls, + context: Context, + packages: Sequence[str], + *, + apivfs: bool = True, + ) -> None: + cls.invoke(context, "install", packages, apivfs=apivfs) + + # systemd-gpt-auto-generator is disabled by default in Ubuntu: + # https://git.launchpad.net/ubuntu/+source/systemd/tree/debian/systemd.links?h=ubuntu/noble-proposed. + # Let's make sure it is enabled by default in our images. + (context.root / "etc/systemd/system-generators/systemd-gpt-auto-generator").unlink(missing_ok=True) + + @classmethod + def remove(cls, context: Context, packages: Sequence[str]) -> None: + cls.invoke(context, "purge", packages, apivfs=True) + @classmethod def sync(cls, context: Context, force: bool) -> None: cls.invoke(context, "update") diff --git a/mkosi/installer/dnf.py b/mkosi/installer/dnf.py index 13b97edff..dec412932 100644 --- a/mkosi/installer/dnf.py +++ b/mkosi/installer/dnf.py @@ -228,6 +228,20 @@ class Dnf(PackageManager): if any(p.name.startswith(prefix) for prefix in ("dnf", "hawkey", "yum")): p.unlink() + @classmethod + def install( + cls, + context: Context, + packages: Sequence[str], + *, + apivfs: bool = True, + ) -> None: + cls.invoke(context, "install", packages, apivfs=apivfs) + + @classmethod + def remove(cls, context: Context, packages: Sequence[str]) -> None: + cls.invoke(context, "remove", packages, apivfs=True) + @classmethod def sync(cls, context: Context, force: bool, arguments: Sequence[str] = ()) -> None: cls.invoke( diff --git a/mkosi/installer/pacman.py b/mkosi/installer/pacman.py index 115c5b0cb..a11589b52 100644 --- a/mkosi/installer/pacman.py +++ b/mkosi/installer/pacman.py @@ -180,6 +180,21 @@ class Pacman(PackageManager): stdout=stdout, ) + @classmethod + def install( + cls, + context: Context, + packages: Sequence[str], + *, + apivfs: bool = True, + ) -> None: + arguments = ["--needed", "--assume-installed", "initramfs", *packages] + cls.invoke(context, "--sync", arguments, apivfs=apivfs) + + @classmethod + def remove(cls, context: Context, packages: Sequence[str]) -> None: + cls.invoke(context, "--remove", ["--nosave", "--recursive", *packages], apivfs=True) + @classmethod def keyring(cls, context: Context) -> None: def sandbox() -> AbstractContextManager[list[PathString]]: diff --git a/mkosi/installer/zypper.py b/mkosi/installer/zypper.py index b39377e28..8d78fc1fc 100644 --- a/mkosi/installer/zypper.py +++ b/mkosi/installer/zypper.py @@ -132,6 +132,26 @@ class Zypper(PackageManager): stdout=stdout, ) + @classmethod + def install( + cls, + context: Context, + packages: Sequence[str], + *, + apivfs: bool = True, + ) -> None: + arguments = [ + "--download", "in-advance", + "--recommends" if context.config.with_recommends else "--no-recommends", + *packages, + ] # fmt: skip + + cls.invoke(context, "install", arguments, apivfs=apivfs) + + @classmethod + def remove(cls, context: Context, packages: Sequence[str]) -> None: + cls.invoke(context, "remove", ["--clean-deps", *packages], apivfs=True) + @classmethod def sync(cls, context: Context, force: bool, arguments: Sequence[str] = ()) -> None: cls.invoke(context, "refresh", [*(["--force"] if force else []), *arguments]) -- 2.47.2