| // 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_file_value_serializer.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/service_config.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) { |
| JSONFileValueDeserializer reader(path); |
| std::string error_str; |
| std::unique_ptr<base::Value> json = reader.Deserialize(nullptr, &error_str); |
| if (!json) { |
| LOG(ERROR) << "Could not load service configuration: " << error_str; |
| return false; |
| } |
| |
| if (!json->is_dict()) { |
| LOG(ERROR) << "Service configuration was not a dictionary"; |
| return false; |
| } |
| |
| const std::string* service_name = json->FindStringKey("service_name"); |
| if (service_name) { |
| config->service_name = *service_name; |
| } |
| |
| const base::Value* om_dict = json->FindDictKey("object_manager"); |
| if (om_dict) { |
| const std::string* object_manager_name = om_dict->FindStringKey("name"); |
| if (object_manager_name) { |
| config->object_manager.name = *object_manager_name; |
| } else if (!config->service_name.empty()) { |
| config->object_manager.name = config->service_name + ".ObjectManager"; |
| } |
| |
| const std::string* object_manager_object_path = |
| om_dict->FindStringKey("object_path"); |
| if (object_manager_object_path) { |
| config->object_manager.object_path = *object_manager_object_path; |
| } |
| |
| if (config->object_manager.name.empty()) { |
| LOG(ERROR) << "Object manager name is missing."; |
| return false; |
| } |
| } |
| |
| const base::Value* list = json->FindListKey("ignore_interfaces"); |
| if (list) { |
| config->ignore_interfaces.reserve(list->GetList().size()); |
| for (const base::Value& item : list->GetList()) { |
| const std::string* interface_name = item.GetIfString(); |
| if (!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; |
| AdaptorGenerator adaptor_gen; |
| if (!adaptor_gen.GenerateAdaptors(parser.interfaces(), |
| RemoveQuotes(adaptor_file))) { |
| LOG(ERROR) << "Failed to output adaptor."; |
| return 1; |
| } |
| } |
| |
| ProxyGenerator proxy_gen; |
| 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 (!proxy_gen.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 (!proxy_gen.GenerateMocks(config, parser.interfaces(), mock_path, |
| proxy_include_path, |
| use_literal_include_path)) { |
| LOG(ERROR) << "Failed to output mock."; |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |