QEMU main repository: Please see https://www.qemu.org/docs/master/devel/submitting-a-patch.html for how to submit changes to QEMU. Pull Requests are ignored. Please only use release tarballs from the QEMU website. http://www.qemu.org
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
173 lines
5.4 KiB
173 lines
5.4 KiB
#!/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()
|
|
|