[PATCH 15/37] scripts/modinfo: add --skip-missing-deps

marcandre.lureau@redhat.com posted 37 patches 2 days, 2 hours ago
Maintainers: Gerd Hoffmann <kraxel@redhat.com>, Christian Schoenebeck <qemu_oss@crudebyte.com>, "Marc-André Lureau" <marcandre.lureau@redhat.com>, "Philippe Mathieu-Daudé" <philmd@linaro.org>, Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>, Thomas Huth <huth@tuxfamily.org>, Alexandre Ratchov <alex@caoua.org>, Laurent Vivier <laurent@vivier.eu>, Manos Pitsidianakis <manos.pitsidianakis@linaro.org>, "Michael S. Tsirkin" <mst@redhat.com>, Alistair Francis <alistair@alistair23.me>, "Edgar E. Iglesias" <edgar.iglesias@gmail.com>, Peter Maydell <peter.maydell@linaro.org>, Paolo Bonzini <pbonzini@redhat.com>, "Alex Bennée" <alex.bennee@linaro.org>, "Daniel P. Berrangé" <berrange@redhat.com>, Eduardo Habkost <eduardo@habkost.net>, John Snow <jsnow@redhat.com>, Cleber Rosa <crosa@redhat.com>
[PATCH 15/37] scripts/modinfo: add --skip-missing-deps
Posted by marcandre.lureau@redhat.com 2 days, 2 hours ago
From: Marc-André Lureau <marcandre.lureau@redhat.com>

Add --skip-missing-deps flag that prints warnings for missing
dependencies but continues without exiting with error code 1.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 scripts/modinfo-generate.py | 94 ++++++++++++++++++++++++++++---------
 1 file changed, 71 insertions(+), 23 deletions(-)

diff --git a/scripts/modinfo-generate.py b/scripts/modinfo-generate.py
index e7d35242414..e231c129196 100644
--- a/scripts/modinfo-generate.py
+++ b/scripts/modinfo-generate.py
@@ -34,7 +34,8 @@ def parse_line(line: str) -> tuple[str, str]:
             continue
     return (kind, data)
 
-def generate(name: str, lines: list[str], enabled: set[str]) -> Optional[set[str]]:
+def parse_modinfo(name: str, lines: list[str], enabled: set[str]) -> Optional[dict]:
+    """Parse a modinfo file and return module metadata, or None if disabled."""
     arch = ""
     objs = []
     deps = []
@@ -54,21 +55,39 @@ def generate(name: str, lines: list[str], enabled: set[str]) -> Optional[set[str
                 # don't add a module which dependency is not enabled
                 # in kconfig
                 if data.strip() not in enabled:
-                    print(f"    /* module {data.strip()} isn't enabled in Kconfig. */")
-                    print("/* },{ */")
                     return None
             else:
                 print("unknown:", kind)
                 exit(1)
 
-    print(f'    .name = "{name}",')
-    if arch != "":
-        print(f"    .arch = {arch},")
-    print_array("objs", objs)
-    print_array("deps", deps)
-    print_array("opts", opts)
+    return {
+        'name': name,
+        'arch': arch,
+        'objs': objs,
+        'deps': deps,
+        'opts': opts,
+        'dep_names': {dep.strip('" ') for dep in deps}
+    }
+
+def generate(modinfo: str, mod: Optional[dict],
+             skip_reason: Optional[str]) -> None:
+    """Generate C code for a module."""
+    print(f"    /* {modinfo} */")
+    if mod is None:
+        if skip_reason == "missing_deps":
+            print("    /* module has missing dependencies. */")
+        else:
+            print("    /* module isn't enabled in Kconfig. */")
+        print("/* },{ */")
+        return
+
+    print(f'    .name = "{mod["name"]}",')
+    if mod['arch'] != "":
+        print(f"    .arch = {mod['arch']},")
+    print_array("objs", mod['objs'])
+    print_array("deps", mod['deps'])
+    print_array("opts", mod['opts'])
     print("},{")
-    return {dep.strip('" ') for dep in deps}
 
 def print_pre() -> None:
     print("/* generated by scripts/modinfo-generate.py */")
@@ -86,6 +105,8 @@ def main() -> None:
     )
     parser.add_argument('--devices',
                         help='path to config-device.mak')
+    parser.add_argument('--skip-missing-deps', action='store_true',
+                        help='warn if a dependency is missing and continue')
     parser.add_argument('modinfo', nargs='+',
                         help='modinfo files to process')
     args = parser.parse_args()
@@ -99,27 +120,54 @@ def main() -> None:
                 if config[1].rstrip() == 'y':
                     enabled.add(config[0][7:])  # remove CONFIG_
 
-    deps = set()
-    modules = set()
-    print_pre()
+    # all_modules: modinfo path -> (basename, parsed module or None, skip_reason)
+    all_modules = {}
     for modinfo in args.modinfo:
         with open(modinfo) as f:
             lines = f.readlines()
-        print(f"    /* {modinfo} */")
         (basename, _) = os.path.splitext(modinfo)
-        moddeps = generate(basename, lines, enabled)
-        if moddeps is not None:
-            modules.add(basename)
-            deps.update(moddeps)
-    print_post()
+        mod = parse_modinfo(basename, lines, enabled)
+        skip_reason = "kconfig" if mod is None else None
+        all_modules[modinfo] = (basename, mod, skip_reason)
+
+    # Collect all available module names
+    available = {basename for basename, mod, _ in all_modules.values()
+                 if mod is not None}
 
-    error = False
-    for dep in deps.difference(modules):
+    # Collect all dependencies
+    all_deps = set()
+    for basename, mod, _ in all_modules.values():
+        if mod is not None:
+            all_deps.update(mod['dep_names'])
+
+    # Check for missing dependencies
+    missing = all_deps.difference(available)
+    for dep in missing:
         print(f"Dependency {dep} cannot be satisfied", file=sys.stderr)
-        error = True
 
-    if error:
+    if missing and not args.skip_missing_deps:
         exit(1)
 
+    # When skipping missing deps, iteratively remove modules with
+    # unsatisfiable dependencies
+    if args.skip_missing_deps and missing:
+        changed = True
+        while changed:
+            changed = False
+            for modinfo, (basename, mod, skip_reason) in list(all_modules.items()):
+                if mod is None:
+                    continue
+                if not mod['dep_names'].issubset(available):
+                    available.discard(basename)
+                    all_modules[modinfo] = (basename, None, "missing_deps")
+                    changed = True
+
+    # generate output
+    print_pre()
+    for modinfo in args.modinfo:
+        (basename, mod, skip_reason) = all_modules[modinfo]
+        generate(modinfo, mod, skip_reason)
+    print_post()
+
 if __name__ == "__main__":
     main()
-- 
2.52.0