• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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