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


Reply via email to