1 /*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <stdlib.h>
18
19 #include <limits>
20 #include <map>
21 #include <memory>
22 #include <set>
23 #include <string>
24
25 #include <google/protobuf/compiler/code_generator.h>
26 #include <google/protobuf/compiler/plugin.h>
27 #include <google/protobuf/descriptor.h>
28 #include <google/protobuf/descriptor.pb.h>
29 #include <google/protobuf/io/printer.h>
30 #include <google/protobuf/io/zero_copy_stream.h>
31
32 #include "perfetto/ext/base/string_utils.h"
33
34 namespace protozero {
35 namespace {
36
37 using google::protobuf::Descriptor;
38 using google::protobuf::EnumDescriptor;
39 using google::protobuf::EnumValueDescriptor;
40 using google::protobuf::FieldDescriptor;
41 using google::protobuf::FileDescriptor;
42 using google::protobuf::compiler::GeneratorContext;
43 using google::protobuf::io::Printer;
44 using google::protobuf::io::ZeroCopyOutputStream;
45 using perfetto::base::SplitString;
46 using perfetto::base::StripChars;
47 using perfetto::base::StripPrefix;
48 using perfetto::base::StripSuffix;
49 using perfetto::base::ToUpper;
50 using perfetto::base::Uppercase;
51
Assert(bool condition)52 void Assert(bool condition) {
53 if (!condition)
54 abort();
55 }
56
57 struct FileDescriptorComp {
operator ()protozero::__anon01f160e30111::FileDescriptorComp58 bool operator()(const FileDescriptor* lhs, const FileDescriptor* rhs) const {
59 int comp = lhs->name().compare(rhs->name());
60 Assert(comp != 0 || lhs == rhs);
61 return comp < 0;
62 }
63 };
64
65 struct DescriptorComp {
operator ()protozero::__anon01f160e30111::DescriptorComp66 bool operator()(const Descriptor* lhs, const Descriptor* rhs) const {
67 int comp = lhs->full_name().compare(rhs->full_name());
68 Assert(comp != 0 || lhs == rhs);
69 return comp < 0;
70 }
71 };
72
73 struct EnumDescriptorComp {
operator ()protozero::__anon01f160e30111::EnumDescriptorComp74 bool operator()(const EnumDescriptor* lhs, const EnumDescriptor* rhs) const {
75 int comp = lhs->full_name().compare(rhs->full_name());
76 Assert(comp != 0 || lhs == rhs);
77 return comp < 0;
78 }
79 };
80
ProtoStubName(const FileDescriptor * proto)81 inline std::string ProtoStubName(const FileDescriptor* proto) {
82 return StripSuffix(proto->name(), ".proto") + ".pzc";
83 }
84
85 class GeneratorJob {
86 public:
GeneratorJob(const FileDescriptor * file,Printer * stub_h_printer)87 GeneratorJob(const FileDescriptor* file, Printer* stub_h_printer)
88 : source_(file), stub_h_(stub_h_printer) {}
89
GenerateStubs()90 bool GenerateStubs() {
91 Preprocess();
92 GeneratePrologue();
93 for (const EnumDescriptor* enumeration : enums_)
94 GenerateEnumDescriptor(enumeration);
95 for (const Descriptor* message : messages_)
96 GenerateMessageDescriptor(message);
97 GenerateEpilogue();
98 return error_.empty();
99 }
100
SetOption(const std::string & name,const std::string & value)101 void SetOption(const std::string& name, const std::string& value) {
102 if (name == "wrapper_namespace") {
103 wrapper_namespace_ = value;
104 } else if (name == "guard_strip_prefix") {
105 guard_strip_prefix_ = value;
106 } else if (name == "guard_add_prefix") {
107 guard_add_prefix_ = value;
108 } else if (name == "path_strip_prefix") {
109 path_strip_prefix_ = value;
110 } else if (name == "path_add_prefix") {
111 path_add_prefix_ = value;
112 } else if (name == "invoker") {
113 invoker_ = value;
114 } else {
115 Abort(std::string() + "Unknown plugin option '" + name + "'.");
116 }
117 }
118
119 // If generator fails to produce stubs for a particular proto definitions
120 // it finishes with undefined output and writes the first error occurred.
GetFirstError() const121 const std::string& GetFirstError() const { return error_; }
122
123 private:
124 // Only the first error will be recorded.
Abort(const std::string & reason)125 void Abort(const std::string& reason) {
126 if (error_.empty())
127 error_ = reason;
128 }
129
130 // Get C++ class name corresponding to proto descriptor.
131 // Nested names are splitted by underscores. Underscores in type names aren't
132 // prohibited but not recommended in order to avoid name collisions.
133 template <class T>
GetCppClassName(const T * descriptor)134 inline std::string GetCppClassName(const T* descriptor) {
135 return StripChars(descriptor->full_name(), ".", '_');
136 }
137
FieldTypeToPackedBufferType(FieldDescriptor::Type type)138 const char* FieldTypeToPackedBufferType(FieldDescriptor::Type type) {
139 switch (type) {
140 case FieldDescriptor::TYPE_ENUM:
141 case FieldDescriptor::TYPE_INT32:
142 return "Int32";
143 case FieldDescriptor::TYPE_INT64:
144 return "Int64";
145 case FieldDescriptor::TYPE_UINT32:
146 return "Uint32";
147 case FieldDescriptor::TYPE_UINT64:
148 return "Uint64";
149 case FieldDescriptor::TYPE_SINT32:
150 return "Sint32";
151 case FieldDescriptor::TYPE_SINT64:
152 return "Sint64";
153 case FieldDescriptor::TYPE_FIXED32:
154 return "Fixed32";
155 case FieldDescriptor::TYPE_FIXED64:
156 return "Fixed64";
157 case FieldDescriptor::TYPE_SFIXED32:
158 return "Sfixed32";
159 case FieldDescriptor::TYPE_SFIXED64:
160 return "Sfixed64";
161 case FieldDescriptor::TYPE_FLOAT:
162 return "Float";
163 case FieldDescriptor::TYPE_DOUBLE:
164 return "Double";
165 case FieldDescriptor::TYPE_BOOL:
166 case FieldDescriptor::TYPE_STRING:
167 case FieldDescriptor::TYPE_BYTES:
168 case FieldDescriptor::TYPE_MESSAGE:
169 case FieldDescriptor::TYPE_GROUP:
170 break;
171 }
172 Abort("Unsupported packed type");
173 return "";
174 }
FieldToCppTypeName(const FieldDescriptor * field)175 std::string FieldToCppTypeName(const FieldDescriptor* field) {
176 switch (field->type()) {
177 case FieldDescriptor::TYPE_BOOL:
178 return "bool";
179 case FieldDescriptor::TYPE_INT32:
180 return "int32_t";
181 case FieldDescriptor::TYPE_INT64:
182 return "int64_t";
183 case FieldDescriptor::TYPE_UINT32:
184 return "uint32_t";
185 case FieldDescriptor::TYPE_UINT64:
186 return "uint64_t";
187 case FieldDescriptor::TYPE_SINT32:
188 return "int32_t";
189 case FieldDescriptor::TYPE_SINT64:
190 return "int64_t";
191 case FieldDescriptor::TYPE_FIXED32:
192 return "uint32_t";
193 case FieldDescriptor::TYPE_FIXED64:
194 return "uint64_t";
195 case FieldDescriptor::TYPE_SFIXED32:
196 return "int32_t";
197 case FieldDescriptor::TYPE_SFIXED64:
198 return "int64_t";
199 case FieldDescriptor::TYPE_FLOAT:
200 return "float";
201 case FieldDescriptor::TYPE_DOUBLE:
202 return "double";
203 case FieldDescriptor::TYPE_ENUM:
204 return "enum " + GetCppClassName(field->enum_type());
205 case FieldDescriptor::TYPE_STRING:
206 case FieldDescriptor::TYPE_BYTES:
207 return "const char*";
208 case FieldDescriptor::TYPE_MESSAGE:
209 return GetCppClassName(field->message_type());
210 case FieldDescriptor::TYPE_GROUP:
211 Abort("Groups not supported.");
212 return "";
213 }
214 Abort("Unrecognized FieldDescriptor::Type.");
215 return "";
216 }
217
CollectDescriptors()218 void CollectDescriptors() {
219 // Collect message descriptors in DFS order.
220 std::vector<const Descriptor*> stack;
221 stack.reserve(static_cast<size_t>(source_->message_type_count()));
222 for (int i = 0; i < source_->message_type_count(); ++i)
223 stack.push_back(source_->message_type(i));
224
225 while (!stack.empty()) {
226 const Descriptor* message = stack.back();
227 stack.pop_back();
228
229 if (message->extension_count() > 0) {
230 if (message->field_count() > 0 || message->nested_type_count() > 0 ||
231 message->enum_type_count() > 0) {
232 Abort("message with extend blocks shouldn't contain anything else");
233 }
234
235 // Iterate over all fields in "extend" blocks.
236 for (int i = 0; i < message->extension_count(); ++i) {
237 const FieldDescriptor* extension = message->extension(i);
238
239 // Protoc plugin API does not group fields in "extend" blocks.
240 // As the support for extensions in protozero is limited, the code
241 // assumes that extend blocks are located inside a wrapper message and
242 // name of this message is used to group them.
243 std::string extension_name = extension->extension_scope()->name();
244 extensions_[extension_name].push_back(extension);
245 }
246 } else {
247 messages_.push_back(message);
248 for (int i = 0; i < message->nested_type_count(); ++i) {
249 stack.push_back(message->nested_type(i));
250 // Emit a forward declaration of nested message types, as the outer
251 // class will refer to them when creating type aliases.
252 referenced_messages_.insert(message->nested_type(i));
253 }
254 }
255 }
256
257 // Collect enums.
258 for (int i = 0; i < source_->enum_type_count(); ++i)
259 enums_.push_back(source_->enum_type(i));
260
261 if (source_->extension_count() > 0) {
262 // TODO(b/336524288): emit field numbers
263 }
264
265 for (const Descriptor* message : messages_) {
266 for (int i = 0; i < message->enum_type_count(); ++i) {
267 enums_.push_back(message->enum_type(i));
268 }
269 }
270 }
271
CollectDependencies()272 void CollectDependencies() {
273 // Public import basically means that callers only need to import this
274 // proto in order to use the stuff publicly imported by this proto.
275 for (int i = 0; i < source_->public_dependency_count(); ++i)
276 public_imports_.insert(source_->public_dependency(i));
277
278 if (source_->weak_dependency_count() > 0)
279 Abort("Weak imports are not supported.");
280
281 // Validations. Collect public imports (of collected imports) in DFS order.
282 // Visibilty for current proto:
283 // - all imports listed in current proto,
284 // - public imports of everything imported (recursive).
285 std::vector<const FileDescriptor*> stack;
286 for (int i = 0; i < source_->dependency_count(); ++i) {
287 const FileDescriptor* imp = source_->dependency(i);
288 stack.push_back(imp);
289 if (public_imports_.count(imp) == 0) {
290 private_imports_.insert(imp);
291 }
292 }
293
294 while (!stack.empty()) {
295 const FileDescriptor* imp = stack.back();
296 stack.pop_back();
297 for (int i = 0; i < imp->public_dependency_count(); ++i) {
298 stack.push_back(imp->public_dependency(i));
299 }
300 }
301
302 // Collect descriptors of messages and enums used in current proto.
303 // It will be used to generate necessary forward declarations and
304 // check that everything lays in the same namespace.
305 for (const Descriptor* message : messages_) {
306 for (int i = 0; i < message->field_count(); ++i) {
307 const FieldDescriptor* field = message->field(i);
308
309 if (field->type() == FieldDescriptor::TYPE_MESSAGE) {
310 if (public_imports_.count(field->message_type()->file()) == 0) {
311 // Avoid multiple forward declarations since
312 // public imports have been already included.
313 referenced_messages_.insert(field->message_type());
314 }
315 } else if (field->type() == FieldDescriptor::TYPE_ENUM) {
316 if (public_imports_.count(field->enum_type()->file()) == 0) {
317 referenced_enums_.insert(field->enum_type());
318 }
319 }
320 }
321 }
322 }
323
Preprocess()324 void Preprocess() {
325 // Package name maps to a series of namespaces.
326 package_ = source_->package();
327 namespaces_ = SplitString(package_, ".");
328 if (!wrapper_namespace_.empty())
329 namespaces_.push_back(wrapper_namespace_);
330
331 full_namespace_prefix_ = "";
332 for (size_t i = 0; i < namespaces_.size(); i++) {
333 full_namespace_prefix_ += namespaces_[i];
334 if (i + 1 != namespaces_.size()) {
335 full_namespace_prefix_ += "_";
336 }
337 }
338
339 CollectDescriptors();
340 CollectDependencies();
341 }
342
GenerateGuard()343 std::string GenerateGuard() {
344 std::string guard = StripSuffix(source_->name(), ".proto");
345 guard = ToUpper(guard);
346 guard = StripChars(guard, ".-/\\", '_');
347 guard = StripPrefix(guard, guard_strip_prefix_);
348 guard = guard_add_prefix_ + guard + "_PZC_H_";
349 return guard;
350 }
351
352 // Print top header, namespaces and forward declarations.
GeneratePrologue()353 void GeneratePrologue() {
354 stub_h_->Print(
355 R"(/*
356 * Copyright (C) 2023 The Android Open Source Project
357 *
358 * Licensed under the Apache License, Version 2.0 (the "License");
359 * you may not use this file except in compliance with the License.
360 * You may obtain a copy of the License at
361 *
362 * http://www.apache.org/licenses/LICENSE-2.0
363 *
364 * Unless required by applicable law or agreed to in writing, software
365 * distributed under the License is distributed on an "AS IS" BASIS,
366 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
367 * See the License for the specific language governing permissions and
368 * limitations under the License.
369 */
370
371 )");
372 stub_h_->Print("// Autogenerated by the ProtoZero C compiler plugin.\n");
373 if (!invoker_.empty()) {
374 stub_h_->Print("// Invoked by $invoker$\n", "invoker", invoker_);
375 }
376 stub_h_->Print("// DO NOT EDIT.\n");
377
378 stub_h_->Print(
379 "#ifndef $guard$\n"
380 "#define $guard$\n\n"
381 "#include <stdbool.h>\n"
382 "#include <stdint.h>\n\n"
383 "#include \"perfetto/public/pb_macros.h\"\n",
384 "guard", GenerateGuard());
385
386 // Print includes for public imports and enums which cannot be forward
387 // declared.
388 std::vector<std::string> imports;
389 for (const FileDescriptor* dependency : public_imports_) {
390 imports.push_back(ProtoStubName(dependency));
391 }
392 for (const EnumDescriptor* e : referenced_enums_) {
393 if (e->file() != source_) {
394 imports.push_back(ProtoStubName(e->file()));
395 }
396 }
397
398 std::sort(imports.begin(), imports.end());
399
400 for (const std::string& imp : imports) {
401 std::string include_path = imp;
402 if (!path_strip_prefix_.empty()) {
403 include_path = StripPrefix(imp, path_strip_prefix_);
404 }
405 include_path = path_add_prefix_ + include_path;
406
407 stub_h_->Print("#include \"$name$.h\"\n", "name", include_path);
408 }
409 stub_h_->Print("\n");
410
411 // Print forward declarations.
412 for (const Descriptor* message : referenced_messages_) {
413 stub_h_->Print("PERFETTO_PB_MSG_DECL($class$);\n", "class",
414 GetCppClassName(message));
415 }
416
417 stub_h_->Print("\n");
418 }
419
GenerateEnumDescriptor(const EnumDescriptor * enumeration)420 void GenerateEnumDescriptor(const EnumDescriptor* enumeration) {
421 if (enumeration->containing_type()) {
422 stub_h_->Print("PERFETTO_PB_ENUM_IN_MSG($msg$, $class$){\n", "msg",
423 GetCppClassName(enumeration->containing_type()), "class",
424 enumeration->name());
425 } else {
426 stub_h_->Print("PERFETTO_PB_ENUM($class$){\n", "class",
427 GetCppClassName(enumeration));
428 }
429 stub_h_->Indent();
430
431 for (int i = 0; i < enumeration->value_count(); ++i) {
432 const EnumValueDescriptor* value = enumeration->value(i);
433 const std::string value_name = value->name();
434
435 if (enumeration->containing_type()) {
436 stub_h_->Print(
437 "PERFETTO_PB_ENUM_IN_MSG_ENTRY($msg$, $val$) = $number$,\n", "msg",
438 GetCppClassName(enumeration->containing_type()), "val", value_name,
439 "number", std::to_string(value->number()));
440 } else {
441 stub_h_->Print("PERFETTO_PB_ENUM_ENTRY($val$) = $number$, \n", "val",
442 full_namespace_prefix_ + "_" + value_name, "number",
443 std::to_string(value->number()));
444 }
445 }
446 stub_h_->Outdent();
447 stub_h_->Print("};\n\n");
448 }
449
450 // Packed repeated fields are encoded as a length-delimited field on the wire,
451 // where the payload is the concatenation of invidually encoded elements.
GeneratePackedRepeatedFieldDescriptor(const std::string & message_cpp_type,const FieldDescriptor * field)452 void GeneratePackedRepeatedFieldDescriptor(
453 const std::string& message_cpp_type,
454 const FieldDescriptor* field) {
455 std::map<std::string, std::string> setter;
456 setter["id"] = std::to_string(field->number());
457 setter["name"] = field->lowercase_name();
458 setter["class"] = message_cpp_type;
459 setter["buffer_type"] = FieldTypeToPackedBufferType(field->type());
460 stub_h_->Print(
461 setter,
462 "PERFETTO_PB_FIELD($class$, PACKED, $buffer_type$, $name$, $id$);\n");
463 }
464
GenerateSimpleFieldDescriptor(const std::string & message_cpp_type,const FieldDescriptor * field)465 void GenerateSimpleFieldDescriptor(const std::string& message_cpp_type,
466 const FieldDescriptor* field) {
467 std::map<std::string, std::string> setter;
468 setter["id"] = std::to_string(field->number());
469 setter["name"] = field->lowercase_name();
470 setter["ctype"] = FieldToCppTypeName(field);
471 setter["class"] = message_cpp_type;
472
473 switch (field->type()) {
474 case FieldDescriptor::TYPE_BYTES:
475 case FieldDescriptor::TYPE_STRING:
476 stub_h_->Print(
477 setter,
478 "PERFETTO_PB_FIELD($class$, STRING, const char*, $name$, $id$);\n");
479 break;
480 case FieldDescriptor::TYPE_UINT64:
481 case FieldDescriptor::TYPE_UINT32:
482 case FieldDescriptor::TYPE_INT64:
483 case FieldDescriptor::TYPE_INT32:
484 case FieldDescriptor::TYPE_BOOL:
485 case FieldDescriptor::TYPE_ENUM:
486 stub_h_->Print(
487 setter,
488 "PERFETTO_PB_FIELD($class$, VARINT, $ctype$, $name$, $id$);\n");
489 break;
490 case FieldDescriptor::TYPE_SINT64:
491 case FieldDescriptor::TYPE_SINT32:
492 stub_h_->Print(
493 setter,
494 "PERFETTO_PB_FIELD($class$, ZIGZAG, $ctype$, $name$, $id$);\n");
495 break;
496 case FieldDescriptor::TYPE_SFIXED32:
497 case FieldDescriptor::TYPE_FIXED32:
498 case FieldDescriptor::TYPE_FLOAT:
499 stub_h_->Print(
500 setter,
501 "PERFETTO_PB_FIELD($class$, FIXED32, $ctype$, $name$, $id$);\n");
502 break;
503 case FieldDescriptor::TYPE_SFIXED64:
504 case FieldDescriptor::TYPE_FIXED64:
505 case FieldDescriptor::TYPE_DOUBLE:
506 stub_h_->Print(
507 setter,
508 "PERFETTO_PB_FIELD($class$, FIXED64, $ctype$, $name$, $id$);\n");
509 break;
510 case FieldDescriptor::TYPE_MESSAGE:
511 case FieldDescriptor::TYPE_GROUP:
512 Abort("Groups not supported.");
513 break;
514 }
515 }
516
GenerateNestedMessageFieldDescriptor(const std::string & message_cpp_type,const FieldDescriptor * field)517 void GenerateNestedMessageFieldDescriptor(const std::string& message_cpp_type,
518 const FieldDescriptor* field) {
519 std::string inner_class = GetCppClassName(field->message_type());
520 stub_h_->Print(
521 "PERFETTO_PB_FIELD($class$, MSG, $inner_class$, $name$, $id$);\n",
522 "class", message_cpp_type, "id", std::to_string(field->number()),
523 "name", field->lowercase_name(), "inner_class", inner_class);
524 }
525
GenerateMessageDescriptor(const Descriptor * message)526 void GenerateMessageDescriptor(const Descriptor* message) {
527 stub_h_->Print("PERFETTO_PB_MSG($name$);\n", "name",
528 GetCppClassName(message));
529
530 // Field descriptors.
531 for (int i = 0; i < message->field_count(); ++i) {
532 GenerateFieldDescriptor(GetCppClassName(message), message->field(i));
533 }
534 stub_h_->Print("\n");
535 }
536
GenerateFieldDescriptor(const std::string & message_cpp_type,const FieldDescriptor * field)537 void GenerateFieldDescriptor(const std::string& message_cpp_type,
538 const FieldDescriptor* field) {
539 // GenerateFieldMetadata(message_cpp_type, field);
540 if (field->is_packed()) {
541 GeneratePackedRepeatedFieldDescriptor(message_cpp_type, field);
542 } else if (field->type() != FieldDescriptor::TYPE_MESSAGE) {
543 GenerateSimpleFieldDescriptor(message_cpp_type, field);
544 } else {
545 GenerateNestedMessageFieldDescriptor(message_cpp_type, field);
546 }
547 }
548
GenerateEpilogue()549 void GenerateEpilogue() {
550 stub_h_->Print("#endif // $guard$\n", "guard", GenerateGuard());
551 }
552
553 const FileDescriptor* const source_;
554 Printer* const stub_h_;
555 std::string error_;
556
557 std::string package_;
558 std::string wrapper_namespace_;
559 std::string guard_strip_prefix_;
560 std::string guard_add_prefix_;
561 std::string path_strip_prefix_;
562 std::string path_add_prefix_;
563 std::string invoker_;
564 std::vector<std::string> namespaces_;
565 std::string full_namespace_prefix_;
566 std::vector<const Descriptor*> messages_;
567 std::vector<const EnumDescriptor*> enums_;
568 std::map<std::string, std::vector<const FieldDescriptor*>> extensions_;
569
570 // The custom *Comp comparators are to ensure determinism of the generator.
571 std::set<const FileDescriptor*, FileDescriptorComp> public_imports_;
572 std::set<const FileDescriptor*, FileDescriptorComp> private_imports_;
573 std::set<const Descriptor*, DescriptorComp> referenced_messages_;
574 std::set<const EnumDescriptor*, EnumDescriptorComp> referenced_enums_;
575 };
576
577 class ProtoZeroCGenerator : public google::protobuf::compiler::CodeGenerator {
578 public:
579 explicit ProtoZeroCGenerator();
580 ~ProtoZeroCGenerator() override;
581
582 // CodeGenerator implementation
583 bool Generate(const google::protobuf::FileDescriptor* file,
584 const std::string& options,
585 GeneratorContext* context,
586 std::string* error) const override;
587 };
588
ProtoZeroCGenerator()589 ProtoZeroCGenerator::ProtoZeroCGenerator() {}
590
~ProtoZeroCGenerator()591 ProtoZeroCGenerator::~ProtoZeroCGenerator() {}
592
Generate(const FileDescriptor * file,const std::string & options,GeneratorContext * context,std::string * error) const593 bool ProtoZeroCGenerator::Generate(const FileDescriptor* file,
594 const std::string& options,
595 GeneratorContext* context,
596 std::string* error) const {
597 const std::unique_ptr<ZeroCopyOutputStream> stub_h_file_stream(
598 context->Open(ProtoStubName(file) + ".h"));
599
600 // Variables are delimited by $.
601 Printer stub_h_printer(stub_h_file_stream.get(), '$');
602 GeneratorJob job(file, &stub_h_printer);
603
604 // Parse additional options.
605 for (const std::string& option : SplitString(options, ",")) {
606 std::vector<std::string> option_pair = SplitString(option, "=");
607 job.SetOption(option_pair[0], option_pair[1]);
608 }
609
610 if (!job.GenerateStubs()) {
611 *error = job.GetFirstError();
612 return false;
613 }
614 return true;
615 }
616
617 } // namespace
618 } // namespace protozero
619
main(int argc,char * argv[])620 int main(int argc, char* argv[]) {
621 protozero::ProtoZeroCGenerator generator;
622 return google::protobuf::compiler::PluginMain(argc, argv, &generator);
623 }
624