Skip to content

Commit e4b7633

Browse files
committed
scripts: Introduce kvm_bindings.py
`kvm_bindings.py` contains functions used to extract `serde_impl` marked structures from our `kvm` repo, add custom attributes to those structures, and bindings generation using `bindgen-ctl` (>= 0.71.1) from prepared headers. Signed-off-by: Ruoqing He <[email protected]>
1 parent 5ef415e commit e4b7633

File tree

1 file changed

+129
-0
lines changed

1 file changed

+129
-0
lines changed

scripts/lib/kvm_bindings.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Copyright 2025 © Institute of Software, CAS. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
import re
5+
import os
6+
import subprocess
7+
from pathlib import Path
8+
from lib.kernel_source import prepare_source
9+
from lib import SUPPORT_ARCHS
10+
11+
12+
KVM_BINDINGS_DIR = "kvm-bindings/src/"
13+
14+
15+
def generate_kvm_bindings(args):
16+
installed_header_path = prepare_source(args)
17+
18+
# If arch is not provided, install headers for all supported archs
19+
if args.arch is None:
20+
for arch in SUPPORT_ARCHS:
21+
generate_bindings(
22+
installed_header_path, arch, args.attribute, args.output_path
23+
)
24+
else:
25+
generate_bindings(
26+
installed_header_path, args.arch, args.attribute, args.output_path
27+
)
28+
29+
30+
def generate_bindings(
31+
installed_header_path: str, arch: str, attribute: str, output_path: str
32+
):
33+
try:
34+
# Locate `kvm.h` of specific architecture
35+
arch_headers = os.path.join(installed_header_path, f"{arch}_headers")
36+
kvm_header = Path(os.path.join(arch_headers, f"include/linux/kvm.h"))
37+
if not kvm_header.is_file():
38+
raise FileNotFoundError(f"KVM header missing at {kvm_header}")
39+
40+
structs = capture_serde(arch)
41+
if not structs:
42+
raise RuntimeError(
43+
f"No structs found for {arch}, you need to invoke this command under rustvmm/kvm repo root"
44+
)
45+
46+
# Build bindgen-cli command with dynamic paths and custom attribute for
47+
# structures
48+
base_cmd = [
49+
"bindgen",
50+
os.path.abspath(kvm_header),
51+
"--impl-debug",
52+
"--impl-partialeq",
53+
"--with-derive-default",
54+
"--with-derive-partialeq",
55+
]
56+
57+
for struct in structs:
58+
base_cmd += ["--with-attribute-custom-struct", f"{struct}={attribute}"]
59+
60+
# Add include paths relative to source directory
61+
base_cmd += ["--", f"-I{arch_headers}/include"] # Use absolute include path
62+
63+
print(f"\nGenerating bindings for {arch}...")
64+
bindings = subprocess.run(
65+
base_cmd, check=True, capture_output=True, text=True, encoding="utf-8"
66+
).stdout
67+
68+
print("Successfully generated bindings")
69+
70+
output_file_path = f"{output_path}/{arch}/bindings.rs"
71+
72+
print(f"Generating to: {output_file_path}")
73+
74+
except subprocess.CalledProcessError as e:
75+
err_msg = f"Bindgen failed (code {e.returncode})"
76+
raise RuntimeError(err_msg) from e
77+
except Exception as e:
78+
raise RuntimeError(f"Generation failed: {str(e)}") from e
79+
80+
try:
81+
with open(output_file_path, "w") as f:
82+
f.write(bindings)
83+
84+
# Format with rustfmt
85+
subprocess.run(["rustfmt", output_file_path], check=True)
86+
print(f"Generation succeeded: {output_file_path}")
87+
except subprocess.CalledProcessError:
88+
raise RuntimeError("rustfmt formatting failed")
89+
except IOError as e:
90+
raise RuntimeError(f"File write error: {str(e)}")
91+
92+
93+
def capture_serde(arch: str) -> list[str]:
94+
"""
95+
Parse serde implementations for specified architecture
96+
"""
97+
98+
# Locate `serialize.rs` of specific architecture
99+
target_path = Path(f"{KVM_BINDINGS_DIR}/{arch}/serialize.rs")
100+
101+
# Validate file existence
102+
if not target_path.is_file():
103+
raise FileNotFoundError(
104+
f"Serialization file not found for {arch}: {target_path}"
105+
)
106+
107+
print(f"Extracting serde structs of {arch} from: {target_path}")
108+
109+
content = target_path.read_text(encoding="utf-8")
110+
111+
pattern = re.compile(
112+
r"serde_impls!\s*\{\s*(?P<struct>.*?)\s*\}", re.DOTALL | re.MULTILINE
113+
)
114+
115+
# Extract struct list from matched block
116+
match = pattern.search(content)
117+
if not match:
118+
raise ValueError(f"No serde_impls! block found in {target_path}")
119+
120+
struct_list = match.group("struct")
121+
122+
structs = []
123+
for line in struct_list.splitlines():
124+
for word in line.split():
125+
clean_word = word.strip().rstrip(",")
126+
if clean_word:
127+
structs.append(clean_word)
128+
129+
return structs

0 commit comments

Comments
 (0)