mirror of
https://github.com/qemu/qemu.git
synced 2026-04-23 06:33:31 +00:00
Move raw FFI bindings generation to separate crates. This makes it possible to reuse bindgen declarations for a header file in its dependencies (this was not the case before this change), while keeping multiple -sys crates to avoid rebuilding all the code whenever something changes. Because the -sys crates are generated in dependency order, this also enforces that the crates are organized in something that resembles the dependencies between C headers. The meson.build for rust-safe crates becomes simpler, and it should be possible in the future to let Meson's cargo support handle most of it. Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> [General cleanup and Python script. - Paolo] Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
165 lines
4.9 KiB
Python
165 lines
4.9 KiB
Python
#!/usr/bin/env python3
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
"""
|
|
Generate bindgen arguments from Cargo.toml metadata for QEMU's Rust FFI bindings.
|
|
|
|
Author: Paolo Bonzini <pbonzini@redhat.com>
|
|
|
|
Copyright (C) 2025 Red Hat, Inc.
|
|
|
|
This script processes Cargo.toml file for QEMU's bindings crates (util-sys,
|
|
chardev-sys, qom-sys, etc.); it generates bindgen command lines that allow
|
|
easy customization and that export the right headers in each bindings crate.
|
|
|
|
For detailed information, see docs/devel/rust.rst.
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import sys
|
|
import argparse
|
|
from pathlib import Path
|
|
from dataclasses import dataclass
|
|
from typing import Iterable, List, Dict, Any
|
|
|
|
try:
|
|
import tomllib
|
|
except ImportError:
|
|
import tomli as tomllib # type: ignore
|
|
|
|
INCLUDE_RE = re.compile(r'^#include\s+"([^"]+)"')
|
|
OPTIONS = [
|
|
"bitfield-enum",
|
|
"newtype-enum",
|
|
"newtype-global-enum",
|
|
"rustified-enum",
|
|
"rustified-non-exhaustive-enum",
|
|
"constified-enum",
|
|
"constified-enum-module",
|
|
"normal-alias",
|
|
"new-type-alias",
|
|
"new-type-alias-deref",
|
|
"bindgen-wrapper-union",
|
|
"manually-drop-union",
|
|
"blocklist-type",
|
|
"blocklist-function",
|
|
"blocklist-item",
|
|
"blocklist-file",
|
|
"blocklist-var",
|
|
"opaque-type",
|
|
"no-partialeq",
|
|
"no-copy",
|
|
"no-debug",
|
|
"no-default",
|
|
"no-hash",
|
|
"must-use-type",
|
|
"with-derive-custom",
|
|
"with-derive-custom-struct",
|
|
"with-derive-custom-enum",
|
|
"with-derive-custom-union",
|
|
"with-attribute-custom",
|
|
"with-attribute-custom-struct",
|
|
"with-attribute-custom-enum",
|
|
"with-attribute-custom-union",
|
|
]
|
|
|
|
|
|
@dataclass
|
|
class BindgenInfo:
|
|
cmd_args: List[str]
|
|
inputs: List[str]
|
|
|
|
|
|
def extract_includes(lines: Iterable[str]) -> List[str]:
|
|
"""Extract #include directives from a file."""
|
|
includes: List[str] = []
|
|
for line in lines:
|
|
match = INCLUDE_RE.match(line.strip())
|
|
if match:
|
|
includes.append(match.group(1))
|
|
return includes
|
|
|
|
|
|
def build_bindgen_args(metadata: Dict[str, Any]) -> List[str]:
|
|
"""Build command line arguments from [package.metadata.bindgen]."""
|
|
args: List[str] = []
|
|
for key, values in metadata.items():
|
|
if key in OPTIONS:
|
|
flag = f"--{key}"
|
|
assert isinstance(values, list)
|
|
for value in values:
|
|
args.append(flag)
|
|
args.append(value)
|
|
|
|
return args
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(
|
|
description="Generate bindgen arguments from Cargo.toml metadata"
|
|
)
|
|
parser.add_argument(
|
|
"directories", nargs="+", help="Directories containing Cargo.toml files"
|
|
)
|
|
parser.add_argument(
|
|
"-I",
|
|
"--include-root",
|
|
default=None,
|
|
help="Base path for --allowlist-file/--blocklist-file",
|
|
)
|
|
parser.add_argument("--source-dir", default=os.getcwd(), help="Source directory")
|
|
parser.add_argument("-o", "--output", required=True, help="Output file")
|
|
parser.add_argument("--dep-file", help="Dependency file to write")
|
|
args = parser.parse_args()
|
|
|
|
prev_allowlist_files: Dict[str, object] = {}
|
|
bindgen_infos: Dict[str, BindgenInfo] = {}
|
|
|
|
os.chdir(args.source_dir)
|
|
include_root = args.include_root or args.source_dir
|
|
for directory in args.directories:
|
|
cargo_path = Path(directory) / "Cargo.toml"
|
|
inputs = [str(Path(args.source_dir) / cargo_path)]
|
|
|
|
with open(cargo_path, "rb") as f:
|
|
cargo_toml = tomllib.load(f)
|
|
|
|
metadata = cargo_toml.get("package", {}).get("metadata", {}).get("bindgen", {})
|
|
input_file = Path(directory) / metadata["header"]
|
|
inputs.append(str(Path(args.source_dir) / input_file))
|
|
|
|
cmd_args = build_bindgen_args(metadata)
|
|
|
|
# Each include file is allowed for this file and blocked in the
|
|
# next ones
|
|
for blocklist_path in prev_allowlist_files:
|
|
cmd_args.extend(["--blocklist-file", blocklist_path])
|
|
with open(input_file, "r", encoding="utf-8", errors="ignore") as f:
|
|
includes = extract_includes(f)
|
|
for allowlist_file in includes + metadata.get("additional-files", []):
|
|
allowlist_path = Path(include_root) / allowlist_file
|
|
cmd_args.extend(["--allowlist-file", str(allowlist_path)])
|
|
prev_allowlist_files.setdefault(str(allowlist_path), True)
|
|
|
|
bindgen_infos[directory] = BindgenInfo(cmd_args=cmd_args, inputs=inputs)
|
|
|
|
# now write the output
|
|
with open(args.output, "w") as f:
|
|
for directory, info in bindgen_infos.items():
|
|
args_sh = " ".join(info.cmd_args)
|
|
f.write(f"{directory}={args_sh}\n")
|
|
|
|
if args.dep_file:
|
|
with open(args.dep_file, "w") as f:
|
|
deps: List[str] = []
|
|
for info in bindgen_infos.values():
|
|
deps += info.inputs
|
|
f.write(f"{os.path.basename(args.output)}: {' '.join(deps)}\n")
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|