// Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "google/protobuf/compiler/objectivec/import_writer.h" #include #include #include #include #include "absl/container/flat_hash_map.h" #include "absl/log/absl_check.h" #include "absl/strings/ascii.h" #include "absl/strings/match.h" #include "google/protobuf/compiler/objectivec/line_consumer.h" #include "google/protobuf/compiler/objectivec/names.h" #include "google/protobuf/io/printer.h" // NOTE: src/google/protobuf/compiler/plugin.cc makes use of cerr for some // error cases, so it seems to be ok to use as a back door for errors. namespace google { namespace protobuf { namespace compiler { namespace objectivec { namespace { class ProtoFrameworkCollector : public LineConsumer { public: explicit ProtoFrameworkCollector( absl::flat_hash_map* inout_proto_file_to_framework_name) : map_(inout_proto_file_to_framework_name) {} bool ConsumeLine(absl::string_view line, std::string* out_error) override; private: absl::flat_hash_map* map_; }; bool ProtoFrameworkCollector::ConsumeLine(absl::string_view line, std::string* out_error) { int offset = line.find(':'); if (offset == absl::string_view::npos) { *out_error = absl::StrCat( "Framework/proto file mapping line without colon sign: '", line, "'."); return false; } absl::string_view framework_name = absl::StripAsciiWhitespace(line.substr(0, offset)); absl::string_view proto_file_list = absl::StripAsciiWhitespace(line.substr(offset + 1)); int start = 0; while (start < proto_file_list.length()) { offset = proto_file_list.find(',', start); if (offset == absl::string_view::npos) { offset = proto_file_list.length(); } absl::string_view proto_file = absl::StripAsciiWhitespace( proto_file_list.substr(start, offset - start)); if (!proto_file.empty()) { auto existing_entry = map_->find(proto_file); if (existing_entry != map_->end()) { std::cerr << "warning: duplicate proto file reference, replacing " "framework entry for '" << proto_file << "' with '" << framework_name << "' (was '" << existing_entry->second << "')." << std::endl; std::cerr.flush(); } if (absl::StrContains(proto_file, ' ')) { std::cerr << "note: framework mapping file had a proto file with a " "space in, hopefully that isn't a missing comma: '" << proto_file << "'" << std::endl; std::cerr.flush(); } (*map_)[proto_file] = std::string(framework_name); } start = offset + 1; } return true; } } // namespace ImportWriter::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) : generate_for_named_framework_(generate_for_named_framework), named_framework_to_proto_path_mappings_path_( named_framework_to_proto_path_mappings_path), runtime_import_prefix_(runtime_import_prefix), for_bundled_proto_(for_bundled_proto), need_to_parse_mapping_file_(true) {} void ImportWriter::AddFile(const FileDescriptor* file, const std::string& header_extension) { if (IsProtobufLibraryBundledProtoFile(file)) { // The imports of the WKTs are only needed within the library itself, // in other cases, they get skipped because the generated code already // import GPBProtocolBuffers.h and hence proves them. if (for_bundled_proto_) { protobuf_imports_.emplace_back( absl::StrCat("GPB", FilePathBasename(file), header_extension)); } return; } auto module_name = ModuleForFile(file); if (!module_name.empty()) { other_framework_imports_.emplace_back(absl::StrCat( module_name, "/", FilePathBasename(file), header_extension)); return; } if (!generate_for_named_framework_.empty()) { other_framework_imports_.push_back( absl::StrCat(generate_for_named_framework_, "/", FilePathBasename(file), header_extension)); return; } other_imports_.push_back(FilePath(file) + header_extension); } void ImportWriter::AddRuntimeImport(const std::string& header_name) { protobuf_imports_.push_back(header_name); } std::string ImportWriter::ModuleForFile(const FileDescriptor* file) { ABSL_DCHECK(!IsProtobufLibraryBundledProtoFile(file)); // Lazy parse any mappings. if (need_to_parse_mapping_file_) { ParseFrameworkMappings(); } auto proto_lookup = proto_file_to_framework_name_.find(file->name()); if (proto_lookup != proto_file_to_framework_name_.end()) { return proto_lookup->second; } return ""; } void ImportWriter::PrintFileImports(io::Printer* p) const { for (const auto& header : other_framework_imports_) { p->Emit({{"header", header}}, R"objc( #import <$header$> )objc"); } for (const auto& header : other_imports_) { p->Emit({{"header", header}}, R"objc( #import "$header$" )objc"); } } void ImportWriter::PrintRuntimeImports(io::Printer* p, bool default_cpp_symbol) const { // Given an override, use that. if (!runtime_import_prefix_.empty()) { for (const auto& header : protobuf_imports_) { p->Emit({{"import_prefix", runtime_import_prefix_}, {"header", header}}, R"objc( #import "$import_prefix$/$header$" )objc"); } return; } // If bundled, no need to do the framework support below. if (for_bundled_proto_) { ABSL_DCHECK(!default_cpp_symbol); for (const auto& header : protobuf_imports_) { p->Emit({{"header", header}}, R"objc( #import "$header$" )objc"); } return; } p->Emit( { {"cpp_symbol", ProtobufFrameworkImportSymbol(ProtobufLibraryFrameworkName)}, {"maybe_default_cpp_symbol", [&] { if (default_cpp_symbol) { p->Emit( R"objc( // This CPP symbol can be defined to use imports that match up to the framework // imports needed when using CocoaPods. #if !defined($cpp_symbol$) #define $cpp_symbol$ 0 #endif )objc"); } }}, {"framework_name", ProtobufLibraryFrameworkName}, {"framework_imports", [&] { for (const auto& header : protobuf_imports_) { p->Emit({{"header", header}}, R"objc( #import <$framework_name$/$header$> )objc"); } }}, {"raw_imports", [&] { for (const auto& header : protobuf_imports_) { p->Emit({{"header", header}}, R"objc( #import "$header$" )objc"); } }}, }, R"objc( $maybe_default_cpp_symbol$ #if $cpp_symbol$ $framework_imports$ #else $raw_imports$ #endif )objc"); } void ImportWriter::ParseFrameworkMappings() { need_to_parse_mapping_file_ = false; if (named_framework_to_proto_path_mappings_path_.empty()) { return; // Nothing to do. } ProtoFrameworkCollector collector(&proto_file_to_framework_name_); std::string parse_error; if (!ParseSimpleFile(named_framework_to_proto_path_mappings_path_, &collector, &parse_error)) { std::cerr << "error parsing " << named_framework_to_proto_path_mappings_path_ << " : " << parse_error << std::endl; std::cerr.flush(); } } } // namespace objectivec } // namespace compiler } // namespace protobuf } // namespace google