From: Daan De Meyer Date: Fri, 17 Oct 2025 07:36:46 +0000 (+0200) Subject: Simplify implementation of distribution installers X-Git-Tag: v26~72^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=92da7305e6480643e0c9a119cac7ef79ef878c61;p=thirdparty%2Fmkosi.git Simplify implementation of distribution installers - Use __init_subclass__() to automatically have distribution installers register themselves with the DistributionInstaller class on import - Remove duplicated methods from Distribution that only forward to the corresponding DistributionInstaller and instead have callers access the Installer class directly. Make it a property to make this less verbose --- diff --git a/mkosi/__init__.py b/mkosi/__init__.py index bbe56b1ae..f73b99c7f 100644 --- a/mkosi/__init__.py +++ b/mkosi/__init__.py @@ -265,8 +265,10 @@ def install_distribution(context: Context) -> None: if not packages: return - with complete_step(f"Installing extra packages for {context.config.distribution.pretty_name()}"): - context.config.distribution.package_manager(context.config).install(context, packages) + with complete_step( + f"Installing extra packages for {context.config.distribution.installer.pretty_name()}" + ): + context.config.distribution.installer.package_manager(context.config).install(context, packages) else: if context.config.overlay or context.config.output_format.is_extension_image(): if packages: @@ -276,8 +278,8 @@ def install_distribution(context: Context) -> None: ) return - with complete_step(f"Installing {context.config.distribution.pretty_name()}"): - context.config.distribution.install(context) + with complete_step(f"Installing {context.config.distribution.installer.pretty_name()}"): + context.config.distribution.installer.install(context) if context.config.machine_id: with umask(~0o755): @@ -305,7 +307,9 @@ def install_distribution(context: Context) -> None: (context.root / "boot/loader/entries.srel").write_text("type1\n") if packages: - context.config.distribution.package_manager(context.config).install(context, packages) + context.config.distribution.installer.package_manager(context.config).install( + context, packages + ) for f in ( "var/lib/systemd/random-seed", @@ -324,10 +328,12 @@ def install_build_packages(context: Context) -> None: return with ( - complete_step(f"Installing build packages for {context.config.distribution.pretty_name()}"), + complete_step( + f"Installing build packages for {context.config.distribution.installer.pretty_name()}" + ), setup_build_overlay(context), ): - context.config.distribution.package_manager(context.config).install( + context.config.distribution.installer.package_manager(context.config).install( context, context.config.build_packages ) @@ -336,8 +342,10 @@ def install_volatile_packages(context: Context) -> None: if not context.config.volatile_packages: return - with complete_step(f"Installing volatile packages for {context.config.distribution.pretty_name()}"): - context.config.distribution.package_manager(context.config).install( + with complete_step( + f"Installing volatile packages for {context.config.distribution.installer.pretty_name()}" + ): + context.config.distribution.installer.package_manager(context.config).install( context, context.config.volatile_packages, allow_downgrade=True ) @@ -350,7 +358,7 @@ def remove_packages(context: Context) -> None: with complete_step(f"Removing {len(context.config.remove_packages)} packages…"): try: - context.config.distribution.package_manager(context.config).remove( + context.config.distribution.installer.package_manager(context.config).remove( context, context.config.remove_packages ) except NotImplementedError: @@ -610,7 +618,7 @@ def run_configure_scripts(config: Config) -> Config: RELEASE=config.release, ARCHITECTURE=str(config.architecture), QEMU_ARCHITECTURE=config.architecture.to_qemu(), - DISTRIBUTION_ARCHITECTURE=config.distribution.architecture(config.architecture), + DISTRIBUTION_ARCHITECTURE=config.distribution.installer.architecture(config.architecture), SRCDIR="/work/src", MKOSI_UID=str(os.getuid()), MKOSI_GID=str(os.getgid()), @@ -658,7 +666,7 @@ def run_sync_scripts(config: Config) -> None: DISTRIBUTION=str(config.distribution), RELEASE=config.release, ARCHITECTURE=str(config.architecture), - DISTRIBUTION_ARCHITECTURE=config.distribution.architecture(config.architecture), + DISTRIBUTION_ARCHITECTURE=config.distribution.installer.architecture(config.architecture), SRCDIR="/work/src", MKOSI_UID=str(os.getuid()), MKOSI_GID=str(os.getgid()), @@ -737,7 +745,7 @@ def script_maybe_chroot_sandbox( *(["--ro-bind-try", "/etc/resolv.conf", "/etc/resolv.conf"] if network else []), *(["--suppress-chown"] if suppress_chown else []), ], - **context.config.distribution.package_manager(context.config).scripts(context), + **context.config.distribution.installer.package_manager(context.config).scripts(context), } # fmt: skip with finalize_host_scripts(context, helpers) as hd: @@ -747,7 +755,7 @@ def script_maybe_chroot_sandbox( options=[ *options, *context.rootoptions(), - *context.config.distribution.package_manager(context.config).mounts(context), + *context.config.distribution.installer.package_manager(context.config).mounts(context), ], scripts=hd, ) as sandbox: # fmt: skip @@ -774,7 +782,9 @@ def run_prepare_scripts(context: Context, build: bool) -> None: DISTRIBUTION=str(context.config.distribution), RELEASE=context.config.release, ARCHITECTURE=str(context.config.architecture), - DISTRIBUTION_ARCHITECTURE=context.config.distribution.architecture(context.config.architecture), + DISTRIBUTION_ARCHITECTURE=context.config.distribution.installer.architecture( + context.config.architecture + ), BUILDROOT="/buildroot", SRCDIR="/work/src", CHROOT_SRCDIR="/work/src", @@ -845,7 +855,9 @@ def run_build_scripts(context: Context) -> None: DISTRIBUTION=str(context.config.distribution), RELEASE=context.config.release, ARCHITECTURE=str(context.config.architecture), - DISTRIBUTION_ARCHITECTURE=context.config.distribution.architecture(context.config.architecture), + DISTRIBUTION_ARCHITECTURE=context.config.distribution.installer.architecture( + context.config.architecture + ), BUILDROOT="/buildroot", DESTDIR="/work/dest", CHROOT_DESTDIR="/work/dest", @@ -923,7 +935,9 @@ def run_postinst_scripts(context: Context) -> None: DISTRIBUTION=str(context.config.distribution), RELEASE=context.config.release, ARCHITECTURE=str(context.config.architecture), - DISTRIBUTION_ARCHITECTURE=context.config.distribution.architecture(context.config.architecture), + DISTRIBUTION_ARCHITECTURE=context.config.distribution.installer.architecture( + context.config.architecture + ), BUILDROOT="/buildroot", OUTPUTDIR="/work/out", CHROOT_OUTPUTDIR="/work/out", @@ -995,7 +1009,9 @@ def run_finalize_scripts(context: Context) -> None: DISTRIBUTION=str(context.config.distribution), RELEASE=context.config.release, ARCHITECTURE=str(context.config.architecture), - DISTRIBUTION_ARCHITECTURE=context.config.distribution.architecture(context.config.architecture), + DISTRIBUTION_ARCHITECTURE=context.config.distribution.installer.architecture( + context.config.architecture + ), BUILDROOT="/buildroot", OUTPUTDIR="/work/out", CHROOT_OUTPUTDIR="/work/out", @@ -1067,7 +1083,9 @@ def run_postoutput_scripts(context: Context) -> None: DISTRIBUTION=str(context.config.distribution), RELEASE=context.config.release, ARCHITECTURE=str(context.config.architecture), - DISTRIBUTION_ARCHITECTURE=context.config.distribution.architecture(context.config.architecture), + DISTRIBUTION_ARCHITECTURE=context.config.distribution.installer.architecture( + context.config.architecture + ), SRCDIR="/work/src", OUTPUTDIR="/work/out", MKOSI_UID=str(os.getuid()), @@ -1264,7 +1282,9 @@ def install_package_directories(context: Context, directories: Sequence[Path]) - for d in directories: for p in itertools.chain.from_iterable( d.glob(glob) - for glob in context.config.distribution.package_manager(context.config).package_globs() + for glob in context.config.distribution.installer.package_manager( + context.config + ).package_globs() ): shutil.copy(p, context.repository, follow_symlinks=True) @@ -3471,7 +3491,7 @@ def make_disk( f"""\ [Partition] Type=root - Format={context.config.distribution.filesystem()} + Format={context.config.distribution.installer.filesystem()} CopyFiles=/ Minimize=guess """ @@ -3818,7 +3838,7 @@ def createrepo(context: Context) -> Iterator[None]: finally: if context.repository.stat().st_mtime_ns != st.st_mtime_ns: with complete_step("Rebuilding local package repository"): - context.config.distribution.createrepo(context) + context.config.distribution.installer.package_manager(context.config).createrepo(context) def make_rootdir(context: Context) -> None: @@ -3858,7 +3878,7 @@ def build_image(context: Context) -> None: or context.config.finalize_scripts ) - context.config.distribution.setup(context) + context.config.distribution.installer.setup(context) if wantrepo: with createrepo(context): install_package_directories(context, context.config.package_directories) @@ -4082,7 +4102,7 @@ def run_box(args: Args, config: Config) -> None: def run_latest_snapshot(args: Args, config: Config) -> None: - print(config.distribution.latest_snapshot(config)) + print(config.distribution.installer.latest_snapshot(config)) def run_shell(args: Args, config: Config) -> None: @@ -4465,7 +4485,7 @@ def run_clean_scripts(config: Config) -> None: DISTRIBUTION=str(config.distribution), RELEASE=config.release, ARCHITECTURE=str(config.architecture), - DISTRIBUTION_ARCHITECTURE=config.distribution.architecture(config.architecture), + DISTRIBUTION_ARCHITECTURE=config.distribution.installer.architecture(config.architecture), SRCDIR="/work/src", OUTPUTDIR="/work/out", MKOSI_UID=str(os.getuid()), @@ -4710,7 +4730,7 @@ def sync_repository_metadata( ) ) - subdir = last.distribution.package_manager(last).subdir(last) + subdir = last.distribution.installer.package_manager(last).subdir(last) for d in ("cache", "lib"): (metadata_dir / d / subdir).mkdir(parents=True, exist_ok=True) @@ -4732,12 +4752,12 @@ def sync_repository_metadata( context.root.mkdir(mode=0o755) install_sandbox_trees(context.config, context.sandbox_tree) - context.config.distribution.setup(context) + context.config.distribution.installer.setup(context) - context.config.distribution.keyring(context) + context.config.distribution.installer.keyring(context) with complete_step("Syncing package manager metadata"): - context.config.distribution.package_manager(context.config).sync( + context.config.distribution.installer.package_manager(context.config).sync( context, force=context.args.force > 1 or context.config.cacheonly == Cacheonly.never, ) @@ -4748,7 +4768,7 @@ def sync_repository_metadata( # We just synced package manager metadata, in the case of dnf, this means we can now iterate the # synced repository metadata directories and use that to create the corresponding directories in the # package cache directory. - for srcsubdir, _ in last.distribution.package_manager(last).package_subdirs(src): + for srcsubdir, _ in last.distribution.installer.package_manager(last).package_subdirs(src): (dst / srcsubdir).mkdir(parents=True, exist_ok=True) return keyring_dir, metadata_dir diff --git a/mkosi/bootloader.py b/mkosi/bootloader.py index 4ebf1d8f5..14403bbbc 100644 --- a/mkosi/bootloader.py +++ b/mkosi/bootloader.py @@ -187,7 +187,7 @@ def find_grub_binary(config: Config, binary: str) -> Optional[Path]: def prepare_grub_config(context: Context) -> Optional[Path]: - config = context.root / "efi" / context.config.distribution.grub_prefix() / "grub.cfg" + config = context.root / "efi" / context.config.distribution.installer.grub_prefix() / "grub.cfg" with umask(~0o700): config.parent.mkdir(exist_ok=True) @@ -212,7 +212,9 @@ def prepare_grub_config(context: Context) -> Optional[Path]: earlyconfig.parent.mkdir(parents=True, exist_ok=True) # Read the actual config file from the root of the ESP. - earlyconfig.write_text(f"configfile /{context.config.distribution.grub_prefix()}/grub.cfg\n") + earlyconfig.write_text( + f"configfile /{context.config.distribution.installer.grub_prefix()}/grub.cfg\n" + ) return config @@ -231,6 +233,8 @@ def grub_mkimage( directory = find_grub_directory(context, target=target) assert directory + prefix = context.config.distribution.installer.grub_prefix() + with ( complete_step(f"Generating grub image for {target}"), tempfile.NamedTemporaryFile("w", prefix="grub-early-config") as earlyconfig, @@ -238,8 +242,8 @@ def grub_mkimage( earlyconfig.write( textwrap.dedent( f"""\ - search --no-floppy --set=root --file /{context.config.distribution.grub_prefix()}/grub.cfg - set prefix=($root)/{context.config.distribution.grub_prefix()} + search --no-floppy --set=root --file /{prefix}/grub.cfg + set prefix=($root)/{prefix} """ ) ) @@ -251,7 +255,7 @@ def grub_mkimage( mkimage, "--directory", "/grub", "--config", workdir(Path(earlyconfig.name)), - "--prefix", f"/{context.config.distribution.grub_prefix()}", + "--prefix", f"/{prefix}", "--output", workdir(output) if output else "/grub/core.img", "--format", target, *(["--sbat", os.fspath(workdir(sbat))] if sbat else []), @@ -403,7 +407,7 @@ def install_grub(context: Context) -> None: if context.config.secure_boot: sign_efi_binary(context, output, output) - dst = context.root / "efi" / context.config.distribution.grub_prefix() / "fonts" + dst = context.root / "efi" / context.config.distribution.installer.grub_prefix() / "fonts" with umask(~0o700): dst.mkdir(parents=True, exist_ok=True) diff --git a/mkosi/config.py b/mkosi/config.py index 87b214fcb..c5d75f6aa 100644 --- a/mkosi/config.py +++ b/mkosi/config.py @@ -1033,19 +1033,19 @@ def config_default_release(namespace: dict[str, Any]) -> str: if namespace["distribution"] == hd and hr is not None: return hr - return cast(str, namespace["distribution"].default_release()) + return cast(str, namespace["distribution"].installer.default_release()) def config_default_tools_tree_distribution(namespace: dict[str, Any]) -> Distribution: if d := os.getenv("MKOSI_HOST_DISTRIBUTION"): - return Distribution(d).default_tools_tree_distribution() + return Distribution(d).installer.default_tools_tree_distribution() or Distribution(d) detected = detect_distribution()[0] if not detected: return Distribution.custom - return detected.default_tools_tree_distribution() + return detected.installer.default_tools_tree_distribution() or detected def config_default_repository_key_fetch(namespace: dict[str, Any]) -> bool: @@ -2356,7 +2356,7 @@ class Config: # running inside or outside of the mkosi box environment. To avoid these issues, don't cache the # package manager used in the tools tree cache manifest. **( - {"package_manager": self.distribution.package_manager(self).executable(self)} + {"package_manager": self.distribution.installer.package_manager(self).executable(self)} if self.image != "tools" else {} ), @@ -2367,7 +2367,10 @@ class Config: (p.name, p.stat().st_mtime_ns) for d in self.package_directories for p in sorted( - flatten(d.glob(glob) for glob in self.distribution.package_manager(self).package_globs()) + flatten( + d.glob(glob) + for glob in self.distribution.installer.package_manager(self).package_globs() + ) ) ], "repositories": sorted(self.repositories), @@ -3626,7 +3629,9 @@ SETTINGS: list[ConfigSetting[Any]] = [ parse=config_parse_string, match=config_make_string_matcher(), default_factory_depends=("tools_tree_distribution",), - default_factory=lambda ns: d.default_release() if (d := ns["tools_tree_distribution"]) else None, + default_factory=( + lambda ns: d.installer.default_release() if (d := ns["tools_tree_distribution"]) else None + ), help="Set the release to use for the default tools tree", scope=SettingScope.tools, ), @@ -4330,7 +4335,7 @@ SPECIFIERS = ( ), Specifier( char="F", - callback=lambda ns, config: ns["distribution"].filesystem(), + callback=lambda ns, config: ns["distribution"].installer.filesystem(), depends=("distribution",), ), Specifier( @@ -5232,7 +5237,7 @@ def want_default_initrd(config: Config) -> bool: return False if config.bootable == ConfigFeature.auto and not any( - config.distribution.is_kernel_package(p) + config.distribution.installer.is_kernel_package(p) for p in itertools.chain(config.packages, config.volatile_packages) ): return False diff --git a/mkosi/distributions/__init__.py b/mkosi/distributions/__init__.py index aae64480a..b0435ca50 100644 --- a/mkosi/distributions/__init__.py +++ b/mkosi/distributions/__init__.py @@ -4,7 +4,7 @@ import enum import importlib import urllib.parse from pathlib import Path -from typing import TYPE_CHECKING, Optional, cast +from typing import TYPE_CHECKING, Optional from mkosi.log import die from mkosi.util import StrEnum, read_env_file @@ -23,60 +23,6 @@ class PackageType(StrEnum): apk = enum.auto() -class DistributionInstaller: - @classmethod - def pretty_name(cls) -> str: - raise NotImplementedError - - @classmethod - def package_manager(cls, config: "Config") -> type["PackageManager"]: - raise NotImplementedError - - @classmethod - def keyring(cls, context: "Context") -> None: - pass - - @classmethod - def setup(cls, context: "Context") -> None: - raise NotImplementedError - - @classmethod - def install(cls, context: "Context") -> None: - raise NotImplementedError - - @classmethod - def filesystem(cls) -> str: - return "ext4" - - @classmethod - def architecture(cls, arch: "Architecture") -> str: - raise NotImplementedError - - @classmethod - def package_type(cls) -> PackageType: - return PackageType.none - - @classmethod - def default_release(cls) -> str: - return "" - - @classmethod - def default_tools_tree_distribution(cls) -> Optional["Distribution"]: - return None - - @classmethod - def grub_prefix(cls) -> str: - return "grub" - - @classmethod - def latest_snapshot(cls, config: "Config") -> str: - die(f"{cls.pretty_name()} does not support snapshots") - - @classmethod - def is_kernel_package(cls, package: str) -> bool: - return False - - class Distribution(StrEnum): # Please consult docs/distribution-policy.md and contact one # of the mkosi maintainers before implementing a new distribution. @@ -123,54 +69,69 @@ class Distribution(StrEnum): Distribution.alma, ) - def pretty_name(self) -> str: - return self.installer().pretty_name() + @property + def installer(self) -> type["DistributionInstaller"]: + importlib.import_module(f"mkosi.distributions.{self.name}") + return DistributionInstaller.registry[self] - def package_manager(self, config: "Config") -> type["PackageManager"]: - return self.installer().package_manager(config) - def keyring(self, context: "Context") -> None: - return self.installer().keyring(context) +class DistributionInstaller: + registry: dict[Distribution, "type[DistributionInstaller]"] = {} - def setup(self, context: "Context") -> None: - return self.installer().setup(context) + def __init_subclass__(cls, distribution: Distribution): + cls.registry[distribution] = cls - def install(self, context: "Context") -> None: - return self.installer().install(context) + @classmethod + def pretty_name(cls) -> str: + raise NotImplementedError - def filesystem(self) -> str: - return self.installer().filesystem() + @classmethod + def package_manager(cls, config: "Config") -> type["PackageManager"]: + raise NotImplementedError - def architecture(self, arch: "Architecture") -> str: - return self.installer().architecture(arch) + @classmethod + def keyring(cls, context: "Context") -> None: + pass - def package_type(self) -> PackageType: - return self.installer().package_type() + @classmethod + def setup(cls, context: "Context") -> None: + raise NotImplementedError - def default_release(self) -> str: - return self.installer().default_release() + @classmethod + def install(cls, context: "Context") -> None: + raise NotImplementedError - def default_tools_tree_distribution(self) -> "Distribution": - return self.installer().default_tools_tree_distribution() or self + @classmethod + def filesystem(cls) -> str: + return "ext4" - def grub_prefix(self) -> str: - return self.installer().grub_prefix() + @classmethod + def architecture(cls, arch: "Architecture") -> str: + raise NotImplementedError - def createrepo(self, context: "Context") -> None: - return self.installer().package_manager(context.config).createrepo(context) + @classmethod + def package_type(cls) -> PackageType: + return PackageType.none - def latest_snapshot(self, config: "Config") -> str: - return self.installer().latest_snapshot(config) + @classmethod + def default_release(cls) -> str: + return "" - def is_kernel_package(self, package: str) -> bool: - return self.installer().is_kernel_package(package) + @classmethod + def default_tools_tree_distribution(cls) -> Optional[Distribution]: + return None - def installer(self) -> type[DistributionInstaller]: - modname = str(self).replace("-", "_") - mod = importlib.import_module(f"mkosi.distributions.{modname}") - installer = getattr(mod, "Installer") - assert issubclass(installer, DistributionInstaller) - return cast(type[DistributionInstaller], installer) + @classmethod + def grub_prefix(cls) -> str: + return "grub" + + @classmethod + def latest_snapshot(cls, config: "Config") -> str: + die(f"{cls.pretty_name()} does not support snapshots") + + @classmethod + def is_kernel_package(cls, package: str) -> bool: + return False def detect_distribution(root: Path = Path("/")) -> tuple[Optional[Distribution], Optional[str]]: diff --git a/mkosi/distributions/alma.py b/mkosi/distributions/alma.py index 3920be776..30ba0796d 100644 --- a/mkosi/distributions/alma.py +++ b/mkosi/distributions/alma.py @@ -1,12 +1,12 @@ # SPDX-License-Identifier: LGPL-2.1-or-later from mkosi.context import Context -from mkosi.distributions import centos, join_mirror +from mkosi.distributions import Distribution, centos, join_mirror from mkosi.installer.rpm import RpmRepository, find_rpm_gpgkey from mkosi.log import die -class Installer(centos.Installer): +class Installer(centos.Installer, distribution=Distribution.alma): @classmethod def pretty_name(cls) -> str: return "AlmaLinux" diff --git a/mkosi/distributions/arch.py b/mkosi/distributions/arch.py index a331ffb0c..7fcc817ff 100644 --- a/mkosi/distributions/arch.py +++ b/mkosi/distributions/arch.py @@ -9,12 +9,12 @@ from mkosi.archive import extract_tar from mkosi.config import Architecture, Config from mkosi.context import Context from mkosi.curl import curl -from mkosi.distributions import DistributionInstaller, PackageType, join_mirror +from mkosi.distributions import Distribution, DistributionInstaller, PackageType, join_mirror from mkosi.installer.pacman import Pacman, PacmanRepository from mkosi.log import complete_step, die -class Installer(DistributionInstaller): +class Installer(DistributionInstaller, distribution=Distribution.arch): @classmethod def pretty_name(cls) -> str: return "Arch Linux" diff --git a/mkosi/distributions/azure.py b/mkosi/distributions/azure.py index 4049b85ec..b21420150 100644 --- a/mkosi/distributions/azure.py +++ b/mkosi/distributions/azure.py @@ -5,6 +5,7 @@ from collections.abc import Iterable from mkosi.config import Architecture from mkosi.context import Context from mkosi.distributions import ( + Distribution, fedora, join_mirror, ) @@ -13,7 +14,7 @@ from mkosi.installer.rpm import RpmRepository, find_rpm_gpgkey, setup_rpm from mkosi.log import die -class Installer(fedora.Installer): +class Installer(fedora.Installer, distribution=Distribution.azure): @classmethod def pretty_name(cls) -> str: return "Azure Linux" diff --git a/mkosi/distributions/centos.py b/mkosi/distributions/centos.py index 40b0ffc1a..c2827c07e 100644 --- a/mkosi/distributions/centos.py +++ b/mkosi/distributions/centos.py @@ -20,7 +20,7 @@ from mkosi.versioncomp import GenericVersion CENTOS_SIG_REPO_PRIORITY = 50 -class Installer(DistributionInstaller): +class Installer(DistributionInstaller, distribution=Distribution.centos): @classmethod def pretty_name(cls) -> str: return "CentOS" diff --git a/mkosi/distributions/custom.py b/mkosi/distributions/custom.py index 8680b6f80..8684f8503 100644 --- a/mkosi/distributions/custom.py +++ b/mkosi/distributions/custom.py @@ -2,11 +2,11 @@ from mkosi.config import Architecture, Config from mkosi.context import Context -from mkosi.distributions import DistributionInstaller +from mkosi.distributions import Distribution, DistributionInstaller from mkosi.installer import PackageManager -class Installer(DistributionInstaller): +class Installer(DistributionInstaller, distribution=Distribution.custom): @classmethod def pretty_name(cls) -> str: return "Custom" diff --git a/mkosi/distributions/debian.py b/mkosi/distributions/debian.py index 4eef0a23a..7520a91df 100644 --- a/mkosi/distributions/debian.py +++ b/mkosi/distributions/debian.py @@ -11,14 +11,14 @@ from mkosi.archive import extract_tar from mkosi.config import Architecture, Config from mkosi.context import Context from mkosi.curl import curl -from mkosi.distributions import DistributionInstaller, PackageType, join_mirror +from mkosi.distributions import Distribution, DistributionInstaller, PackageType, join_mirror from mkosi.installer.apt import Apt, AptRepository from mkosi.log import die from mkosi.run import run, workdir from mkosi.sandbox import umask -class Installer(DistributionInstaller): +class Installer(DistributionInstaller, distribution=Distribution.debian): @classmethod def pretty_name(cls) -> str: return "Debian" @@ -145,7 +145,9 @@ class Installer(DistributionInstaller): "sparc" : ["lib64"], "sparc64" : ["lib32", "lib64"], "x32" : ["lib32", "lib64", "libx32"], - }.get(context.config.distribution.architecture(context.config.architecture), []) # fmt: skip + }.get( + context.config.distribution.installer.architecture(context.config.architecture), [] + ) # fmt: skip with umask(~0o755): for d in subdirs: diff --git a/mkosi/distributions/fedora.py b/mkosi/distributions/fedora.py index ee690d4a6..ad7a34335 100644 --- a/mkosi/distributions/fedora.py +++ b/mkosi/distributions/fedora.py @@ -10,6 +10,7 @@ from mkosi.config import Architecture, Config from mkosi.context import Context from mkosi.curl import curl from mkosi.distributions import ( + Distribution, DistributionInstaller, PackageType, join_mirror, @@ -99,7 +100,7 @@ def find_fedora_rpm_gpgkeys(context: Context) -> Iterable[str]: i += 1 -class Installer(DistributionInstaller): +class Installer(DistributionInstaller, distribution=Distribution.fedora): @classmethod def pretty_name(cls) -> str: return "Fedora Linux" diff --git a/mkosi/distributions/kali.py b/mkosi/distributions/kali.py index 0f64cddbe..bb204c91e 100644 --- a/mkosi/distributions/kali.py +++ b/mkosi/distributions/kali.py @@ -10,7 +10,7 @@ from mkosi.installer.apt import AptRepository from mkosi.log import die -class Installer(debian.Installer): +class Installer(debian.Installer, distribution=Distribution.kali): @classmethod def pretty_name(cls) -> str: return "Kali Linux" diff --git a/mkosi/distributions/mageia.py b/mkosi/distributions/mageia.py index b95cacb5b..b41bb0745 100644 --- a/mkosi/distributions/mageia.py +++ b/mkosi/distributions/mageia.py @@ -4,13 +4,13 @@ from collections.abc import Iterable from mkosi.config import Architecture from mkosi.context import Context -from mkosi.distributions import fedora, join_mirror +from mkosi.distributions import Distribution, fedora, join_mirror from mkosi.installer.dnf import Dnf from mkosi.installer.rpm import RpmRepository, find_rpm_gpgkey from mkosi.log import die -class Installer(fedora.Installer): +class Installer(fedora.Installer, distribution=Distribution.mageia): @classmethod def pretty_name(cls) -> str: return "Mageia" diff --git a/mkosi/distributions/openmandriva.py b/mkosi/distributions/openmandriva.py index 1ea47a05e..82c35174c 100644 --- a/mkosi/distributions/openmandriva.py +++ b/mkosi/distributions/openmandriva.py @@ -4,13 +4,13 @@ from collections.abc import Iterable from mkosi.config import Architecture from mkosi.context import Context -from mkosi.distributions import fedora, join_mirror +from mkosi.distributions import Distribution, fedora, join_mirror from mkosi.installer.dnf import Dnf from mkosi.installer.rpm import RpmRepository, find_rpm_gpgkey from mkosi.log import die -class Installer(fedora.Installer): +class Installer(fedora.Installer, distribution=Distribution.openmandriva): @classmethod def pretty_name(cls) -> str: return "OpenMandriva" diff --git a/mkosi/distributions/opensuse.py b/mkosi/distributions/opensuse.py index dfeb5ad26..9b1e29bca 100644 --- a/mkosi/distributions/opensuse.py +++ b/mkosi/distributions/opensuse.py @@ -9,7 +9,7 @@ from xml.etree import ElementTree from mkosi.config import Architecture, Config from mkosi.context import Context from mkosi.curl import curl -from mkosi.distributions import DistributionInstaller, PackageType, join_mirror +from mkosi.distributions import Distribution, DistributionInstaller, PackageType, join_mirror from mkosi.installer.dnf import Dnf from mkosi.installer.rpm import RpmRepository, find_rpm_gpgkey, setup_rpm from mkosi.installer.zypper import Zypper @@ -18,7 +18,7 @@ from mkosi.mounts import finalize_certificate_mounts from mkosi.run import run -class Installer(DistributionInstaller): +class Installer(DistributionInstaller, distribution=Distribution.opensuse): @classmethod def pretty_name(cls) -> str: return "openSUSE" diff --git a/mkosi/distributions/postmarketos.py b/mkosi/distributions/postmarketos.py index 13209f9d5..a537246c8 100644 --- a/mkosi/distributions/postmarketos.py +++ b/mkosi/distributions/postmarketos.py @@ -11,7 +11,7 @@ from mkosi.installer.apk import Apk, ApkRepository from mkosi.log import complete_step, die -class Installer(DistributionInstaller): +class Installer(DistributionInstaller, distribution=Distribution.postmarketos): @classmethod def pretty_name(cls) -> str: return "postmarketOS" diff --git a/mkosi/distributions/rhel.py b/mkosi/distributions/rhel.py index 04c2860d6..d9fccc16d 100644 --- a/mkosi/distributions/rhel.py +++ b/mkosi/distributions/rhel.py @@ -5,13 +5,13 @@ from pathlib import Path from typing import Any, Optional from mkosi.context import Context -from mkosi.distributions import centos, join_mirror +from mkosi.distributions import Distribution, centos, join_mirror from mkosi.installer.rpm import RpmRepository, find_rpm_gpgkey from mkosi.log import die from mkosi.run import exists_in_sandbox, glob_in_sandbox -class Installer(centos.Installer): +class Installer(centos.Installer, distribution=Distribution.rhel): @classmethod def pretty_name(cls) -> str: return "RHEL" diff --git a/mkosi/distributions/rhel_ubi.py b/mkosi/distributions/rhel_ubi.py index 6e4e8cd99..8e0a9fe72 100644 --- a/mkosi/distributions/rhel_ubi.py +++ b/mkosi/distributions/rhel_ubi.py @@ -3,12 +3,12 @@ from collections.abc import Iterable from mkosi.context import Context -from mkosi.distributions import centos, join_mirror +from mkosi.distributions import Distribution, centos, join_mirror from mkosi.installer.rpm import RpmRepository, find_rpm_gpgkey from mkosi.log import die -class Installer(centos.Installer): +class Installer(centos.Installer, distribution=Distribution.rhel_ubi): @classmethod def pretty_name(cls) -> str: return "RHEL UBI" diff --git a/mkosi/distributions/rocky.py b/mkosi/distributions/rocky.py index 5829121bf..6a4a7d650 100644 --- a/mkosi/distributions/rocky.py +++ b/mkosi/distributions/rocky.py @@ -1,12 +1,12 @@ # SPDX-License-Identifier: LGPL-2.1-or-later from mkosi.context import Context -from mkosi.distributions import centos, join_mirror +from mkosi.distributions import Distribution, centos, join_mirror from mkosi.installer.rpm import RpmRepository, find_rpm_gpgkey from mkosi.log import die -class Installer(centos.Installer): +class Installer(centos.Installer, distribution=Distribution.rocky): @classmethod def pretty_name(cls) -> str: return "Rocky Linux" diff --git a/mkosi/distributions/ubuntu.py b/mkosi/distributions/ubuntu.py index 2a1996f61..db8cd88e9 100644 --- a/mkosi/distributions/ubuntu.py +++ b/mkosi/distributions/ubuntu.py @@ -14,7 +14,7 @@ from mkosi.log import die from mkosi.util import startswith -class Installer(debian.Installer): +class Installer(debian.Installer, distribution=Distribution.ubuntu): @classmethod def pretty_name(cls) -> str: return "Ubuntu" diff --git a/mkosi/installer/__init__.py b/mkosi/installer/__init__.py index 2647fca41..471e749bb 100644 --- a/mkosi/installer/__init__.py +++ b/mkosi/installer/__init__.py @@ -37,6 +37,10 @@ class PackageManager: def scripts(cls, context: Context) -> dict[str, list[PathString]]: return {} + @classmethod + def architecture(cls, context: Context) -> str: + return context.config.distribution.installer.architecture(context.config.architecture) + @classmethod def finalize_environment(cls, context: Context) -> dict[str, str]: env = { @@ -78,13 +82,13 @@ class PackageManager: if context.config.local_mirror and (mirror := startswith(context.config.local_mirror, "file://")): mounts += ["--ro-bind", mirror, mirror] - subdir = context.config.distribution.package_manager(context.config).subdir(context.config) + subdir = context.config.distribution.installer.package_manager(context.config).subdir(context.config) src = context.metadata_dir / "lib" / subdir mounts += ["--bind", src, Path("/var/lib") / subdir] src = context.metadata_dir / "cache" / subdir - caches = context.config.distribution.package_manager(context.config).package_subdirs(src) + caches = context.config.distribution.installer.package_manager(context.config).package_subdirs(src) # If there are no package cache subdirectories, we always operate on the package cache directory, # since we can't do any mount tricks to combine caches from different locations in this case. @@ -192,7 +196,7 @@ def clean_package_manager_metadata(context: Context) -> None: Try them all regardless of the distro: metadata is only removed if the package manager is not present in the image. """ - subdir = context.config.distribution.package_manager(context.config).subdir(context.config) + subdir = context.config.distribution.installer.package_manager(context.config).subdir(context.config) if context.config.clean_package_metadata == ConfigFeature.disabled: return @@ -207,7 +211,9 @@ def clean_package_manager_metadata(context: Context) -> None: # tar image (which are often used as a base tree for extension images and thus should retain package # manager metadata) or if the corresponding package manager is installed in the image. - executable = context.config.distribution.package_manager(context.config).executable(context.config) + executable = context.config.distribution.installer.package_manager(context.config).executable( + context.config + ) remove = [] for tool, paths in ( diff --git a/mkosi/installer/apk.py b/mkosi/installer/apk.py index 6cf8749ec..6489cac97 100644 --- a/mkosi/installer/apk.py +++ b/mkosi/installer/apk.py @@ -72,7 +72,7 @@ class Apk(PackageManager): "--cache-packages", "--cache-dir", "/var/cache/apk", "--cache-predownload", - "--arch", context.config.distribution.architecture(context.config.architecture), + "--arch", cls.architecture(context), "--no-interactive", "--preserve-env", "--keys-dir", "/etc/apk/keys", @@ -141,7 +141,7 @@ class Apk(PackageManager): return # Move apk files to arch-specific directory - arch = context.config.distribution.architecture(context.config.architecture) + arch = cls.architecture(context) arch_dir = context.repository / arch arch_dir.mkdir(exist_ok=True) for package in packages: diff --git a/mkosi/installer/apt.py b/mkosi/installer/apt.py index 9ffec6457..1cd63c2a9 100644 --- a/mkosi/installer/apt.py +++ b/mkosi/installer/apt.py @@ -165,12 +165,10 @@ class Apt(PackageManager): @classmethod def cmd(cls, context: Context, command: str = "apt-get") -> list[PathString]: - debarch = context.config.distribution.architecture(context.config.architecture) - cmdline: list[PathString] = [ command, - "-o", f"APT::Architecture={debarch}", - "-o", f"APT::Architectures={debarch}", + "-o", f"APT::Architecture={cls.architecture(context)}", + "-o", f"APT::Architectures={cls.architecture(context)}", "-o", f"APT::Install-Recommends={str(context.config.with_recommends).lower()}", "-o", "APT::Immediate-Configure=off", "-o", "APT::Get::Assume-Yes=true", diff --git a/mkosi/installer/dnf.py b/mkosi/installer/dnf.py index 3c7a6fc0e..3e026ab56 100644 --- a/mkosi/installer/dnf.py +++ b/mkosi/installer/dnf.py @@ -187,9 +187,7 @@ class Dnf(PackageManager): cmdline += ["--setopt=cacheonly=metadata"] if not context.config.architecture.is_native(): - cmdline += [ - f"--forcearch={context.config.distribution.architecture(context.config.architecture)}" - ] + cmdline += [f"--forcearch={cls.architecture(context)}"] if not context.config.with_docs: cmdline += ["--no-docs" if dnf == "dnf5" else "--nodocs"] diff --git a/mkosi/installer/pacman.py b/mkosi/installer/pacman.py index dc42af8cf..4d0359b5c 100644 --- a/mkosi/installer/pacman.py +++ b/mkosi/installer/pacman.py @@ -108,7 +108,7 @@ class Pacman(PackageManager): SigLevel = {sig_level} LocalFileSigLevel = Optional ParallelDownloads = 5 - Architecture = {context.config.distribution.architecture(context.config.architecture)} + Architecture = {cls.architecture(context)} """ ) @@ -164,7 +164,7 @@ class Pacman(PackageManager): "--cachedir=/var/cache/pacman/mkosi", "--cachedir=/var/cache/pacman/pkg", "--hookdir=/buildroot/etc/pacman.d/hooks", - "--arch", context.config.distribution.architecture(context.config.architecture), + "--arch", cls.architecture(context), "--color", "auto", "--noconfirm", ] # fmt: skip diff --git a/mkosi/manifest.py b/mkosi/manifest.py index b087c872d..89a5028d4 100644 --- a/mkosi/manifest.py +++ b/mkosi/manifest.py @@ -80,11 +80,11 @@ class Manifest: def record_packages(self) -> None: with complete_step("Recording packages in manifest…"): - if self.context.config.distribution.package_type() == PackageType.rpm: + if self.context.config.distribution.installer.package_type() == PackageType.rpm: self.record_rpm_packages() - if self.context.config.distribution.package_type() == PackageType.deb: + if self.context.config.distribution.installer.package_type() == PackageType.deb: self.record_deb_packages() - if self.context.config.distribution.package_type() == PackageType.pkg: + if self.context.config.distribution.installer.package_type() == PackageType.pkg: self.record_pkg_packages() def record_rpm_packages(self) -> None: diff --git a/mkosi/qemu.py b/mkosi/qemu.py index d3ea1924d..64f6c655e 100644 --- a/mkosi/qemu.py +++ b/mkosi/qemu.py @@ -634,7 +634,7 @@ def qemu_version(config: Config, binary: Path) -> GenericVersion: def want_scratch(config: Config) -> bool: return config.runtime_scratch == ConfigFeature.enabled or ( config.runtime_scratch == ConfigFeature.auto - and config.find_binary(f"mkfs.{config.distribution.filesystem()}") is not None + and config.find_binary(f"mkfs.{config.distribution.installer.filesystem()}") is not None ) @@ -643,7 +643,7 @@ def generate_scratch_fs(config: Config) -> Iterator[Path]: with tempfile.NamedTemporaryFile(dir="/var/tmp", prefix="mkosi-scratch-") as scratch: maybe_make_nocow(Path(scratch.name)) scratch.truncate(1024**4) - fs = config.distribution.filesystem() + fs = config.distribution.installer.filesystem() extra = config.finalize_environment().get(f"SYSTEMD_REPART_MKFS_OPTIONS_{fs.upper()}", "") run( [f"mkfs.{fs}", "-L", "scratch", "-q", *extra.split(), workdir(Path(scratch.name))], @@ -1441,7 +1441,9 @@ def run_qemu(args: Args, config: Config) -> None: "-blockdev", ",".join(blockdev), "-device", "virtio-blk-pci,drive=scratch", ] # fmt: skip - kcl += [f"systemd.mount-extra=LABEL=scratch:/var/tmp:{config.distribution.filesystem()}"] + kcl += [ + f"systemd.mount-extra=LABEL=scratch:/var/tmp:{config.distribution.installer.filesystem()}" + ] if config.output_format == OutputFormat.cpio: cmdline += ["-initrd", fname] diff --git a/tests/test_config.py b/tests/test_config.py index f5fb9c9f1..12dcf1f30 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1515,7 +1515,9 @@ def test_tools(tmp_path: Path) -> None: assert tools host = detect_distribution()[0] if host: - assert tools.distribution == host.default_tools_tree_distribution() + assert tools.distribution == ( + host.installer.default_tools_tree_distribution() or tools.distribution + ) (d / "mkosi.tools.conf").write_text( f""" diff --git a/tests/test_initrd.py b/tests/test_initrd.py index 143d49cfc..77a88fc88 100644 --- a/tests/test_initrd.py +++ b/tests/test_initrd.py @@ -78,7 +78,7 @@ def test_initrd_lvm(config: ImageConfig) -> None: run(["lvm", "lvcreate", "--devicesfile", "", "-An", "-l", "100%FREE", "-n", "lv0", "vg_mkosi"]) run(["lvm", "lvs", "--devicesfile", ""]) run(["udevadm", "wait", "--timeout=30", "/dev/vg_mkosi/lv0"]) - run([f"mkfs.{image.config.distribution.filesystem()}", "-L", "root", "/dev/vg_mkosi/lv0"]) + run([f"mkfs.{image.config.distribution.installer.filesystem()}", "-L", "root", "/dev/vg_mkosi/lv0"]) src = Path(stack.enter_context(tempfile.TemporaryDirectory())) run(["systemd-dissect", "--mount", "--mkdir", Path(image.output_dir) / "image.raw", src]) @@ -140,7 +140,7 @@ def test_initrd_luks(config: ImageConfig, passphrase: Path) -> None: f"""\ [Partition] Type=root - Format={config.distribution.filesystem()} + Format={config.distribution.installer.filesystem()} Minimize=guess Encrypt=key-file CopyFiles=/ @@ -190,7 +190,7 @@ def test_initrd_luks_lvm(config: ImageConfig, passphrase: Path) -> None: run(["lvm", "lvcreate", "--devicesfile", "", "-An", "-l", "100%FREE", "-n", "lv0", "vg_mkosi"]) run(["lvm", "lvs", "--devicesfile", ""]) run(["udevadm", "wait", "--timeout=30", "/dev/vg_mkosi/lv0"]) - run([f"mkfs.{image.config.distribution.filesystem()}", "-L", "root", "/dev/vg_mkosi/lv0"]) + run([f"mkfs.{image.config.distribution.installer.filesystem()}", "-L", "root", "/dev/vg_mkosi/lv0"]) src = Path(stack.enter_context(tempfile.TemporaryDirectory())) run(["systemd-dissect", "--mount", "--mkdir", Path(image.output_dir) / "image.raw", src])