From 3b8644d32c50e86872584829af4b8caf180235ab Mon Sep 17 00:00:00 2001 From: Derek Bailey Date: Sun, 8 Jan 2023 15:01:33 -0800 Subject: [PATCH] Defined CodeGenerator Interface and implement C++ (#7771) --- BUILD.bazel | 1 + CMakeLists.txt | 1 + include/flatbuffers/code_generator.h | 73 ++++++++++++++++++ include/flatbuffers/flatc.h | 19 ++++- src/flatc.cpp | 109 ++++++++++++++++++++++++--- src/flatc_main.cpp | 9 +++ src/idl_gen_cpp.cpp | 47 ++++++++++++ 7 files changed, 244 insertions(+), 15 deletions(-) create mode 100644 include/flatbuffers/code_generator.h diff --git a/BUILD.bazel b/BUILD.bazel index f2aac0ae3..f88da4155 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -44,6 +44,7 @@ filegroup( "include/flatbuffers/bfbs_generator.h", "include/flatbuffers/buffer.h", "include/flatbuffers/buffer_ref.h", + "include/flatbuffers/code_generator.h", "include/flatbuffers/code_generators.h", "include/flatbuffers/default_allocator.h", "include/flatbuffers/detached_buffer.h", diff --git a/CMakeLists.txt b/CMakeLists.txt index 525075a31..c56fb381c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,6 +141,7 @@ set(FlatBuffers_Library_SRCS include/flatbuffers/buffer_ref.h include/flatbuffers/default_allocator.h include/flatbuffers/detached_buffer.h + include/flatbuffers/code_generator.h include/flatbuffers/flatbuffer_builder.h include/flatbuffers/flatbuffers.h include/flatbuffers/flexbuffers.h diff --git a/include/flatbuffers/code_generator.h b/include/flatbuffers/code_generator.h new file mode 100644 index 000000000..af8292fae --- /dev/null +++ b/include/flatbuffers/code_generator.h @@ -0,0 +1,73 @@ +/* + * Copyright 2023 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLATBUFFERS_CODE_GENERATOR_H_ +#define FLATBUFFERS_CODE_GENERATOR_H_ + +#include + +#include "flatbuffers/idl.h" + +namespace flatbuffers { + +// An code generator interface for producing converting flatbuffer schema into +// code. +class CodeGenerator { + public: + virtual ~CodeGenerator() = default; + + enum Status { + OK = 0, + ERROR = 1, + NOT_IMPLEMENTED = 2, + }; + + // Generate code from the provided `parser`. + // + // DEPRECATED: prefer using the other overload of GenerateCode for bfbs. + virtual Status GenerateCode(const Parser &parser, const std::string &path, + const std::string &filename) = 0; + + // Generate code from the provided `buffer` of given `length`. The buffer is a + // serialized reflection.fbs. + virtual Status GenerateCode(const uint8_t *buffer, int64_t length) = 0; + + virtual Status GenerateMakeRule(const Parser &parser, const std::string &path, + const std::string &filename, + std::string &output) = 0; + + virtual Status GenerateGrpcCode(const Parser &parser, const std::string &path, + const std::string &filename) = 0; + + virtual bool IsSchemaOnly() const = 0; + + virtual bool SupportsBfbsGeneration() const = 0; + + virtual IDLOptions::Language Language() const = 0; + virtual std::string LanguageName() const = 0; + + protected: + CodeGenerator() = default; + + private: + // Copying is not supported. + CodeGenerator(const CodeGenerator &) = delete; + CodeGenerator &operator=(const CodeGenerator &) = delete; +}; + +} // namespace flatbuffers + +#endif // FLATBUFFERS_CODE_GENERATOR_H_ diff --git a/include/flatbuffers/flatc.h b/include/flatbuffers/flatc.h index 23eefaa0d..4a85961ca 100644 --- a/include/flatbuffers/flatc.h +++ b/include/flatbuffers/flatc.h @@ -20,15 +20,21 @@ #include #include #include +#include #include #include "flatbuffers/bfbs_generator.h" +#include "flatbuffers/code_generator.h" #include "flatbuffers/flatbuffers.h" #include "flatbuffers/idl.h" #include "flatbuffers/util.h" namespace flatbuffers { +// TODO(derekbailey): It would be better to define these as normal includes and +// not as extern functions. But this can be done at a later time. +extern std::unique_ptr NewCppCodeGenerator(); + extern void LogCompilerWarn(const std::string &warn); extern void LogCompilerError(const std::string &err); @@ -54,6 +60,8 @@ struct FlatCOptions { bool schema_binary = false; bool grpc_enabled = false; bool requires_bfbs = false; + + std::vector> generators; }; struct FlatCOption { @@ -110,10 +118,13 @@ class FlatCompiler { explicit FlatCompiler(const InitParams ¶ms) : params_(params) {} + bool RegisterCodeGenerator(const std::string& flag, + std::shared_ptr code_generator); + int Compile(const FlatCOptions &options); - std::string GetShortUsageString(const std::string& program_name) const; - std::string GetUsageString(const std::string& program_name) const; + std::string GetShortUsageString(const std::string &program_name) const; + std::string GetUsageString(const std::string &program_name) const; // Parse the FlatC options from command line arguments. FlatCOptions ParseFromCommandLineArguments(int argc, const char **argv); @@ -141,7 +152,9 @@ class FlatCompiler { Parser GetConformParser(const FlatCOptions &options); std::unique_ptr GenerateCode(const FlatCOptions &options, - Parser &conform_parser); + Parser &conform_parser); + + std::map> code_generators_; InitParams params_; }; diff --git a/src/flatc.cpp b/src/flatc.cpp index 4827ee770..c61efd4c6 100644 --- a/src/flatc.cpp +++ b/src/flatc.cpp @@ -24,6 +24,7 @@ #include "annotated_binary_text_gen.h" #include "binary_annotator.h" +#include "flatbuffers/code_generator.h" #include "flatbuffers/idl.h" #include "flatbuffers/util.h" @@ -308,6 +309,7 @@ std::string FlatCompiler::GetShortUsageString( const std::string &program_name) const { std::stringstream ss; ss << "Usage: " << program_name << " ["; + // TODO(derekbailey): These should be generated from this.generators for (size_t i = 0; i < params_.num_generators; ++i) { const Generator &g = params_.generators[i]; AppendShortOption(ss, g.option); @@ -330,6 +332,7 @@ std::string FlatCompiler::GetUsageString( std::stringstream ss; ss << "Usage: " << program_name << " [OPTION]... FILE... [-- BINARY_FILE...]\n"; + // TODO(derekbailey): These should be generated from this.generators for (size_t i = 0; i < params_.num_generators; ++i) { const Generator &g = params_.generators[i]; AppendOption(ss, g.option, 80, 25); @@ -613,20 +616,41 @@ FlatCOptions FlatCompiler::ParseFromCommandLineArguments(int argc, if (++argi >= argc) Error("missing path following: " + arg, true); options.annotate_schema = flatbuffers::PosixPath(argv[argi]); } else { - for (size_t i = 0; i < params_.num_generators; ++i) { - if (arg == "--" + params_.generators[i].option.long_opt || - arg == "-" + params_.generators[i].option.short_opt) { - options.generator_enabled[i] = true; - options.any_generator = true; - opts.lang_to_generate |= params_.generators[i].lang; - if (params_.generators[i].bfbs_generator) { - opts.binary_schema_comments = true; - options.requires_bfbs = true; - } - goto found; + // Look up if the command line argument refers to a code generator. + auto code_generator_it = code_generators_.find(arg); + if (code_generator_it != code_generators_.end()) { + std::shared_ptr code_generator = + code_generator_it->second; + + // TODO(derekbailey): remove in favor of just checking if + // generators.empty(). + options.any_generator = true; + opts.lang_to_generate |= code_generator->Language(); + + if (code_generator->SupportsBfbsGeneration()) { + opts.binary_schema_comments = true; + options.requires_bfbs = true; } + + options.generators.push_back(std::move(code_generator)); + } else { + // TODO(derekbailey): deprecate the following logic in favor of the + // code generator map above. + for (size_t i = 0; i < params_.num_generators; ++i) { + if (arg == "--" + params_.generators[i].option.long_opt || + arg == "-" + params_.generators[i].option.short_opt) { + options.generator_enabled[i] = true; + options.any_generator = true; + opts.lang_to_generate |= params_.generators[i].lang; + if (params_.generators[i].bfbs_generator) { + opts.binary_schema_comments = true; + options.requires_bfbs = true; + } + goto found; + } + } + Error("unknown commandline argument: " + arg, true); } - Error("unknown commandline argument: " + arg, true); found:; } @@ -789,6 +813,57 @@ std::unique_ptr FlatCompiler::GenerateCode(const FlatCOptions &options, bfbs_length = parser->builder_.GetSize(); } + for (const std::shared_ptr &code_generator : + options.generators) { + if (options.print_make_rules) { + std::string make_rule; + const CodeGenerator::Status status = code_generator->GenerateMakeRule( + *parser, options.output_path, filename, make_rule); + if (status == CodeGenerator::Status::OK && !make_rule.empty()) { + printf("%s\n", + flatbuffers::WordWrap(make_rule, 80, " ", " \\").c_str()); + } else { + Error("Cannot generate make rule for " + + code_generator->LanguageName()); + } + } else { + flatbuffers::EnsureDirExists(options.output_path); + + // Prefer bfbs generators if present. + if (code_generator->SupportsBfbsGeneration()) { + const CodeGenerator::Status status = + code_generator->GenerateCode(bfbs_buffer, bfbs_length); + if (status != CodeGenerator::Status::OK) { + Error("Unable to generate " + code_generator->LanguageName() + + " for " + filebase + " using bfbs generator."); + } + } else { + if ((!code_generator->IsSchemaOnly() || + (is_schema || is_binary_schema)) && + code_generator->GenerateCode(*parser, options.output_path, + filebase) != + CodeGenerator::Status::OK) { + Error("Unable to generate " + code_generator->LanguageName() + + " for " + filebase); + } + } + } + + if (options.grpc_enabled) { + const CodeGenerator::Status status = code_generator->GenerateGrpcCode( + *parser, options.output_path, filebase); + + if (status == CodeGenerator::Status::NOT_IMPLEMENTED) { + Warn("GRPC interface generator not implemented for " + + code_generator->LanguageName()); + } else if (status == CodeGenerator::Status::ERROR) { + Error("Unable to generate GRPC interface for " + + code_generator->LanguageName()); + } + } + } + + // TODO(derekbailey): Deprecate the following in favor to the above. for (size_t i = 0; i < params_.num_generators; ++i) { if (options.generator_enabled[i]) { if (!options.print_make_rules) { @@ -934,4 +1009,14 @@ int FlatCompiler::Compile(const FlatCOptions &options) { return 0; } +bool FlatCompiler::RegisterCodeGenerator( + const std::string &flag, std::shared_ptr code_generator) { + if (code_generators_.find(flag) != code_generators_.end()) { + Error("multiple generators registered under: " + flag, false, false); + return false; + } + code_generators_[flag] = std::move(code_generator); + return true; +} + } // namespace flatbuffers diff --git a/src/flatc_main.cpp b/src/flatc_main.cpp index dc9e36251..5092c29fd 100644 --- a/src/flatc_main.cpp +++ b/src/flatc_main.cpp @@ -20,9 +20,12 @@ #include "bfbs_gen_lua.h" #include "bfbs_gen_nim.h" #include "flatbuffers/base.h" +#include "flatbuffers/code_generator.h" #include "flatbuffers/flatc.h" #include "flatbuffers/util.h" + + static const char *g_program_name = nullptr; static void Warn(const flatbuffers::FlatCompiler *flatc, @@ -159,6 +162,12 @@ int main(int argc, const char *argv[]) { flatbuffers::FlatCompiler flatc(params); + std::shared_ptr cpp_generator = + flatbuffers::NewCppCodeGenerator(); + + flatc.RegisterCodeGenerator("--cpp", cpp_generator); + flatc.RegisterCodeGenerator("-c", cpp_generator); + // Create the FlatC options by parsing the command line arguments. const flatbuffers::FlatCOptions &options = flatc.ParseFromCommandLineArguments(argc, argv); diff --git a/src/idl_gen_cpp.cpp b/src/idl_gen_cpp.cpp index 262eb1ab0..ad534b64d 100644 --- a/src/idl_gen_cpp.cpp +++ b/src/idl_gen_cpp.cpp @@ -17,6 +17,7 @@ // independent from idl_parser, since this code is not needed for most clients #include +#include #include #include @@ -3871,4 +3872,50 @@ std::string CPPMakeRule(const Parser &parser, const std::string &path, return make_rule; } +namespace { + +class CppCodeGenerator : public CodeGenerator { + public: + Status GenerateCode(const Parser &parser, const std::string &path, + const std::string &filename) override { + if (!GenerateCPP(parser, path, filename)) { return Status::ERROR; } + return Status::OK; + } + + // Generate code from the provided `buffer` of given `length`. The buffer is a + // serialized reflection.fbs. + Status GenerateCode(const uint8_t *buffer, int64_t length) override { + (void) buffer; + (void) length; + return Status::NOT_IMPLEMENTED; + } + + Status GenerateMakeRule(const Parser &parser, const std::string &path, + const std::string &filename, + std::string &output) override { + output = CPPMakeRule(parser, path, filename); + return Status::OK; + } + + Status GenerateGrpcCode(const Parser &parser, const std::string &path, + const std::string &filename) override { + if (!GenerateCppGRPC(parser, path, filename)) { return Status::ERROR; } + return Status::OK; + } + + bool IsSchemaOnly() const override { return true; } + + bool SupportsBfbsGeneration() const override { return false; } + + IDLOptions::Language Language() const override { return IDLOptions::kCpp; } + + std::string LanguageName() const override { return "C++"; } +}; + +} // namespace + +std::unique_ptr NewCppCodeGenerator() { + return std::unique_ptr(new CppCodeGenerator()); +} + } // namespace flatbuffers