910e62b5创建于 1月15日历史提交
// 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;

  for (GDBusInterfaceInfo** interface = node->interfaces;
       interface != nullptr && *interface != nullptr;
       UNSAFE_TODO(++interface)) {
    std::string namespace_name = Namespace((**interface).name);
    output << "namespace remoting::" << 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 remoting::" << namespace_name << std::endl
           << std::endl;
  }

  output << "#endif  // " << header_guard << std::endl;

  g_dbus_node_info_unref(node);
}