]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Simplify implementation of distribution installers
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Fri, 17 Oct 2025 07:36:46 +0000 (09:36 +0200)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Fri, 17 Oct 2025 08:20:53 +0000 (10:20 +0200)
- 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

29 files changed:
mkosi/__init__.py
mkosi/bootloader.py
mkosi/config.py
mkosi/distributions/__init__.py
mkosi/distributions/alma.py
mkosi/distributions/arch.py
mkosi/distributions/azure.py
mkosi/distributions/centos.py
mkosi/distributions/custom.py
mkosi/distributions/debian.py
mkosi/distributions/fedora.py
mkosi/distributions/kali.py
mkosi/distributions/mageia.py
mkosi/distributions/openmandriva.py
mkosi/distributions/opensuse.py
mkosi/distributions/postmarketos.py
mkosi/distributions/rhel.py
mkosi/distributions/rhel_ubi.py
mkosi/distributions/rocky.py
mkosi/distributions/ubuntu.py
mkosi/installer/__init__.py
mkosi/installer/apk.py
mkosi/installer/apt.py
mkosi/installer/dnf.py
mkosi/installer/pacman.py
mkosi/manifest.py
mkosi/qemu.py
tests/test_config.py
tests/test_initrd.py

index bbe56b1ae314b0ac475f9d9437033805b98d7950..f73b99c7f7157b793e44e054a9d03b0b155bd97c 100644 (file)
@@ -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
index 4ebf1d8f5c2679cd61b64b004adb7077437f149f..14403bbbcbe1b5e7eab08bd1d30e3bde4a5f2e5b 100644 (file)
@@ -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)
 
index 87b214fcb219df8ae55604e3dc01fd35b89c3987..c5d75f6aa26800ed3064c45fc541dd1e02e7fb23 100644 (file)
@@ -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
index aae64480ad98dda7905da2cd1ea2639daa5b2a89..b0435ca50a3444291300faa564a642a1b3970304 100644 (file)
@@ -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]]:
index 3920be7760a4141ee5c4f228c52629d3ae2785bb..30ba0796db706f5ecca45e94d69b6680f4065f93 100644 (file)
@@ -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"
index a331ffb0cb87ddd89a4c4bb98d70d550eb012c8a..7fcc817ff4637a77340b973995c5add891acf0a2 100644 (file)
@@ -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"
index 4049b85ec508fcc081222a2d9d032ff87f2a09b2..b214201509b4399e1169126cbb040af9ca8d220b 100644 (file)
@@ -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"
index 40b0ffc1a84a164a58933c89336b182050a8daf2..c2827c07e8777058b4fb167141da806c68831ffc 100644 (file)
@@ -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"
index 8680b6f801c62456b217b0eb637b74529b7061aa..8684f850339e6272001cd24a36079aafb9f00a20 100644 (file)
@@ -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"
index 4eef0a23a06201613ccf688f0ca1c1699b4fa7d3..7520a91dfc7449a12d4f0c11a547e89bda8b47bb 100644 (file)
@@ -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:
index ee690d4a64962be3c404f57a70aa5d4a859c2580..ad7a34335c4f21eb5b1dec44c70c2442dc82d5ab 100644 (file)
@@ -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"
index 0f64cddbe06a6e2e3d1310f59c0c4ce8ed84ff9b..bb204c91ef60d18eeb2752eca068bba6e788965e 100644 (file)
@@ -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"
index b95cacb5b7ec7e301cd4ce629724abb6a294ef40..b41bb0745ebe511bc53c3c6ea6bcd94381a6464b 100644 (file)
@@ -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"
index 1ea47a05ea32149fc139fedf176f80c1978582d4..82c35174cc21dbd4b76438593edb09e52367e7a0 100644 (file)
@@ -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"
index dfeb5ad26098bb1258919a1f7ee2698bc6b4bffe..9b1e29bca0f2e43fd8d7224fd0028055a3ead977 100644 (file)
@@ -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"
index 13209f9d5b6ddc45bc4073f6bdccc9064dd65baf..a537246c8bbf06e1bc20103abb05a67272786451 100644 (file)
@@ -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"
index 04c2860d6705dc83284b3d24f499ec37767fd839..d9fccc16ddb35fe6514535558e4accf8d3a9821e 100644 (file)
@@ -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"
index 6e4e8cd9907b4d9e7a8a0c3924fd16089f5c7163..8e0a9fe72cc53f9b2517517f2b403a254bd4e767 100644 (file)
@@ -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"
index 5829121bf29468fce8ada7c2373092543817dbb0..6a4a7d650be21ac1a167ae6b87550d588977db14 100644 (file)
@@ -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"
index 2a1996f61bb30c85dd293555eff8e39bbec505b7..db8cd88e9744ea89203f85dd1ffc994606333b04 100644 (file)
@@ -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"
index 2647fca410f9c7d57feda368819e3bb1ea48bd01..471e749bb0ecfa9375d7d70559e942f3cb0c696a 100644 (file)
@@ -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 (
index 6cf8749ec46fd7453accae0fb7032f1c09780776..6489cac9753451519f0a611cb679775cb6828279 100644 (file)
@@ -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:
index 9ffec6457af742cc413038556684da8d26ccd081..1cd63c2a92944f3fcd06d290d8faf236473810f2 100644 (file)
@@ -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",
index 3c7a6fc0e7976e58d7fe329abc3e6d490995fb6d..3e026ab56be0f5071b0522276499b38db0147e48 100644 (file)
@@ -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"]
index dc42af8cf00d4ec9b0072c8fdfd07f30e7f95318..4d0359b5c04bea70184af3e8eddb14d43611acea 100644 (file)
@@ -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
index b087c872d0acd268718742c9a63c7ba4553b1689..89a5028d44e94cc3e1ef6fcaea0ab543be19d82c 100644 (file)
@@ -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:
index d3ea1924d87c0e1eb0d718086528935b29f1b7c0..64f6c655e93c75ff9ea448e59054cd79473f1bc3 100644 (file)
@@ -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]
index f5fb9c9f1bd606901da92220159d3adc69c91dc3..12dcf1f307678fa950b5945caa5e174fa933cb93 100644 (file)
@@ -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"""
index 143d49cfcb5d6ed031da450b2550f9d82a5844ed..77a88fc8859006e782073cb93ccaaae414b40fda 100644 (file)
@@ -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])