| // Protocol Buffers - Google's data interchange format | |
| // Copyright 2023 Google LLC. All rights reserved. | |
| // | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file or at | |
| // https://developers.google.com/open-source/licenses/bsd | |
| #include "hpb_generator/generator.h" | |
| #include <memory> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "google/protobuf/descriptor.pb.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/str_replace.h" | |
| #include "google/protobuf/compiler/code_generator.h" | |
| #include "google/protobuf/compiler/code_generator_lite.h" | |
| #include "hpb_generator/context.h" | |
| #include "hpb_generator/gen_enums.h" | |
| #include "hpb_generator/gen_extensions.h" | |
| #include "hpb_generator/gen_messages.h" | |
| #include "hpb_generator/gen_utils.h" | |
| #include "hpb_generator/names.h" | |
| #include "google/protobuf/descriptor.h" | |
| #include "upb_generator/c/names.h" | |
| #include "upb_generator/minitable/names.h" | |
| namespace google { | |
| namespace protobuf { | |
| namespace hpb_generator { | |
| namespace { | |
| using FileDescriptor = ::google::protobuf::FileDescriptor; | |
| void WriteTypedefForwardingHeader( | |
| const google::protobuf::FileDescriptor* file, | |
| const std::vector<const google::protobuf::Descriptor*>& file_messages, Context& ctx); | |
| void WriteHeaderMessageForwardDecls(const google::protobuf::FileDescriptor* file, | |
| Context& ctx); | |
| void WriteMessageImplementations(const google::protobuf::FileDescriptor* file, | |
| Context& ctx); | |
| void WriteForwardDecls(const google::protobuf::FileDescriptor* file, Context& ctx) { | |
| for (int i = 0; i < file->public_dependency_count(); ++i) { | |
| const auto target_file_messages = | |
| SortedMessages(file->public_dependency(i)); | |
| WriteTypedefForwardingHeader(file->public_dependency(i), | |
| target_file_messages, ctx); | |
| } | |
| const auto this_file_messages = SortedMessages(file); | |
| WriteTypedefForwardingHeader(file, this_file_messages, ctx); | |
| } | |
| void WriteHeader(const google::protobuf::FileDescriptor* file, Context& ctx, | |
| absl::string_view info_path) { | |
| if (ctx.options().backend == Backend::CPP) { | |
| EmitFileWarning(file, ctx); | |
| ctx.Emit({{"filename", ToPreproc(file->name())}}, | |
| R"cc( | |
| #ifndef $filename$_HPB_PROTO_H_ | |
| #define $filename$_HPB_PROTO_H_ | |
| )cc"); | |
| // Import headers for proto public dependencies. | |
| for (int i = 0; i < file->public_dependency_count(); i++) { | |
| if (i == 0) { | |
| ctx.Emit("// Public Imports.\n"); | |
| } | |
| ctx.Emit({{"header", CppHeaderFilename(file->public_dependency(i))}}, | |
| "#include \"$header$\"\n"); | |
| if (i == file->public_dependency_count() - 1) { | |
| ctx.Emit("\n"); | |
| } | |
| } | |
| ctx.Emit( | |
| "#include \"hpb/internal/os_macros_undef.inc\"\n"); | |
| if (!info_path.empty()) { | |
| ctx.Emit({{"info_path", std::string(info_path)}}, | |
| R"cc( | |
| #ifdef KYTHE_IS_RUNNING | |
| #pragma kythe_metadata "$info_path$" | |
| #endif // KYTHE_IS_RUNNING | |
| )cc"); | |
| } | |
| const std::vector<const google::protobuf::Descriptor*> this_file_messages = | |
| SortedMessages(file); | |
| const std::vector<const google::protobuf::FieldDescriptor*> | |
| this_file_exts{}; // TODO: extensions | |
| if (!this_file_messages.empty()) { | |
| ctx.Emit("\n"); | |
| } | |
| WriteHeaderMessageForwardDecls(file, ctx); | |
| WriteForwardDecls(file, ctx); | |
| std::vector<const google::protobuf::EnumDescriptor*> this_file_enums = | |
| SortedEnums(file); | |
| WrapNamespace(file, ctx, [&]() { | |
| // Write Class and Enums. | |
| WriteEnumDeclarations(this_file_enums, ctx); | |
| ctx.Emit("\n"); | |
| // TODO: class decls | |
| // TODO: extension identifiers | |
| }); | |
| const auto msgs = SortedMessages(file); | |
| for (auto message : msgs) { | |
| ctx.Emit({{"type", QualifiedClassName(message)}, | |
| {"class_name", ClassName(message)}, | |
| {"namespace", absl::StrCat(absl::StrReplaceAll(file->package(), | |
| {{".", "::"}}), | |
| "::protos")}}, | |
| R"cc( | |
| #include "hpb/internal/internal.h" | |
| // message stubs | |
| namespace $namespace$ { | |
| class $class_name$ { | |
| public: | |
| using CProxy = bool; | |
| using Proxy = bool; | |
| using Access = bool; | |
| $class_name$() = default; | |
| private: | |
| $class_name$($type$* msg) : msg_(msg) {} | |
| $type$* msg_; | |
| $type$* msg() const { return msg_; } | |
| friend struct ::hpb::internal::PrivateAccess; | |
| }; | |
| } // namespace $namespace$ | |
| )cc"); | |
| } | |
| ctx.Emit( | |
| "#include " | |
| "\"hpb/internal/os_macros_restore.inc\"\n"); | |
| ctx.Emit({{"filename", ToPreproc(file->name())}}, | |
| "#endif /* $filename$_HPB_PROTO_H_ */\n"); | |
| return; | |
| } | |
| EmitFileWarning(file, ctx); | |
| ctx.Emit({{"filename", ToPreproc(file->name())}}, | |
| R"cc( | |
| #ifndef $filename$_HPB_PROTO_H_ | |
| #define $filename$_HPB_PROTO_H_ | |
| #include "hpb/repeated_field.h" | |
| #include "absl/status/statusor.h" | |
| #include "absl/strings/string_view.h" | |
| )cc"); | |
| // Import headers for proto public dependencies. | |
| for (int i = 0; i < file->public_dependency_count(); i++) { | |
| if (i == 0) { | |
| ctx.Emit("// Public Imports.\n"); | |
| } | |
| ctx.Emit({{"header", CppHeaderFilename(file->public_dependency(i))}}, | |
| "#include \"$header$\"\n"); | |
| if (i == file->public_dependency_count() - 1) { | |
| ctx.Emit("\n"); | |
| } | |
| } | |
| ctx.Emit("#include \"upb/port/def.inc\"\n"); | |
| ctx.Emit( | |
| "#include \"hpb/internal/os_macros_undef.inc\"\n"); | |
| if (!info_path.empty()) { | |
| ctx.Emit({{"info_path", std::string(info_path)}}, | |
| R"cc( | |
| #ifdef KYTHE_IS_RUNNING | |
| #pragma kythe_metadata "$info_path$" | |
| #endif // KYTHE_IS_RUNNING | |
| )cc"); | |
| } | |
| const std::vector<const google::protobuf::Descriptor*> this_file_messages = | |
| SortedMessages(file); | |
| const std::vector<const google::protobuf::FieldDescriptor*> this_file_exts = | |
| SortedExtensions(file); | |
| if (!this_file_messages.empty()) { | |
| ctx.Emit("\n"); | |
| } | |
| WriteHeaderMessageForwardDecls(file, ctx); | |
| WriteForwardDecls(file, ctx); | |
| std::vector<const google::protobuf::EnumDescriptor*> this_file_enums = | |
| SortedEnums(file); | |
| WrapNamespace(file, ctx, [&]() { | |
| // Write Class and Enums. | |
| WriteEnumDeclarations(this_file_enums, ctx); | |
| ctx.Emit("\n"); | |
| for (auto message : this_file_messages) { | |
| WriteMessageClassDeclarations(message, this_file_exts, this_file_enums, | |
| ctx); | |
| } | |
| ctx.Emit("\n"); | |
| WriteExtensionIdentifiersHeader(this_file_exts, ctx); | |
| ctx.Emit("\n"); | |
| }); | |
| ctx.Emit("namespace hpb::internal {\n"); | |
| const std::vector<const google::protobuf::Descriptor*> messages_to_emit_helpers = | |
| SortedMessages(file); | |
| for (auto desc : messages_to_emit_helpers) { | |
| if (desc->map_key() != nullptr) { | |
| continue; | |
| } | |
| std::string outer_namespace = | |
| absl::StrCat(NamespaceFromPackageName(file->package()), "::"); | |
| if (file->package().empty()) { | |
| outer_namespace = ""; | |
| } | |
| ctx.Emit({{"class_name", ClassName(desc)}, | |
| {"minitable_name", | |
| upb::generator::MiniTableMessageVarName(desc->full_name())}, | |
| {"outer_namespace", outer_namespace}, | |
| {"c_api_msg_type", | |
| upb::generator::CApiMessageType(desc->full_name())}}, | |
| R"cc( | |
| template <> | |
| struct AssociatedUpbTypes<$outer_namespace$$class_name$> { | |
| using CMessageType = $c_api_msg_type$; | |
| static inline const upb_MiniTable* kMiniTable = &$minitable_name$; | |
| }; | |
| )cc"); | |
| } | |
| ctx.Emit("} // namespace hpb::internal\n"); | |
| ctx.Emit( | |
| "#include \"hpb/internal/os_macros_restore.inc\"\n"); | |
| ctx.Emit("\n#include \"upb/port/undef.inc\"\n\n"); | |
| // End of "C" section. | |
| ctx.Emit({{"filename", ToPreproc(file->name())}}, | |
| "#endif /* $filename$_HPB_PROTO_H_ */\n"); | |
| } | |
| // Writes a .hpb.cc source file. | |
| void WriteSource(const google::protobuf::FileDescriptor* file, Context& ctx) { | |
| if (ctx.options().backend == Backend::CPP) { | |
| ctx.Emit("// Placeholder hpb C++ source stub"); | |
| return; | |
| } | |
| EmitFileWarning(file, ctx); | |
| ctx.Emit({{"header", CppHeaderFilename(file)}}, | |
| R"cc( | |
| #include <stddef.h> | |
| #include "absl/log/absl_check.h" | |
| #include "absl/strings/string_view.h" | |
| #include "$header$" | |
| )cc"); | |
| for (int i = 0; i < file->dependency_count(); i++) { | |
| if (ctx.options().strip_feature_includes && | |
| compiler::IsKnownFeatureProto(file->dependency(i)->name())) { | |
| // Strip feature imports for editions codegen tests. | |
| continue; | |
| } | |
| ctx.Emit({{"header", CppHeaderFilename(file->dependency(i))}}, | |
| "#include \"$header$\"\n"); | |
| } | |
| ctx.Emit("#include \"upb/port/def.inc\"\n"); | |
| WrapNamespace(file, ctx, [&]() { WriteMessageImplementations(file, ctx); }); | |
| ctx.Emit("#include \"upb/port/undef.inc\"\n\n"); | |
| } | |
| void WriteMessageImplementations(const google::protobuf::FileDescriptor* file, | |
| Context& ctx) { | |
| const std::vector<const google::protobuf::FieldDescriptor*> file_exts = | |
| SortedExtensions(file); | |
| const std::vector<const google::protobuf::Descriptor*> this_file_messages = | |
| SortedMessages(file); | |
| for (auto message : this_file_messages) { | |
| WriteMessageImplementation(message, file_exts, ctx); | |
| } | |
| } | |
| void WriteTypedefForwardingHeader( | |
| const google::protobuf::FileDescriptor* file, | |
| const std::vector<const google::protobuf::Descriptor*>& file_messages, Context& ctx) { | |
| WrapNamespace(file, ctx, [&]() { | |
| // Forward-declare types defined in this file. | |
| for (auto message : file_messages) { | |
| ctx.Emit({{"class_name", ClassName(message)}}, | |
| R"cc( | |
| class $class_name$; | |
| namespace internal { | |
| class $class_name$Access; | |
| class $class_name$Proxy; | |
| class $class_name$CProxy; | |
| } // namespace internal | |
| )cc"); | |
| } | |
| }); | |
| ctx.Emit("\n"); | |
| } | |
| /// Writes includes for upb C minitables and fwd.h for transitive typedefs. | |
| void WriteHeaderMessageForwardDecls(const google::protobuf::FileDescriptor* file, | |
| Context& ctx) { | |
| // Import forward-declaration of types defined in this file. | |
| if (ctx.options().backend == Backend::UPB) { | |
| ctx.Emit({{"upb_filename", UpbCFilename(file)}}, | |
| "#include \"$upb_filename$\"\n"); | |
| } | |
| WriteForwardDecls(file, ctx); | |
| // Import forward-declaration of types in dependencies. | |
| for (int i = 0; i < file->dependency_count(); ++i) { | |
| if (ctx.options().strip_feature_includes && | |
| compiler::IsKnownFeatureProto(file->dependency(i)->name())) { | |
| // Strip feature imports for editions codegen tests. | |
| continue; | |
| } | |
| WriteForwardDecls(file->dependency(i), ctx); | |
| } | |
| ctx.Emit("\n"); | |
| } | |
| } // namespace | |
| bool Generator::Generate(const google::protobuf::FileDescriptor* file, | |
| const std::string& parameter, | |
| protoc::GeneratorContext* context, | |
| std::string* error) const { | |
| { | |
| bool strip_nonfunctional_codegen = false; | |
| bool annotate_headers = false; | |
| Backend backend = Backend::UPB; | |
| std::vector<std::pair<std::string, std::string>> params; | |
| google::protobuf::compiler::ParseGeneratorParameter(parameter, ¶ms); | |
| for (const auto& pair : params) { | |
| if (pair.first == "experimental_strip_nonfunctional_codegen") { | |
| strip_nonfunctional_codegen = true; | |
| } else if (pair.first == "backend" && pair.second == "cpp") { | |
| backend = Backend::CPP; | |
| } else if (pair.first == "annotate_headers") { | |
| annotate_headers = true; | |
| } else { | |
| *error = "Unknown parameter: " + pair.first; | |
| return false; | |
| } | |
| } | |
| std::unique_ptr<GeneratedCodeInfo> annotations; | |
| std::unique_ptr<io::AnnotationCollector> annotation_collector; | |
| if (annotate_headers) { | |
| annotations = std::make_unique<GeneratedCodeInfo>(); | |
| annotation_collector = | |
| std::make_unique<io::AnnotationProtoCollector<GeneratedCodeInfo>>( | |
| annotations.get()); | |
| } | |
| std::string meta_filename = ""; | |
| if (annotate_headers) { | |
| meta_filename = | |
| absl::StrCat(compiler::StripProto(file->name()), ".hpb.h.meta"); | |
| } | |
| // Write model.hpb.h | |
| Options options = {.backend = backend, | |
| .strip_feature_includes = strip_nonfunctional_codegen, | |
| .annotation_collector = annotation_collector.get()}; | |
| std::unique_ptr<google::protobuf::io::ZeroCopyOutputStream> header_output_stream( | |
| context->Open(CppHeaderFilename(file))); | |
| Context hdr_ctx(file, header_output_stream.get(), options); | |
| WriteHeader(file, hdr_ctx, meta_filename); | |
| // Write model.hpb.cc | |
| std::unique_ptr<google::protobuf::io::ZeroCopyOutputStream> cc_output_stream( | |
| context->Open(CppSourceFilename(file))); | |
| auto cc_ctx = Context(file, cc_output_stream.get(), options); | |
| WriteSource(file, cc_ctx); | |
| if (annotate_headers) { | |
| std::unique_ptr<google::protobuf::io::ZeroCopyOutputStream> meta_output_stream( | |
| context->Open(meta_filename)); | |
| ABSL_CHECK( | |
| annotations->SerializeToZeroCopyStream(meta_output_stream.get())); | |
| } | |
| return true; | |
| } | |
| } | |
| } // namespace hpb_generator | |
| } // namespace protobuf | |
| } // namespace google |