• 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/import_writer.h"
9 
10 #include <iostream>
11 #include <ostream>
12 #include <string>
13 #include <vector>
14 
15 #include "absl/container/flat_hash_map.h"
16 #include "absl/log/absl_check.h"
17 #include "absl/strings/ascii.h"
18 #include "absl/strings/match.h"
19 #include "absl/strings/str_cat.h"
20 #include "absl/strings/string_view.h"
21 #include "google/protobuf/compiler/objectivec/line_consumer.h"
22 #include "google/protobuf/compiler/objectivec/names.h"
23 #include "google/protobuf/descriptor.h"
24 #include "google/protobuf/io/printer.h"
25 
26 // NOTE: src/google/protobuf/compiler/plugin.cc makes use of cerr for some
27 // error cases, so it seems to be ok to use as a back door for errors.
28 
29 namespace google {
30 namespace protobuf {
31 namespace compiler {
32 namespace objectivec {
33 
34 namespace {
35 
36 class ProtoFrameworkCollector : public LineConsumer {
37  public:
ProtoFrameworkCollector(absl::flat_hash_map<std::string,std::string> * inout_proto_file_to_framework_name)38   explicit ProtoFrameworkCollector(
39       absl::flat_hash_map<std::string, std::string>*
40           inout_proto_file_to_framework_name)
41       : map_(inout_proto_file_to_framework_name) {}
42 
43   bool ConsumeLine(absl::string_view line, std::string* out_error) override;
44 
45  private:
46   absl::flat_hash_map<std::string, std::string>* map_;
47 };
48 
ConsumeLine(absl::string_view line,std::string * out_error)49 bool ProtoFrameworkCollector::ConsumeLine(absl::string_view line,
50                                           std::string* out_error) {
51   int offset = line.find(':');
52   if (offset == absl::string_view::npos) {
53     *out_error = absl::StrCat(
54         "Framework/proto file mapping line without colon sign: '", line, "'.");
55     return false;
56   }
57   absl::string_view framework_name =
58       absl::StripAsciiWhitespace(line.substr(0, offset));
59   absl::string_view proto_file_list =
60       absl::StripAsciiWhitespace(line.substr(offset + 1));
61 
62   int start = 0;
63   while (start < proto_file_list.length()) {
64     offset = proto_file_list.find(',', start);
65     if (offset == absl::string_view::npos) {
66       offset = proto_file_list.length();
67     }
68 
69     absl::string_view proto_file = absl::StripAsciiWhitespace(
70         proto_file_list.substr(start, offset - start));
71     if (!proto_file.empty()) {
72       auto existing_entry = map_->find(proto_file);
73       if (existing_entry != map_->end()) {
74         std::cerr << "warning: duplicate proto file reference, replacing "
75                      "framework entry for '"
76                   << proto_file << "' with '" << framework_name << "' (was '"
77                   << existing_entry->second << "')." << std::endl;
78         std::cerr.flush();
79       }
80 
81       if (absl::StrContains(proto_file, ' ')) {
82         std::cerr << "note: framework mapping file had a proto file with a "
83                      "space in, hopefully that isn't a missing comma: '"
84                   << proto_file << "'" << std::endl;
85         std::cerr.flush();
86       }
87 
88       (*map_)[proto_file] = std::string(framework_name);
89     }
90 
91     start = offset + 1;
92   }
93 
94   return true;
95 }
96 
97 }  // namespace
98 
ImportWriter(const std::string & generate_for_named_framework,const std::string & named_framework_to_proto_path_mappings_path,const std::string & runtime_import_prefix,bool for_bundled_proto)99 ImportWriter::ImportWriter(
100     const std::string& generate_for_named_framework,
101     const std::string& named_framework_to_proto_path_mappings_path,
102     const std::string& runtime_import_prefix, bool for_bundled_proto)
103     : generate_for_named_framework_(generate_for_named_framework),
104       named_framework_to_proto_path_mappings_path_(
105           named_framework_to_proto_path_mappings_path),
106       runtime_import_prefix_(runtime_import_prefix),
107       for_bundled_proto_(for_bundled_proto),
108       need_to_parse_mapping_file_(true) {}
109 
AddFile(const FileDescriptor * file,const std::string & header_extension)110 void ImportWriter::AddFile(const FileDescriptor* file,
111                            const std::string& header_extension) {
112   if (IsProtobufLibraryBundledProtoFile(file)) {
113     // The imports of the WKTs are only needed within the library itself,
114     // in other cases, they get skipped because the generated code already
115     // import GPBProtocolBuffers.h and hence proves them.
116     if (for_bundled_proto_) {
117       protobuf_imports_.emplace_back(
118           absl::StrCat("GPB", FilePathBasename(file), header_extension));
119     }
120     return;
121   }
122 
123   auto module_name = ModuleForFile(file);
124 
125   if (!module_name.empty()) {
126     other_framework_imports_.emplace_back(absl::StrCat(
127         module_name, "/", FilePathBasename(file), header_extension));
128     return;
129   }
130 
131   if (!generate_for_named_framework_.empty()) {
132     other_framework_imports_.push_back(
133         absl::StrCat(generate_for_named_framework_, "/", FilePathBasename(file),
134                      header_extension));
135     return;
136   }
137 
138   other_imports_.push_back(FilePath(file) + header_extension);
139 }
140 
AddRuntimeImport(const std::string & header_name)141 void ImportWriter::AddRuntimeImport(const std::string& header_name) {
142   protobuf_imports_.push_back(header_name);
143 }
144 
ModuleForFile(const FileDescriptor * file)145 std::string ImportWriter::ModuleForFile(const FileDescriptor* file) {
146   ABSL_DCHECK(!IsProtobufLibraryBundledProtoFile(file));
147 
148   // Lazy parse any mappings.
149   if (need_to_parse_mapping_file_) {
150     ParseFrameworkMappings();
151   }
152 
153   auto proto_lookup = proto_file_to_framework_name_.find(file->name());
154 
155   if (proto_lookup != proto_file_to_framework_name_.end()) {
156     return proto_lookup->second;
157   }
158 
159   return "";
160 }
161 
PrintFileImports(io::Printer * p) const162 void ImportWriter::PrintFileImports(io::Printer* p) const {
163   for (const auto& header : other_framework_imports_) {
164     p->Emit({{"header", header}},
165             R"objc(
166               #import <$header$>
167             )objc");
168   }
169 
170   for (const auto& header : other_imports_) {
171     p->Emit({{"header", header}},
172             R"objc(
173               #import "$header$"
174             )objc");
175   }
176 }
177 
PrintRuntimeImports(io::Printer * p,bool default_cpp_symbol) const178 void ImportWriter::PrintRuntimeImports(io::Printer* p,
179                                        bool default_cpp_symbol) const {
180   // Given an override, use that.
181   if (!runtime_import_prefix_.empty()) {
182     for (const auto& header : protobuf_imports_) {
183       p->Emit({{"import_prefix", runtime_import_prefix_}, {"header", header}},
184               R"objc(
185                 #import "$import_prefix$/$header$"
186               )objc");
187     }
188     return;
189   }
190 
191   // If bundled, no need to do the framework support below.
192   if (for_bundled_proto_) {
193     ABSL_DCHECK(!default_cpp_symbol);
194     for (const auto& header : protobuf_imports_) {
195       p->Emit({{"header", header}},
196               R"objc(
197                 #import "$header$"
198               )objc");
199     }
200     return;
201   }
202 
203   p->Emit(
204       {
205           {"cpp_symbol",
206            ProtobufFrameworkImportSymbol(ProtobufLibraryFrameworkName)},
207           {"maybe_default_cpp_symbol",
208            [&] {
209              if (default_cpp_symbol) {
210                p->Emit(
211                    R"objc(
212                      // This CPP symbol can be defined to use imports that match up to the framework
213                      // imports needed when using CocoaPods.
214                      #if !defined($cpp_symbol$)
215                       #define $cpp_symbol$ 0
216                      #endif
217                    )objc");
218              }
219            }},
220           {"framework_name", ProtobufLibraryFrameworkName},
221           {"framework_imports",
222            [&] {
223              for (const auto& header : protobuf_imports_) {
224                p->Emit({{"header", header}},
225                        R"objc(
226                          #import <$framework_name$/$header$>
227                        )objc");
228              }
229            }},
230           {"raw_imports",
231            [&] {
232              for (const auto& header : protobuf_imports_) {
233                p->Emit({{"header", header}},
234                        R"objc(
235                          #import "$header$"
236                        )objc");
237              }
238            }},
239       },
240       R"objc(
241         $maybe_default_cpp_symbol$
242 
243         #if $cpp_symbol$
244          $framework_imports$
245         #else
246          $raw_imports$
247         #endif
248       )objc");
249 }
250 
ParseFrameworkMappings()251 void ImportWriter::ParseFrameworkMappings() {
252   need_to_parse_mapping_file_ = false;
253   if (named_framework_to_proto_path_mappings_path_.empty()) {
254     return;  // Nothing to do.
255   }
256 
257   ProtoFrameworkCollector collector(&proto_file_to_framework_name_);
258   std::string parse_error;
259   if (!ParseSimpleFile(named_framework_to_proto_path_mappings_path_, &collector,
260                        &parse_error)) {
261     std::cerr << "error parsing "
262               << named_framework_to_proto_path_mappings_path_ << " : "
263               << parse_error << std::endl;
264     std::cerr.flush();
265   }
266 }
267 
268 }  // namespace objectivec
269 }  // namespace compiler
270 }  // namespace protobuf
271 }  // namespace google
272