]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Store repository metadata snapshot in /var
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Thu, 1 Feb 2024 10:48:28 +0000 (11:48 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Thu, 1 Feb 2024 11:31:18 +0000 (12:31 +0100)
This makes us more compatible with third-party base trees as the
repository metadata is stored in the canonical locations by using
/var.

This also means that the repository metadata becomes subject to the
regular CleanPackageMetadata= cleanup rules.

NEWS.md
mkosi/__init__.py
mkosi/context.py
mkosi/installer/__init__.py
mkosi/installer/apt.py
mkosi/installer/pacman.py
mkosi/installer/zypper.py
mkosi/resources/mkosi.md

diff --git a/NEWS.md b/NEWS.md
index 24a11fc17664ab1c3a67f00b6c50f8b465b6ba4c..4b656020a2d916cb5082c70065ec8bd58d5c1190 100644 (file)
--- a/NEWS.md
+++ b/NEWS.md
   `CacheDirectory=` is only used for incremental cached images now.
 - Repository metadata is now synced once at the start of each image
   build and never during an image build. Each image includes a snapshot
-  of the repository metadata in `/mkosi` so that incremental images and
-  extension images can reuse the same snapshot. When building an image
-  intended to be used with `BaseTrees=`, disable `CleanPackageMetadata=`
-  to make sure the repository metadata in `/mkosi` is not cleaned up,
-  otherwise any extension images using this image as their base tree
-  will not be able to install additional packages.
+  of the repository metadata in the canonical locations in `/var` so
+  that incremental images and extension images can reuse the same
+  snapshot. When building an image intended to be used with
+  `BaseTrees=`, disable `CleanPackageMetadata=` to make sure the
+  repository metadata in `/var` is not cleaned up, otherwise any
+  extension images using this image as their base tree will not be able
+  to install additional packages.
 - Implemented `CacheOnly=metadata`. Note that in the JSON output, the
   value of `CacheOnly=` will now be a string instead of a boolean.
 
index 39090194ac3416b390303ab6c87a9a70d3143e88..2b92536210f6e68a827ac121a07e2fa63ddbb7da 100644 (file)
@@ -2949,11 +2949,15 @@ def copy_repository_metadata(context: Context) -> None:
     if have_cache(context.config) or context.config.base_trees:
         return
 
-    if context.package_cache_dir.exists() and any(context.package_cache_dir.iterdir()):
-        return
-
     subdir = context.config.distribution.package_manager(context.config).subdir(context.config)
 
+    # Don't copy anything if the repository metadata directories are already populated.
+    if (
+        any((context.package_cache_dir / "cache" / subdir).glob("*")) or
+        any((context.package_cache_dir / "lib" / subdir).glob("*"))
+    ):
+        return
+
     for d in ("cache", "lib"):
         src = context.config.package_cache_dir_or_default() / d / subdir
         if not src.exists():
index 2220ed10d390aadb30295d2c7d92600b69ee05b1..799e00b8e40a0db6c569b4cbe1d6405ebb7ee9dc 100644 (file)
@@ -27,7 +27,7 @@ class Context:
         self.config = config
         self.workspace = workspace
         self.resources = resources
-        self.package_cache_dir = package_cache_dir or (self.root / "mkosi")
+        self.package_cache_dir = package_cache_dir or (self.root / "var")
 
         with umask(~0o755):
             # Using a btrfs subvolume as the upperdir in an overlayfs results in EXDEV so make sure we create
index 95674fab5edf31a71c33cb82e892e7020ffe7b91..d79049745785b7ae6be2236804394907d1412bbd 100644 (file)
@@ -7,12 +7,16 @@ from mkosi.config import Config, ConfigFeature, OutputFormat
 from mkosi.context import Context
 from mkosi.run import find_binary
 from mkosi.sandbox import finalize_crypto_mounts
-from mkosi.tree import move_tree, rmtree
+from mkosi.tree import copy_tree, rmtree
 from mkosi.types import PathString
 from mkosi.util import flatten
 
 
 class PackageManager:
+    @classmethod
+    def executable(cls, config: Config) -> str:
+        return "custom"
+
     @classmethod
     def subdir(cls, config: Config) -> Path:
         return Path("custom")
@@ -73,26 +77,31 @@ def clean_package_manager_metadata(context: Context) -> None:
     ):
         # Instead of removing the package cache directory from the image, we move it to the workspace so it stays
         # available for later steps and is automatically removed along with the workspace when the build finishes.
