The 'qapi.backend.QAPIBackend' class defines an API contract for code generators. The current generator is put into a new class 'qapi.backend.QAPICBackend' and made to be the default impl.
A custom generator can be requested using the '-k' arg which takes a fully qualified python class name qapi-gen.py -k the.python.module.QAPIMyBackend Signed-off-by: Daniel P. Berrangé <berra...@redhat.com> --- This is an impl of the idea I mentioned at: https://lists.nongnu.org/archive/html/qemu-devel/2025-02/msg03475.html With this change, it is possible for the Go generator code to live outside of qemu.git, invoked using: $ PYTHONPATH=/path/to/qemu.git/scripts \ python /path/to/qemu.git/scripts/qapi-gen.py \ -o somedir \ -k qapi.golang.golang.QAPIGoBackend \ /path/to/qemu.git/qga/qapi-schema.json The external app could just expect qemu.git to be checkedout somewhere convenient, or could use a git submodule to reference it. scripts/qapi/backend.py | 96 +++++++++++++++++++++++++++++++++++++++++ scripts/qapi/main.py | 65 ++++++++-------------------- 2 files changed, 113 insertions(+), 48 deletions(-) create mode 100644 scripts/qapi/backend.py diff --git a/scripts/qapi/backend.py b/scripts/qapi/backend.py new file mode 100644 index 0000000000..b6873fd2e3 --- /dev/null +++ b/scripts/qapi/backend.py @@ -0,0 +1,96 @@ +# This work is licensed under the terms of the GNU GPL, version 2 or later. +# See the COPYING file in the top-level directory. + +from abc import ABC, abstractmethod +from typing import Optional + +from .commands import gen_commands +from .common import must_match +from .events import gen_events +from .features import gen_features +from .introspect import gen_introspect +from .schema import QAPISchema +from .types import gen_types +from .visit import gen_visit + + +def invalid_prefix_char(prefix: str) -> Optional[str]: + match = must_match(r'([A-Za-z_.-][A-Za-z0-9_.-]*)?', prefix) + if match.end() != len(prefix): + return prefix[match.end()] + return None + + +class QAPIBackend(ABC): + + def run(self, + schema_file: str, + output_dir: str, + prefix: str, + unmask: bool = False, + builtins: bool = False, + gen_tracing: bool = False) -> None: + """ + Run the code generator for the given schema into the target directory. + + :param schema_file: The primary QAPI schema file. + :param output_dir: The output directory to store generated code. + :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? + + :raise QAPIError: On failures. + """ + assert invalid_prefix_char(prefix) is None + + schema = QAPISchema(schema_file) + self.generate(schema, output_dir, prefix, unmask, builtins, gen_tracing) + + @abstractmethod + def generate(self, + schema: QAPISchema, + output_dir: str, + prefix: str, + unmask: bool, + builtins: bool, + gen_tracing: bool) -> None: + """ + Generate code for the given schema into the target directory. + + :param schema: The primary QAPI schema object. + :param output_dir: The output directory to store generated code. + :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? + + :raise QAPIError: On failures. + """ + pass + + +class QAPICBackend(QAPIBackend): + + def generate(self, + schema: QAPISchema, + output_dir: str, + prefix: str, + unmask: bool, + builtins: bool, + gen_tracing: bool) -> None: + """ + Generate C code for the given schema into the target directory. + + :param schema_file: The primary QAPI schema file. + :param output_dir: The output directory to store generated code. + :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? + + :raise QAPIError: On failures. + """ + gen_types(schema, output_dir, prefix, builtins) + gen_features(schema, output_dir, prefix) + gen_visit(schema, output_dir, prefix, builtins) + gen_commands(schema, output_dir, prefix, gen_tracing) + gen_events(schema, output_dir, prefix) + gen_introspect(schema, output_dir, prefix, unmask) diff --git a/scripts/qapi/main.py b/scripts/qapi/main.py index 324081b9fc..35552dffce 100644 --- a/scripts/qapi/main.py +++ b/scripts/qapi/main.py @@ -8,53 +8,18 @@ """ import argparse +from importlib import import_module import sys -from typing import Optional -from .commands import gen_commands -from .common import must_match from .error import QAPIError -from .events import gen_events -from .features import gen_features -from .introspect import gen_introspect -from .schema import QAPISchema -from .types import gen_types -from .visit import gen_visit +from .backend import invalid_prefix_char -def invalid_prefix_char(prefix: str) -> Optional[str]: - match = must_match(r'([A-Za-z_.-][A-Za-z0-9_.-]*)?', prefix) - if match.end() != len(prefix): - return prefix[match.end()] - return None - - -def generate(schema_file: str, - output_dir: str, - prefix: str, - unmask: bool = False, - builtins: bool = False, - gen_tracing: bool = False) -> None: - """ - Generate C code for the given schema into the target directory. - - :param schema_file: The primary QAPI schema file. - :param output_dir: The output directory to store generated code. - :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? - - :raise QAPIError: On failures. - """ - assert invalid_prefix_char(prefix) is None - - schema = QAPISchema(schema_file) - gen_types(schema, output_dir, prefix, builtins) - gen_features(schema, output_dir, prefix) - gen_visit(schema, output_dir, prefix, builtins) - gen_commands(schema, output_dir, prefix, gen_tracing) - gen_events(schema, output_dir, prefix) - gen_introspect(schema, output_dir, prefix, unmask) +def import_class_from_string(path): + module_path, _, class_name = path.rpartition('.') + mod = import_module(module_path) + klass = getattr(mod, class_name) + return klass def main() -> int: @@ -77,6 +42,8 @@ def main() -> int: parser.add_argument('-u', '--unmask-non-abi-names', action='store_true', dest='unmask', help="expose non-ABI names in introspection") + parser.add_argument('-k', '--backend', default="qapi.backend.QAPICBackend", + help="Python module name for code generator") # Option --suppress-tracing exists so we can avoid solving build system # problems. TODO Drop it when we no longer need it. @@ -92,13 +59,15 @@ def main() -> int: print(f"{sys.argv[0]}: {msg}", file=sys.stderr) return 1 + backendclass = import_class_from_string(args.backend) try: - generate(args.schema, - output_dir=args.output_dir, - prefix=args.prefix, - unmask=args.unmask, - builtins=args.builtins, - gen_tracing=not args.suppress_tracing) + backend = backendclass() + backend.run(args.schema, + output_dir=args.output_dir, + prefix=args.prefix, + unmask=args.unmask, + builtins=args.builtins, + gen_tracing=not args.suppress_tracing) except QAPIError as err: print(err, file=sys.stderr) return 1 -- 2.47.1