Message ID | 20220401224104.145961-2-victortoso@redhat.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | qapi: add generator for Golang interface | expand |
On Sat, Apr 02, 2022 at 12:40:57AM +0200, Victor Toso wrote: > This patch handles QAPI enum types and generates its equivalent in Go. > > The highlights of this implementation are: > > 1. For each QAPI enum, we will define an int32 type in Go to be the > assigned type of this specific enum I think there's a potential case to be made for considering representation of enums as strings in Golang, as it more gracefully degrades. Consider the 'SHUTDOWN' event in QEMU, which has a 'reason' field. This implementation has type ShutdownCause int32 const ( ShutdownCauseNone ShutdownCause = iota ShutdownCauseHostError // An error prevents further use of guest ShutdownCauseHostQmpQuit // Reaction to the QMP command 'quit' ShutdownCauseHostQmpSystemReset // Reaction to the QMP command 'system_reset' ShutdownCauseHostSignal // Reaction to a signal, such as SIGINT ShutdownCauseHostUi // Reaction to a UI event, like window close ShutdownCauseGuestShutdown // Guest shutdown/suspend request, via ACPI or other hardware-specific means ShutdownCauseGuestReset // Guest reset request, and command line turns that into a shutdown ShutdownCauseGuestPanic // Guest panicked, and command line turns that into a shutdown ShutdownCauseSubsystemReset // Partial guest reset that does not trigger QMP events and ignores --no-reboot. This is useful for sanitizing hypercalls on s390 that are used during kexec/kdump/boot ) and type ShutdownEvent struct { Guest bool `json:"guest"` // If true, the shutdown was triggered by a guest request (such as a guest-initiated ACPI shutdown request or other hardware-specific action) rather than a host request (such as sending qemu a SIGINT). (since 2.10) Reason ShutdownCause `json:"reason"` // The @ShutdownCause which resulted in the SHUTDOWN. (since 4.0) } Consider that the application is compiled against the QAPI generated API from QEMU 7.0. The application is believed to run happily against 7.1, because app doesn't need any of the functionality added in 7.1 QAPI. But consider that QEMU 7.1 had introduced a new shutdown reason value. The application may want to know that the guest has shutdown, but does NOT care about the reason for the shutdown. Since the ShutdownReason is implemented as an int though, the unmarshalling for the reason field is going to fail. If the enums were just represented as strings, then we can gracefully accept any new enum value that arrives in future. The application can thus also still log the shutdown reason string, even though it was not a value explicitly known to the generated API. > > 2. While in the Go codebase we can use the generated enum values, the > specification requires that, on the wire, the enumeration type's > value to be represented by its string name. > > For this reason, each Go type that represent's a QAPI enum will be > implementing the Marshaler[0] and Unmarshaler[1] interfaces to > seamless handle QMP's string to Go int32 value and vice-versa. > > 3. Naming: CamelCase will be used in any identifier that we want to > export [2], which is everything in this patch. With regards, Daniel
Hi, On Tue, May 10, 2022 at 11:06:16AM +0100, Daniel P. Berrangé wrote: > On Sat, Apr 02, 2022 at 12:40:57AM +0200, Victor Toso wrote: > > This patch handles QAPI enum types and generates its equivalent in Go. > > > > The highlights of this implementation are: > > > > 1. For each QAPI enum, we will define an int32 type in Go to be the > > assigned type of this specific enum > > I think there's a potential case to be made for considering > representation of enums as strings in Golang, as it more > gracefully degrades. > > Consider the 'SHUTDOWN' event in QEMU, which has a 'reason' field. > > This implementation has > > type ShutdownCause int32 > > const ( > ShutdownCauseNone ShutdownCause = iota > ShutdownCauseHostError // An error prevents further use of guest > ShutdownCauseHostQmpQuit // Reaction to the QMP command 'quit' > ShutdownCauseHostQmpSystemReset // Reaction to the QMP command 'system_reset' > ShutdownCauseHostSignal // Reaction to a signal, such as SIGINT > ShutdownCauseHostUi // Reaction to a UI event, like window close > ShutdownCauseGuestShutdown // Guest shutdown/suspend request, via ACPI or other hardware-specific means > ShutdownCauseGuestReset // Guest reset request, and command line turns that into a shutdown > ShutdownCauseGuestPanic // Guest panicked, and command line turns that into a shutdown > ShutdownCauseSubsystemReset // Partial guest reset that does not trigger QMP events and ignores --no-reboot. This is useful for sanitizing hypercalls on s390 that are used during kexec/kdump/boot > ) > > and > > type ShutdownEvent struct { > Guest bool `json:"guest"` // If true, the shutdown was triggered by a guest request (such as a guest-initiated ACPI shutdown request or other hardware-specific action) rather than a host request (such as sending qemu a SIGINT). (since 2.10) > Reason ShutdownCause `json:"reason"` // The @ShutdownCause which resulted in the SHUTDOWN. (since 4.0) > } > > Consider that the application is compiled against the QAPI > generated API from QEMU 7.0. The application is believed to run > happily against 7.1, because app doesn't need any of the > functionality added in 7.1 QAPI. But consider that QEMU 7.1 > had introduced a new shutdown reason value. > > The application may want to know that the guest has shutdown, > but does NOT care about the reason for the shutdown. > > Since the ShutdownReason is implemented as an int though, the > unmarshalling for the reason field is going to fail. Marshalling does error if you try to convert an int that is not in the range of the enum type. Unmarshalling should not error in this case, but the field ends up not being set which defaults to 0 (in this case, ShutdownCauseNone). That could mislead the *actual* reason but not without a warning which is expected in this case, imho. (I know is not an actual warning, just a Println, but it'll be fixed in the next iteration) | func (s *ShutdownCause) UnmarshalJSON(data []byte) error { | var name string | | if err := json.Unmarshal(data, &name); err != nil { | return err | } | | switch name { | case "none": | (*s) = ShutdownCauseNone | case "host-error": | (*s) = ShutdownCauseHostError | case "host-qmp-quit": | (*s) = ShutdownCauseHostQmpQuit | case "host-qmp-system-reset": | (*s) = ShutdownCauseHostQmpSystemReset | case "host-signal": | (*s) = ShutdownCauseHostSignal | case "host-ui": | (*s) = ShutdownCauseHostUi | case "guest-shutdown": | (*s) = ShutdownCauseGuestShutdown | case "guest-reset": | (*s) = ShutdownCauseGuestReset | case "guest-panic": | (*s) = ShutdownCauseGuestPanic | case "subsystem-reset": | (*s) = ShutdownCauseSubsystemReset | default: | fmt.Println("Failed to decode ShutdownCause", *s) | } | return nil | } > If the enums were just represented as strings, then we can > gracefully accept any new enum value that arrives in future. > The application can thus also still log the shutdown reason > string, even though it was not a value explicitly known to the > generated API. As a string, the warning should still exist and default value of "" or nil for ptr would apply. IMHO, between string + warning and int + warning, I'd still go with int here. That's a design decision to be made. All in all, I don't know much about the changes in QEMU/QAPI per version to take this decision, so I'll rely on you and the list for this, not just for enums but for the other types too. > > 2. While in the Go codebase we can use the generated enum values, the > > specification requires that, on the wire, the enumeration type's > > value to be represented by its string name. > > > > For this reason, each Go type that represent's a QAPI enum will be > > implementing the Marshaler[0] and Unmarshaler[1] interfaces to > > seamless handle QMP's string to Go int32 value and vice-versa. > > > > 3. Naming: CamelCase will be used in any identifier that we want to > > export [2], which is everything in this patch. Cheers, Victor
On Tue, May 10, 2022 at 01:15:32PM +0200, Victor Toso wrote: > Hi, > > On Tue, May 10, 2022 at 11:06:16AM +0100, Daniel P. Berrangé wrote: > > On Sat, Apr 02, 2022 at 12:40:57AM +0200, Victor Toso wrote: > > > This patch handles QAPI enum types and generates its equivalent in Go. > > > > > > The highlights of this implementation are: > > > > > > 1. For each QAPI enum, we will define an int32 type in Go to be the > > > assigned type of this specific enum > > > > I think there's a potential case to be made for considering > > representation of enums as strings in Golang, as it more > > gracefully degrades. > > > > Consider the 'SHUTDOWN' event in QEMU, which has a 'reason' field. > > > > This implementation has > > > > type ShutdownCause int32 > > > > const ( > > ShutdownCauseNone ShutdownCause = iota > > ShutdownCauseHostError // An error prevents further use of guest > > ShutdownCauseHostQmpQuit // Reaction to the QMP command 'quit' > > ShutdownCauseHostQmpSystemReset // Reaction to the QMP command 'system_reset' > > ShutdownCauseHostSignal // Reaction to a signal, such as SIGINT > > ShutdownCauseHostUi // Reaction to a UI event, like window close > > ShutdownCauseGuestShutdown // Guest shutdown/suspend request, via ACPI or other hardware-specific means > > ShutdownCauseGuestReset // Guest reset request, and command line turns that into a shutdown > > ShutdownCauseGuestPanic // Guest panicked, and command line turns that into a shutdown > > ShutdownCauseSubsystemReset // Partial guest reset that does not trigger QMP events and ignores --no-reboot. This is useful for sanitizing hypercalls on s390 that are used during kexec/kdump/boot > > ) > > > > and > > > > type ShutdownEvent struct { > > Guest bool `json:"guest"` // If true, the shutdown was triggered by a guest request (such as a guest-initiated ACPI shutdown request or other hardware-specific action) rather than a host request (such as sending qemu a SIGINT). (since 2.10) > > Reason ShutdownCause `json:"reason"` // The @ShutdownCause which resulted in the SHUTDOWN. (since 4.0) > > } > > > > Consider that the application is compiled against the QAPI > > generated API from QEMU 7.0. The application is believed to run > > happily against 7.1, because app doesn't need any of the > > functionality added in 7.1 QAPI. But consider that QEMU 7.1 > > had introduced a new shutdown reason value. > > > > The application may want to know that the guest has shutdown, > > but does NOT care about the reason for the shutdown. > > > > Since the ShutdownReason is implemented as an int though, the > > unmarshalling for the reason field is going to fail. > > Marshalling does error if you try to convert an int that is not > in the range of the enum type. > > Unmarshalling should not error in this case, but the field ends > up not being set which defaults to 0 (in this case, > ShutdownCauseNone). That could mislead the *actual* reason but > not without a warning which is expected in this case, imho. > > (I know is not an actual warning, just a Println, but it'll be > fixed in the next iteration) I don't thinnk we should be emitting warnings/printlns at all in this case though. The app should be able to consume the enum without needing a warning. If the app wants to validate against a specific set of constants, it can decide to emit a warning if there was a case it can't handle. We shouldn't emit warnings/errors in the unmarshalling step though,as it isn't the right place to decide on such policy. > > | func (s *ShutdownCause) UnmarshalJSON(data []byte) error { > | var name string > | > | if err := json.Unmarshal(data, &name); err != nil { > | return err > | } > | > | switch name { > | case "none": > | (*s) = ShutdownCauseNone > | case "host-error": > | (*s) = ShutdownCauseHostError > | case "host-qmp-quit": > | (*s) = ShutdownCauseHostQmpQuit > | case "host-qmp-system-reset": > | (*s) = ShutdownCauseHostQmpSystemReset > | case "host-signal": > | (*s) = ShutdownCauseHostSignal > | case "host-ui": > | (*s) = ShutdownCauseHostUi > | case "guest-shutdown": > | (*s) = ShutdownCauseGuestShutdown > | case "guest-reset": > | (*s) = ShutdownCauseGuestReset > | case "guest-panic": > | (*s) = ShutdownCauseGuestPanic > | case "subsystem-reset": > | (*s) = ShutdownCauseSubsystemReset > | default: > | fmt.Println("Failed to decode ShutdownCause", *s) > | } > | return nil > | } > > > If the enums were just represented as strings, then we can > > gracefully accept any new enum value that arrives in future. > > The application can thus also still log the shutdown reason > > string, even though it was not a value explicitly known to the > > generated API. > > As a string, the warning should still exist and default value of > "" or nil for ptr would apply. IMHO, between string + warning and > int + warning, I'd still go with int here. > > That's a design decision to be made. All in all, I don't know > much about the changes in QEMU/QAPI per version to take this > decision, so I'll rely on you and the list for this, not just for > enums but for the other types too. Essentially every release of QEMU will have QAPI changes. Most of the time these are append-only changes. ie a new struct, new command, new field, new enum value. Sometimes there will be removals due to deprecation, though note my other reply saying that we really ought to stop doing removals from the schema, and instead just annotate when a field stops being used. With regards, Daniel
Hi, On Tue, May 10, 2022 at 12:19:57PM +0100, Daniel P. Berrangé wrote: > > Marshalling does error if you try to convert an int that is not > > in the range of the enum type. > > > > Unmarshalling should not error in this case, but the field ends > > up not being set which defaults to 0 (in this case, > > ShutdownCauseNone). That could mislead the *actual* reason but > > not without a warning which is expected in this case, imho. > > > > (I know is not an actual warning, just a Println, but it'll be > > fixed in the next iteration) > > I don't thinnk we should be emitting warnings/printlns at all > in this case though. The app should be able to consume the enum > without needing a warning. If the app wants to validate > against a specific set of constants, it can decide to emit a > warning if there was a case it can't handle. We shouldn't emit > warnings/errors in the unmarshalling step though,as it isn't > the right place to decide on such policy. I think it is useful to know that, a binary compiled to qapi-go 7.0 but running with qemu 7.1 would have some mismatches. It could help detect issues (e.g: Why my program doesn't know/show the reason for shutdown?). So, some sort of --verbose option for this level should exist. > > | func (s *ShutdownCause) UnmarshalJSON(data []byte) error { > > | var name string > > | > > | if err := json.Unmarshal(data, &name); err != nil { > > | return err > > | } > > | > > | switch name { > > | case "none": > > | (*s) = ShutdownCauseNone > > | case "host-error": > > | (*s) = ShutdownCauseHostError > > | case "host-qmp-quit": > > | (*s) = ShutdownCauseHostQmpQuit > > | case "host-qmp-system-reset": > > | (*s) = ShutdownCauseHostQmpSystemReset > > | case "host-signal": > > | (*s) = ShutdownCauseHostSignal > > | case "host-ui": > > | (*s) = ShutdownCauseHostUi > > | case "guest-shutdown": > > | (*s) = ShutdownCauseGuestShutdown > > | case "guest-reset": > > | (*s) = ShutdownCauseGuestReset > > | case "guest-panic": > > | (*s) = ShutdownCauseGuestPanic > > | case "subsystem-reset": > > | (*s) = ShutdownCauseSubsystemReset > > | default: > > | fmt.Println("Failed to decode ShutdownCause", *s) > > | } > > | return nil > > | } > > > > > If the enums were just represented as strings, then we can > > > gracefully accept any new enum value that arrives in future. > > > The application can thus also still log the shutdown reason > > > string, even though it was not a value explicitly known to the > > > generated API. > > > > As a string, the warning should still exist and default value of > > "" or nil for ptr would apply. IMHO, between string + warning and > > int + warning, I'd still go with int here. > > > > That's a design decision to be made. All in all, I don't know > > much about the changes in QEMU/QAPI per version to take this > > decision, so I'll rely on you and the list for this, not just for > > enums but for the other types too. > > Essentially every release of QEMU will have QAPI changes. Most of > the time these are append-only changes. ie a new struct, new command, > new field, new enum value. Sometimes there will be removals due > to deprecation, though note my other reply saying that we really > ought to stop doing removals from the schema, and instead just > annotate when a field stops being used. Ok, thanks. Cheers, Victor
On Tue, May 10, 2022 at 01:28:39PM +0200, Victor Toso wrote: > Hi, > > On Tue, May 10, 2022 at 12:19:57PM +0100, Daniel P. Berrangé wrote: > > > Marshalling does error if you try to convert an int that is not > > > in the range of the enum type. > > > > > > Unmarshalling should not error in this case, but the field ends > > > up not being set which defaults to 0 (in this case, > > > ShutdownCauseNone). That could mislead the *actual* reason but > > > not without a warning which is expected in this case, imho. > > > > > > (I know is not an actual warning, just a Println, but it'll be > > > fixed in the next iteration) > > > > I don't thinnk we should be emitting warnings/printlns at all > > in this case though. The app should be able to consume the enum > > without needing a warning. If the app wants to validate > > against a specific set of constants, it can decide to emit a > > warning if there was a case it can't handle. We shouldn't emit > > warnings/errors in the unmarshalling step though,as it isn't > > the right place to decide on such policy. > > I think it is useful to know that, a binary compiled to qapi-go > 7.0 but running with qemu 7.1 would have some mismatches. It > could help detect issues (e.g: Why my program doesn't know/show > the reason for shutdown?). If apps are actually looking at the shutdown reasons, it is trivial for the app themselves to log any unexpected reasons. They should in fact do that regardless, because if someone re-compiles the app against a new version of the QEMU Go API, there may be new ShutdownCause's defined that their codes does not handle. There's no compile time check from Go side that their code has handled all ShutdownCause values, since there's no struct enum type in Go. This again pushes me to towards the view that the enums should just remain as strings, the use of integers gives no functional benefit AFAICT, given the lack of a real enum type in Go. With regards, Daniel
diff --git a/qapi/meson.build b/qapi/meson.build index 656ef0e039..0951692332 100644 --- a/qapi/meson.build +++ b/qapi/meson.build @@ -90,6 +90,7 @@ qapi_nonmodule_outputs = [ 'qapi-init-commands.h', 'qapi-init-commands.c', 'qapi-events.h', 'qapi-events.c', 'qapi-emit-events.c', 'qapi-emit-events.h', + 'qapibara.go', ] # First build all sources diff --git a/scripts/qapi/golang.py b/scripts/qapi/golang.py new file mode 100644 index 0000000000..070d4cbbae --- /dev/null +++ b/scripts/qapi/golang.py @@ -0,0 +1,225 @@ +""" +Golang QAPI generator +""" +# Copyright (c) 2021 Red Hat Inc. +# +# Authors: +# Victor Toso <victortoso@redhat.com> +# +# This work is licensed under the terms of the GNU GPL, version 2. +# See the COPYING file in the top-level directory. + +# Just for type hint on self +from __future__ import annotations + +import os +from typing import Dict, List, Optional + +from .schema import ( + QAPISchema, + QAPISchemaVisitor, + QAPISchemaEnumMember, + QAPISchemaFeature, + QAPISchemaIfCond, + QAPISchemaObjectType, + QAPISchemaObjectTypeMember, + QAPISchemaVariants, +) +from .source import QAPISourceInfo + +class QAPISchemaGenGolangVisitor(QAPISchemaVisitor): + + def __init__(self, prefix: str): + super().__init__() + self.target = {name: "" for name in ["enum"]} + self.schema = None + self._docmap = {} + self.golang_package_name = "qapi" + + def visit_begin(self, schema): + self.schema = schema + + # Every Go file needs to reference its package name + for target in self.target: + self.target[target] = f"package {self.golang_package_name}\n" + + # 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 + + def visit_end(self): + self.schema = None + + def visit_object_type(self: QAPISchemaGenGolangVisitor, + name: str, + info: Optional[QAPISourceInfo], + ifcond: QAPISchemaIfCond, + features: List[QAPISchemaFeature], + base: Optional[QAPISchemaObjectType], + members: List[QAPISchemaObjectTypeMember], + variants: Optional[QAPISchemaVariants] + ) -> None: + pass + + def visit_alternate_type(self: QAPISchemaGenGolangVisitor, + name: str, + info: Optional[QAPISourceInfo], + ifcond: QAPISchemaIfCond, + features: List[QAPISchemaFeature], + variants: QAPISchemaVariants + ) -> None: + pass + + def visit_enum_type(self: QAPISchemaGenGolangVisitor, + name: str, + info: Optional[QAPISourceInfo], + ifcond: QAPISchemaIfCond, + features: List[QAPISchemaFeature], + members: List[QAPISchemaEnumMember], + prefix: Optional[str] + ) -> None: + doc = self._docmap.get(name, None) + doc_struct, doc_fields = qapi_to_golang_struct_docs(doc) + + value = qapi_to_field_name_enum(members[0].name) + fields = f"\t{name}{value} {name} = iota\n" + for member in members[1:]: + field_doc = " " + doc_fields.get(member.name, "") if doc_fields else "" + value = qapi_to_field_name_enum(member.name) + fields += f"\t{name}{value}{field_doc}\n" + + self.target["enum"] += f''' +{doc_struct} +type {name} int32 +const ( +{fields[:-1]} +) +{generate_marshal_methods_enum(members)} +''' + + def visit_array_type(self, name, info, ifcond, element_type): + pass + + def visit_command(self, + name: str, + info: Optional[QAPISourceInfo], + ifcond: QAPISchemaIfCond, + features: List[QAPISchemaFeature], + arg_type: Optional[QAPISchemaObjectType], + ret_type: Optional[QAPISchemaType], + gen: bool, + success_response: bool, + boxed: bool, + allow_oob: bool, + allow_preconfig: bool, + coroutine: bool) -> None: + pass + + def visit_event(self, name, info, ifcond, features, arg_type, boxed): + pass + + def write(self, output_dir: str) -> None: + for module_name, content in self.target.items(): + go_module = module_name + "s.go" + go_dir = "go" + pathname = os.path.join(output_dir, go_dir, go_module) + odir = os.path.dirname(pathname) + os.makedirs(odir, exist_ok=True) + + with open(pathname, "w") as outfile: + outfile.write(content) + + +def gen_golang(schema: QAPISchema, + output_dir: str, + prefix: str) -> None: + vis = QAPISchemaGenGolangVisitor(prefix) + schema.visit(vis) + vis.write(output_dir) + +def generate_marshal_methods_enum(members: List[QAPISchemaEnumMember]) -> str: + type = qapi_to_go_type_name(members[0].defined_in, "enum") + + marshal_switch_cases = "" + unmarshal_switch_cases = "" + for i in range(len(members)): + go_type = type + qapi_to_field_name_enum(members[i].name) + name = members[i].name + + marshal_switch_cases += f''' + case {go_type}: + return []byte(`"{name}"`), nil''' + + unmarshal_switch_cases += f''' + case "{name}": + (*s) = {go_type}''' + + return f''' +func (s {type}) MarshalJSON() ([]byte, error) {{ + switch s {{ +{marshal_switch_cases[1:]} + default: + fmt.Println("Failed to decode {type}", s) + }} + return nil, errors.New("Failed") +}} + +func (s *{type}) UnmarshalJSON(data []byte) error {{ + var name string + + if err := json.Unmarshal(data, &name); err != nil {{ + return err + }} + + switch name {{ +{unmarshal_switch_cases[1:]} + default: + fmt.Println("Failed to decode {type}", *s) + }} + return nil +}} +''' + +# Takes the documentation object of a specific type and returns +# that type's documentation followed by its member's docs. +def qapi_to_golang_struct_docs(doc: QAPIDoc) -> (str, Dict[str, str]): + if doc is None: + return "// No documentation available", None + + main = "" + if len(doc.body.text) > 0: + main = f"// {doc.body.text}".replace("\n", "\n// ") + + for section in doc.sections: + # Skip sections that are not useful for Golang consumers + if section.name and "TODO" in section.name: + continue + + # Small hack to only add // when doc.body.text was empty + prefix = "// " if len(main) == 0 else "\n\n" + main += f"{prefix}{section.name}: {section.text}".replace("\n", "\n// ") + + 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 qapi_to_field_name_enum(name: str) -> str: + return name.title().replace("-", "") + +def qapi_to_go_type_name(name: str, meta: str) -> str: + if name.startswith("q_obj_"): + name = name[6:] + + # We want to keep CamelCase for Golang types. We want to avoid removing + # already set CameCase names while fixing uppercase ones, eg: + # 1) q_obj_SocketAddress_base -> SocketAddressBase + # 2) q_obj_WATCHDOG-arg -> WatchdogArg + words = [word for word in name.replace("_", "-").split("-")] + name = words[0].title() if words[0].islower() or words[0].isupper() else words[0] + name += ''.join(word.title() for word in words[1:]) + return name diff --git a/scripts/qapi/main.py b/scripts/qapi/main.py index fc216a53d3..661fb1e091 100644 --- a/scripts/qapi/main.py +++ b/scripts/qapi/main.py @@ -15,6 +15,7 @@ from .common import must_match from .error import QAPIError from .events import gen_events +from .golang import gen_golang from .introspect import gen_introspect from .schema import QAPISchema from .types import gen_types @@ -54,6 +55,7 @@ def generate(schema_file: str, gen_events(schema, output_dir, prefix) gen_introspect(schema, output_dir, prefix, unmask) + gen_golang(schema, output_dir, prefix) def main() -> int: """
This patch handles QAPI enum types and generates its equivalent in Go. The highlights of this implementation are: 1. For each QAPI enum, we will define an int32 type in Go to be the assigned type of this specific enum 2. While in the Go codebase we can use the generated enum values, the specification requires that, on the wire, the enumeration type's value to be represented by its string name. For this reason, each Go type that represent's a QAPI enum will be implementing the Marshaler[0] and Unmarshaler[1] interfaces to seamless handle QMP's string to Go int32 value and vice-versa. 3. Naming: CamelCase will be used in any identifier that we want to export [2], which is everything in this patch. [0] https://pkg.go.dev/encoding/json#Marshaler [1] https://pkg.go.dev/encoding/json#Unmarshaler [2] https://go.dev/ref/spec#Exported_identifiers Signed-off-by: Victor Toso <victortoso@redhat.com> --- qapi/meson.build | 1 + scripts/qapi/golang.py | 225 +++++++++++++++++++++++++++++++++++++++++ scripts/qapi/main.py | 2 + 3 files changed, 228 insertions(+) create mode 100644 scripts/qapi/golang.py