From 25288987eb0e7c207c31b8765abcf506659d56ba Mon Sep 17 00:00:00 2001 From: Justin Huang Date: Mon, 20 Mar 2023 19:52:59 +1100 Subject: [PATCH] vm_tools: Adds sommelier shim gen files + Adds scripts and template files to generate wayland shims. * Modifies build scripts to generate and build these files BUG=b:255439799 TEST=CQ - This is generating test infra stuff. Cq-Depend: chromium:4369582, chrome-internal:5651758 Change-Id: Ice996b9e985d6d85a6ffc762ee9629c4e48d8fef Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/4349388 Reviewed-by: Chloe Pelling Tested-by: Justin Huang Reviewed-by: Nic Hollingum Commit-Queue: Justin Huang --- BUILD.gn | 47 +++++---- gen-shim.py | 175 ++++++++++++++++++++++++++++++++ gen/mock-protocol-shim.h.jinja2 | 23 +++++ gen/protocol-shim.cc.jinja2 | 44 ++++++++ gen/protocol-shim.h.jinja2 | 47 +++++++++ meson.build | 26 ++++- shim_gen.gni | 48 +++++++++ 7 files changed, 390 insertions(+), 20 deletions(-) create mode 100755 gen-shim.py create mode 100644 gen/mock-protocol-shim.h.jinja2 create mode 100644 gen/protocol-shim.cc.jinja2 create mode 100644 gen/protocol-shim.h.jinja2 create mode 100644 shim_gen.gni diff --git a/BUILD.gn b/BUILD.gn index ee19d88..09dabe5 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -3,6 +3,7 @@ # found in the LICENSE file. import("//common-mk/pkg_config.gni") +import("shim_gen.gni") import("wayland_protocol.gni") group("all") { @@ -36,25 +37,32 @@ if (!defined(dark_frame_color)) { dark_frame_color = "\"#323639\"" } +wayland_protocols = [ + "protocol/aura-shell.xml", + "protocol/drm.xml", + "protocol/gaming-input-unstable-v2.xml", + "protocol/gtk-shell.xml", + "protocol/keyboard-extension-unstable-v1.xml", + "protocol/linux-dmabuf-unstable-v1.xml", + "protocol/linux-explicit-synchronization-unstable-v1.xml", + "protocol/pointer-constraints-unstable-v1.xml", + "protocol/relative-pointer-unstable-v1.xml", + "protocol/text-input-extension-unstable-v1.xml", + "protocol/text-input-unstable-v1.xml", + "protocol/text-input-x11-unstable-v1.xml", + "protocol/viewporter.xml", + "protocol/xdg-output-unstable-v1.xml", + "protocol/xdg-shell.xml", +] + wayland_protocol_library("sommelier-protocol") { out_dir = "include" - sources = [ - "protocol/aura-shell.xml", - "protocol/drm.xml", - "protocol/gaming-input-unstable-v2.xml", - "protocol/gtk-shell.xml", - "protocol/keyboard-extension-unstable-v1.xml", - "protocol/linux-dmabuf-unstable-v1.xml", - "protocol/linux-explicit-synchronization-unstable-v1.xml", - "protocol/pointer-constraints-unstable-v1.xml", - "protocol/relative-pointer-unstable-v1.xml", - "protocol/text-input-extension-unstable-v1.xml", - "protocol/text-input-unstable-v1.xml", - "protocol/text-input-x11-unstable-v1.xml", - "protocol/viewporter.xml", - "protocol/xdg-output-unstable-v1.xml", - "protocol/xdg-shell.xml", - ] + sources = wayland_protocols +} + +gen_shim("sommelier-shims") { + out_dir = "include" + sources = wayland_protocols } gaming_defines = [ "GAMEPAD_SUPPORT" ] @@ -129,7 +137,10 @@ static_library("libsommelier") { "xkbcommon", ] + tracing_pkg_deps libs = [ "m" ] + tracing_libs - deps = [ ":sommelier-protocol" ] + gaming_deps + deps = [ + ":sommelier-protocol", + ":sommelier-shims", + ] + gaming_deps } executable("sommelier") { diff --git a/gen-shim.py b/gen-shim.py new file mode 100755 index 0000000..9501927 --- /dev/null +++ b/gen-shim.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +# Copyright 2023 The ChromiumOS Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""A C++ code generator of Wayland protocol shims.""" + +# pylint: enable=import-error +import os +import sys +from xml.etree import ElementTree + +# pylint: disable=import-error +from jinja2 import Template + + +def CppTypeForWaylandEventType(xml_type_string, interface): + """Generates the type for a wayland event argument.""" + if xml_type_string == "new_id" or xml_type_string == "object": + return "struct wl_resource *" + else: + return CppTypeForWaylandType(xml_type_string, interface) + + +def CppTypeForWaylandType(xml_type_string, interface): + """Generates the type for generic wayland type.""" + if xml_type_string == "int" or xml_type_string == "fd": + return "int32_t" + elif xml_type_string == "uint" or xml_type_string == "new_id": + return "uint32_t" + elif xml_type_string == "fixed": + return "wl_fixed_t" + elif xml_type_string == "string": + return "const char *" + elif xml_type_string == "object": + return "struct %s *" % interface + elif xml_type_string == "array": + return "struct wl_array *" + else: + raise ValueError("Invalid Type conversion: %s" % xml_type_string) + + +def GetRequestReturnType(args): + """Gets the return type of a Wayland request.""" + for arg in args: + if arg.attrib["type"] == "new_id": + if "interface" in arg.attrib: + return "struct %s *" % arg.attrib["interface"] + else: + return "void *" + return "void" + + +def RequestXmlToJinjaInput(request): + """Parses a element into dictionary form for use of jinja template.""" + method = {"name": request.attrib["name"], "args": [], "ret": ""} + method["ret"] = GetRequestReturnType(request.findall("arg")) + + for arg in request.findall("arg"): + if arg.attrib["type"] == "new_id": + if not arg.attrib.get("interface"): + method["args"].append( + { + "type": "const struct wl_interface *", + "name": "interface", + } + ) + method["args"].append({"type": "uint32_t", "name": "version"}) + else: + method["args"].append( + { + "name": arg.attrib["name"], + "type": CppTypeForWaylandType( + arg.attrib["type"], arg.attrib.get("interface", "") + ), + } + ) + return method + + +def EventXmlToJinjaInput(event): + """Parses an element into dictionary for for use of jinja template.""" + return { + "name": event.attrib["name"], + "args": [ + { + "name": arg.attrib["name"], + "type": CppTypeForWaylandEventType( + arg.attrib["type"], arg.attrib.get("interface", "") + ), + } + for arg in event.findall("arg") + ], + } + + +def InterfaceXmlToJinjaInput(interface): + """Creates an interface dict for XML interface input.""" + interf = { + "name": "".join( + [i.capitalize() for i in interface.attrib["name"].split("_")] + ) + + "Shim", + "name_underscore": interface.attrib["name"], + "methods": [ + RequestXmlToJinjaInput(i) for i in interface.findall("request") + ], + "events": [EventXmlToJinjaInput(i) for i in interface.findall("event")], + } + return interf + + +def GenerateShims(in_xml, out_directory): + """Generates shims for Wayland Protocols.""" + with open( + os.path.dirname(os.path.abspath(__file__)) + + "/gen/protocol-shim.h.jinja2", + encoding="utf-8", + ) as f: + shim_template = Template(f.read()) + with open( + os.path.dirname(os.path.abspath(__file__)) + + "/gen/protocol-shim.cc.jinja2", + encoding="utf-8", + ) as f: + shim_impl_template = Template(f.read()) + with open( + os.path.dirname(os.path.abspath(__file__)) + + "/gen/mock-protocol-shim.h.jinja2", + encoding="utf-8", + ) as f: + mock_template = Template(f.read()) + + tree = ElementTree.parse(in_xml) + root = tree.getroot() + + filename = os.path.basename(in_xml).split(".")[0] + + # Because some protocol files don't have the protocol name == file name, we + # have to infer the name from the file name instead (gtk-shell :eyes:) + protocol = { + "interfaces": [ + InterfaceXmlToJinjaInput(i) for i in root.findall("interface") + ], + "name_hyphen": filename, + "name_underscore": filename.replace("-", "_"), + } + + with open( + out_directory + "/" + protocol["name_hyphen"] + "-shim.h", + "w", + encoding="utf-8", + ) as f: + f.write(shim_template.render(protocol=protocol)) + + with open( + out_directory + "/" + protocol["name_hyphen"] + "-shim.cc", + "w", + encoding="utf-8", + ) as f: + f.write(shim_impl_template.render(protocol=protocol)) + + with open( + out_directory + "/mock-" + protocol["name_hyphen"] + "-shim.h", + "w", + encoding="utf-8", + ) as f: + f.write(mock_template.render(protocol=protocol)) + + +if __name__ == "__main__": + source_xml = sys.argv[1] + out_dir = sys.argv[2] + + GenerateShims(source_xml, out_dir) diff --git a/gen/mock-protocol-shim.h.jinja2 b/gen/mock-protocol-shim.h.jinja2 new file mode 100644 index 0000000..1d5b8ac --- /dev/null +++ b/gen/mock-protocol-shim.h.jinja2 @@ -0,0 +1,23 @@ +// Copyright 2023 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// NOTE: This code is automatically generated and any changes to this file will be overwritten. + +#ifndef VM_TOOLS_SOMMELIER_GEN_MOCK_{{ protocol.name_underscore | upper }}_SHIM_H_ +#define VM_TOOLS_SOMMELIER_GEN_MOCK_{{ protocol.name_underscore | upper }}_SHIM_H_ + +#include "{{ protocol.name_hyphen }}-shim.h" + +{%- for interface in protocol.interfaces %} +class Mock{{interface.name}} { + public: + {%- for method in interface.methods %} + MOCK_METHOD({{ method.ret }}, {{ method.name }}, ({% for arg in method.args %}{{ arg.type }} {{ arg.name }}{% if not loop.last %},{% endif %}{% endfor %}), (override)); + {% endfor -%} + {%- for event in interface.events %} + MOCK_METHOD(void, {{ event.name }}, ({% for arg in event.args %}{{ arg.type }} {{ arg.name }}{% if not loop.last %},{% endif %}{% endfor %}), (override)); + {% endfor -%} +}; +{% endfor -%} + +#endif // VM_TOOLS_SOMMELIER_GEN_MOCK_{{ protocol.name_underscore | upper }}_SHIM_H_ diff --git a/gen/protocol-shim.cc.jinja2 b/gen/protocol-shim.cc.jinja2 new file mode 100644 index 0000000..49d8a08 --- /dev/null +++ b/gen/protocol-shim.cc.jinja2 @@ -0,0 +1,44 @@ +// Copyright 2023 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// NOTE: This code is automatically generated and any changes to this file will be overwritten. + +#include "{{ protocol.name_hyphen }}-shim.h" + +{%- for interface in protocol.interfaces %} + +void {{ interface.name }}::set_user_data(struct {{interface.name_underscore}} *{{interface.name_underscore}}, void* user_data) { + {{ interface.name_underscore }}_set_user_data({{ interface.name_underscore }}, user_data); +} + +void* {{ interface.name }}::get_user_data(struct {{ interface.name_underscore }} *{{interface.name_underscore}}) { + return {{ interface.name_underscore }}_get_user_data({{ interface.name_underscore }}); +} + +{%- for method in interface.methods %} +{{ method.ret }} {{ interface.name }}::{{method.name}}( + struct {{ interface.name_underscore }} *{{ interface.name_underscore }}{% for arg in method.args %}, + {{ arg.type }} {{ arg.name }}{%endfor%}) { + {% if method.ret %}return {% endif %}{{ interface.name_underscore }}_{{ method.name }}({{ interface.name_underscore }}{% for arg in method.args %}, {{ arg.name }}{% endfor %}); +} +{% endfor -%} + +{%- for event in interface.events %} +void {{ interface.name}}::send_{{event.name}}( + struct wl_resource* resource{% for arg in event.args %}, + {{ arg.type }} {{ arg.name }}{% endfor %}) { + {{ interface.name_underscore }}_send_{{ event.name }}(resource{% for arg in event.args %}, {{ arg.name }}{% endfor %}); +} +{% endfor -%} + + +static {{ interface.name }}* {{ interface.name_underscore }}_singleton = nullptr; + +{{ interface.name }}* {{ interface.name_underscore | lower }}_shim() { + return {{ interface.name_underscore }}_singleton; +} + +void set_{{ interface.name_underscore }}_shim ({{ interface.name }}* shim) { + {{ interface.name_underscore }}_singleton = shim; +} +{% endfor -%} diff --git a/gen/protocol-shim.h.jinja2 b/gen/protocol-shim.h.jinja2 new file mode 100644 index 0000000..a6a2e6b --- /dev/null +++ b/gen/protocol-shim.h.jinja2 @@ -0,0 +1,47 @@ +{# NOTE: This combines both the client and server APIs together because sommelier is both -#} +// Copyright 2023 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// NOTE: This code is automatically generated and any changes to this file will be overwritten. + +#ifndef VM_TOOLS_SOMMELIER_GEN_{{ protocol.name_underscore | upper }}_SHIM_H_ +#define VM_TOOLS_SOMMELIER_GEN_{{ protocol.name_underscore | upper }}_SHIM_H_ + +#include "{{ protocol.name_hyphen }}-client-protocol.h" // NOLINT(build/include_directory) +#include "{{ protocol.name_hyphen }}-server-protocol.h" // NOLINT(build/include_directory) + +{# This generates a series of virtual functions which calls the underlying +wayland protocol functions. It's mostly modelled off wayland-scanner, ref: +https://chromium.googlesource.com/external/wayland/wayland/+/refs/heads/master/src/scanner.c#932 -#} +{% for interface in protocol.interfaces %} +class {{ interface.name }} { + public: + {{ interface.name }}() = default; + {{ interface.name }}({{ interface.name }}&&) = delete; + {{ interface.name }}& operator=({{ interface.name }}&&) = delete; + + {# Logic comes from wayland scanner -#} + {# Stub logic -> https://chromium.googlesource.com/external/wayland/wayland/+/refs/heads/master/src/scanner.c#1007 -#} + virtual void set_user_data(struct {{ interface.name_underscore }} *{{ interface.name_underscore}}, void *user_data); + + virtual void* get_user_data(struct {{ interface.name_underscore }} *{{ interface.name_underscore }}); + + {%- for method in interface.methods %} + virtual {{ method.ret }} {{ method.name }}( + struct {{ interface.name_underscore }} *{{ interface.name_underscore }}{% for arg in method.args %}, + {{ arg.type }} {{ arg.name }}{% endfor %}); + {% endfor -%} + + {# Event logic -> https://chromium.googlesource.com/external/wayland/wayland/+/refs/heads/master/src/scanner.c#1074 -#} + {%- for event in interface.events %} + virtual void send_{{ event.name }}( + struct wl_resource * resource{% for arg in event.args %}, + {{ arg.type }} {{arg.name}}{% endfor %}); + {% endfor -%} +}; + +{{ interface.name }}* {{ interface.name_underscore | lower }}_shim(); +void set_{{ interface.name_underscore }}_shim ({{ interface.name }}* shim); +{% endfor %} + +#endif // VM_TOOLS_SOMMELIER_GEN_{{ protocol.name_underscore | upper}}_SHIM_H_ diff --git a/meson.build b/meson.build index 14d1157..aa16f58 100644 --- a/meson.build +++ b/meson.build @@ -62,6 +62,29 @@ foreach p : wl_protocols endforeach endforeach +#===============# +# Codegen Shims # +#===============# + +# Ensure jinja2 exists within the python3 install. +if run_command( + 'python3', '-c', 'import jinja2', + check: false).returncode() != 0 + error('python3 jinja2 missing') +endif + +python_program = find_program('gen-shim.py') +shim_generator = generator( + python_program, + output: ['@BASENAME@-shim.cc', '@BASENAME@-shim.h', 'mock-@BASENAME@-shim.h'], + arguments: ['@INPUT@', '@BUILD_DIR@'] +) + +shim_outs = [] +foreach p : wl_protocols + shim_outs += shim_generator.process(p) +endforeach + #==========# # Perfetto # #==========# @@ -83,7 +106,6 @@ endif gamepad_sources = [] gamepad_dependencies = [] - if get_option('gamepad') gamepad_sources = [ 'sommelier-gaming.cc', @@ -145,7 +167,7 @@ libsommelier = static_library('sommelier', 'virtualization/virtwl_channel.cc', 'virtualization/virtgpu_channel.cc', 'xcb/xcb-shim.cc', - ] + wl_outs + tracing_sources + gamepad_sources, + ] + wl_outs + tracing_sources + gamepad_sources + shim_outs, dependencies: [ meson.get_compiler('cpp').find_library('m'), dependency('gbm'), diff --git a/shim_gen.gni b/shim_gen.gni new file mode 100644 index 0000000..ff5e90c --- /dev/null +++ b/shim_gen.gni @@ -0,0 +1,48 @@ +# Copyright 2023 The ChromiumOS Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# GN template to generate wayland shmis given Wayland XML files. +# Parameters: +# sources +# Wayland protocol description XML file paths. +# out_dir (optional) +# Directory to output generated source files. Relative to gen/ directory. +template("gen_shim") { + forward_variables_from(invoker, [ "out_dir" ]) + if (!defined(out_dir)) { + out_dir = "." + } + wayland_dir = "${root_gen_dir}/${out_dir}" + + action_foreach("gen-shims") { + sources = invoker.sources + data = [ + "gen/mock-protocol-shim.h.jinja2", + "gen/protocol-shim.h.jinja2", + "gen/protocol-shim.cc.jinja2", + ] + script = "gen-shim.py" + outputs = [ + "${wayland_dir}/{{source_name_part}}-shim.h", + "${wayland_dir}/{{source_name_part}}-shim.cc", + "${wayland_dir}/mock-{{source_name_part}}-shim.h", + ] + args = [ + "{{source}}", + "${wayland_dir}", + ] + } + + static_library(target_name) { + if (defined(invoker.configs)) { + configs += invoker.configs + } + deps = [ ":gen-shims" ] + sources = [] + sources += get_target_outputs(":gen-shims") + if (defined(invoker.deps)) { + deps += invoker.deps + } + } +}