master
py 173 lines 5.37 KB
Raw
1 #!/usr/bin/env python3
2
3 import argparse
4 import os
5 import sys
6 from typing import Optional
7
8
9 def print_array(name: str, values: list[str]) -> None:
10 if len(values) == 0:
11 return
12 list = ", ".join(values)
13 print(f" .{name} = ((const char*[]){{ {list}, NULL }}),")
14
15 def parse_line(line: str) -> tuple[str, str]:
16 kind = ""
17 data = ""
18 get_kind = False
19 get_data = False
20 for item in line.split():
21 if item == "MODINFO_START":
22 get_kind = True
23 continue
24 if item.startswith("MODINFO_END"):
25 get_data = False
26 continue
27 if get_kind:
28 kind = item
29 get_kind = False
30 get_data = True
31 continue
32 if get_data:
33 data += " " + item
34 continue
35 return (kind, data)
36
37 def parse_modinfo(name: str, lines: list[str], enabled: set[str]) -> Optional[dict]:
38 """Parse a modinfo file and return module metadata, or None if disabled."""
39 arch = ""
40 objs = []
41 deps = []
42 opts = []
43 for line in lines:
44 if "MODINFO_START" in line:
45 (kind, data) = parse_line(line)
46 if kind == 'obj':
47 objs.append(data)
48 elif kind == 'dep':
49 deps.append(data)
50 elif kind == 'opts':
51 opts.append(data)
52 elif kind == 'arch':
53 arch = data
54 elif kind == 'kconfig':
55 # don't add a module which dependency is not enabled
56 # in kconfig
57 if data.strip() not in enabled:
58 return None
59 else:
60 print("unknown:", kind)
61 exit(1)
62
63 return {
64 'name': name,
65 'arch': arch,
66 'objs': objs,
67 'deps': deps,
68 'opts': opts,
69 'dep_names': {dep.strip('" ') for dep in deps}
70 }
71
72 def generate(modinfo: str, mod: Optional[dict],
73 skip_reason: Optional[str]) -> None:
74 """Generate C code for a module."""
75 print(f" /* {modinfo} */")
76 if mod is None:
77 if skip_reason == "missing_deps":
78 print(" /* module has missing dependencies. */")
79 else:
80 print(" /* module isn't enabled in Kconfig. */")
81 print("/* },{ */")
82 return
83
84 print(f' .name = "{mod["name"]}",')
85 if mod['arch'] != "":
86 print(f" .arch = {mod['arch']},")
87 print_array("objs", mod['objs'])
88 print_array("deps", mod['deps'])
89 print_array("opts", mod['opts'])
90 print("},{")
91
92 def print_pre() -> None:
93 print("/* generated by scripts/modinfo-generate.py */")
94 print("#include \"qemu/osdep.h\"")
95 print("#include \"qemu/module.h\"")
96 print("const QemuModinfo qemu_modinfo[] = {{")
97
98 def print_post() -> None:
99 print(" /* end of list */")
100 print("}};")
101
102 def main() -> None:
103 parser = argparse.ArgumentParser(
104 description='Generate C code for QEMU module info'
105 )
106 parser.add_argument('--devices',
107 help='path to config-device.mak')
108 parser.add_argument('--skip-missing-deps', action='store_true',
109 help='warn if a dependency is missing and continue')
110 parser.add_argument('modinfo', nargs='+',
111 help='modinfo files to process')
112 args = parser.parse_args()
113
114 # get all devices enabled in kconfig, from *-config-device.mak
115 enabled = set()
116 if args.devices:
117 with open(args.devices) as file:
118 for line in file.readlines():
119 config = line.split('=')
120 if config[1].rstrip() == 'y':
121 enabled.add(config[0][7:]) # remove CONFIG_
122
123 # all_modules: modinfo path -> (basename, parsed module or None, skip_reason)
124 all_modules = {}
125 for modinfo in args.modinfo:
126 with open(modinfo) as f:
127 lines = f.readlines()
128 (basename, _) = os.path.splitext(modinfo)
129 mod = parse_modinfo(basename, lines, enabled)
130 skip_reason = "kconfig" if mod is None else None
131 all_modules[modinfo] = (basename, mod, skip_reason)
132
133 # Collect all available module names
134 available = {basename for basename, mod, _ in all_modules.values()
135 if mod is not None}
136
137 # Collect all dependencies
138 all_deps = set()
139 for basename, mod, _ in all_modules.values():
140 if mod is not None:
141 all_deps.update(mod['dep_names'])
142
143 # Check for missing dependencies
144 missing = all_deps.difference(available)
145 for dep in missing:
146 print(f"Dependency {dep} cannot be satisfied", file=sys.stderr)
147
148 if missing and not args.skip_missing_deps:
149 exit(1)
150
151 # When skipping missing deps, iteratively remove modules with
152 # unsatisfiable dependencies
153 if args.skip_missing_deps and missing:
154 changed = True
155 while changed:
156 changed = False
157 for modinfo, (basename, mod, skip_reason) in list(all_modules.items()):
158 if mod is None:
159 continue
160 if not mod['dep_names'].issubset(available):
161 available.discard(basename)
162 all_modules[modinfo] = (basename, None, "missing_deps")
163 changed = True
164
165 # generate output
166 print_pre()
167 for modinfo in args.modinfo:
168 (basename, mod, skip_reason) = all_modules[modinfo]
169 generate(modinfo, mod, skip_reason)
170 print_post()
171
172 if __name__ == "__main__":
173 main()