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