@@ -13,7 +13,7 @@
# Just for type hint on self
from __future__ import annotations
-import os, shutil
+import os, shutil, textwrap
from typing import List, Optional
from ..schema import (
@@ -30,6 +30,65 @@
)
from ..source import QAPISourceInfo
+TEMPLATE_GENERATED_HEADER = """
+/*
+ * Copyright 2025 Red Hat, Inc.
+ * SPDX-License-Identifier: (MIT-0 and GPL-2.0-or-later)
+ */
+
+/****************************************************************************
+ * THIS CODE HAS BEEN GENERATED. DO NOT CHANGE IT DIRECTLY *
+ ****************************************************************************/
+package {package_name}
+"""
+
+TEMPLATE_GO_IMPORTS = """
+import (
+{imports}
+)
+"""
+
+TEMPLATE_ENUM = """
+type {name} string
+
+const (
+{fields}
+)
+"""
+
+
+# Takes the documentation object of a specific type and returns
+# that type's documentation and its member's docs.
+def qapi_to_golang_struct_docs(doc: QAPIDoc) -> (str, Dict[str, str]):
+ if doc is None:
+ return "", {}
+
+ cmt = "// "
+ fmt = textwrap.TextWrapper(
+ width=70, initial_indent=cmt, subsequent_indent=cmt
+ )
+ main = fmt.fill(doc.body.text)
+
+ for section in doc.sections:
+ # TODO is not a relevant section to Go applications
+ if section.tag in ["TODO"]:
+ continue
+
+ if main != "":
+ # Give empty line as space for the tag.
+ main += "\n//\n"
+
+ tag = "" if section.tag is None else f"{section.tag}: "
+ text = section.text.replace(" ", " ")
+ main += fmt.fill(f"{tag}{text}")
+
+ fields = {}
+ for key, value in doc.args.items():
+ if len(value.text) > 0:
+ fields[key] = " ".join(value.text.replace("\n", " ").split())
+
+ return main, fields
+
def gen_golang(schema: QAPISchema, output_dir: str, prefix: str) -> None:
vis = QAPISchemaGenGolangVisitor(prefix)
@@ -37,20 +96,90 @@ def gen_golang(schema: QAPISchema, output_dir: str, prefix: str) -> None:
vis.write(output_dir)
+def qapi_to_field_name_enum(name: str) -> str:
+ return name.title().replace("-", "")
+
+
+def fetch_indent_blocks_over_enum_with_docs(
+ name: str, members: List[QAPISchemaEnumMember], docfields: Dict[str, str]
+) -> Tuple[int]:
+ maxname = 0
+ blocks: List[int] = [0]
+ for member in members:
+ # For simplicity, every time we have doc, we add a new indent block
+ hasdoc = member.name is not None and member.name in docfields
+
+ enum_name = f"{name}{qapi_to_field_name_enum(member.name)}"
+ maxname = (
+ max(maxname, len(enum_name)) if not hasdoc else len(enum_name)
+ )
+
+ if hasdoc:
+ blocks.append(maxname)
+ else:
+ blocks[-1] = maxname
+
+ return blocks
+
+
+def generate_content_from_dict(data: dict[str, str]) -> str:
+ content = ""
+
+ for name in sorted(data):
+ content += data[name]
+
+ return content.replace("\n\n\n", "\n\n")
+
+
+def generate_template_imports(words: List[str]) -> str:
+ if len(words) == 0:
+ return ""
+
+ if len(words) == 1:
+ return '\nimport "{words[0]}"\n'
+
+ return TEMPLATE_GO_IMPORTS.format(
+ imports="\n".join(f'\t"{w}"' for w in words)
+ )
+
+
class QAPISchemaGenGolangVisitor(QAPISchemaVisitor):
# pylint: disable=too-many-arguments
def __init__(self, _: str):
super().__init__()
gofiles = ("protocol.go",)
+ # Map each qapi type to the necessary Go imports
+ types = {
+ "enum": [],
+ }
+
self.schema: QAPISchema
self.golang_package_name = "qapi"
self.duplicate = list(gofiles)
+ self.enums: dict[str, str] = {}
+ self.docmap = {}
+
+ self.types = dict.fromkeys(types, "")
+ self.types_import = types
def visit_begin(self, schema: QAPISchema) -> None:
self.schema = schema
+ # iterate once in schema.docs to map doc objects to its name
+ for doc in schema.docs:
+ if doc.symbol is None:
+ continue
+ self.docmap[doc.symbol] = doc
+
+ for qapitype, imports in self.types_import.items():
+ self.types[qapitype] = TEMPLATE_GENERATED_HEADER[1:].format(
+ package_name=self.golang_package_name
+ )
+ self.types[qapitype] += generate_template_imports(imports)
+
def visit_end(self) -> None:
del self.schema
+ self.types["enum"] += generate_content_from_dict(self.enums)
def visit_object_type(
self,
@@ -83,7 +212,51 @@ def visit_enum_type(
members: List[QAPISchemaEnumMember],
prefix: Optional[str],
) -> None:
- pass
+ assert name not in self.enums
+ doc = self.docmap.get(name, None)
+ maindoc, docfields = qapi_to_golang_struct_docs(doc)
+
+ # The logic below is to generate QAPI enums as blocks of Go consts
+ # each with its own type for type safety inside Go applications.
+ #
+ # Block of const() blocks are vertically indented so we have to
+ # first iterate over all names to calculate space between
+ # $var_name and $var_type. This is achieved by helper function
+ # @fetch_indent_blocks_over_enum_with_docs()
+ #
+ # A new indentation block is defined by empty line or a comment.
+
+ indent_block = iter(
+ fetch_indent_blocks_over_enum_with_docs(name, members, docfields)
+ )
+ maxname = next(indent_block)
+ fields = ""
+ for index, member in enumerate(members):
+ # For simplicity, every time we have doc, we go to next indent block
+ hasdoc = member.name is not None and member.name in docfields
+
+ if hasdoc:
+ maxname = next(indent_block)
+
+ enum_name = f"{name}{qapi_to_field_name_enum(member.name)}"
+ name2type = " " * (maxname - len(enum_name) + 1)
+
+ if hasdoc:
+ docstr = (
+ textwrap.TextWrapper(width=80)
+ .fill(docfields[member.name])
+ .replace("\n", "\n\t// ")
+ )
+ fields += f"""\t// {docstr}\n"""
+
+ fields += f"""\t{enum_name}{name2type}{name} = "{member.name}"\n"""
+
+ if maindoc != "":
+ maindoc = f"\n{maindoc}"
+
+ self.enums[name] = maindoc + TEMPLATE_ENUM.format(
+ name=name, fields=fields[:-1]
+ )
def visit_array_type(
self,
@@ -133,3 +306,11 @@ def write(self, outdir: str) -> None:
srcpath = os.path.join(srcdir, filename)
dstpath = os.path.join(targetpath, filename)
shutil.copyfile(srcpath, dstpath)
+
+ # Types to be generated
+ for qapitype, content in self.types.items():
+ gofile = f"gen_type_{qapitype}.go"
+ pathname = os.path.join(targetpath, gofile)
+
+ with open(pathname, "w", encoding="utf8") as outfile:
+ outfile.write(content)
This patch handles QAPI enum types and generates its equivalent in Go. We sort the output based on enum's type name. Enums are being handled as strings in Golang. 1. For each QAPI enum, we will define a string type in Go to be the assigned type of this specific enum. 2. Naming: CamelCase will be used in any identifier that we want to export, which is everything. Example: qapi: | ## | # @DisplayProtocol: | # | # Display protocols which support changing password options. | # | # Since: 7.0 | ## | { 'enum': 'DisplayProtocol', | 'data': [ 'vnc', 'spice' ] } go: | // Display protocols which support changing password options. | // | // Since: 7.0 | type DisplayProtocol string | | const ( | DisplayProtocolVnc DisplayProtocol = "vnc" | DisplayProtocolSpice DisplayProtocol = "spice" | ) Signed-off-by: Victor Toso <victortoso@redhat.com> --- scripts/qapi/golang/golang.py | 185 +++++++++++++++++++++++++++++++++- 1 file changed, 183 insertions(+), 2 deletions(-)