blob: 6a966e7214659fbaf50332a1f706fc00445b244a [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <gio/gio.h>
#include <glib.h>
#include <fstream>
#include <iostream>
#include <ostream>
#include <string>
#include <string_view>
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/strings/string_util.h"
// This utility handles conversion from an XML-format D-Bus interface definition
// to a header file containing method, property, and signal spec classes for use
// with GDBusConnectionRef. It should be run from the src/ directory with the
// output specified with a relative path. (Otherwise the generated header guard
// will be incorrect.) Sample usage:
//
// gen_dbus_interface --input=/path/to/com.example.Interface.xml \
// --output=remoting/host/linux/dbus_interfaces/com_example_Interface.h
//
// Typically, the XML interface definition will come from one of three sources:
//
// 1. Installed by service: Services providing public D-Bus APIs may install
// interface definitions in /usr/share/dbus-1/interfaces/.
// 2. From the service's source tree: Even if not installed, services may
// include an interface XML file in their source tree.
// 3. Via D-Bus introspection: If the service supports introspection, an XML
// interface definition can be obtained via busctl:
//
// busctl --user introspect --xml-interface org.foo.Bar /org/foo/Bar
//
// The output will include all interfaces implemented by the `/org/foo/Bar`
// object exported by the service at `org.foo.Bar`. Edit it to include only
// the interface of interest and feed the result to this utility.
//
// After generation, the result should be formatted and an appropriate copyright
// header added.
//
// If no XML interface definition is available, a header file can be created
// manually following the style of the existing header files.
// Converts a relative path from src/ to an appropriate header guard.
std::string HeaderGuard(const base::FilePath& header) {
// For now assumes command is being run from src/ and header is a relative
// path from there. Can be made smarter if needed.
std::string result = base::ToUpperASCII(header.MaybeAsASCII());
base::ReplaceChars(result, "/.", "_", &result);
return result + "_";
}
// Converts a D-Bus interface name into a valid C++ namespace identifier.
std::string Namespace(std::string_view interface) {
std::string result;
base::ReplaceChars(interface, ".", "_", &result);
return result;
}
// Writes a tuple of parameters, with each parameter on its own line followed by
// a line comment giving the parameter name.
void WriteParameters(std::ostream& output, GDBusArgInfo** args) {
if (args == nullptr || *args == nullptr) {
output << " \"()\"" << std::endl;
return;
}
output << " \"(\"" << std::endl;
for (GDBusArgInfo** arg = args; *arg != nullptr; UNSAFE_TODO(++arg)) {
output << " \"" << (**arg).signature << "\" // " << (**arg).name
<< std::endl;
}
output << " \")\"" << std::endl;
}
int main(int argc, char* argv[]) {
base::CommandLine::Init(argc, argv);
base::FilePath input_path =
base::CommandLine::ForCurrentProcess()->GetSwitchValuePath("input");
base::FilePath output_path =
base::CommandLine::ForCurrentProcess()->GetSwitchValuePath("output");
if (input_path.empty() || output_path.empty()) {
std::cerr << "Usage: gen_dbus_interface --input=com.example.Interface.xml "
"--output=remoting/host/linux/dbus_interfaces/"
"com_example_Interface.h"
<< std::endl;
return 1;
}
std::string header_guard = HeaderGuard(output_path);
if (header_guard.empty()) {
std::cerr << "Output name is not ASCII" << std::endl;
return 1;
}
std::string xml;
if (!base::ReadFileToString(input_path, &xml)) {
std::cerr << "Failed to read input file.";
}
GError* error = nullptr;
GDBusNodeInfo* node = g_dbus_node_info_new_for_xml(xml.c_str(), &error);
if (error) {
std::cerr << "Error parsing xml: " << error->message << std::endl;
g_error_free(error);
return 1;
}
std::ofstream output(output_path.value());
if (!output) {
std::cerr << "Failed to open output file for writing." << std::endl;
g_dbus_node_info_unref(node);
return 1;
}
output << "// This file was generated from " << input_path.BaseName().value()
<< std::endl
<< std::endl;
output << "#ifndef " << header_guard << std::endl;
output << "#define " << header_guard << std::endl << std::endl;
output << "#include \"remoting/host/linux/gvariant_type.h\"" << std::endl
<< std::endl;
output << "namespace remoting {" << std::endl << std::endl;
for (GDBusInterfaceInfo** interface = node->interfaces;
interface != nullptr && *interface != nullptr;
UNSAFE_TODO(++interface)) {
std::string namespace_name = Namespace((**interface).name);
output << "namespace " << namespace_name << " {" << std::endl << std::endl;
for (GDBusMethodInfo** method = (**interface).methods;
method != nullptr && *method != nullptr; UNSAFE_TODO(++method)) {
output << "// method" << std::endl;
output << "struct " << (**method).name << " {" << std::endl;
output << " static constexpr char kInterfaceName[] = \""
<< (**interface).name << "\";" << std::endl;
output << " static constexpr char kMethodName[] = \"" << (**method).name
<< "\";" << std::endl;
output << " static constexpr gvariant::Type kInType{" << std::endl;
WriteParameters(output, (**method).in_args);
output << " };" << std::endl;
output << " static constexpr gvariant::Type kOutType{" << std::endl;
WriteParameters(output, (**method).out_args);
output << " };" << std::endl;
output << "};" << std::endl << std::endl;
}
for (GDBusPropertyInfo** property = (**interface).properties;
property != nullptr && *property != nullptr; UNSAFE_TODO(++property)) {
output << "// property" << std::endl;
output << "struct " << (**property).name << " {" << std::endl;
output << " static constexpr char kInterfaceName[] = \""
<< (**interface).name << "\";" << std::endl;
output << " static constexpr char kPropertyName[] = \""
<< (**property).name << "\";" << std::endl;
output << " static constexpr gvariant::Type kType{\""
<< (**property).signature << "\"};" << std::endl;
output << " static constexpr bool kReadable = "
<< ((**property).flags & G_DBUS_PROPERTY_INFO_FLAGS_READABLE
? "true;"
: "false;")
<< std::endl;
output << " static constexpr bool kWritable = "
<< ((**property).flags & G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE
? "true;"
: "false;")
<< std::endl;
output << "};" << std::endl << std::endl;
}
for (GDBusSignalInfo** signal = (**interface).signals;
signal != nullptr && *signal != nullptr; UNSAFE_TODO(++signal)) {
output << "// signal" << std::endl;
output << "struct " << (**signal).name << " {" << std::endl;
output << " static constexpr char kInterfaceName[] = \""
<< (**interface).name << "\";" << std::endl;
output << " static constexpr char kSignalName[] = \"" << (**signal).name
<< "\";" << std::endl;
output << " static constexpr gvariant::Type kType{" << std::endl;
WriteParameters(output, (**signal).args);
output << " };" << std::endl;
output << "};" << std::endl << std::endl;
}
output << "} // namespace " << namespace_name << std::endl << std::endl;
}
output << "} // namespace remoting" << std::endl << std::endl;
output << "#endif // " << header_guard << std::endl;
g_dbus_node_info_unref(node);
}