From: Victor Toso <victortoso@redhat.com>
To: qemu-devel@nongnu.org
Cc: "Markus Armbruster" <armbru@redhat.com>,
"John Snow" <jsnow@redhat.com>,
"Daniel P . Berrangé" <berrange@redhat.com>,
"Andrea Bolognani" <abologna@redhat.com>
Subject: [PATCH v2 08/11] qapi: golang: Generate qapi's event types in Go
Date: Mon, 16 Oct 2023 17:27:01 +0200 [thread overview]
Message-ID: <20231016152704.221611-9-victortoso@redhat.com> (raw)
In-Reply-To: <20231016152704.221611-1-victortoso@redhat.com>
This patch handles QAPI event types and generates data structures in
Go that handles it.
We also define a Event interface and two helper functions MarshalEvent
and UnmarshalEvent.
Example:
qapi:
| { 'event': 'MEMORY_DEVICE_SIZE_CHANGE',
| 'data': { '*id': 'str', 'size': 'size', 'qom-path' : 'str'} }
go:
| type MemoryDeviceSizeChangeEvent struct {
| MessageTimestamp Timestamp `json:"-"`
| Id *string `json:"id,omitempty"`
| Size uint64 `json:"size"`
| QomPath string `json:"qom-path"`
| }
usage:
| input := `{"event":"MEMORY_DEVICE_SIZE_CHANGE",` +
| `"timestamp":{"seconds":1588168529,"microseconds":201316},` +
| `"data":{"id":"vm0","size":1073741824,"qom-path":"/machine/unattached/device[2]"}}`
| e, err := UnmarshalEvent([]byte(input)
| if err != nil {
| panic(err)
| }
| if e.GetName() == `MEMORY_DEVICE_SIZE_CHANGE` {
| m := e.(*MemoryDeviceSizeChangeEvent)
| // m.QomPath == "/machine/unattached/device[2]"
| }
Signed-off-by: Victor Toso <victortoso@redhat.com>
---
scripts/qapi/golang.py | 122 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 119 insertions(+), 3 deletions(-)
diff --git a/scripts/qapi/golang.py b/scripts/qapi/golang.py
index bc6206797a..81b320d6dd 100644
--- a/scripts/qapi/golang.py
+++ b/scripts/qapi/golang.py
@@ -215,6 +215,54 @@
}}
"""
+TEMPLATE_EVENT = """
+type Timestamp struct {{
+\tSeconds int64 `json:"seconds"`
+\tMicroseconds int64 `json:"microseconds"`
+}}
+
+type Event interface {{
+\tGetName() string
+\tGetTimestamp() Timestamp
+}}
+
+func MarshalEvent(e Event) ([]byte, error) {{
+\tm := make(map[string]any)
+\tm["event"] = e.GetName()
+\tm["timestamp"] = e.GetTimestamp()
+\tif bytes, err := json.Marshal(e); err != nil {{
+\t\treturn []byte{{}}, err
+\t}} else if len(bytes) > 2 {{
+\t\tm["data"] = e
+\t}}
+\treturn json.Marshal(m)
+}}
+
+func UnmarshalEvent(data []byte) (Event, error) {{
+\tbase := struct {{
+\t\tName string `json:"event"`
+\t\tMessageTimestamp Timestamp `json:"timestamp"`
+\t}}{{}}
+\tif err := json.Unmarshal(data, &base); err != nil {{
+\t\treturn nil, fmt.Errorf("Failed to decode event: %s", string(data))
+\t}}
+
+\tswitch base.Name {{{cases}
+\t}}
+\treturn nil, errors.New("Failed to recognize event")
+}}
+"""
+
+TEMPLATE_EVENT_METHODS = """
+func (s *{type_name}) GetName() string {{
+\treturn "{name}"
+}}
+
+func (s *{type_name}) GetTimestamp() Timestamp {{
+\treturn s.MessageTimestamp
+}}
+"""
+
def gen_golang(schema: QAPISchema, output_dir: str, prefix: str) -> None:
vis = QAPISchemaGenGolangVisitor(prefix)
@@ -238,7 +286,7 @@ def qapi_to_field_name_enum(name: str) -> str:
return name.title().replace("-", "")
-def qapi_to_go_type_name(name: str) -> str:
+def qapi_to_go_type_name(name: str, meta: Optional[str] = None) -> str:
if qapi_name_is_object(name):
name = name[6:]
@@ -253,6 +301,11 @@ def qapi_to_go_type_name(name: str) -> str:
name += "".join(word.title() for word in words[1:])
+ types = ["event"]
+ if meta in types:
+ name = name[:-3] if name.endswith("Arg") else name
+ name += meta.title().replace(" ", "")
+
return name
@@ -608,6 +661,15 @@ def qapi_to_golang_struct(
discriminator = None if not variants else variants.tag_member.name
fields, with_nullable = recursive_base(self, base, discriminator)
+ if info.defn_meta == "event":
+ fields.insert(
+ 0,
+ {
+ "name": "MessageTimestamp",
+ "type": "Timestamp",
+ "tag": """`json:"-"`""",
+ },
+ )
if members:
for member in members:
@@ -651,7 +713,7 @@ def qapi_to_golang_struct(
fields.append(field)
with_nullable = True if nullable else with_nullable
- type_name = qapi_to_go_type_name(name)
+ type_name = qapi_to_go_type_name(name, info.defn_meta)
content = generate_struct_type(type_name, fields, ident)
if with_nullable:
content += struct_with_nullable_generate_marshal(
@@ -825,6 +887,28 @@ def generate_template_alternate(
return content
+def generate_template_event(events: dict[str, Tuple[str, str]]) -> str:
+ cases = ""
+ content = ""
+ for name in sorted(events):
+ case_type, gocode = events[name]
+ content += gocode
+ cases += f"""
+\tcase "{name}":
+\t\tevent := struct {{
+\t\t\tData {case_type} `json:"data"`
+\t\t}}{{}}
+
+\t\tif err := json.Unmarshal(data, &event); err != nil {{
+\t\t\treturn nil, fmt.Errorf("Failed to unmarshal: %s", string(data))
+\t\t}}
+\t\tevent.Data.MessageTimestamp = base.MessageTimestamp
+\t\treturn &event.Data, nil
+"""
+ content += TEMPLATE_EVENT.format(cases=cases)
+ return content
+
+
def generate_content_from_dict(data: dict[str, str]) -> str:
content = ""
@@ -841,12 +925,14 @@ def __init__(self, _: str):
types = (
"alternate",
"enum",
+ "event",
"helper",
"struct",
"union",
)
self.target = dict.fromkeys(types, "")
self.schema: QAPISchema
+ self.events: dict[str, Tuple[str, str]] = {}
self.golang_package_name = "qapi"
self.enums: dict[str, str] = {}
self.alternates: dict[str, str] = {}
@@ -901,6 +987,7 @@ def visit_end(self) -> None:
self.target["alternate"] += generate_content_from_dict(self.alternates)
self.target["struct"] += generate_content_from_dict(self.structs)
self.target["union"] += generate_content_from_dict(self.unions)
+ self.target["event"] += generate_template_event(self.events)
def visit_object_type(
self,
@@ -1027,7 +1114,36 @@ def visit_event(
arg_type: Optional[QAPISchemaObjectType],
boxed: bool,
) -> None:
- pass
+ assert name == info.defn_name
+ assert name not in self.events
+ type_name = qapi_to_go_type_name(name, info.defn_meta)
+
+ if isinstance(arg_type, QAPISchemaObjectType):
+ content = qapi_to_golang_struct(
+ self,
+ name,
+ arg_type.info,
+ arg_type.ifcond,
+ arg_type.features,
+ arg_type.base,
+ arg_type.members,
+ arg_type.variants,
+ )
+ else:
+ args: List[dict[str:str]] = []
+ args.append(
+ {
+ "name": "MessageTimestamp",
+ "type": "Timestamp",
+ "tag": """`json:"-"`""",
+ }
+ )
+ content = generate_struct_type(type_name, args)
+
+ content += TEMPLATE_EVENT_METHODS.format(
+ name=name, type_name=type_name
+ )
+ self.events[name] = (type_name, content)
def write(self, output_dir: str) -> None:
for module_name, content in self.target.items():
--
2.41.0
next prev parent reply other threads:[~2023-10-16 15:28 UTC|newest]
Thread overview: 43+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-10-16 15:26 [PATCH v2 00/11] qapi-go: add generator for Golang interface Victor Toso
2023-10-16 15:26 ` [PATCH v2 01/11] qapi: re-establish linting baseline Victor Toso
2023-10-16 15:26 ` [PATCH v2 02/11] scripts: qapi: black format main.py Victor Toso
2023-10-18 11:00 ` Markus Armbruster
2023-10-18 11:13 ` Daniel P. Berrangé
2023-10-18 15:23 ` Victor Toso
2023-10-19 5:42 ` Markus Armbruster
2023-10-19 7:30 ` Daniel P. Berrangé
2023-10-16 15:26 ` [PATCH v2 03/11] qapi: golang: Generate qapi's enum types in Go Victor Toso
2023-10-16 15:26 ` [PATCH v2 04/11] qapi: golang: Generate qapi's alternate " Victor Toso
2023-11-06 15:28 ` Andrea Bolognani
2023-11-06 15:52 ` Victor Toso
2023-11-06 16:12 ` Andrea Bolognani
2023-11-09 17:34 ` Andrea Bolognani
2023-11-09 19:01 ` Victor Toso
2023-11-10 9:58 ` Andrea Bolognani
2023-11-10 15:43 ` Victor Toso
2023-10-16 15:26 ` [PATCH v2 05/11] qapi: golang: Generate qapi's struct " Victor Toso
2023-10-16 15:26 ` [PATCH v2 06/11] qapi: golang: structs: Address 'null' members Victor Toso
2023-10-16 15:27 ` [PATCH v2 07/11] qapi: golang: Generate qapi's union types in Go Victor Toso
2023-11-09 17:29 ` Andrea Bolognani
2023-11-09 18:35 ` Victor Toso
2023-11-10 9:54 ` Andrea Bolognani
2023-11-10 15:45 ` Victor Toso
2023-10-16 15:27 ` Victor Toso [this message]
2023-11-09 17:59 ` [PATCH v2 08/11] qapi: golang: Generate qapi's event " Andrea Bolognani
2023-11-09 19:13 ` Victor Toso
2023-11-10 9:52 ` Andrea Bolognani
2023-10-16 15:27 ` [PATCH v2 09/11] qapi: golang: Generate qapi's command " Victor Toso
2023-10-16 15:27 ` [PATCH v2 10/11] qapi: golang: Add CommandResult type to Go Victor Toso
2023-11-09 18:24 ` Andrea Bolognani
2023-11-09 19:31 ` Victor Toso
2023-11-10 9:46 ` Andrea Bolognani
2023-10-16 15:27 ` [PATCH v2 11/11] docs: add notes on Golang code generator Victor Toso
2023-10-18 11:47 ` Markus Armbruster
2023-10-18 16:21 ` Victor Toso
2023-10-19 6:56 ` Markus Armbruster
2023-10-27 17:33 ` [PATCH v2 00/11] qapi-go: add generator for Golang interface Victor Toso
2023-10-31 16:42 ` Andrea Bolognani
2023-11-03 18:34 ` Andrea Bolognani
2023-11-06 12:00 ` Victor Toso
2023-11-09 18:35 ` Andrea Bolognani
2023-11-09 19:03 ` Victor Toso
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20231016152704.221611-9-victortoso@redhat.com \
--to=victortoso@redhat.com \
--cc=abologna@redhat.com \
--cc=armbru@redhat.com \
--cc=berrange@redhat.com \
--cc=jsnow@redhat.com \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).