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