From nobody Wed Sep 10 00:16:29 2025 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail(p=none dis=none) header.from=redhat.com Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1631018973039627.5281409061117; Tue, 7 Sep 2021 05:49:33 -0700 (PDT) Received: from localhost ([::1]:58836 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1mNaXa-0003AD-5u for importer@patchew.org; Tue, 07 Sep 2021 08:49:31 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:42638) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mNa8Y-0004k2-LY for qemu-devel@nongnu.org; Tue, 07 Sep 2021 08:23:38 -0400 Received: from us-smtp-delivery-124.mimecast.com ([170.10.133.124]:48076) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mNa8L-0007ti-GL for qemu-devel@nongnu.org; Tue, 07 Sep 2021 08:23:37 -0400 Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-230-NGMCS6RJMnqDFWAmf3rvpw-1; Tue, 07 Sep 2021 08:23:23 -0400 Received: from smtp.corp.redhat.com (int-mx08.intmail.prod.int.phx2.redhat.com [10.5.11.23]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 88342835DE1 for ; Tue, 7 Sep 2021 12:23:22 +0000 (UTC) Received: from localhost (unknown [10.39.208.23]) by smtp.corp.redhat.com (Postfix) with ESMTP id 51B9019733; Tue, 7 Sep 2021 12:23:13 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1631017404; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=8TzOn+P9Hh5f2yl0uOc582fZprACT7UpapXov530jGA=; b=EvLUx8AaGmfNREYpTSlpZA0yXzbsgewhmI8aQDlgTmMlveQSQtYmq3/C7moEBdL6SorJwe n5hfxfNNRgJjgn+0cOabLxGu8UXka6Hx0VMlNmh+CzFQWRHt5fjUs6M1IGGsKlVomrUs35 8nH63jm3yMwS3ogPTrFemW9ZM8s+lNM= X-MC-Unique: NGMCS6RJMnqDFWAmf3rvpw-1 From: marcandre.lureau@redhat.com To: qemu-devel@nongnu.org Subject: [RFC v3 16/32] scripts/qapi: add Rust FFI bindings generation Date: Tue, 7 Sep 2021 16:19:27 +0400 Message-Id: <20210907121943.3498701-17-marcandre.lureau@redhat.com> In-Reply-To: <20210907121943.3498701-1-marcandre.lureau@redhat.com> References: <20210907121943.3498701-1-marcandre.lureau@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.84 on 10.5.11.23 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=marcandre.lureau@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=170.10.133.124; envelope-from=marcandre.lureau@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: -24 X-Spam_score: -2.5 X-Spam_bar: -- X-Spam_report: (-2.5 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.391, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: pbonzini@redhat.com, berrange@redhat.com, armbru@redhat.com, stefanha@redhat.com, =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: fail (Header signature does not verify) X-ZM-MESSAGEID: 1631018973651100001 From: Marc-Andr=C3=A9 Lureau Generate the C QAPI types in Rust, with a few common niceties to Debug/Clone/Copy the Rust type. Signed-off-by: Marc-Andr=C3=A9 Lureau --- meson.build | 4 +- scripts/qapi/main.py | 23 ++- scripts/qapi/rs.py | 170 ++++++++++++++++ scripts/qapi/rs_ffi.py | 446 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 635 insertions(+), 8 deletions(-) create mode 100644 scripts/qapi/rs.py create mode 100644 scripts/qapi/rs_ffi.py diff --git a/meson.build b/meson.build index 29d218d35a..b45f409eb4 100644 --- a/meson.build +++ b/meson.build @@ -2012,7 +2012,9 @@ qapi_gen_depends =3D [ meson.source_root() / 'scripts= /qapi/__init__.py', meson.source_root() / 'scripts/qapi/types.py', meson.source_root() / 'scripts/qapi/visit.py', meson.source_root() / 'scripts/qapi/common.py', - meson.source_root() / 'scripts/qapi-gen.py' + meson.source_root() / 'scripts/qapi/rs.py', + meson.source_root() / 'scripts/qapi/rs_ffi.py', + meson.source_root() / 'scripts/qapi-gen.py', ] =20 tracetool =3D [ diff --git a/scripts/qapi/main.py b/scripts/qapi/main.py index f2ea6e0ce4..deba72ee4e 100644 --- a/scripts/qapi/main.py +++ b/scripts/qapi/main.py @@ -16,6 +16,7 @@ from .error import QAPIError from .events import gen_events from .introspect import gen_introspect +from .rs_ffi import gen_rs_ffitypes from .schema import QAPISchema from .types import gen_types from .visit import gen_visit @@ -32,7 +33,8 @@ def generate(schema_file: str, output_dir: str, prefix: str, unmask: bool =3D False, - builtins: bool =3D False) -> None: + builtins: bool =3D False, + rust: bool =3D False) -> None: """ Generate C code for the given schema into the target directory. =20 @@ -41,17 +43,21 @@ def generate(schema_file: str, :param prefix: Optional C-code prefix for symbol names. :param unmask: Expose non-ABI names through introspection? :param builtins: Generate code for built-in types? + :param rust: Generate Rust binding code? =20 :raise QAPIError: On failures. """ assert invalid_prefix_char(prefix) is None =20 schema =3D QAPISchema(schema_file) - gen_types(schema, output_dir, prefix, builtins) - gen_visit(schema, output_dir, prefix, builtins) - gen_commands(schema, output_dir, prefix) - gen_events(schema, output_dir, prefix) - gen_introspect(schema, output_dir, prefix, unmask) + if rust: + gen_rs_ffitypes(schema, output_dir, prefix) + else: + gen_types(schema, output_dir, prefix, builtins) + gen_visit(schema, output_dir, prefix, builtins) + gen_commands(schema, output_dir, prefix) + gen_events(schema, output_dir, prefix) + gen_introspect(schema, output_dir, prefix, unmask) =20 =20 def main() -> int: @@ -74,6 +80,8 @@ def main() -> int: parser.add_argument('-u', '--unmask-non-abi-names', action=3D'store_tr= ue', dest=3D'unmask', help=3D"expose non-ABI names in introspection") + parser.add_argument('-r', '--rust', action=3D'store_true', + help=3D"generate Rust binding code") parser.add_argument('schema', action=3D'store') args =3D parser.parse_args() =20 @@ -88,7 +96,8 @@ def main() -> int: output_dir=3Dargs.output_dir, prefix=3Dargs.prefix, unmask=3Dargs.unmask, - builtins=3Dargs.builtins) + builtins=3Dargs.builtins, + rust=3Dargs.rust) except QAPIError as err: print(f"{sys.argv[0]}: {str(err)}", file=3Dsys.stderr) return 1 diff --git a/scripts/qapi/rs.py b/scripts/qapi/rs.py new file mode 100644 index 0000000000..be42329fa4 --- /dev/null +++ b/scripts/qapi/rs.py @@ -0,0 +1,170 @@ +# This work is licensed under the terms of the GNU GPL, version 2. +# See the COPYING file in the top-level directory. +""" +QAPI Rust generator +""" + +import os +import re +import subprocess +from typing import NamedTuple, Optional + +from .common import POINTER_SUFFIX +from .gen import QAPIGen +from .schema import QAPISchemaModule, QAPISchemaVisitor + + +# see to_snake_case() below +snake_case =3D re.compile(r'((?<=3D[a-z0-9])[A-Z]|(?!^)[A-Z](?=3D[a-z]))') + + +rs_name_trans =3D str.maketrans('.-', '__') + + +# Map @name to a valid Rust identifier. +# If @protect, avoid returning certain ticklish identifiers (like +# keywords) by prepending raw identifier prefix 'r#'. +def rs_name(name: str, protect: bool =3D True) -> str: + name =3D name.translate(rs_name_trans) + if name[0].isnumeric(): + name =3D '_' + name + if not protect: + return name + # based from the list: + # https://doc.rust-lang.org/reference/keywords.html + if name in ('Self', 'abstract', 'as', 'async', + 'await', 'become', 'box', 'break', + 'const', 'continue', 'crate', 'do', + 'dyn', 'else', 'enum', 'extern', + 'false', 'final', 'fn', 'for', + 'if', 'impl', 'in', 'let', + 'loop', 'macro', 'match', 'mod', + 'move', 'mut', 'override', 'priv', + 'pub', 'ref', 'return', 'self', + 'static', 'struct', 'super', 'trait', + 'true', 'try', 'type', 'typeof', + 'union', 'unsafe', 'unsized', 'use', + 'virtual', 'where', 'while', 'yield'): + name =3D 'r#' + name + # avoid some clashes with the standard library + if name in ('String',): + name =3D 'Qapi' + name + + return name + + +class CType(NamedTuple): + is_pointer: bool + is_const: bool + is_list: bool + c_type: str + + +def rs_ctype_parse(c_type: str) -> CType: + is_pointer =3D False + if c_type.endswith(POINTER_SUFFIX): + is_pointer =3D True + c_type =3D c_type[:-len(POINTER_SUFFIX)] + is_list =3D c_type.endswith('List') + is_const =3D False + if c_type.startswith('const '): + is_const =3D True + c_type =3D c_type[6:] + + c_type =3D rs_name(c_type) + return CType(is_pointer, is_const, is_list, c_type) + + +def rs_ffitype(c_type: str, ffi_ns: str =3D 'qapi_ffi::', + list_as_newp: bool =3D False) -> str: + (is_pointer, is_const, is_list, c_type) =3D rs_ctype_parse(c_type) + c_type =3D rs_name(c_type) + to_rs =3D { + 'bool': 'bool', + 'char': 'libc::c_char', # for clarity (i8) + 'double': 'f64', + 'int': 'i64', + 'int16': 'i16', + 'int16_t': 'i16', + 'int32': 'i32', + 'int32_t': 'i32', + 'int64': 'i64', + 'int64_t': 'i64', + 'int8': 'i8', + 'int8_t': 'i8', + 'uint16': 'u16', + 'uint16_t': 'u16', + 'uint32': 'u32', + 'uint32_t': 'u32', + 'uint64': 'u64', + 'uint64_t': 'u64', + 'uint8': 'u8', + 'uint8_t': 'u8', + 'QObject': 'common::ffi::QObject', + 'QNull': 'common::ffi::QNull', + } + + ret =3D '' + if is_const and is_pointer: + ret +=3D '*const ' + elif is_pointer: + ret +=3D '*mut ' + if c_type in to_rs: + ret +=3D to_rs[c_type] + else: + ret +=3D ffi_ns + c_type + + if is_list and list_as_newp: + ret =3D 'NewPtr<{}>'.format(ret) + + return ret + + +def to_camel_case(value: str) -> str: + # special case for last enum value + if value =3D=3D '_MAX': + return value + raw_id =3D False + if value.startswith('r#'): + raw_id =3D True + value =3D value[2:] + value =3D ''.join('_' + word if word[0].isdigit() + else word[:1].upper() + word[1:] + for word in filter(None, re.split("[-_]+", value))) + if raw_id: + return 'r#' + value + return value + + +def to_snake_case(value: str) -> str: + return snake_case.sub(r'_\1', value).lower() + + +class QAPIGenRs(QAPIGen): + pass + + +class QAPISchemaRsVisitor(QAPISchemaVisitor): + + def __init__(self, prefix: str, what: str): + super().__init__() + self._prefix =3D prefix + self._what =3D what + self._gen =3D QAPIGenRs(self._prefix + self._what + '.rs') + self._main_module: Optional[str] =3D None + + def visit_module(self, name: Optional[str]) -> None: + if name is None: + return + if QAPISchemaModule.is_user_module(name): + if self._main_module is None: + self._main_module =3D name + + def write(self, output_dir: str) -> None: + self._gen.write(output_dir) + + pathname =3D os.path.join(output_dir, self._gen.fname) + try: + subprocess.check_call(['rustfmt', pathname]) + except FileNotFoundError: + pass diff --git a/scripts/qapi/rs_ffi.py b/scripts/qapi/rs_ffi.py new file mode 100644 index 0000000000..59e834b36a --- /dev/null +++ b/scripts/qapi/rs_ffi.py @@ -0,0 +1,446 @@ +# This work is licensed under the terms of the GNU GPL, version 2. +# See the COPYING file in the top-level directory. +""" +QAPI Rust sys/ffi generator +""" + +from pathlib import Path +from typing import ( + Dict, + List, + Optional, + Set, +) + +from .cabi import CABI, CABIEnum, gen_object_cabi +from .common import mcgen +from .rs import ( + QAPISchemaRsVisitor, + rs_ffitype, + rs_name, + to_camel_case, + to_snake_case, +) +from .schema import ( + QAPISchema, + QAPISchemaEnumMember, + QAPISchemaFeature, + QAPISchemaIfCond, + QAPISchemaModule, + QAPISchemaObjectType, + QAPISchemaObjectTypeMember, + QAPISchemaType, + QAPISchemaVariants, +) +from .source import QAPISourceInfo + + +objects_seen =3D set() + + +def gen_rs_ffi_enum(name: str, + ifcond: QAPISchemaIfCond, + members: List[QAPISchemaEnumMember]) -> str: + # append automatically generated _max value + enum_members =3D members + [QAPISchemaEnumMember('_MAX', None)] + + ret =3D mcgen(''' + +%(cfg)s +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(C)] +pub enum %(rs_name)s { +''', + cfg=3Difcond.rsgen(), + rs_name=3Drs_name(name)) + + for member in enum_members: + ret +=3D mcgen(''' + %(cfg)s + %(c_enum)s, +''', + cfg=3Dmember.ifcond.rsgen(), + c_enum=3Dto_camel_case(rs_name(member.name))) + # picked the first, since that's what malloc0 does + # but arguably could use _MAX instead, or a qapi annotation + default =3D to_camel_case(rs_name(enum_members[0].name)) + ret +=3D mcgen(''' +} + +%(cfg)s +impl Default for %(rs_name)s { + #[inline] + fn default() -> %(rs_name)s { + Self::%(default)s + } +} +''', + cfg=3Difcond.rsgen(), + rs_name=3Drs_name(name), + default=3Ddefault) + return ret + + +def gen_rs_ffi_struct_members(members: + List[QAPISchemaObjectTypeMember]) -> str: + ret =3D '' + for memb in members: + if memb.optional: + ret +=3D mcgen(''' + %(cfg)s + pub has_%(rs_name)s: bool, +''', + cfg=3Dmemb.ifcond.rsgen(), + rs_name=3Dto_snake_case(rs_name(memb.name, + protect=3DFalse))) + ret +=3D mcgen(''' + %(cfg)s + pub %(rs_name)s: %(rs_ffitype)s, +''', + cfg=3Dmemb.ifcond.rsgen(), + rs_ffitype=3Drs_ffitype(memb.type.c_type(), ''), + rs_name=3Dto_snake_case(rs_name(memb.name))) + return ret + + +def gen_rs_ffi_free(name: str, ifcond: QAPISchemaIfCond) -> str: + name =3D rs_name(name, protect=3DFalse) + typ =3D rs_name(name) + return mcgen(''' + +%(cfg)s +extern "C" { + pub fn qapi_free_%(name)s(obj: *mut %(ty)s); +} +''', cfg=3Difcond.rsgen(), name=3Dname, ty=3Dtyp) + + +def gen_rs_ffi_variants(name: str, + ifcond: QAPISchemaIfCond, + variants: Optional[QAPISchemaVariants]) -> str: + ret =3D mcgen(''' + +%(cfg)s +#[repr(C)] +#[derive(Copy, Clone)] +pub union %(rs_name)s { /* union tag is @%(tag_name)s */ +''', + cfg=3Difcond.rsgen(), + tag_name=3Drs_name(variants.tag_member.name), + rs_name=3Drs_name(name)) + + empty =3D True + for var in variants.variants: + if var.type.name =3D=3D 'q_empty': + continue + empty =3D False + ret +=3D mcgen(''' + %(cfg)s + pub %(rs_name)s: %(rs_ffitype)s, +''', + cfg=3Dvar.ifcond.rsgen(), + rs_ffitype=3Drs_ffitype(var.type.c_unboxed_type(), ''= ), + rs_name=3Drs_name(var.name)) + + ret +=3D mcgen(''' + pub qapi_dummy: QapiDummy, +''') + + ret +=3D mcgen(''' +} + +%(cfg)s +impl ::std::fmt::Debug for %(rs_name)s { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + f.debug_struct(&format!("%(rs_name)s @ {:?}", self as *const _)) + .finish() + } +} +''', cfg=3Difcond.rsgen(), rs_name=3Drs_name(name)) + + if empty: + ret +=3D mcgen(''' + +%(cfg)s +impl ::std::default::Default for %(rs_name)s { + fn default() -> Self { + Self { qapi_dummy: QapiDummy } + } +} +''', cfg=3Difcond.rsgen(), rs_name=3Drs_name(name)) + return ret + + +def gen_rs_ffi_object(name: str, + ifcond: QAPISchemaIfCond, + base: Optional[QAPISchemaObjectType], + members: List[QAPISchemaObjectTypeMember], + variants: Optional[QAPISchemaVariants]) -> str: + if name in objects_seen: + return '' + + ret =3D '' + objects_seen.add(name) + unionty =3D name + 'Union' + if variants: + for var in variants.variants: + if isinstance(var.type, QAPISchemaObjectType): + ret +=3D gen_rs_ffi_object(var.type.name, + var.type.ifcond, + var.type.base, + var.type.local_members, + var.type.variants) + ret +=3D gen_rs_ffi_variants(unionty, ifcond, variants) + + ret +=3D gen_rs_ffi_free(name, ifcond) + ret +=3D mcgen(''' + +%(cfg)s +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct %(rs_name)s { +''', + cfg=3Difcond.rsgen(), + rs_name=3Drs_name(name)) + + if base: + if not base.is_implicit(): + ret +=3D mcgen(''' + // Members inherited: +''') + ret +=3D gen_rs_ffi_struct_members(base.members) + if not base.is_implicit(): + ret +=3D mcgen(''' + // Own members: +''') + + ret +=3D gen_rs_ffi_struct_members(members) + if variants: + ret +=3D mcgen(''' + pub u: %(unionty)s +''', unionty=3Drs_name(unionty)) + + empty =3D False + # for compatibility with C ABI + if (not base or base.is_empty()) and not members and not variants: + empty =3D True + ret +=3D mcgen(''' + pub qapi_dummy_for_empty_struct: u8, +''') + ret +=3D mcgen(''' +} +''') + + if empty: + ret +=3D mcgen(''' + +%(cfg)s +impl Default for %(rs_name)s { + fn default() -> Self { + Self { qapi_dummy_for_empty_struct: 0 } + } +} +''', + cfg=3Difcond.rsgen(), + rs_name=3Drs_name(name)) + + return ret + + +def gen_rs_ffi_variant(name: str, + ifcond: QAPISchemaIfCond, + variants: Optional[QAPISchemaVariants]) -> str: + if name in objects_seen: + return '' + + objects_seen.add(name) + + ret =3D '' + gen_variants =3D '' + for var in variants.variants: + if var.type.name =3D=3D 'q_empty': + continue + typ =3D rs_ffitype(var.type.c_unboxed_type(), '') + gen_variants +=3D mcgen(''' + %(cfg)s + pub %(mem_name)s: %(rs_ffitype)s, +''', + cfg=3Dvar.ifcond.rsgen(), + rs_ffitype=3Dtyp, + mem_name=3Drs_name(var.name)) + if not gen_variants: + ret +=3D mcgen(''' + impl ::std::default::Default for %(rs_name)sUnion { + fn default() -> Self { + Self { qapi_dummy: QapiDummy } + } + } +''') + gen_variants +=3D mcgen(''' + pub qapi_dummy: QapiDummy, +''') + + ret +=3D gen_rs_ffi_free(name, ifcond) + ret +=3D mcgen(''' + +%(cfg)s +#[repr(C)] +#[derive(Copy,Clone)] +pub union %(rs_name)sUnion { + %(variants)s +} + +%(cfg)s +impl ::std::fmt::Debug for %(rs_name)sUnion { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + f.debug_struct(&format!("%(rs_name)sUnion @ {:?}", self as *const = _)) + .finish() + } +} + +%(cfg)s +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct %(rs_name)s { + pub %(tag)s: QType, + pub u: %(rs_name)sUnion, +} +''', + cfg=3Difcond.rsgen(), + rs_name=3Drs_name(name), + tag=3Drs_name(variants.tag_member.name), + variants=3Dgen_variants) + return ret + + +def gen_rs_ffi_array(name: str, + ifcond: QAPISchemaIfCond, + element_type: QAPISchemaType) -> str: + ret =3D mcgen(''' + +%(cfg)s +#[repr(C)] +#[derive(Copy, Clone)] +pub struct %(rs_name)s { + pub next: *mut %(rs_name)s, + pub value: %(rs_ffitype)s, +} + +%(cfg)s +impl ::std::fmt::Debug for %(rs_name)s { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + f.debug_struct(&format!("%(rs_name)s @ {:?}", self as *const _)) + .finish() + } +} +''', + cfg=3Difcond.rsgen(), + rs_name=3Drs_name(name), + rs_ffitype=3Drs_ffitype(element_type.c_type(), '')) + ret +=3D gen_rs_ffi_free(name, ifcond) + return ret + + +class QAPISchemaGenRsFFITypeVisitor(QAPISchemaRsVisitor): + + def __init__(self, prefix: str): + super().__init__(prefix, 'qapi-ffi-types') + self._cabi: Dict[str, CABI] =3D {} + self._cabi_functions: List[str] =3D [] + self._cabi_functions_called: Set[str] =3D set() + + def _cabi_add(self, cabis: List[CABI]) -> None: + for cabi in cabis: + self._cabi.setdefault(cabi.name, cabi) + + def visit_begin(self, schema: QAPISchema) -> None: + # gen_object() is recursive, ensure it doesn't visit the empty type + objects_seen.add(schema.the_empty_object_type.name) + self._gen.preamble_add( + mcgen(''' +// generated by qapi-gen, DO NOT EDIT + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct QapiDummy; +''')) + + def _get_qapi_cabi_fn(self, name: str) -> str: + fn_name =3D 'cabi' + if QAPISchemaModule.is_builtin_module(name): + fn_name +=3D '_builtin' + elif name !=3D self._main_module: + name =3D Path(name).stem + fn_name +=3D '_' + rs_name(name, False) + return fn_name + + def visit_include(self, name: str, info: Optional[QAPISourceInfo]) -> = None: + super().visit_include(name, info) + cabi_fn =3D self._get_qapi_cabi_fn(name) + if cabi_fn not in self._cabi_functions_called: + self._cabi_functions.append(cabi_fn) + + def visit_module_end(self, name: str) -> None: + cabi_gen =3D "".join(f' {fn}();\n' for fn in self._cabi_functio= ns) + self._cabi_functions_called |=3D set(self._cabi_functions) + self._cabi_functions =3D [] + cabi_gen +=3D "".join( + [c.gen_rs() for _, c in sorted(self._cabi.items())] + ) + self._cabi =3D {} + fn_name =3D self._get_qapi_cabi_fn(name) + self._gen.add(mcgen(''' +#[cfg(QAPI_CABI)] +pub(crate) fn %(fn_name)s() { +%(cabi_gen)s +} +''', fn_name=3Dfn_name, cabi_gen=3Dcabi_gen)) + + def visit_enum_type(self, + name: str, + info: Optional[QAPISourceInfo], + ifcond: QAPISchemaIfCond, + features: List[QAPISchemaFeature], + members: List[QAPISchemaEnumMember], + prefix: Optional[str]) -> None: + self._gen.add(gen_rs_ffi_enum(name, ifcond, members)) + self._cabi_add([CABIEnum(name, ifcond, members, prefix)]) + + def visit_array_type(self, + name: str, + info: Optional[QAPISourceInfo], + ifcond: QAPISchemaIfCond, + element_type: QAPISchemaType) -> None: + self._gen.add(gen_rs_ffi_array(name, ifcond, element_type)) + + def visit_object_type(self, + name: str, + info: Optional[QAPISourceInfo], + ifcond: QAPISchemaIfCond, + features: List[QAPISchemaFeature], + base: Optional[QAPISchemaObjectType], + members: List[QAPISchemaObjectTypeMember], + variants: Optional[QAPISchemaVariants]) -> None: + # Nothing to do for the special empty builtin + if name =3D=3D 'q_empty': + return + self._gen.add(gen_rs_ffi_object(name, ifcond, base, members, varia= nts)) + self._cabi_add(gen_object_cabi(name, ifcond, base, members, varian= ts)) + + def visit_alternate_type(self, + name: str, + info: QAPISourceInfo, + ifcond: QAPISchemaIfCond, + features: List[QAPISchemaFeature], + variants: QAPISchemaVariants) -> None: + self._gen.add(gen_rs_ffi_variant(name, ifcond, variants)) + self._cabi_add(gen_object_cabi(name, ifcond, None, + [variants.tag_member], variants)) + + +def gen_rs_ffitypes(schema: QAPISchema, + output_dir: str, + prefix: str) -> None: + vis =3D QAPISchemaGenRsFFITypeVisitor(prefix) + schema.visit(vis) + vis.write(output_dir) --=20 2.33.0.113.g6c40894d24