From: Daan De Meyer Date: Wed, 26 Jul 2023 13:09:42 +0000 (+0200) Subject: Introduce dnf.py for dnf related logic X-Git-Tag: v15~55^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=edb0c41d807310f0d4927cecefccaef483c1eee0;p=thirdparty%2Fmkosi.git Introduce dnf.py for dnf related logic We put this in a new "installer" submodule as well. --- diff --git a/mkosi/distributions/alma.py b/mkosi/distributions/alma.py index dd66169ac..309a2cefa 100644 --- a/mkosi/distributions/alma.py +++ b/mkosi/distributions/alma.py @@ -2,7 +2,7 @@ from mkosi.config import MkosiConfig from mkosi.distributions.centos import CentosInstaller -from mkosi.distributions.fedora import Repo +from mkosi.installer.dnf import Repo class AlmaInstaller(CentosInstaller): diff --git a/mkosi/distributions/centos.py b/mkosi/distributions/centos.py index cddd943ab..70fc495e6 100644 --- a/mkosi/distributions/centos.py +++ b/mkosi/distributions/centos.py @@ -8,7 +8,7 @@ from pathlib import Path from mkosi.architecture import Architecture from mkosi.config import MkosiConfig from mkosi.distributions import DistributionInstaller -from mkosi.distributions.fedora import Repo, invoke_dnf, setup_dnf +from mkosi.installer.dnf import Repo, invoke_dnf, setup_dnf from mkosi.log import complete_step, die from mkosi.state import MkosiState from mkosi.tree import rmtree diff --git a/mkosi/distributions/fedora.py b/mkosi/distributions/fedora.py index 53dcdd397..50ad25791 100644 --- a/mkosi/distributions/fedora.py +++ b/mkosi/distributions/fedora.py @@ -1,19 +1,12 @@ # SPDX-License-Identifier: LGPL-2.1+ -import os -import shutil -from collections.abc import Iterable, Mapping, Sequence -from pathlib import Path -from textwrap import dedent -from typing import Any, NamedTuple +from collections.abc import Sequence from mkosi.architecture import Architecture from mkosi.distributions import DistributionInstaller +from mkosi.installer.dnf import Repo, invoke_dnf, setup_dnf from mkosi.log import die -from mkosi.run import bwrap from mkosi.state import MkosiState -from mkosi.tree import rmtree -from mkosi.util import sort_packages class FedoraInstaller(DistributionInstaller): @@ -101,132 +94,3 @@ def fedora_release_at_least(release: str, threshold: str) -> bool: return False # If neither is 'rawhide', both must be integers return int(release) >= int(threshold) - - -class Repo(NamedTuple): - id: str - url: str - gpgurls: tuple[str, ...] - enabled: bool = True - - -def setup_dnf(state: MkosiState, repos: Sequence[Repo]) -> None: - config = state.pkgmngr / "etc/dnf/dnf.conf" - - if not config.exists(): - config.parent.mkdir(exist_ok=True, parents=True) - config.write_text( - dedent( - """\ - [main] - install_weak_deps=0 - """ - ) - ) - - repofile = state.pkgmngr / "etc/yum.repos.d/mkosi.repo" - if not repofile.exists(): - repofile.parent.mkdir(exist_ok=True, parents=True) - with repofile.open("w") as f: - for repo in repos: - f.write( - dedent( - f"""\ - [{repo.id}] - name={repo.id} - {repo.url} - gpgcheck=1 - enabled={int(repo.enabled)} - """ - ) - ) - - for i, url in enumerate(repo.gpgurls): - f.write("gpgkey=" if i == 0 else len("gpgkey=") * " ") - f.write(f"{url}\n") - - -def invoke_dnf( - state: MkosiState, - command: str, - packages: Iterable[str], - env: Mapping[str, Any] = {}, - filelists: bool = True, - apivfs: bool = True -) -> None: - state.pkgmngr.joinpath("etc/dnf/vars").mkdir(exist_ok=True, parents=True) - state.pkgmngr.joinpath("etc/yum.repos.d").mkdir(exist_ok=True, parents=True) - state.pkgmngr.joinpath("var/lib/dnf").mkdir(exist_ok=True, parents=True) - - # dnf5 does not support building for foreign architectures yet (missing --forcearch) - dnf = shutil.which("dnf5") if state.config.architecture.is_native() else None - dnf = dnf or shutil.which("dnf") or "yum" - - cmdline = [ - dnf, - "--assumeyes", - f"--config={state.pkgmngr / 'etc/dnf/dnf.conf'}", - "--best", - f"--releasever={state.config.release}", - f"--installroot={state.root}", - "--setopt=keepcache=1", - f"--setopt=cachedir={state.cache_dir}", - f"--setopt=reposdir={state.pkgmngr / 'etc/yum.repos.d'}", - f"--setopt=varsdir={state.pkgmngr / 'etc/dnf/vars'}", - f"--setopt=persistdir={state.pkgmngr / 'var/lib/dnf'}", - "--setopt=check_config_file_age=0", - "--no-plugins" if dnf.endswith("dnf5") else "--noplugins", - ] - - # Make sure we download filelists so all dependencies can be resolved. - # See https://bugzilla.redhat.com/show_bug.cgi?id=2180842 - if dnf.endswith("dnf5") and filelists: - cmdline += ["--setopt=optional_metadata_types=filelists"] - - if not state.config.repository_key_check: - cmdline += ["--nogpgcheck"] - - if state.config.repositories: - opt = "--enable-repo" if dnf.endswith("dnf5") else "--enablerepo" - cmdline += [f"{opt}={repo}" for repo in state.config.repositories] - - # TODO: this breaks with a local, offline repository created with 'createrepo' - if state.config.cache_only and not state.config.local_mirror: - cmdline += ["--cacheonly"] - - if not state.config.architecture.is_native(): - cmdline += [f"--forcearch={state.installer.architecture(state.config.architecture)}"] - - if not state.config.with_docs: - cmdline += ["--no-docs" if dnf.endswith("dnf5") else "--nodocs"] - - cmdline += [command, *sort_packages(packages)] - - bwrap(cmdline, - apivfs=state.root if apivfs else None, - env=dict(KERNEL_INSTALL_BYPASS="1") | env | state.config.environment) - - fixup_rpmdb_location(state.root) - - # The log directory is always interpreted relative to the install root so there's nothing we can do but - # to remove the log files from the install root afterwards. - for p in (state.root / "var/log").iterdir(): - if any(p.name.startswith(prefix) for prefix in ("dnf", "hawkey", "yum")): - p.unlink() - - -def fixup_rpmdb_location(root: Path) -> None: - # On Debian, rpm/dnf ship with a patch to store the rpmdb under ~/ so it needs to be copied back in the - # right location, otherwise the rpmdb will be broken. See: https://bugs.debian.org/1004863. We also - # replace it with a symlink so that any further rpm operations immediately use the correct location. - rpmdb_home = root / "root/.rpmdb" - if not rpmdb_home.exists() or rpmdb_home.is_symlink(): - return - - # Take into account the new location in F36 - rpmdb = root / "usr/lib/sysimage/rpm" - if not rpmdb.exists(): - rpmdb = root / "var/lib/rpm" - rmtree(rpmdb) - shutil.move(rpmdb_home, rpmdb) - rpmdb_home.symlink_to(os.path.relpath(rpmdb, start=rpmdb_home.parent)) diff --git a/mkosi/distributions/mageia.py b/mkosi/distributions/mageia.py index edd4be9f0..0cd1af4b2 100644 --- a/mkosi/distributions/mageia.py +++ b/mkosi/distributions/mageia.py @@ -4,7 +4,7 @@ from collections.abc import Sequence from mkosi.architecture import Architecture from mkosi.distributions import DistributionInstaller -from mkosi.distributions.fedora import Repo, invoke_dnf, setup_dnf +from mkosi.installer.dnf import Repo, invoke_dnf, setup_dnf from mkosi.log import die from mkosi.state import MkosiState diff --git a/mkosi/distributions/openmandriva.py b/mkosi/distributions/openmandriva.py index 2331bcaa8..1702a8272 100644 --- a/mkosi/distributions/openmandriva.py +++ b/mkosi/distributions/openmandriva.py @@ -4,7 +4,7 @@ from collections.abc import Sequence from mkosi.architecture import Architecture from mkosi.distributions import DistributionInstaller -from mkosi.distributions.fedora import Repo, invoke_dnf, setup_dnf +from mkosi.installer.dnf import Repo, invoke_dnf, setup_dnf from mkosi.log import die from mkosi.state import MkosiState diff --git a/mkosi/distributions/opensuse.py b/mkosi/distributions/opensuse.py index fe4de2493..43d149e3a 100644 --- a/mkosi/distributions/opensuse.py +++ b/mkosi/distributions/opensuse.py @@ -8,7 +8,7 @@ from collections.abc import Sequence from mkosi.architecture import Architecture from mkosi.distributions import DistributionInstaller -from mkosi.distributions.fedora import Repo, fixup_rpmdb_location, invoke_dnf, setup_dnf +from mkosi.installer.dnf import Repo, fixup_rpmdb_location, invoke_dnf, setup_dnf from mkosi.log import die from mkosi.run import bwrap from mkosi.state import MkosiState diff --git a/mkosi/distributions/rocky.py b/mkosi/distributions/rocky.py index 52cc55047..1a3119f34 100644 --- a/mkosi/distributions/rocky.py +++ b/mkosi/distributions/rocky.py @@ -2,7 +2,7 @@ from mkosi.config import MkosiConfig from mkosi.distributions.centos import CentosInstaller -from mkosi.distributions.fedora import Repo +from mkosi.installer.dnf import Repo class RockyInstaller(CentosInstaller): diff --git a/mkosi/installer/__init__.py b/mkosi/installer/__init__.py new file mode 100644 index 000000000..5d742c4f7 --- /dev/null +++ b/mkosi/installer/__init__.py @@ -0,0 +1 @@ +# SPDX-License-Identifier: LGPL-2.1+ diff --git a/mkosi/installer/dnf.py b/mkosi/installer/dnf.py new file mode 100644 index 000000000..f0e334028 --- /dev/null +++ b/mkosi/installer/dnf.py @@ -0,0 +1,141 @@ +# SPDX-License-Identifier: LGPL-2.1+ +import os +import shutil +import textwrap +from collections.abc import Iterable, Mapping, Sequence +from pathlib import Path +from typing import Any, NamedTuple + +from mkosi.run import bwrap +from mkosi.state import MkosiState +from mkosi.tree import rmtree +from mkosi.util import sort_packages + + +class Repo(NamedTuple): + id: str + url: str + gpgurls: tuple[str, ...] + enabled: bool = True + + +def setup_dnf(state: MkosiState, repos: Sequence[Repo]) -> None: + config = state.pkgmngr / "etc/dnf/dnf.conf" + + if not config.exists(): + config.parent.mkdir(exist_ok=True, parents=True) + config.write_text( + textwrap.dedent( + """\ + [main] + install_weak_deps=0 + """ + ) + ) + + repofile = state.pkgmngr / "etc/yum.repos.d/mkosi.repo" + if not repofile.exists(): + repofile.parent.mkdir(exist_ok=True, parents=True) + with repofile.open("w") as f: + for repo in repos: + f.write( + textwrap.dedent( + f"""\ + [{repo.id}] + name={repo.id} + {repo.url} + gpgcheck=1 + enabled={int(repo.enabled)} + """ + ) + ) + + for i, url in enumerate(repo.gpgurls): + f.write("gpgkey=" if i == 0 else len("gpgkey=") * " ") + f.write(f"{url}\n") + + +def invoke_dnf( + state: MkosiState, + command: str, + packages: Iterable[str], + env: Mapping[str, Any] = {}, + filelists: bool = True, + apivfs: bool = True +) -> None: + state.pkgmngr.joinpath("etc/dnf/vars").mkdir(exist_ok=True, parents=True) + state.pkgmngr.joinpath("etc/yum.repos.d").mkdir(exist_ok=True, parents=True) + state.pkgmngr.joinpath("var/lib/dnf").mkdir(exist_ok=True, parents=True) + + # dnf5 does not support building for foreign architectures yet (missing --forcearch) + dnf = shutil.which("dnf5") if state.config.architecture.is_native() else None + dnf = dnf or shutil.which("dnf") or "yum" + + cmdline = [ + dnf, + "--assumeyes", + f"--config={state.pkgmngr / 'etc/dnf/dnf.conf'}", + "--best", + f"--releasever={state.config.release}", + f"--installroot={state.root}", + "--setopt=keepcache=1", + f"--setopt=cachedir={state.cache_dir}", + f"--setopt=reposdir={state.pkgmngr / 'etc/yum.repos.d'}", + f"--setopt=varsdir={state.pkgmngr / 'etc/dnf/vars'}", + f"--setopt=persistdir={state.pkgmngr / 'var/lib/dnf'}", + "--setopt=check_config_file_age=0", + "--no-plugins" if dnf.endswith("dnf5") else "--noplugins", + ] + + # Make sure we download filelists so all dependencies can be resolved. + # See https://bugzilla.redhat.com/show_bug.cgi?id=2180842 + if dnf.endswith("dnf5") and filelists: + cmdline += ["--setopt=optional_metadata_types=filelists"] + + if not state.config.repository_key_check: + cmdline += ["--nogpgcheck"] + + if state.config.repositories: + opt = "--enable-repo" if dnf.endswith("dnf5") else "--enablerepo" + cmdline += [f"{opt}={repo}" for repo in state.config.repositories] + + # TODO: this breaks with a local, offline repository created with 'createrepo' + if state.config.cache_only and not state.config.local_mirror: + cmdline += ["--cacheonly"] + + if not state.config.architecture.is_native(): + cmdline += [f"--forcearch={state.installer.architecture(state.config.architecture)}"] + + if not state.config.with_docs: + cmdline += ["--no-docs" if dnf.endswith("dnf5") else "--nodocs"] + + cmdline += [command, *sort_packages(packages)] + + bwrap(cmdline, + apivfs=state.root if apivfs else None, + env=dict(KERNEL_INSTALL_BYPASS="1") | env | state.config.environment) + + fixup_rpmdb_location(state.root) + + # The log directory is always interpreted relative to the install root so there's nothing we can do but + # to remove the log files from the install root afterwards. + for p in (state.root / "var/log").iterdir(): + if any(p.name.startswith(prefix) for prefix in ("dnf", "hawkey", "yum")): + p.unlink() + + +def fixup_rpmdb_location(root: Path) -> None: + # On Debian, rpm/dnf ship with a patch to store the rpmdb under ~/ so it needs to be copied back in the + # right location, otherwise the rpmdb will be broken. See: https://bugs.debian.org/1004863. We also + # replace it with a symlink so that any further rpm operations immediately use the correct location. + rpmdb_home = root / "root/.rpmdb" + if not rpmdb_home.exists() or rpmdb_home.is_symlink(): + return + + # Take into account the new location in F36 + rpmdb = root / "usr/lib/sysimage/rpm" + if not rpmdb.exists(): + rpmdb = root / "var/lib/rpm" + rmtree(rpmdb) + shutil.move(rpmdb_home, rpmdb) + rpmdb_home.symlink_to(os.path.relpath(rpmdb, start=rpmdb_home.parent))