]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
kmod: Stop retrieving dependency info of all modules
authorDaanDeMeyer <daan.j.demeyer@gmail.com>
Thu, 25 Dec 2025 19:48:47 +0000 (20:48 +0100)
committerDaanDeMeyer <daan.j.demeyer@gmail.com>
Fri, 26 Dec 2025 09:02:49 +0000 (10:02 +0100)
Instead of running modinfo once to retrieve the dependency information
of all modules, let's only retrieve the dependency information of the
modules that are to be included in the image and their transitive
dependencies. This means we have to run modinfo multiple times, but with
far fewer modules than before. This ends up being faster than retrieving
the dependency information of all modules, especially after the optimization
from e276dac87a530efac4376a5059b980f2d43460f5.

For the mkosi default image build on Arch Linux this reduces the time for
calculating the required kernel modules and firmware on my laptop from 5s
to 0.5s.

Co-Authored-By: Laurence Kiln <246209442+LaurenceKiln@users.noreply.github.com>
mkosi/kmod.py

index 925194bfe4571f78ea1eb937fd5f206e2b5edbe7..875fc03d042faf045b920a358a4cb201a3dcda5a 100644 (file)
@@ -209,7 +209,7 @@ class ModuleDependencyInfo:
 
 
 def modinfo(context: Context, kver: str, modules: Sequence[str]) -> dict[str, ModuleDependencyInfo]:
-    cmdline = ["modinfo", "--set-version", kver, "--null"]
+    cmdline = ["modinfo", "--modname", "--set-version", kver, "--null"]
 
     if context.config.output_format.is_extension_image() and not context.config.overlay:
         cmdline += ["--basedir", "/buildroot"]
@@ -272,45 +272,42 @@ def resolve_module_dependencies(
     root directory.
     """
     modulesd = Path("usr/lib/modules") / kver
+
     if (p := context.root / modulesd / "modules.builtin").exists():
-        builtin = set(module_path_to_name(Path(m)) for m in p.read_text().splitlines())
+        builtin = {module_path_to_name(Path(m)) for m in p.read_text().splitlines()}
     else:
         builtin = set()
+
     with chdir(context.root):
         allmodules = set(modulesd.rglob("*.ko*"))
     nametofile = {module_path_to_name(m): m for m in allmodules}
 
-    moddep: dict[str, ModuleDependencyInfo] = {}
-
-    # We could run modinfo once for each module but that's slow. Luckily we can pass multiple modules to
-    # modinfo and it'll process them all in a single go. We get the modinfo for all modules to build
-    # a map that maps the module name to both its module dependencies and its firmware dependencies.
-    # Because there's more kernel modules than the max number of accepted CLI arguments, we split the
-    # modules list up into chunks if needed.
-    for i in range(0, len(nametofile.keys()), 8500):
-        chunk = list(nametofile.keys())[i : i + 8500]
-        moddep |= modinfo(context, kver, chunk)
-
     todo = [*builtin, *modules]
     mods = set()
     firmware = set()
 
     while todo:
-        m = todo.pop()
-        if m in mods:
-            continue
-
-        depinfo = moddep.get(m)
-        if not depinfo:
-            continue
-
-        for d in depinfo.modules:
-            if d not in nametofile and d not in builtin:
-                logging.warning(f"{d} is a dependency of {m} but is not installed, ignoring ")
-
-        mods.add(m)
-        todo += depinfo.modules
-        firmware.update(depinfo.firmware)
+        moddep: dict[str, ModuleDependencyInfo] = {}
+
+        # We could run modinfo once for each module but that's slow. Luckily we can pass multiple modules
+        # to modinfo and it'll process them all in a single go. We get the modinfo for all modules to
+        # build a map that maps the module name to both its module dependencies and its firmware
+        # dependencies. Because there's more kernel modules than the max number of accepted CLI
+        # arguments, we split the modules list up into chunks if needed.
+        for i in range(0, len(todo), 8500):
+            chunk = todo[i : i + 8500]
+            moddep |= modinfo(context, kver, chunk)
+
+        todo = []
+
+        for name, depinfo in moddep.items():
+            for d in depinfo.modules:
+                if d not in nametofile and d not in builtin:
+                    logging.warning(f"{d} is a dependency of {name} but is not installed, ignoring ")
+
+            mods.add(name)
+            firmware.update(depinfo.firmware)
+            todo += [m for m in depinfo.modules if m not in mods]
 
     return set(nametofile[m] for m in mods if m in nametofile), set(firmware)