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