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.
RemoveQuotes(const std::string & path)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.
SanitizeFilePath(const std::string & path)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.
LoadConfig(const base::FilePath & path,ServiceConfig * config)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 
main(int argc,char ** argv)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