• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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 "src/protozero/protoc_plugin/protozero_generator.h"
18 
19 #include <map>
20 #include <memory>
21 #include <set>
22 #include <string>
23 
24 #include "google/protobuf/descriptor.h"
25 #include "google/protobuf/io/printer.h"
26 #include "google/protobuf/io/zero_copy_stream.h"
27 #include "google/protobuf/stubs/strutil.h"
28 
29 namespace protozero {
30 
31 using google::protobuf::Descriptor;  // Message descriptor.
32 using google::protobuf::EnumDescriptor;
33 using google::protobuf::EnumValueDescriptor;
34 using google::protobuf::FieldDescriptor;
35 using google::protobuf::FileDescriptor;
36 using google::protobuf::compiler::GeneratorContext;
37 using google::protobuf::io::Printer;
38 using google::protobuf::io::ZeroCopyOutputStream;
39 
40 using google::protobuf::Split;
41 using google::protobuf::StripPrefixString;
42 using google::protobuf::StripString;
43 using google::protobuf::StripSuffixString;
44 using google::protobuf::UpperString;
45 
46 namespace {
47 
ProtoStubName(const FileDescriptor * proto)48 inline std::string ProtoStubName(const FileDescriptor* proto) {
49   return StripSuffixString(proto->name(), ".proto") + ".pbzero";
50 }
51 
52 class GeneratorJob {
53  public:
GeneratorJob(const FileDescriptor * file,Printer * stub_h_printer,Printer * stub_cc_printer)54   GeneratorJob(const FileDescriptor* file,
55                Printer* stub_h_printer,
56                Printer* stub_cc_printer)
57       : source_(file), stub_h_(stub_h_printer), stub_cc_(stub_cc_printer) {}
58 
GenerateStubs()59   bool GenerateStubs() {
60     Preprocess();
61     GeneratePrologue();
62     for (const EnumDescriptor* enumeration : enums_)
63       GenerateEnumDescriptor(enumeration);
64     for (const Descriptor* message : messages_)
65       GenerateMessageDescriptor(message);
66     GenerateEpilogue();
67     return error_.empty();
68   }
69 
SetOption(const std::string & name,const std::string & value)70   void SetOption(const std::string& name, const std::string& value) {
71     if (name == "wrapper_namespace") {
72       wrapper_namespace_ = value;
73     } else {
74       Abort(std::string() + "Unknown plugin option '" + name + "'.");
75     }
76   }
77 
78   // If generator fails to produce stubs for a particular proto definitions
79   // it finishes with undefined output and writes the first error occured.
GetFirstError() const80   const std::string& GetFirstError() const { return error_; }
81 
82  private:
83   // Only the first error will be recorded.
Abort(const std::string & reason)84   void Abort(const std::string& reason) {
85     if (error_.empty())
86       error_ = reason;
87   }
88 
89   // Get full name (including outer descriptors) of proto descriptor.
90   template <class T>
GetDescriptorName(const T * descriptor)91   inline std::string GetDescriptorName(const T* descriptor) {
92     if (!package_.empty()) {
93       return StripPrefixString(descriptor->full_name(), package_ + ".");
94     } else {
95       return descriptor->full_name();
96     }
97   }
98 
99   // Get C++ class name corresponding to proto descriptor.
100   // Nested names are splitted by underscores. Underscores in type names aren't
101   // prohibited but not recommended in order to avoid name collisions.
102   template <class T>
GetCppClassName(const T * descriptor,bool full=false)103   inline std::string GetCppClassName(const T* descriptor, bool full = false) {
104     std::string name = GetDescriptorName(descriptor);
105     StripString(&name, ".", '_');
106     if (full)
107       name = full_namespace_prefix_ + name;
108     return name;
109   }
110 
GetFieldNumberConstant(const FieldDescriptor * field)111   inline std::string GetFieldNumberConstant(const FieldDescriptor* field) {
112     std::string name = field->camelcase_name();
113     if (!name.empty()) {
114       name.at(0) = static_cast<char>(toupper(name.at(0)));
115       name = "k" + name + "FieldNumber";
116     } else {
117       // Protoc allows fields like 'bool _ = 1'.
118       Abort("Empty field name in camel case notation.");
119     }
120     return name;
121   }
122 
123   // Small enums can be written faster without involving VarInt encoder.
IsTinyEnumField(const FieldDescriptor * field)124   inline bool IsTinyEnumField(const FieldDescriptor* field) {
125     if (field->type() != FieldDescriptor::TYPE_ENUM)
126       return false;
127     const EnumDescriptor* enumeration = field->enum_type();
128 
129     for (int i = 0; i < enumeration->value_count(); ++i) {
130       int32_t value = enumeration->value(i)->number();
131       if (value < 0 || value > 0x7F)
132         return false;
133     }
134     return true;
135   }
136 
CollectDescriptors()137   void CollectDescriptors() {
138     // Collect message descriptors in DFS order.
139     std::vector<const Descriptor*> stack;
140     for (int i = 0; i < source_->message_type_count(); ++i)
141       stack.push_back(source_->message_type(i));
142 
143     while (!stack.empty()) {
144       const Descriptor* message = stack.back();
145       stack.pop_back();
146       messages_.push_back(message);
147       for (int i = 0; i < message->nested_type_count(); ++i) {
148         stack.push_back(message->nested_type(i));
149       }
150     }
151 
152     // Collect enums.
153     for (int i = 0; i < source_->enum_type_count(); ++i)
154       enums_.push_back(source_->enum_type(i));
155 
156     for (const Descriptor* message : messages_) {
157       for (int i = 0; i < message->enum_type_count(); ++i) {
158         enums_.push_back(message->enum_type(i));
159       }
160     }
161   }
162 
CollectDependencies()163   void CollectDependencies() {
164     // Public import basically means that callers only need to import this
165     // proto in order to use the stuff publicly imported by this proto.
166     for (int i = 0; i < source_->public_dependency_count(); ++i)
167       public_imports_.insert(source_->public_dependency(i));
168 
169     if (source_->weak_dependency_count() > 0)
170       Abort("Weak imports are not supported.");
171 
172     // Sanity check. Collect public imports (of collected imports) in DFS order.
173     // Visibilty for current proto:
174     // - all imports listed in current proto,
175     // - public imports of everything imported (recursive).
176     std::vector<const FileDescriptor*> stack;
177     for (int i = 0; i < source_->dependency_count(); ++i) {
178       const FileDescriptor* import = source_->dependency(i);
179       stack.push_back(import);
180       if (public_imports_.count(import) == 0) {
181         private_imports_.insert(import);
182       }
183     }
184 
185     while (!stack.empty()) {
186       const FileDescriptor* import = stack.back();
187       stack.pop_back();
188       // Having imports under different packages leads to unnecessary
189       // complexity with namespaces.
190       if (import->package() != package_)
191         Abort("Imported proto must be in the same package.");
192 
193       for (int i = 0; i < import->public_dependency_count(); ++i) {
194         stack.push_back(import->public_dependency(i));
195       }
196     }
197 
198     // Collect descriptors of messages and enums used in current proto.
199     // It will be used to generate necessary forward declarations and performed
200     // sanity check guarantees that everything lays in the same namespace.
201     for (const Descriptor* message : messages_) {
202       for (int i = 0; i < message->field_count(); ++i) {
203         const FieldDescriptor* field = message->field(i);
204 
205         if (field->type() == FieldDescriptor::TYPE_MESSAGE) {
206           if (public_imports_.count(field->message_type()->file()) == 0) {
207             // Avoid multiple forward declarations since
208             // public imports have been already included.
209             referenced_messages_.insert(field->message_type());
210           }
211         } else if (field->type() == FieldDescriptor::TYPE_ENUM) {
212           if (public_imports_.count(field->enum_type()->file()) == 0) {
213             referenced_enums_.insert(field->enum_type());
214           }
215         }
216       }
217     }
218   }
219 
Preprocess()220   void Preprocess() {
221     // Package name maps to a series of namespaces.
222     package_ = source_->package();
223     namespaces_ = Split(package_, ".");
224     if (!wrapper_namespace_.empty())
225       namespaces_.push_back(wrapper_namespace_);
226 
227     full_namespace_prefix_ = "::";
228     for (const std::string& ns : namespaces_)
229       full_namespace_prefix_ += ns + "::";
230 
231     CollectDescriptors();
232     CollectDependencies();
233   }
234 
235   // Print top header, namespaces and forward declarations.
GeneratePrologue()236   void GeneratePrologue() {
237     std::string greeting =
238         "// Autogenerated by the ProtoZero compiler plugin. DO NOT EDIT.\n";
239     std::string guard = package_ + "_" + source_->name() + "_H_";
240     UpperString(&guard);
241     StripString(&guard, ".-/\\", '_');
242 
243     stub_h_->Print(
244         "$greeting$\n"
245         "#ifndef $guard$\n"
246         "#define $guard$\n\n"
247         "#include <stddef.h>\n"
248         "#include <stdint.h>\n\n"
249         "#include \"perfetto/protozero/proto_field_descriptor.h\"\n"
250         "#include \"perfetto/protozero/message.h\"\n",
251         "greeting", greeting, "guard", guard);
252     stub_cc_->Print(
253         "$greeting$\n"
254         "#include \"$name$.h\"\n",
255         "greeting", greeting, "name", ProtoStubName(source_));
256 
257     // Print includes for public imports.
258     for (const FileDescriptor* dependency : public_imports_) {
259       // Dependency name could contain slashes but importing from upper-level
260       // directories is not possible anyway since build system processes each
261       // proto file individually. Hence proto lookup path is always equal to the
262       // directory where particular proto file is located and protoc does not
263       // allow reference to upper directory (aka ..) in import path.
264       //
265       // Laconically said:
266       // - source_->name() may never have slashes,
267       // - dependency->name() may have slashes but always refers to inner path.
268       stub_h_->Print("#include \"$name$.h\"\n", "name",
269                      ProtoStubName(dependency));
270     }
271     stub_h_->Print("\n");
272 
273     // Print includes for private imports to .cc file.
274     for (const FileDescriptor* dependency : private_imports_) {
275       stub_cc_->Print("#include \"$name$.h\"\n", "name",
276                       ProtoStubName(dependency));
277     }
278     stub_cc_->Print("\n");
279 
280     if (messages_.size() > 0) {
281       stub_cc_->Print(
282           "namespace {\n"
283           "  static const ::protozero::ProtoFieldDescriptor "
284           "kInvalidField = {\"\", "
285           "::protozero::ProtoFieldDescriptor::Type::TYPE_INVALID, "
286           "0, false};\n"
287           "}\n\n");
288     }
289 
290     // Print namespaces.
291     for (const std::string& ns : namespaces_) {
292       stub_h_->Print("namespace $ns$ {\n", "ns", ns);
293       stub_cc_->Print("namespace $ns$ {\n", "ns", ns);
294     }
295     stub_h_->Print("\n");
296     stub_cc_->Print("\n");
297 
298     // Print forward declarations.
299     for (const Descriptor* message : referenced_messages_) {
300       stub_h_->Print("class $class$;\n", "class", GetCppClassName(message));
301     }
302     for (const EnumDescriptor* enumeration : referenced_enums_) {
303       stub_h_->Print("enum $class$ : int32_t;\n", "class",
304                      GetCppClassName(enumeration));
305     }
306     stub_h_->Print("\n");
307   }
308 
GenerateEnumDescriptor(const EnumDescriptor * enumeration)309   void GenerateEnumDescriptor(const EnumDescriptor* enumeration) {
310     stub_h_->Print("enum $class$ : int32_t {\n", "class",
311                    GetCppClassName(enumeration));
312     stub_h_->Indent();
313 
314     std::string value_name_prefix;
315     if (enumeration->containing_type() != nullptr)
316       value_name_prefix = GetCppClassName(enumeration) + "_";
317 
318     for (int i = 0; i < enumeration->value_count(); ++i) {
319       const EnumValueDescriptor* value = enumeration->value(i);
320       stub_h_->Print("$name$ = $number$,\n", "name",
321                      value_name_prefix + value->name(), "number",
322                      std::to_string(value->number()));
323     }
324 
325     stub_h_->Outdent();
326     stub_h_->Print("};\n\n");
327   }
328 
GenerateSimpleFieldDescriptor(const FieldDescriptor * field)329   void GenerateSimpleFieldDescriptor(const FieldDescriptor* field) {
330     std::map<std::string, std::string> setter;
331     setter["id"] = std::to_string(field->number());
332     setter["name"] = field->name();
333     setter["action"] = field->is_repeated() ? "add" : "set";
334 
335     std::string appender;
336     std::string cpp_type;
337 
338     switch (field->type()) {
339       case FieldDescriptor::TYPE_BOOL: {
340         appender = "AppendTinyVarInt";
341         cpp_type = "bool";
342         break;
343       }
344       case FieldDescriptor::TYPE_INT32: {
345         appender = "AppendVarInt";
346         cpp_type = "int32_t";
347         break;
348       }
349       case FieldDescriptor::TYPE_INT64: {
350         appender = "AppendVarInt";
351         cpp_type = "int64_t";
352         break;
353       }
354       case FieldDescriptor::TYPE_UINT32: {
355         appender = "AppendVarInt";
356         cpp_type = "uint32_t";
357         break;
358       }
359       case FieldDescriptor::TYPE_UINT64: {
360         appender = "AppendVarInt";
361         cpp_type = "uint64_t";
362         break;
363       }
364       case FieldDescriptor::TYPE_SINT32: {
365         appender = "AppendSignedVarInt";
366         cpp_type = "int32_t";
367         break;
368       }
369       case FieldDescriptor::TYPE_SINT64: {
370         appender = "AppendSignedVarInt";
371         cpp_type = "int64_t";
372         break;
373       }
374       case FieldDescriptor::TYPE_FIXED32: {
375         appender = "AppendFixed";
376         cpp_type = "uint32_t";
377         break;
378       }
379       case FieldDescriptor::TYPE_FIXED64: {
380         appender = "AppendFixed";
381         cpp_type = "uint64_t";
382         break;
383       }
384       case FieldDescriptor::TYPE_SFIXED32: {
385         appender = "AppendFixed";
386         cpp_type = "int32_t";
387         break;
388       }
389       case FieldDescriptor::TYPE_SFIXED64: {
390         appender = "AppendFixed";
391         cpp_type = "int64_t";
392         break;
393       }
394       case FieldDescriptor::TYPE_FLOAT: {
395         appender = "AppendFixed";
396         cpp_type = "float";
397         break;
398       }
399       case FieldDescriptor::TYPE_DOUBLE: {
400         appender = "AppendFixed";
401         cpp_type = "double";
402         break;
403       }
404       case FieldDescriptor::TYPE_ENUM: {
405         appender = IsTinyEnumField(field) ? "AppendTinyVarInt" : "AppendVarInt";
406         cpp_type = GetCppClassName(field->enum_type(), true);
407         break;
408       }
409       case FieldDescriptor::TYPE_STRING: {
410         appender = "AppendString";
411         cpp_type = "const char*";
412         break;
413       }
414       case FieldDescriptor::TYPE_BYTES: {
415         stub_h_->Print(
416             setter,
417             "void $action$_$name$(const uint8_t* data, size_t size) {\n"
418             "  AppendBytes($id$, data, size);\n"
419             "}\n");
420         return;
421       }
422       case FieldDescriptor::TYPE_GROUP:
423       case FieldDescriptor::TYPE_MESSAGE: {
424         Abort("Unsupported field type.");
425         return;
426       }
427     }
428     setter["appender"] = appender;
429     setter["cpp_type"] = cpp_type;
430     stub_h_->Print(setter,
431                    "void $action$_$name$($cpp_type$ value) {\n"
432                    "  $appender$($id$, value);\n"
433                    "}\n");
434 
435     // For strings also generate a variant for non-null terminated strings.
436     if (field->type() == FieldDescriptor::TYPE_STRING) {
437       stub_h_->Print(setter,
438                      "// Doesn't check for null terminator.\n"
439                      "// Expects |value| to be at least |size| long.\n"
440                      "void $action$_$name$($cpp_type$ value, size_t size) {\n"
441                      "  AppendBytes($id$, value, size);\n"
442                      "}\n");
443     }
444   }
445 
GenerateNestedMessageFieldDescriptor(const FieldDescriptor * field)446   void GenerateNestedMessageFieldDescriptor(const FieldDescriptor* field) {
447     std::string action = field->is_repeated() ? "add" : "set";
448     std::string inner_class = GetCppClassName(field->message_type());
449     std::string outer_class = GetCppClassName(field->containing_type());
450 
451     stub_h_->Print("$inner_class$* $action$_$name$();\n", "name", field->name(),
452                    "action", action, "inner_class", inner_class);
453     stub_cc_->Print(
454         "$inner_class$* $outer_class$::$action$_$name$() {\n"
455         "  return BeginNestedMessage<$inner_class$>($id$);\n"
456         "}\n\n",
457         "id", std::to_string(field->number()), "name", field->name(), "action",
458         action, "inner_class", inner_class, "outer_class", outer_class);
459   }
460 
GenerateReflectionForMessageFields(const Descriptor * message)461   void GenerateReflectionForMessageFields(const Descriptor* message) {
462     const bool has_fields = (message->field_count() > 0);
463 
464     // Field number constants.
465     if (has_fields) {
466       stub_h_->Print("enum : int32_t {\n");
467       stub_h_->Indent();
468 
469       for (int i = 0; i < message->field_count(); ++i) {
470         const FieldDescriptor* field = message->field(i);
471         stub_h_->Print("$name$ = $id$,\n", "name",
472                        GetFieldNumberConstant(field), "id",
473                        std::to_string(field->number()));
474       }
475       stub_h_->Outdent();
476       stub_h_->Print("};\n");
477     }
478 
479     // Fields reflection table.
480     stub_h_->Print(
481         "static const ::protozero::ProtoFieldDescriptor* "
482         "GetFieldDescriptor(uint32_t field_id);\n");
483 
484     std::string class_name = GetCppClassName(message);
485     if (has_fields) {
486       stub_cc_->Print(
487           "static const ::protozero::ProtoFieldDescriptor "
488           "kFields_$class$[] = {\n",
489           "class", class_name);
490       stub_cc_->Indent();
491       for (int i = 0; i < message->field_count(); ++i) {
492         const FieldDescriptor* field = message->field(i);
493         std::string type_const =
494             std::string("TYPE_") + FieldDescriptor::TypeName(field->type());
495         UpperString(&type_const);
496         stub_cc_->Print(
497             "{\"$name$\", "
498             "::protozero::ProtoFieldDescriptor::Type::$type$, "
499             "$number$, $is_repeated$},\n",
500             "name", field->name(), "type", type_const, "number",
501             std::to_string(field->number()), "is_repeated",
502             std::to_string(field->is_repeated()));
503       }
504       stub_cc_->Outdent();
505       stub_cc_->Print("};\n\n");
506     }
507 
508     // Fields reflection getter.
509     stub_cc_->Print(
510         "const ::protozero::ProtoFieldDescriptor* "
511         "$class$::GetFieldDescriptor(uint32_t field_id) {\n",
512         "class", class_name);
513     stub_cc_->Indent();
514     if (has_fields) {
515       stub_cc_->Print("switch (field_id) {\n");
516       stub_cc_->Indent();
517       for (int i = 0; i < message->field_count(); ++i) {
518         stub_cc_->Print(
519             "case $field$:\n"
520             "  return &kFields_$class$[$id$];\n",
521             "class", class_name, "field",
522             GetFieldNumberConstant(message->field(i)), "id", std::to_string(i));
523       }
524       stub_cc_->Print(
525           "default:\n"
526           "  return &kInvalidField;\n");
527       stub_cc_->Outdent();
528       stub_cc_->Print("}\n");
529     } else {
530       stub_cc_->Print("return &kInvalidField;\n");
531     }
532     stub_cc_->Outdent();
533     stub_cc_->Print("}\n\n");
534   }
535 
GenerateMessageDescriptor(const Descriptor * message)536   void GenerateMessageDescriptor(const Descriptor* message) {
537     stub_h_->Print(
538         "class $name$ : public ::protozero::Message {\n"
539         " public:\n",
540         "name", GetCppClassName(message));
541     stub_h_->Indent();
542 
543     GenerateReflectionForMessageFields(message);
544 
545     // Using statements for nested messages.
546     for (int i = 0; i < message->nested_type_count(); ++i) {
547       const Descriptor* nested_message = message->nested_type(i);
548       stub_h_->Print("using $local_name$ = $global_name$;\n", "local_name",
549                      nested_message->name(), "global_name",
550                      GetCppClassName(nested_message, true));
551     }
552 
553     // Using statements for nested enums.
554     for (int i = 0; i < message->enum_type_count(); ++i) {
555       const EnumDescriptor* nested_enum = message->enum_type(i);
556       stub_h_->Print("using $local_name$ = $global_name$;\n", "local_name",
557                      nested_enum->name(), "global_name",
558                      GetCppClassName(nested_enum, true));
559     }
560 
561     // Values of nested enums.
562     for (int i = 0; i < message->enum_type_count(); ++i) {
563       const EnumDescriptor* nested_enum = message->enum_type(i);
564       std::string value_name_prefix = GetCppClassName(nested_enum) + "_";
565 
566       for (int j = 0; j < nested_enum->value_count(); ++j) {
567         const EnumValueDescriptor* value = nested_enum->value(j);
568         stub_h_->Print("static const $class$ $name$ = $full_name$;\n", "class",
569                        nested_enum->name(), "name", value->name(), "full_name",
570                        value_name_prefix + value->name());
571       }
572     }
573 
574     // Field descriptors.
575     for (int i = 0; i < message->field_count(); ++i) {
576       const FieldDescriptor* field = message->field(i);
577       if (field->is_packed()) {
578         Abort("Packed repeated fields are not supported.");
579         return;
580       }
581       if (field->type() != FieldDescriptor::TYPE_MESSAGE) {
582         GenerateSimpleFieldDescriptor(field);
583       } else {
584         GenerateNestedMessageFieldDescriptor(field);
585       }
586     }
587 
588     stub_h_->Outdent();
589     stub_h_->Print("};\n\n");
590   }
591 
GenerateEpilogue()592   void GenerateEpilogue() {
593     for (unsigned i = 0; i < namespaces_.size(); ++i) {
594       stub_h_->Print("} // Namespace.\n");
595       stub_cc_->Print("} // Namespace.\n");
596     }
597     stub_h_->Print("#endif  // Include guard.\n");
598   }
599 
600   const FileDescriptor* const source_;
601   Printer* const stub_h_;
602   Printer* const stub_cc_;
603   std::string error_;
604 
605   std::string package_;
606   std::string wrapper_namespace_;
607   std::vector<std::string> namespaces_;
608   std::string full_namespace_prefix_;
609   std::vector<const Descriptor*> messages_;
610   std::vector<const EnumDescriptor*> enums_;
611 
612   std::set<const FileDescriptor*> public_imports_;
613   std::set<const FileDescriptor*> private_imports_;
614   std::set<const Descriptor*> referenced_messages_;
615   std::set<const EnumDescriptor*> referenced_enums_;
616 };
617 
618 }  // namespace
619 
ProtoZeroGenerator()620 ProtoZeroGenerator::ProtoZeroGenerator() {}
621 
~ProtoZeroGenerator()622 ProtoZeroGenerator::~ProtoZeroGenerator() {}
623 
Generate(const FileDescriptor * file,const std::string & options,GeneratorContext * context,std::string * error) const624 bool ProtoZeroGenerator::Generate(const FileDescriptor* file,
625                                   const std::string& options,
626                                   GeneratorContext* context,
627                                   std::string* error) const {
628   const std::unique_ptr<ZeroCopyOutputStream> stub_h_file_stream(
629       context->Open(ProtoStubName(file) + ".h"));
630   const std::unique_ptr<ZeroCopyOutputStream> stub_cc_file_stream(
631       context->Open(ProtoStubName(file) + ".cc"));
632 
633   // Variables are delimited by $.
634   Printer stub_h_printer(stub_h_file_stream.get(), '$');
635   Printer stub_cc_printer(stub_cc_file_stream.get(), '$');
636   GeneratorJob job(file, &stub_h_printer, &stub_cc_printer);
637 
638   // Parse additional options.
639   for (const std::string& option : Split(options, ",")) {
640     std::vector<std::string> option_pair = Split(option, "=");
641     job.SetOption(option_pair[0], option_pair[1]);
642   }
643 
644   if (!job.GenerateStubs()) {
645     *error = job.GetFirstError();
646     return false;
647   }
648   return true;
649 }
650 
651 }  // namespace protozero
652