summaryrefslogtreecommitdiff
path: root/scripts/modinfo-generate.py
blob: e231c129196c1a394cbbc11b7206a4221ba6f1d3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#!/usr/bin/env python3

import argparse
import os
import sys
from typing import Optional


def print_array(name: str, values: list[str]) -> None:
    if len(values) == 0:
        return
    list = ", ".join(values)
    print(f"    .{name} = ((const char*[]){{ {list}, NULL }}),")

def parse_line(line: str) -> tuple[str, str]:
    kind = ""
    data = ""
    get_kind = False
    get_data = False
    for item in line.split():
        if item == "MODINFO_START":
            get_kind = True
            continue
        if item.startswith("MODINFO_END"):
            get_data = False
            continue
        if get_kind:
            kind = item
            get_kind = False
            get_data = True
            continue
        if get_data:
            data += " " + item
            continue
    return (kind, data)

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 = []
    opts = []
    for line in lines:
        if "MODINFO_START" in line:
            (kind, data) = parse_line(line)
            if kind == 'obj':
                objs.append(data)
            elif kind == 'dep':
                deps.append(data)
            elif kind == 'opts':
                opts.append(data)
            elif kind == 'arch':
                arch = data
            elif kind == 'kconfig':
                # don't add a module which dependency is not enabled
                # in kconfig
                if data.strip() not in enabled:
                    return None
            else:
                print("unknown:", kind)
                exit(1)

    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("},{")

def print_pre() -> None:
    print("/* generated by scripts/modinfo-generate.py */")
    print("#include \"qemu/osdep.h\"")
    print("#include \"qemu/module.h\"")
    print("const QemuModinfo qemu_modinfo[] = {{")

def print_post() -> None:
    print("    /* end of list */")
    print("}};")

def main() -> None:
    parser = argparse.ArgumentParser(
        description='Generate C code for QEMU module info'
    )
    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()

    # get all devices enabled in kconfig, from *-config-device.mak
    enabled = set()
    if args.devices:
        with open(args.devices) as file:
            for line in file.readlines():
                config = line.split('=')
                if config[1].rstrip() == 'y':
                    enabled.add(config[0][7:])  # remove CONFIG_

    # 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()
        (basename, _) = os.path.splitext(modinfo)
        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}

    # 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)

    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()