// 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; }