• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc.  All rights reserved.
3 //
4 // Use of this source code is governed by a BSD-style
5 // license that can be found in the LICENSE file or at
6 // https://developers.google.com/open-source/licenses/bsd
7 
8 #include "google/protobuf/compiler/objectivec/generator.h"
9 
10 #include <cstddef>
11 #include <cstdlib>
12 #include <fstream>
13 #include <iostream>
14 #include <memory>
15 #include <string>
16 #include <utility>
17 #include <vector>
18 
19 #include "absl/container/flat_hash_set.h"
20 #include "absl/memory/memory.h"
21 #include "absl/strings/ascii.h"
22 #include "absl/strings/match.h"
23 #include "absl/strings/str_cat.h"
24 #include "absl/strings/str_split.h"
25 #include "absl/strings/string_view.h"
26 #include "absl/strings/strip.h"
27 #include "google/protobuf/compiler/code_generator.h"
28 #include "google/protobuf/compiler/objectivec/file.h"
29 #include "google/protobuf/compiler/objectivec/names.h"
30 #include "google/protobuf/compiler/objectivec/options.h"
31 #include "google/protobuf/descriptor.h"
32 #include "google/protobuf/io/printer.h"
33 #include "google/protobuf/io/zero_copy_stream.h"
34 
35 namespace google {
36 namespace protobuf {
37 namespace compiler {
38 namespace objectivec {
39 
40 namespace {
41 
42 // Convert a string with "yes"/"no" (case insensitive) to a boolean, returning
43 // true/false for if the input string was a valid value. The empty string is
44 // also treated as a true value. If the input string is invalid, `result` is
45 // unchanged.
StringToBool(const std::string & value,bool * result)46 bool StringToBool(const std::string& value, bool* result) {
47   std::string upper_value(value);
48   absl::AsciiStrToUpper(&upper_value);
49   if (upper_value == "NO") {
50     *result = false;
51     return true;
52   }
53   if (upper_value == "YES" || upper_value.empty()) {
54     *result = true;
55     return true;
56   }
57 
58   return false;
59 }
60 
NumberedObjCMFileName(absl::string_view basename,int number)61 std::string NumberedObjCMFileName(absl::string_view basename, int number) {
62   return absl::StrCat(basename, ".out/", number, ".pbobjc.m");
63 }
64 
65 }  // namespace
66 
HasGenerateAll() const67 bool ObjectiveCGenerator::HasGenerateAll() const { return true; }
68 
Generate(const FileDescriptor * file,const std::string & parameter,GeneratorContext * context,std::string * error) const69 bool ObjectiveCGenerator::Generate(const FileDescriptor* file,
70                                    const std::string& parameter,
71                                    GeneratorContext* context,
72                                    std::string* error) const {
73   *error = "Unimplemented Generate() method. Call GenerateAll() instead.";
74   return false;
75 }
76 
GenerateAll(const std::vector<const FileDescriptor * > & files,const std::string & parameter,GeneratorContext * context,std::string * error) const77 bool ObjectiveCGenerator::GenerateAll(
78     const std::vector<const FileDescriptor*>& files,
79     const std::string& parameter, GeneratorContext* context,
80     std::string* error) const {
81   // -----------------------------------------------------------------
82   // Parse generator options. These options are passed to the compiler using the
83   // --objc_opt flag. The options are passed as a comma separated list of
84   // options along with their values. If the option appears multiple times, only
85   // the last value will be considered.
86   //
87   // e.g. protoc ...
88   // --objc_opt=expected_prefixes=file.txt,generate_for_named_framework=MyFramework
89 
90   Options validation_options;
91   GenerationOptions generation_options;
92 
93   std::vector<std::pair<std::string, std::string> > options;
94   ParseGeneratorParameter(parameter, &options);
95   for (size_t i = 0; i < options.size(); i++) {
96     if (options[i].first == "expected_prefixes_path") {
97       // Path to find a file containing the expected prefixes
98       // (objc_class_prefix "PREFIX") for proto packages (package NAME). The
99       // generator will then issue warnings/errors if in the proto files being
100       // generated the option is not listed/wrong/etc in the file.
101       //
102       // The format of the file is:
103       //   - An entry is a line of "package=prefix".
104       //   - Comments start with "#".
105       //   - A comment can go on a line after a expected package/prefix pair.
106       //     (i.e. - "package=prefix # comment")
107       //   - For files that do NOT have a proto package (not recommended), an
108       //     entry can be made as "no_package:PATH=prefix", where PATH is the
109       //     path for the .proto file.
110       //
111       // There is no validation that the prefixes are good prefixes, it is
112       // assumed that they are when you create the file.
113       validation_options.expected_prefixes_path = options[i].second;
114     } else if (options[i].first == "expected_prefixes_suppressions") {
115       // A semicolon delimited string that lists the paths of .proto files to
116       // exclude from the package prefix validations (expected_prefixes_path).
117       // This is provided as an "out", to skip some files being checked.
118       for (absl::string_view split_piece :
119            absl::StrSplit(options[i].second, ';', absl::SkipEmpty())) {
120         validation_options.expected_prefixes_suppressions.push_back(
121             std::string(split_piece));
122       }
123     } else if (options[i].first == "prefixes_must_be_registered") {
124       // If objc prefix file option value must be registered to be used. This
125       // option has no meaning if an "expected_prefixes_path" isn't set. The
126       // available options are:
127       //   "no": They don't have to be registered.
128       //   "yes": They must be registered and an error will be raised if a files
129       //     tried to use a prefix that isn't registered.
130       // Default is "no".
131       if (!StringToBool(options[i].second,
132                         &validation_options.prefixes_must_be_registered)) {
133         *error = absl::StrCat(
134             "error: Unknown value for prefixes_must_be_registered: ",
135             options[i].second);
136         return false;
137       }
138     } else if (options[i].first == "require_prefixes") {
139       // If every file must have an objc prefix file option to be used. The
140       // available options are:
141       //   "no": Files can be generated without the prefix option.
142       //   "yes": Files must have the objc prefix option, and an error will be
143       //     raised if a files doesn't have one.
144       // Default is "no".
145       if (!StringToBool(options[i].second,
146                         &validation_options.require_prefixes)) {
147         *error = absl::StrCat("error: Unknown value for require_prefixes: ",
148                               options[i].second);
149         return false;
150       }
151     } else if (options[i].first == "generate_for_named_framework") {
152       // The name of the framework that protos are being generated for. This
153       // will cause the #import statements to be framework based using this
154       // name (i.e. - "#import <NAME/proto.pbobjc.h>).
155       //
156       // NOTE: If this option is used with
157       // named_framework_to_proto_path_mappings_path, then this is effectively
158       // the "default" framework name used for everything that wasn't mapped by
159       // the mapping file.
160       generation_options.generate_for_named_framework = options[i].second;
161     } else if (options[i].first ==
162                "named_framework_to_proto_path_mappings_path") {
163       // Path to find a file containing the list of framework names and proto
164       // files. The generator uses this to decide if a proto file
165       // referenced should use a framework style import vs. a user level import
166       // (#import <FRAMEWORK/file.pbobjc.h> vs #import "dir/file.pbobjc.h").
167       //
168       // The format of the file is:
169       //   - An entry is a line of "frameworkName: file.proto, dir/file2.proto".
170       //   - Comments start with "#".
171       //   - A comment can go on a line after a expected package/prefix pair.
172       //     (i.e. - "frameworkName: file.proto # comment")
173       //
174       // Any number of files can be listed for a framework, just separate them
175       // with commas.
176       //
177       // There can be multiple lines listing the same frameworkName in case it
178       // has a lot of proto files included in it; having multiple lines makes
179       // things easier to read. If a proto file is not configured in the
180       // mappings file, it will use the default framework name if one was passed
181       // with generate_for_named_framework, or the relative path to it's include
182       // path otherwise.
183       generation_options.named_framework_to_proto_path_mappings_path =
184           options[i].second;
185     } else if (options[i].first == "runtime_import_prefix") {
186       // Path to use as a prefix on #imports of runtime provided headers in the
187       // generated files. When integrating ObjC protos into a build system,
188       // this can be used to avoid having to add the runtime directory to the
189       // header search path since the generate #import will be more complete.
190       generation_options.runtime_import_prefix =
191           std::string(absl::StripSuffix(options[i].second, "/"));
192     } else if (options[i].first == "package_to_prefix_mappings_path") {
193       // Path to use for when loading the objc class prefix mappings to use.
194       // The `objc_class_prefix` file option is always honored first if one is
195       // present. This option also has precedent over the use_package_as_prefix
196       // option.
197       //
198       // The format of the file is:
199       //   - An entry is a line of "package=prefix".
200       //   - Comments start with "#".
201       //   - A comment can go on a line after a expected package/prefix pair.
202       //     (i.e. - "package=prefix # comment")
203       //   - For files that do NOT have a proto package (not recommended), an
204       //     entry can be made as "no_package:PATH=prefix", where PATH is the
205       //     path for the .proto file.
206       //
207       SetPackageToPrefixMappingsPath(options[i].second);
208     } else if (options[i].first == "use_package_as_prefix") {
209       // Controls how the symbols should be prefixed to avoid symbols
210       // collisions. The objc_class_prefix file option is always honored, this
211       // is just what to do if that isn't set. The available options are:
212       //   "no": Not prefixed (the existing mode).
213       //   "yes": Make a prefix out of the proto package.
214       bool value = false;
215       if (StringToBool(options[i].second, &value)) {
216         SetUseProtoPackageAsDefaultPrefix(value);
217       } else {
218         *error = absl::StrCat("error: Unknown use_package_as_prefix: ",
219                               options[i].second);
220         return false;
221       }
222     } else if (options[i].first == "proto_package_prefix_exceptions_path") {
223       // Path to find a file containing the list of proto package names that are
224       // exceptions when use_package_as_prefix is enabled. This can be used to
225       // migrate packages one at a time to use_package_as_prefix since there
226       // are likely code updates needed with each one.
227       //
228       // The format of the file is:
229       //   - An entry is a line of "proto.package.name".
230       //   - Comments start with "#".
231       //   - A comment can go on a line after a expected package/prefix pair.
232       //     (i.e. - "some.proto.package # comment")
233       SetProtoPackagePrefixExceptionList(options[i].second);
234     } else if (options[i].first == "package_as_prefix_forced_prefix") {
235       // String to use as the prefix when deriving a prefix from the package
236       // name. So this only applies when use_package_as_prefix is also used.
237       SetForcedPackagePrefix(options[i].second);
238     } else if (options[i].first == "headers_use_forward_declarations") {
239       if (!StringToBool(options[i].second,
240                         &generation_options.headers_use_forward_declarations)) {
241         *error = absl::StrCat(
242             "error: Unknown value for headers_use_forward_declarations: ",
243             options[i].second);
244         return false;
245       }
246     } else if (options[i].first == "strip_custom_options") {
247       // Controls if extensions that define custom options are included the
248       // generated code. Since ObjC protos does not capture these descriptor
249       // options, there normally isn't a need for these extensions. Docs on
250       // custom options:
251       //   https://protobuf.dev/programming-guides/proto2/#customoptions
252       if (!StringToBool(options[i].second,
253                         &generation_options.strip_custom_options)) {
254         *error = absl::StrCat("error: Unknown value for strip_custom_options: ",
255                               options[i].second);
256         return false;
257       }
258     } else if (options[i].first == "generate_minimal_imports") {
259       // Controls if minimal imports should be generated from a files imports.
260       // Since custom options require imports, they current cause generated
261       // imports even though there is nothing captured in the generated code,
262       // this provides smaller imports only for the things referenced. This
263       // could break code in complex cases where code uses types via long
264       // import chains with public imports mixed through the way, as things
265       // that aren't really needed for the local usages could be pruned.
266       if (!StringToBool(options[i].second,
267                         &generation_options.generate_minimal_imports)) {
268         *error =
269             absl::StrCat("error: Unknown value for generate_minimal_imports: ",
270                          options[i].second);
271         return false;
272       }
273     } else if (options[i].first == "experimental_multi_source_generation") {
274       // This is an experimental option, and could be removed or change at any
275       // time; it is not documented in the README.md for that reason.
276       //
277       // Enables a mode where each ObjC class (messages and roots) generates to
278       // a unique .m file; this is to explore impacts on code size when not
279       // compiling/linking with `-ObjC` as then only linker visible needs should
280       // be pulled into the builds.
281       if (!StringToBool(
282               options[i].second,
283               &generation_options.experimental_multi_source_generation)) {
284         *error = absl::StrCat(
285             "error: Unknown value for experimental_multi_source_generation: ",
286             options[i].second);
287         return false;
288       }
289     } else if (options[i].first == "experimental_strip_nonfunctional_codegen") {
290       if (!StringToBool(
291               options[i].second,
292               &generation_options.experimental_strip_nonfunctional_codegen)) {
293         *error = absl::StrCat(
294             "error: Unknown value for "
295             "experimental_strip_nonfunctional_codegen: ",
296             options[i].second);
297         return false;
298       }
299     } else {
300       *error =
301           absl::StrCat("error: Unknown generator option: ", options[i].first);
302       return false;
303     }
304   }
305 
306   // Multi source generation forces:
307   // - off the use of fwd decls in favor of imports
308   // - on the minimal imports support
309   if (generation_options.experimental_multi_source_generation) {
310     generation_options.headers_use_forward_declarations = false;
311     generation_options.generate_minimal_imports = true;
312   }
313   if (generation_options.experimental_strip_nonfunctional_codegen) {
314     generation_options.generate_minimal_imports = true;
315   }
316 
317   // -----------------------------------------------------------------
318 
319   // NOTE: src/google/protobuf/compiler/plugin.cc makes use of cerr for some
320   // error cases, so it seems to be ok to use as a back door for warnings.
321 
322   // This is a way to turn off these warnings, the intent is that if you find
323   // this then you also did as asked and filed an issue so the need for the
324   // generation option is known. But it allows you to keep your builds quiet
325   // after opening the issue. The value of the environment variable should be
326   // a comma separated list of the names of the options to suppress their usage
327   // warning.
328   char* options_warnings_suppressions_cstr =
329       getenv("GPB_OBJC_SUPPRESS_DEPRECATED_OPTIONS_WARNINGS");
330   const absl::string_view options_warnings_suppressions =
331       options_warnings_suppressions_cstr != nullptr
332           ? options_warnings_suppressions_cstr
333           : "";
334   if (generation_options.headers_use_forward_declarations &&
335       !absl::StrContains(options_warnings_suppressions,
336                          "headers_use_forward_declarations")) {
337     std::cerr << "WARNING: headers_use_forward_declarations is enabled, this "
338                  "is deprecated and will be removed in the future. If you have "
339                  "a need for enabling it please file an issue at "
340                  "https://github.com/protocolbuffers/protobuf/issues with "
341                  "your use case."
342               << std::endl;
343     std::cerr.flush();
344   }
345   if (!generation_options.generate_minimal_imports &&
346       !absl::StrContains(options_warnings_suppressions,
347                          "generate_minimal_imports")) {
348     std::cerr << "WARNING: generate_minimal_imports is disabled, this is "
349                  "deprecated and will be removed in the future. If you have a "
350                  "need for disabling it please file an issue at "
351                  "https://github.com/protocolbuffers/protobuf/issues with "
352                  "your use case."
353               << std::endl;
354     std::cerr.flush();
355   }
356   if (!generation_options.strip_custom_options &&
357       !absl::StrContains(options_warnings_suppressions,
358                          "strip_custom_options")) {
359     std::cerr << "WARNING: strip_custom_options is disabled, this is deprecated"
360                  "and will be removed in the future. If you have a need for "
361                  "disabling it please file an issue at "
362                  "https://github.com/protocolbuffers/protobuf/issues with "
363                  "your use case."
364               << std::endl;
365     std::cerr.flush();
366   }
367 
368   // -----------------------------------------------------------------
369 
370   // These are not official generation options and could be removed/changed in
371   // the future and doing that won't count as a breaking change.
372   bool headers_only = getenv("GPB_OBJC_HEADERS_ONLY") != nullptr;
373   absl::flat_hash_set<std::string> skip_impls;
374   if (getenv("GPB_OBJC_SKIP_IMPLS_FILE") != nullptr) {
375     std::ifstream skip_file(getenv("GPB_OBJC_SKIP_IMPLS_FILE"));
376     if (skip_file.is_open()) {
377       std::string line;
378       while (std::getline(skip_file, line)) {
379         skip_impls.insert(line);
380       }
381     } else {
382       *error = "error: Failed to open GPB_OBJC_SKIP_IMPLS_FILE file";
383       return false;
384     }
385   }
386 
387   // -----------------------------------------------------------------
388 
389   // Validate the objc prefix/package pairings.
390   if (!ValidateObjCClassPrefixes(files, validation_options, error)) {
391     // *error will have been filled in.
392     return false;
393   }
394 
395   FileGenerator::CommonState state(!generation_options.strip_custom_options);
396   for (const auto& file : files) {
397     const FileGenerator file_generator(GetEdition(*file), file,
398                                        generation_options, state);
399     std::string filepath = FilePath(file);
400 
401     // Generate header.
402     {
403       auto output =
404           absl::WrapUnique(context->Open(absl::StrCat(filepath, ".pbobjc.h")));
405       io::Printer printer(output.get());
406       file_generator.GenerateHeader(&printer);
407       if (printer.failed()) {
408         *error = absl::StrCat("error: internal error generating a header: ",
409                               file->name());
410         return false;
411       }
412     }
413 
414     // Generate m file(s).
415     if (!headers_only && skip_impls.count(file->name()) == 0) {
416       if (generation_options.experimental_multi_source_generation) {
417         int file_number = 0;
418 
419         // Generate the Root and FileDescriptor (if needed).
420         {
421           std::unique_ptr<io::ZeroCopyOutputStream> output(
422               context->Open(NumberedObjCMFileName(filepath, file_number++)));
423           io::Printer printer(output.get());
424           file_generator.GenerateGlobalSource(&printer);
425           if (printer.failed()) {
426             *error = absl::StrCat(
427                 "error: internal error generating an implementation:",
428                 file->name());
429             return false;
430           }
431         }
432 
433         // Enums only generate C functions, so they can all go in one file as
434         // dead stripping anything not used.
435         if (file_generator.NumEnums() > 0) {
436           std::unique_ptr<io::ZeroCopyOutputStream> output(
437               context->Open(NumberedObjCMFileName(filepath, file_number++)));
438           io::Printer printer(output.get());
439           file_generator.GenerateSourceForEnums(&printer);
440           if (printer.failed()) {
441             *error = absl::StrCat(
442                 "error: internal error generating an enum implementation(s):",
443                 file->name());
444             return false;
445           }
446         }
447 
448         for (int i = 0; i < file_generator.NumMessages(); ++i) {
449           std::unique_ptr<io::ZeroCopyOutputStream> output(
450               context->Open(NumberedObjCMFileName(filepath, file_number++)));
451           io::Printer printer(output.get());
452           file_generator.GenerateSourceForMessage(i, &printer);
453           if (printer.failed()) {
454             *error = absl::StrCat(
455                 "error: internal error generating an message implementation:",
456                 file->name(), "::", i);
457             return false;
458           }
459         }
460       } else {
461         auto output = absl::WrapUnique(
462             context->Open(absl::StrCat(filepath, ".pbobjc.m")));
463         io::Printer printer(output.get());
464         file_generator.GenerateSource(&printer);
465         if (printer.failed()) {
466           *error = absl::StrCat(
467               "error: internal error generating an implementation:",
468               file->name());
469           return false;
470         }
471       }
472     }  // if (!headers_only && skip_impls.count(file->name()) == 0)
473   }  // for(file : files)
474 
475   return true;
476 }
477 
478 }  // namespace objectivec
479 }  // namespace compiler
480 }  // namespace protobuf
481 }  // namespace google
482