Home | History | Annotate | Download | only in chromeos-dbus-bindings
      1 // Copyright 2014 The Chromium OS Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include <memory>
      6 #include <string>
      7 
      8 #include <base/command_line.h>
      9 #include <base/files/file_path.h>
     10 #include <base/files/file_util.h>
     11 #include <base/json/json_reader.h>
     12 #include <base/logging.h>
     13 #include <base/strings/string_util.h>
     14 #include <base/values.h>
     15 #include <brillo/syslog_logging.h>
     16 
     17 #include "chromeos-dbus-bindings/adaptor_generator.h"
     18 #include "chromeos-dbus-bindings/method_name_generator.h"
     19 #include "chromeos-dbus-bindings/proxy_generator.h"
     20 #include "chromeos-dbus-bindings/xml_interface_parser.h"
     21 
     22 using chromeos_dbus_bindings::AdaptorGenerator;
     23 using chromeos_dbus_bindings::MethodNameGenerator;
     24 using chromeos_dbus_bindings::ProxyGenerator;
     25 using chromeos_dbus_bindings::ServiceConfig;
     26 
     27 namespace switches {
     28 
     29 static const char kHelp[] = "help";
     30 static const char kMethodNames[] = "method-names";
     31 static const char kAdaptor[] = "adaptor";
     32 static const char kProxy[] = "proxy";
     33 static const char kMock[] = "mock";
     34 static const char kProxyPathForMocks[] = "proxy-path-in-mocks";
     35 static const char kServiceConfig[] = "service-config";
     36 static const char kHelpMessage[] = "\n"
     37     "generate-chromeos-dbus-bindings itf1.xml [itf2.xml...] [switches]\n"
     38     "    itf1.xml, ... = the input interface file(s) [mandatory].\n"
     39     "Available Switches: \n"
     40     "  --method-names=<method name header filename>\n"
     41     "    The output header file with string constants for each method name.\n"
     42     "  --adaptor=<adaptor header filename>\n"
     43     "    The output header file name containing the DBus adaptor class.\n"
     44     "  --proxy=<proxy header filename>\n"
     45     "    The output header file name containing the DBus proxy class.\n"
     46     "  --mock=<mock header filename>\n"
     47     "    The output header file name containing the DBus proxy mock class.\n"
     48     "  --service-config=<config.json>\n"
     49     "    The DBus service configuration file for the generator.\n";
     50 
     51 }  // namespace switches
     52 
     53 namespace {
     54 // GYP sometimes enclosed the target file name in extra set of quotes like:
     55 //    generate-chromeos-dbus-bindings in.xml "--adaptor=\"out.h\""
     56 // So, this function helps us to remove them.
     57 base::FilePath RemoveQuotes(const std::string& path) {
     58   std::string unquoted;
     59   base::TrimString(path, "\"'", &unquoted);
     60   return base::FilePath{unquoted};
     61 }
     62 
     63 // Makes a canonical path by making the path absolute and by removing any
     64 // '..' which makes base::ReadFileToString() to fail.
     65 base::FilePath SanitizeFilePath(const std::string& path) {
     66   base::FilePath path_in = RemoveQuotes(path);
     67   base::FilePath path_out = base::MakeAbsoluteFilePath(path_in);
     68   if (path_out.value().empty()) {
     69     LOG(WARNING) << "Failed to canonicalize '" << path << "'";
     70     path_out = path_in;
     71   }
     72   return path_out;
     73 }
     74 
     75 
     76 // Load the service configuration from the provided JSON file.
     77 bool LoadConfig(const base::FilePath& path, ServiceConfig *config) {
     78   std::string contents;
     79   if (!base::ReadFileToString(path, &contents))
     80     return false;
     81 
     82   std::unique_ptr<base::Value> json{base::JSONReader::Read(contents).release()};
     83   if (!json)
     84     return false;
     85 
     86   base::DictionaryValue* dict = nullptr;  // Aliased with |json|.
     87   if (!json->GetAsDictionary(&dict))
     88     return false;
     89 
     90   dict->GetStringWithoutPathExpansion("service_name", &config->service_name);
     91 
     92   base::DictionaryValue* om_dict = nullptr;  // Owned by |dict|.
     93   if (dict->GetDictionaryWithoutPathExpansion("object_manager", &om_dict)) {
     94     if (!om_dict->GetStringWithoutPathExpansion("name",
     95                                                 &config->object_manager.name) &&
     96         !config->service_name.empty()) {
     97       config->object_manager.name = config->service_name + ".ObjectManager";
     98     }
     99     om_dict->GetStringWithoutPathExpansion("object_path",
    100                                            &config->object_manager.object_path);
    101     if (config->object_manager.name.empty()) {
    102       LOG(ERROR) << "Object manager name is missing.";
    103       return false;
    104     }
    105   }
    106 
    107   base::ListValue* list = nullptr;  // Owned by |dict|.
    108   if (dict->GetListWithoutPathExpansion("ignore_interfaces", &list)) {
    109     config->ignore_interfaces.reserve(list->GetSize());
    110     for (base::Value* item : *list) {
    111       std::string interface_name;
    112       if (!item->GetAsString(&interface_name)) {
    113         LOG(ERROR) << "Invalid interface name in [ignore_interfaces] section";
    114         return false;
    115       }
    116       config->ignore_interfaces.push_back(interface_name);
    117     }
    118   }
    119 
    120   return true;
    121 }
    122 
    123 }   // anonymous namespace
    124 
    125 int main(int argc, char** argv) {
    126   base::CommandLine::Init(argc, argv);
    127   base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
    128 
    129   // Setup logging to stderr. This also parses some implicit flags using the
    130   // CommandLine singleton.
    131   brillo::InitLog(brillo::kLogToStderr | brillo::kLogHeader);
    132 
    133   if (cl->HasSwitch(switches::kHelp)) {
    134     LOG(INFO) << switches::kHelpMessage;
    135     return 0;
    136   }
    137 
    138   auto input_files = cl->GetArgs();
    139   if (input_files.empty()) {
    140     LOG(ERROR) << "At least one file must be specified.";
    141     LOG(ERROR) << switches::kHelpMessage;
    142     return 1;
    143   }
    144 
    145   ServiceConfig config;
    146   if (cl->HasSwitch(switches::kServiceConfig)) {
    147     std::string config_file = cl->GetSwitchValueASCII(switches::kServiceConfig);
    148     if (!config_file.empty() &&
    149         !LoadConfig(SanitizeFilePath(config_file), &config)) {
    150       LOG(ERROR) << "Failed to load DBus service config file " << config_file;
    151       return 1;
    152     }
    153   }
    154 
    155   chromeos_dbus_bindings::XmlInterfaceParser parser;
    156   for (const auto& input : input_files) {
    157     std::string contents;
    158     if (!base::ReadFileToString(SanitizeFilePath(input), &contents)) {
    159       LOG(ERROR) << "Failed to read file " << input;
    160       return 1;
    161     }
    162     if (!parser.ParseXmlInterfaceFile(contents, config.ignore_interfaces)) {
    163       LOG(ERROR) << "Failed to parse interface file " << input;
    164       return 1;
    165     }
    166   }
    167 
    168   if (cl->HasSwitch(switches::kMethodNames)) {
    169     std::string method_name_file =
    170         cl->GetSwitchValueASCII(switches::kMethodNames);
    171     VLOG(1) << "Outputting method names to " << method_name_file;
    172     if (!MethodNameGenerator::GenerateMethodNames(
    173             parser.interfaces(),
    174             RemoveQuotes(method_name_file))) {
    175       LOG(ERROR) << "Failed to output method names.";
    176       return 1;
    177     }
    178   }
    179 
    180   if (cl->HasSwitch(switches::kAdaptor)) {
    181     std::string adaptor_file = cl->GetSwitchValueASCII(switches::kAdaptor);
    182     VLOG(1) << "Outputting adaptor to " << adaptor_file;
    183     if (!AdaptorGenerator::GenerateAdaptors(parser.interfaces(),
    184                                             RemoveQuotes(adaptor_file))) {
    185       LOG(ERROR) << "Failed to output adaptor.";
    186       return 1;
    187      }
    188   }
    189 
    190   base::FilePath proxy_path;  // Used by both Proxy and Mock generation.
    191   if (cl->HasSwitch(switches::kProxy)) {
    192     std::string proxy_file = cl->GetSwitchValueASCII(switches::kProxy);
    193     proxy_path = RemoveQuotes(proxy_file);
    194     base::NormalizeFilePath(proxy_path, &proxy_path);
    195     VLOG(1) << "Outputting proxy to " << proxy_path.value();
    196     if (!ProxyGenerator::GenerateProxies(config, parser.interfaces(),
    197                                          proxy_path)) {
    198       LOG(ERROR) << "Failed to output proxy.";
    199       return 1;
    200      }
    201   }
    202 
    203   base::FilePath proxy_include_path = proxy_path;
    204   bool use_literal_include_path = false;
    205   if (cl->HasSwitch(switches::kProxyPathForMocks)) {
    206     std::string proxy_file_in_mocks =
    207         cl->GetSwitchValueASCII(switches::kProxyPathForMocks);
    208     proxy_include_path = RemoveQuotes(proxy_file_in_mocks);
    209     use_literal_include_path = true;
    210   }
    211 
    212   if (cl->HasSwitch(switches::kMock)) {
    213     std::string mock_file = cl->GetSwitchValueASCII(switches::kMock);
    214     base::FilePath mock_path = RemoveQuotes(mock_file);
    215     base::NormalizeFilePath(mock_path, &mock_path);
    216     VLOG(1) << "Outputting mock to " << mock_path.value();
    217     if (!ProxyGenerator::GenerateMocks(config, parser.interfaces(), mock_path,
    218                                        proxy_include_path,
    219                                        use_literal_include_path)) {
    220       LOG(ERROR) << "Failed to output mock.";
    221       return 1;
    222      }
    223   }
    224 
    225   return 0;
    226 }
    227