普通文本  |  227行  |  8.07 KB

// Copyright 2014 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>
#include <string>

#include <base/command_line.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/json/json_reader.h>
#include <base/logging.h>
#include <base/strings/string_util.h>
#include <base/values.h>
#include <brillo/syslog_logging.h>

#include "chromeos-dbus-bindings/adaptor_generator.h"
#include "chromeos-dbus-bindings/method_name_generator.h"
#include "chromeos-dbus-bindings/proxy_generator.h"
#include "chromeos-dbus-bindings/xml_interface_parser.h"

using chromeos_dbus_bindings::AdaptorGenerator;
using chromeos_dbus_bindings::MethodNameGenerator;
using chromeos_dbus_bindings::ProxyGenerator;
using chromeos_dbus_bindings::ServiceConfig;

namespace switches {

static const char kHelp[] = "help";
static const char kMethodNames[] = "method-names";
static const char kAdaptor[] = "adaptor";
static const char kProxy[] = "proxy";
static const char kMock[] = "mock";
static const char kProxyPathForMocks[] = "proxy-path-in-mocks";
static const char kServiceConfig[] = "service-config";
static const char kHelpMessage[] = "\n"
    "generate-chromeos-dbus-bindings itf1.xml [itf2.xml...] [switches]\n"
    "    itf1.xml, ... = the input interface file(s) [mandatory].\n"
    "Available Switches: \n"
    "  --method-names=<method name header filename>\n"
    "    The output header file with string constants for each method name.\n"
    "  --adaptor=<adaptor header filename>\n"
    "    The output header file name containing the DBus adaptor class.\n"
    "  --proxy=<proxy header filename>\n"
    "    The output header file name containing the DBus proxy class.\n"
    "  --mock=<mock header filename>\n"
    "    The output header file name containing the DBus proxy mock class.\n"
    "  --service-config=<config.json>\n"
    "    The DBus service configuration file for the generator.\n";

}  // namespace switches

namespace {
// GYP sometimes enclosed the target file name in extra set of quotes like:
//    generate-chromeos-dbus-bindings in.xml "--adaptor=\"out.h\""
// So, this function helps us to remove them.
base::FilePath RemoveQuotes(const std::string& path) {
  std::string unquoted;
  base::TrimString(path, "\"'", &unquoted);
  return base::FilePath{unquoted};
}

// Makes a canonical path by making the path absolute and by removing any
// '..' which makes base::ReadFileToString() to fail.
base::FilePath SanitizeFilePath(const std::string& path) {
  base::FilePath path_in = RemoveQuotes(path);
  base::FilePath path_out = base::MakeAbsoluteFilePath(path_in);
  if (path_out.value().empty()) {
    LOG(WARNING) << "Failed to canonicalize '" << path << "'";
    path_out = path_in;
  }
  return path_out;
}


// Load the service configuration from the provided JSON file.
bool LoadConfig(const base::FilePath& path, ServiceConfig *config) {
  std::string contents;
  if (!base::ReadFileToString(path, &contents))
    return false;

  std::unique_ptr<base::Value> json{base::JSONReader::Read(contents).release()};
  if (!json)
    return false;

  base::DictionaryValue* dict = nullptr;  // Aliased with |json|.
  if (!json->GetAsDictionary(&dict))
    return false;

  dict->GetStringWithoutPathExpansion("service_name", &config->service_name);

  base::DictionaryValue* om_dict = nullptr;  // Owned by |dict|.
  if (dict->GetDictionaryWithoutPathExpansion("object_manager", &om_dict)) {
    if (!om_dict->GetStringWithoutPathExpansion("name",
                                                &config->object_manager.name) &&
        !config->service_name.empty()) {
      config->object_manager.name = config->service_name + ".ObjectManager";
    }
    om_dict->GetStringWithoutPathExpansion("object_path",
                                           &config->object_manager.object_path);
    if (config->object_manager.name.empty()) {
      LOG(ERROR) << "Object manager name is missing.";
      return false;
    }
  }

  base::ListValue* list = nullptr;  // Owned by |dict|.
  if (dict->GetListWithoutPathExpansion("ignore_interfaces", &list)) {
    config->ignore_interfaces.reserve(list->GetSize());
    for (base::Value* item : *list) {
      std::string interface_name;
      if (!item->GetAsString(&interface_name)) {
        LOG(ERROR) << "Invalid interface name in [ignore_interfaces] section";
        return false;
      }
      config->ignore_interfaces.push_back(interface_name);
    }
  }

  return true;
}

}   // anonymous namespace