-        context.package_cache_dir = move_tree(
-            context.package_cache_dir, context.workspace / "package-cache-dir",
-            tools=context.config.tools(),
-            sandbox=context.sandbox(
-                options=[
-                    "--bind", context.package_cache_dir.parent, context.package_cache_dir.parent,
-                    "--bind", context.workspace, context.workspace,
-                ],
-            ),
-        )
+        subdir = context.config.distribution.package_manager(context.config).subdir(context.config)
+
+        for d in ("cache", "lib"):
+            src = context.package_cache_dir / d / subdir
+            if not src.exists():
+                continue
+
+            dst = context.workspace / "package-cache-dir" / d / subdir
+            dst.mkdir(parents=True, exist_ok=True)
+
+            copy_tree(src, dst, sandbox=context.sandbox(options=["--ro-bind", src, src, "--bind", dst, dst]))
+
+        context.package_cache_dir = context.workspace / "package-cache-dir"
 
     if context.config.clean_package_metadata == ConfigFeature.disabled:
         return
 
     always = context.config.clean_package_metadata == ConfigFeature.enabled
+    executable = context.config.distribution.package_manager(context.config).executable(context.config)
+    subdir = context.config.distribution.package_manager(context.config).subdir(context.config)
 
-    for tool, paths in (("rpm",    ["var/lib/rpm", "usr/lib/sysimage/rpm"]),
-                        ("dnf5",   ["usr/lib/sysimage/libdnf5"]),
-                        ("dpkg",   ["var/lib/dpkg"]),
-                        ("pacman", ["var/lib/pacman"])):
+    for tool, paths in (("rpm",      ["var/lib/rpm", "usr/lib/sysimage/rpm"]),
+                        ("dnf5",     ["usr/lib/sysimage/libdnf5"]),
+                        ("dpkg",     ["var/lib/dpkg"]),
+                        (executable, [f"var/lib/{subdir}", f"var/cache/{subdir}"])):
         if always or not find_binary(tool, root=context.root):
             rmtree(*(context.root / p for p in paths),
                    sandbox=context.sandbox(options=["--bind", context.root, context.root]))
index a498aaad0b88a4d8ffff698dd7242d0600efa829..2a532d14f1f2f54ec9d4b1c7589cda34986b5d99 100644 (file)
@@ -34,6 +34,10 @@ class Apt(PackageManager):
                 """
             )
 
+    @classmethod
+    def executable(cls, config: Config) -> str:
+        return "apt"
+
     @classmethod
     def subdir(cls, config: Config) -> Path:
         return Path("apt")
@@ -70,8 +74,6 @@ class Apt(PackageManager):
             (context.root / "var/lib/dpkg").mkdir(parents=True, exist_ok=True)
             (context.root / "var/lib/dpkg/status").touch()
 
-            (context.package_cache_dir / "lib/apt/lists/partial").mkdir(parents=True, exist_ok=True)
-
         # We have a special apt.conf outside of pkgmngr dir that only configures "Dir::Etc" that we pass to APT_CONFIG
         # to tell apt it should read config files from /etc/apt in case this is overridden by distributions. This is
         # required because apt parses CLI configuration options after parsing its configuration files and as such we
index 2dc4dd36997d16b190e283aa37ed6540d6ef9ff1..c1906dce5ea83febae9585fec2b4b6267d3db771 100644 (file)
@@ -21,6 +21,10 @@ class Pacman(PackageManager):
         id: str
         url: str
 
+    @classmethod
+    def executable(cls, config: Config) -> str:
+        return "pacman"
+
     @classmethod
     def subdir(cls, config: Config) -> Path:
         return Path("pacman")
index fd34efe7b1dcd9686c9af4dcdf84aaaad37b90c5..d419147e4a7802717ee76d4912b3e630f30e546c 100644 (file)
@@ -16,6 +16,10 @@ from mkosi.util import sort_packages
 
 
 class Zypper(PackageManager):
+    @classmethod
+    def executable(cls, config: Config) -> str:
+        return "zypper"
+
     @classmethod
     def subdir(cls, config: Config) -> Path:
         return Path("zypp")
index 839d8a8a05ebe0c30d4594efaf667ccb9eb31504..37f5fa44ff9621ca452d2ebf366d703d552683a9 100644 (file)
@@ -986,18 +986,11 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
 `CleanPackageMetadata=`, `--clean-package-metadata=`
 
 : Enable/disable removal of package manager databases and repository
-  metadata in `/mkosi` at the end of installation. Can be specified as
-  `true`, `false`, or `auto` (the default). With `auto`, package manager
-  databases will be removed if the respective package manager executable
-  is *not* present at the end of the installation.
-
-: Note that when not building a tar or directory image, the repository
-  metadata in `/mkosi` is always removed, regardless of this setting as
-  it is only useful for building extensions using `BaseTrees=`.
-
-: Note that when set to `auto`, repository metadata in `/mkosi` is
-  removed regardless of whether the respective package manager
-  executable is present or not.
+  metadata at the end of installation. Can be specified as `true`,
+  `false`, or `auto` (the default). With `auto`, package manager
+  databases and repository metadata will be removed if the respective
+  package manager executable is *not* present at the end of the
+  installation.
 
 `PrepareScripts=`, `--prepare-script=`