int main(int argc, char** argv) {
  base::CommandLine::Init(argc, argv);
  base::CommandLine* cl = base::CommandLine::ForCurrentProcess();

  // Setup logging to stderr. This also parses some implicit flags using the
  // CommandLine singleton.
  brillo::InitLog(brillo::kLogToStderr | brillo::kLogHeader);

  if (cl->HasSwitch(switches::kHelp)) {
    LOG(INFO) << switches::kHelpMessage;
    return 0;
  }

  auto input_files = cl->GetArgs();
  if (input_files.empty()) {
    LOG(ERROR) << "At least one file must be specified.";
    LOG(ERROR) << switches::kHelpMessage;
    return 1;
  }

  ServiceConfig config;
  if (cl->HasSwitch(switches::kServiceConfig)) {
    std::string config_file = cl->GetSwitchValueASCII(switches::kServiceConfig);
    if (!config_file.empty() &&
        !LoadConfig(SanitizeFilePath(config_file), &config)) {
      LOG(ERROR) << "Failed to load DBus service config file " << config_file;
      return 1;
    }
  }

  chromeos_dbus_bindings::XmlInterfaceParser parser;
  for (const auto& input : input_files) {
    std::string contents;
    if (!base::ReadFileToString(SanitizeFilePath(input), &contents)) {
      LOG(ERROR) << "Failed to read file " << input;
      return 1;
    }
    if (!parser.ParseXmlInterfaceFile(contents, config.ignore_interfaces)) {
      LOG(ERROR) << "Failed to parse interface file " << input;
      return 1;
    }
  }

  if (cl->HasSwitch(switches::kMethodNames)) {
    std::string method_name_file =
        cl->GetSwitchValueASCII(switches::kMethodNames);
    VLOG(1) << "Outputting method names to " << method_name_file;
    if (!MethodNameGenerator::GenerateMethodNames(
            parser.interfaces(),
            RemoveQuotes(method_name_file))) {
      LOG(ERROR) << "Failed to output method names.";
      return 1;
    }
  }

  if (cl->HasSwitch(switches::kAdaptor)) {
    std::string adaptor_file = cl->GetSwitchValueASCII(switches::kAdaptor);
    VLOG(1) << "Outputting adaptor to " << adaptor_file;
    if (!AdaptorGenerator::GenerateAdaptors(parser.interfaces(),
                                            RemoveQuotes(adaptor_file))) {
      LOG(ERROR) << "Failed to output adaptor.";
      return 1;
     }
  }

  base::FilePath proxy_path;  // Used by both Proxy and Mock generation.
  if (cl->HasSwitch(switches::kProxy)) {
    std::string proxy_file = cl->GetSwitchValueASCII(switches::kProxy);
    proxy_path = RemoveQuotes(proxy_file);
    base::NormalizeFilePath(proxy_path, &proxy_path);
    VLOG(1) << "Outputting proxy to " << proxy_path.value();
    if (!ProxyGenerator::GenerateProxies(config, parser.interfaces(),
                                         proxy_path)) {
      LOG(ERROR) << "Failed to output proxy.";
      return 1;
     }
  }

  base::FilePath proxy_include_path = proxy_path;
  bool use_literal_include_path = false;
  if (cl->HasSwitch(switches::kProxyPathForMocks)) {
    std::string proxy_file_in_mocks =
        cl->GetSwitchValueASCII(switches::kProxyPathForMocks);
    proxy_include_path = RemoveQuotes(proxy_file_in_mocks);
    use_literal_include_path = true;
  }

  if (cl->HasSwitch(switches::kMock)) {
    std::string mock_file = cl->GetSwitchValueASCII(switches::kMock);
    base::FilePath mock_path = RemoveQuotes(mock_file);
    base::NormalizeFilePath(mock_path, &mock_path);
    VLOG(1) << "Outputting mock to " << mock_path.value();
    if (!ProxyGenerator::GenerateMocks(config, parser.interfaces(), mock_path,
                                       proxy_include_path,
                                       use_literal_include_path)) {
      LOG(ERROR) << "Failed to output mock.";
      return 1;
     }
  }

  return 0;
}