| // Licensed to the Apache Software Foundation (ASF) under one | |
| // or more contributor license agreements. See the NOTICE file | |
| // distributed with this work for additional information | |
| // regarding copyright ownership. The ASF licenses this file | |
| // to you 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. | |
| #include <cerrno> | |
| #include <cstdio> | |
| #include <cstdint> | |
| #include <cstdlib> | |
| #include <exception> | |
| #include <fstream> | |
| #include <iostream> | |
| #include <iterator> | |
| #include <limits> | |
| #include <set> | |
| #include <sstream> | |
| #include <string> | |
| #include <vector> | |
| #include <fcntl.h> | |
| #include <sys/file.h> | |
| #include <sys/stat.h> | |
| #include <unistd.h> | |
| #include <flatbuffers/idl.h> | |
| #if FLATBUFFERS_VERSION_MAJOR != 25 || FLATBUFFERS_VERSION_MINOR != 2 || \ | |
| FLATBUFFERS_VERSION_REVISION != 10 | |
| #error "brpc_flatc requires FlatBuffers 25.2.10 headers and library" | |
| #endif | |
| namespace { | |
| const char kLicense[] = R"(// Licensed to the Apache Software Foundation (ASF) under one | |
| // or more contributor license agreements. See the NOTICE file | |
| // distributed with this work for additional information | |
| // regarding copyright ownership. The ASF licenses this file | |
| // to you 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. | |
| // Generated by brpc_flatc. Do not edit. | |
| )"; | |
| const char kArguments[] = | |
| "::google::protobuf::RpcController* controller,\n" | |
| " const ::brpc::flatbuffers::Message* request,\n" | |
| " ::brpc::flatbuffers::Message* response,\n" | |
| " ::google::protobuf::Closure* done"; | |
| struct Service { | |
| const flatbuffers::ServiceDef* definition; | |
| std::vector<int32_t> ids; | |
| }; | |
| std::string JoinNamespace(const flatbuffers::Definition& definition, | |
| const std::string& separator) { | |
| std::string result; | |
| if (definition.defined_namespace) { | |
| for (const auto& component : definition.defined_namespace->components) { | |
| if (!result.empty()) { | |
| result += separator; | |
| } | |
| result += component; | |
| } | |
| } | |
| return result; | |
| } | |
| std::string Qualified(const flatbuffers::Definition& definition) { | |
| const std::string ns = JoinNamespace(definition, "::"); | |
| return "::" + (ns.empty() ? "" : ns + "::") + definition.name; | |
| } | |
| std::string GuardComponent(const std::string& value) { | |
| static const char digits[] = "0123456789ABCDEF"; | |
| std::string result; | |
| for (unsigned char c : value) { | |
| result += digits[c >> 4]; | |
| result += digits[c & 15]; | |
| } | |
| return result; | |
| } | |
| bool IsCppIdentifier(const std::string& name) { | |
| static const std::set<std::string> keywords = { | |
| "alignas", "alignof", "and", "and_eq", "asm", "auto", "bitand", | |
| "bitor", "bool", "break", "case", "catch", "char", "char8_t", | |
| "char16_t", "char32_t", "class", "compl", "concept", "const", | |
| "const_cast", "consteval", "constexpr", "constinit", "continue", | |
| "co_await", "co_return", "co_yield", "decltype", "default", | |
| "delete", "do", | |
| "double", "dynamic_cast", "else", "enum", "explicit", "export", | |
| "extern", "false", "float", "for", "friend", "goto", "if", | |
| "inline", "int", "long", "mutable", "namespace", "new", | |
| "noexcept", "not", "not_eq", "nullptr", "operator", "or", "or_eq", | |
| "private", "protected", "public", "register", "reinterpret_cast", | |
| "requires", "return", "short", "signed", "sizeof", "static", | |
| "static_assert", "static_cast", "struct", "switch", "template", | |
| "this", "thread_local", "throw", "true", "try", "typedef", | |
| "typeid", "typename", "union", "unsigned", "using", "virtual", | |
| "void", "volatile", "wchar_t", "while", "xor", "xor_eq" | |
| }; | |
| return !name.empty() && keywords.count(name) == 0; | |
| } | |
| std::string FlatcName(const std::string& name) { | |
| // flatc 25.2.10 escapes a different set than the C++20 rejection list. | |
| static const std::set<std::string> extra = { | |
| "atomic_cancel", "atomic_commit", "atomic_noexcept", "import", | |
| "module", "synchronized" | |
| }; | |
| const bool newer_keyword = name == "char8_t" || name == "consteval" || | |
| name == "constinit"; | |
| return ((!IsCppIdentifier(name) && !newer_keyword) || extra.count(name)) | |
| ? name + "_" : name; | |
| } | |
| std::string FlatcNamespace(const flatbuffers::Definition& definition) { | |
| const std::string ns = JoinNamespace(definition, "::"); | |
| return ns.empty() ? "::" : "::" + ns + "::"; | |
| } | |
| std::string FlatcQualifiedType(const flatbuffers::Definition& definition) { | |
| return FlatcNamespace(definition) + FlatcName(definition.name); | |
| } | |
| bool IsReservedIdentifier(const std::string& name, bool global) { | |
| return name.find("__") != std::string::npos || | |
| (!name.empty() && name[0] == '_' && | |
| (global || (name.size() > 1 && name[1] >= 'A' && name[1] <= 'Z'))); | |
| } | |
| bool ValidateName(const flatbuffers::Definition& definition, | |
| std::string* error, bool member = false) { | |
| if (!IsCppIdentifier(definition.name)) { | |
| *error = "C++ keyword is not supported: " + definition.name; | |
| return false; | |
| } | |
| const bool global = !member && (!definition.defined_namespace || | |
| definition.defined_namespace->components.empty()); | |
| if (IsReservedIdentifier(definition.name, global)) { | |
| *error = "reserved C++ identifier: " + definition.name; | |
| return false; | |
| } | |
| if (definition.defined_namespace) { | |
| bool first = true; | |
| for (const auto& component : definition.defined_namespace->components) { | |
| if (IsReservedIdentifier(component, first)) { | |
| *error = "reserved C++ identifier in namespace: " + component; | |
| return false; | |
| } | |
| first = false; | |
| if (!IsCppIdentifier(component)) { | |
| *error = "C++ keyword namespace is not supported: " + component; | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| bool ParseId(const flatbuffers::Value& value, int32_t* id) { | |
| if (value.constant.empty() || | |
| !flatbuffers::IsInteger(value.type.base_type)) { | |
| return false; | |
| } | |
| int64_t number = 0; | |
| for (char c : value.constant) { | |
| if (c < '0' || c > '9') { | |
| return false; | |
| } | |
| number = number * 10 + (c - '0'); | |
| if (number > std::numeric_limits<int32_t>::max()) { | |
| return false; | |
| } | |
| } | |
| *id = static_cast<int32_t>(number); | |
| return true; | |
| } | |
| void CollectRootNames(const flatbuffers::Parser& parser, | |
| std::set<std::string>* names) { | |
| if (!parser.root_struct_def_) { | |
| return; | |
| } | |
| const auto& root = *parser.root_struct_def_; | |
| const std::string ns = FlatcNamespace(root); | |
| const std::string name = FlatcName(root.name); | |
| names->insert(ns + "Get" + name); | |
| names->insert(ns + "GetSizePrefixed" + name); | |
| names->insert(ns + "Verify" + name + "Buffer"); | |
| names->insert(ns + "VerifySizePrefixed" + name + "Buffer"); | |
| names->insert(ns + "Finish" + name + "Buffer"); | |
| names->insert(ns + "FinishSizePrefixed" + name + "Buffer"); | |
| if (!parser.file_identifier_.empty()) { | |
| names->insert(ns + name + "Identifier"); | |
| names->insert(ns + name + "BufferHasIdentifier"); | |
| names->insert(ns + "SizePrefixed" + name + "BufferHasIdentifier"); | |
| } | |
| if (!parser.file_extension_.empty()) { | |
| names->insert(ns + name + "Extension"); | |
| } | |
| } | |
| bool CollectServices(const flatbuffers::Parser& parser, | |
| const std::string& input, const char** include_paths, | |
| std::vector<Service>* services, std::string* error) { | |
| std::set<std::string> generated_class_names = { | |
| "::brpc", "::butil", "::flatbuffers", "::google", "::std" | |
| }; | |
| CollectRootNames(parser, &generated_class_names); | |
| // The parser resets root_type after each include; flatc emits those helpers | |
| // when each imported schema is generated separately. | |
| for (const auto& file : parser.GetIncludedFilesRecursive(input)) { | |
| if (file == input) { | |
| continue; | |
| } | |
| std::string schema; | |
| flatbuffers::Parser included; | |
| if (!flatbuffers::LoadFile(file.c_str(), false, &schema) || | |
| !included.Parse(schema.c_str(), include_paths, file.c_str())) { | |
| *error = "cannot inspect imported root_type: " + file; | |
| return false; | |
| } | |
| CollectRootNames(included, &generated_class_names); | |
| } | |
| // Reserve every namespace prefix, including those from imported schemas. | |
| for (const auto* ns : parser.namespaces_) { | |
| std::string prefix; | |
| for (const auto& component : ns->components) { | |
| prefix += "::" + component; | |
| generated_class_names.insert(prefix); | |
| } | |
| } | |
| for (const auto* definition : parser.services_.vec) { | |
| if (definition->generated) { | |
| const std::string service_name = Qualified(*definition); | |
| generated_class_names.insert(service_name); | |
| generated_class_names.insert(service_name + "_Stub"); | |
| } | |
| } | |
| for (const auto* definition : parser.structs_.vec) { | |
| const std::string ns = FlatcNamespace(*definition); | |
| const std::string name = FlatcName(definition->name); | |
| const std::string type_name = ns + name; | |
| generated_class_names.insert(type_name); | |
| if (!definition->fixed) { | |
| generated_class_names.insert(type_name + "Builder"); | |
| generated_class_names.insert(ns + "Create" + name); | |
| for (const auto* field : definition->fields.vec) { | |
| if (!field->deprecated && | |
| (flatbuffers::IsString(field->value.type) || | |
| flatbuffers::IsVector(field->value.type))) { | |
| generated_class_names.insert( | |
| ns + "Create" + name + "Direct"); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| // Match the default flatc --cpp API, including non-type declarations. | |
| for (const auto* definition : parser.enums_.vec) { | |
| const std::string ns = FlatcNamespace(*definition); | |
| const std::string name = FlatcName(definition->name); | |
| const std::string enum_name = ns + name; | |
| generated_class_names.insert(enum_name); | |
| for (const auto* value : definition->Vals()) { | |
| generated_class_names.insert(enum_name + "_" + FlatcName(value->name)); | |
| } | |
| if (definition->attributes.Lookup("bit_flags")) { | |
| generated_class_names.insert(enum_name + "_NONE"); | |
| generated_class_names.insert(enum_name + "_ANY"); | |
| } else { | |
| generated_class_names.insert(enum_name + "_MIN"); | |
| generated_class_names.insert(enum_name + "_MAX"); | |
| } | |
| generated_class_names.insert(ns + "EnumValues" + name); | |
| generated_class_names.insert(ns + "EnumName" + name); | |
| if (definition->Distance() / definition->size() < 5) { | |
| generated_class_names.insert(ns + "EnumNames" + name); | |
| } | |
| if (definition->is_union) { | |
| if (!definition->uses_multiple_type_instances) { | |
| generated_class_names.insert(enum_name + "Traits"); | |
| } | |
| generated_class_names.insert(ns + "Verify" + name); | |
| generated_class_names.insert( | |
| ns + "Verify" + name + "Vector"); | |
| } | |
| } | |
| for (const auto* definition : parser.services_.vec) { | |
| // Included schemas are generated separately, just as with flatc --cpp. | |
| if (definition->generated) { | |
| continue; | |
| } | |
| if (!ValidateName(*definition, error)) { | |
| return false; | |
| } | |
| if (definition->name == "Stub" || definition->name == "descriptor" || | |
| definition->name == "GetDescriptor" || | |
| definition->name == "FBCallMethod") { | |
| *error = "service name collides with generated API: " + | |
| definition->name; | |
| return false; | |
| } | |
| const std::string service_name = Qualified(*definition); | |
| const std::string stub_name = service_name + "_Stub"; | |
| if (IsReservedIdentifier(definition->name + "_Stub", false)) { | |
| *error = "generated stub uses reserved C++ identifier: " + stub_name; | |
| return false; | |
| } | |
| if (!generated_class_names.insert(service_name).second) { | |
| *error = "generated service class name collides: " + service_name; | |
| return false; | |
| } | |
| if (!generated_class_names.insert(stub_name).second) { | |
| *error = "generated service class name collides: " + stub_name; | |
| return false; | |
| } | |
| Service service = {definition, {}}; | |
| std::set<int32_t> ids; | |
| if (definition->calls.vec.empty()) { | |
| *error = "rpc_service must contain at least one method: " + | |
| definition->name; | |
| return false; | |
| } | |
| for (const auto* call : definition->calls.vec) { | |
| if (!ValidateName(*call, error, true) || | |
| !ValidateName(*call->request, error) || | |
| !ValidateName(*call->response, error)) { | |
| return false; | |
| } | |
| if (call->name == definition->name || | |
| call->name == definition->name + "_Stub" || | |
| call->name == "descriptor" || call->name == "GetDescriptor" || | |
| call->name == "FBCallMethod" || call->name == "Stub" || | |
| call->name == "channel" || call->name == "channel_" || | |
| call->name == "owned_channel_") { | |
| *error = "method name collides with generated API: " + | |
| call->name; | |
| return false; | |
| } | |
| const auto* attribute = call->attributes.Lookup("id"); | |
| int32_t id = 0; | |
| if (!attribute || !ParseId(*attribute, &id)) { | |
| *error = definition->name + "." + call->name + | |
| " requires an explicit nonnegative int32 (id: N)"; | |
| return false; | |
| } | |
| if (!ids.insert(id).second) { | |
| *error = "duplicate method id " + std::to_string(id) + | |
| " in " + definition->name; | |
| return false; | |
| } | |
| if (call->attributes.Lookup("streaming")) { | |
| *error = "streaming RPC is not supported: " + call->name; | |
| return false; | |
| } | |
| service.ids.push_back(id); | |
| } | |
| services->push_back(service); | |
| } | |
| if (services->empty()) { | |
| *error = "input schema contains no rpc_service to generate"; | |
| return false; | |
| } | |
| return true; | |
| } | |
| void OpenNamespace(const flatbuffers::Definition& definition, | |
| std::ostream& out) { | |
| if (definition.defined_namespace) { | |
| for (const auto& component : definition.defined_namespace->components) { | |
| out << "namespace " << component << " {\n"; | |
| } | |
| } | |
| out << '\n'; | |
| } | |
| void CloseNamespace(const flatbuffers::Definition& definition, | |
| std::ostream& out) { | |
| if (definition.defined_namespace) { | |
| const auto& components = definition.defined_namespace->components; | |
| for (auto it = components.rbegin(); it != components.rend(); ++it) { | |
| out << "} // namespace " << *it << '\n'; | |
| } | |
| } | |
| out << '\n'; | |
| } | |
| void GenerateHeader(const Service& service, std::ostream& out) { | |
| const auto& definition = *service.definition; | |
| const std::string& name = definition.name; | |
| OpenNamespace(definition, out); | |
| out << "class " << name << "_Stub;\n\n" | |
| << "class " << name << " : public ::brpc::flatbuffers::Service {\n" | |
| << "public:\n" | |
| << " typedef " << name << "_Stub Stub;\n" | |
| << " static const ::brpc::flatbuffers::ServiceDescriptor* " | |
| "descriptor();\n" | |
| << " const ::brpc::flatbuffers::ServiceDescriptor* " | |
| "GetDescriptor() override;\n" | |
| << " void FBCallMethod(\n" | |
| << " const ::brpc::flatbuffers::MethodDescriptor* method,\n" | |
| << " " << kArguments << ") override;\n"; | |
| for (const auto* call : definition.calls.vec) { | |
| out << " virtual void " << call->name << "(\n" | |
| << " " << kArguments << ");\n"; | |
| } | |
| out << "};\n\n" | |
| << "class " << name << "_Stub : public " << name << " {\n" | |
| << "public:\n" | |
| << " explicit " << name << "_Stub(\n" | |
| << " ::brpc::flatbuffers::RpcChannel* channel,\n" | |
| << " ::brpc::flatbuffers::Service::ChannelOwnership " | |
| "ownership =\n" | |
| << " ::brpc::flatbuffers::Service::" | |
| "STUB_DOESNT_OWN_CHANNEL);\n" | |
| << " // Calls through a brpc::Channel using fb_rpc.\n" | |
| << " explicit " << name << "_Stub(\n" | |
| << " ::brpc::Channel* channel,\n" | |
| << " ::brpc::flatbuffers::Service::ChannelOwnership " | |
| "ownership =\n" | |
| << " ::brpc::flatbuffers::Service::" | |
| "STUB_DOESNT_OWN_CHANNEL);\n" | |
| << " // A disconnected stub fails every call.\n" | |
| << " explicit " << name << "_Stub(\n" | |
| << " ::std::nullptr_t,\n" | |
| << " ::brpc::flatbuffers::Service::ChannelOwnership =\n" | |
| << " ::brpc::flatbuffers::Service::" | |
| "STUB_DOESNT_OWN_CHANNEL);\n" | |
| << " ::brpc::flatbuffers::RpcChannel* channel() const {\n" | |
| << " return channel_;\n" | |
| << " }\n"; | |
| for (const auto* call : definition.calls.vec) { | |
| out << " void " << call->name << "(\n" | |
| << " " << kArguments << ") override;\n"; | |
| } | |
| out << "\nprivate:\n" | |
| << " ::brpc::flatbuffers::RpcChannel* channel_;\n" | |
| << " ::std::unique_ptr<::brpc::flatbuffers::RpcChannel> " | |
| "owned_channel_;\n" | |
| << "};\n\n"; | |
| CloseNamespace(definition, out); | |
| } | |
| void GenerateFailureHandler(std::ostream& out) { | |
| // A local callable cannot be hidden by schema declarations or RPC names. | |
| out << " const auto fail = [controller, done](const char* reason) {\n" | |
| << " if (controller) {\n" | |
| << " controller->SetFailed(reason);\n" | |
| << " }\n" | |
| << " if (done) {\n" | |
| << " done->Run();\n" | |
| << " }\n" | |
| << " };\n"; | |
| } | |
| void GenerateSource(const Service& service, std::ostream& out) { | |
| const auto& definition = *service.definition; | |
| const std::string& name = definition.name; | |
| OpenNamespace(definition, out); | |
| out << "const ::brpc::flatbuffers::ServiceDescriptor* " << name | |
| << "::descriptor() {\n" | |
| << " struct Holder {\n" | |
| << " ::brpc::flatbuffers::ServiceDescriptor value;\n" | |
| << " Holder() {\n" | |
| << " const ::brpc::flatbuffers::BrpcDescriptorTable " | |
| "table = {\n" | |
| << " \"" << JoinNamespace(definition, ".") << "\", \"" | |
| << name << "\",\n \""; | |
| for (size_t i = 0; i < definition.calls.vec.size(); ++i) { | |
| out << (i == 0 ? "" : " ") << definition.calls.vec[i]->name; | |
| } | |
| out << "\", {"; | |
| for (size_t i = 0; i < service.ids.size(); ++i) { | |
| out << (i == 0 ? "" : ", ") << service.ids[i]; | |
| } | |
| out << "}\n };\n" | |
| << " if (value.init(table) != 0) {\n" | |
| << " throw ::std::runtime_error(\n" | |
| << " \"invalid generated service descriptor\");\n" | |
| << " }\n" | |
| << " }\n" | |
| << " };\n" | |
| << " static const Holder holder;\n" | |
| << " return &holder.value;\n" | |
| << "}\n\n" | |
| << "const ::brpc::flatbuffers::ServiceDescriptor* " << name | |
| << "::GetDescriptor() {\n" | |
| << " return descriptor();\n" | |
| << "}\n\n" | |
| << "void " << name << "::FBCallMethod(\n" | |
| << " const ::brpc::flatbuffers::MethodDescriptor* method,\n" | |
| << " " << kArguments << ") {\n"; | |
| GenerateFailureHandler(out); | |
| out << " if (!method || method->service() != descriptor() ||\n" | |
| << " descriptor()->FindMethodByIndex(method->index()) != " | |
| "method) {\n" | |
| << " fail(\"invalid service method\");\n" | |
| << " return;\n" | |
| << " }\n" | |
| << " if (!request || !response) {\n" | |
| << " fail(\"null request or response\");\n" | |
| << " return;\n" | |
| << " }\n" | |
| << " switch (method->index()) {\n"; | |
| for (size_t i = 0; i < definition.calls.vec.size(); ++i) { | |
| const auto& call = *definition.calls.vec[i]; | |
| out << " case " << service.ids[i] << ":\n" | |
| << " if (!request->Verify<" << FlatcQualifiedType(*call.request) | |
| << ">()) {\n" | |
| << " fail(\"invalid " | |
| << call.request->name << " request\");\n" | |
| << " return;\n" | |
| << " }\n" | |
| << " this->" << call.name | |
| << "(controller, request, response, done);\n" | |
| << " return;\n"; | |
| } | |
| out << " default:\n" | |
| << " fail(\"unknown method id\");\n" | |
| << " return;\n" | |
| << " }\n" | |
| << "}\n\n"; | |
| for (const auto* call : definition.calls.vec) { | |
| out << "void " << name << "::" << call->name << "(\n" | |
| << " " << kArguments << ") {\n" | |
| << " (void)request;\n" | |
| << " (void)response;\n"; | |
| GenerateFailureHandler(out); | |
| out << " fail(\"method not implemented: " | |
| << name << "." << call->name << "\");\n" | |
| << "}\n\n"; | |
| } | |
| out << name << "_Stub::" << name << "_Stub(\n" | |
| << " ::brpc::flatbuffers::RpcChannel* channel,\n" | |
| << " ::brpc::flatbuffers::Service::ChannelOwnership ownership)\n" | |
| << " : channel_(channel),\n" | |
| << " owned_channel_(\n" | |
| << " ownership == " | |
| "::brpc::flatbuffers::Service::STUB_OWNS_CHANNEL\n" | |
| << " ? channel : nullptr) {}\n\n" | |
| << name << "_Stub::" << name << "_Stub(\n" | |
| << " ::brpc::Channel* channel,\n" | |
| << " ::brpc::flatbuffers::Service::ChannelOwnership ownership)\n" | |
| << " : channel_(::brpc::flatbuffers::NewChannelAdapter(\n" | |
| << " channel, ownership == " | |
| "::brpc::flatbuffers::Service::STUB_OWNS_CHANNEL)),\n" | |
| << " owned_channel_(channel_) {}\n\n" | |
| << name << "_Stub::" << name << "_Stub(\n" | |
| << " ::std::nullptr_t,\n" | |
| << " ::brpc::flatbuffers::Service::ChannelOwnership)\n" | |
| << " : channel_(nullptr) {}\n\n"; | |
| for (size_t i = 0; i < definition.calls.vec.size(); ++i) { | |
| out << "void " << name << "_Stub::" << definition.calls.vec[i]->name | |
| << "(\n " << kArguments << ") {\n"; | |
| GenerateFailureHandler(out); | |
| out << " if (!channel_) {\n" | |
| << " fail(\"null RPC channel\");\n" | |
| << " return;\n" | |
| << " }\n" | |
| << " channel_->FBCallMethod(\n" | |
| << " descriptor()->method(" << i | |
| << "), controller, request, response, done);\n" | |
| << "}\n\n"; | |
| } | |
| CloseNamespace(definition, out); | |
| } | |
| bool TestFailureEnabled(const char* step) { | |
| #if defined(BRPC_FLATC_TESTING) | |
| const char* value = std::getenv("BRPC_FLATC_TEST_FAILURE"); | |
| if (value) { | |
| const std::string failures = "," + std::string(value) + ","; | |
| return failures.find("," + std::string(step) + ",") != | |
| std::string::npos; | |
| } | |
| #else | |
| (void)step; | |
| #endif | |
| return false; | |
| } | |
| bool WriteFile(const std::string& path, const std::string& contents, | |
| const char* step) { | |
| if (TestFailureEnabled(step)) { | |
| errno = EIO; | |
| return false; | |
| } | |
| std::ofstream stream(path.c_str(), std::ios::binary | std::ios::trunc); | |
| stream << contents; | |
| stream.close(); | |
| return !stream.fail(); | |
| } | |
| bool RenameFile(const std::string& from, const std::string& to, | |
| const char* step) { | |
| if (TestFailureEnabled(step)) { | |
| errno = EIO; | |
| return false; | |
| } | |
| return std::rename(from.c_str(), to.c_str()) == 0; | |
| } | |
| class OutputDirectoryLock { | |
| public: | |
| explicit OutputDirectoryLock(const std::string& directory) | |
| : fd_(open(directory.c_str(), O_RDONLY)) { | |
| if (fd_ < 0) { | |
| return; | |
| } | |
| while (flock(fd_, LOCK_EX) != 0) { | |
| if (errno != EINTR) { | |
| close(fd_); | |
| fd_ = -1; | |
| return; | |
| } | |
| } | |
| } | |
| ~OutputDirectoryLock() { | |
| if (fd_ >= 0) { | |
| close(fd_); | |
| } | |
| } | |
| bool acquired() const { return fd_ >= 0; } | |
| private: | |
| OutputDirectoryLock(const OutputDirectoryLock&) = delete; | |
| OutputDirectoryLock& operator=(const OutputDirectoryLock&) = delete; | |
| int fd_; | |
| }; | |
| bool RemoveFile(const std::string& path) { | |
| return std::remove(path.c_str()) == 0 || errno == ENOENT; | |
| } | |
| bool IsRegularFileOrMissing(const std::string& path) { | |
| struct stat file_status; | |
| if (lstat(path.c_str(), &file_status) == 0) { | |
| return S_ISREG(file_status.st_mode); | |
| } | |
| return errno == ENOENT; | |
| } | |
| bool MoveExistingFile(const std::string& path, const std::string& backup, | |
| const char* step, bool* moved) { | |
| *moved = false; | |
| if (RenameFile(path, backup, step)) { | |
| *moved = true; | |
| return true; | |
| } | |
| return errno == ENOENT; | |
| } | |
| bool RestoreFile(const std::string& path, const std::string& backup, | |
| const char* step, bool had_original) { | |
| if (!RemoveFile(path)) { | |
| return false; | |
| } | |
| return !had_original || RenameFile(backup, path, step); | |
| } | |
| bool RemoveOutputPair(const std::string& header_path, | |
| const std::string& source_path) { | |
| const bool header_removed = RemoveFile(header_path); | |
| const bool source_removed = RemoveFile(source_path); | |
| return header_removed && source_removed; | |
| } | |
| bool PublishFiles(const std::string& output_dir, | |
| const std::string& header_path, | |
| const std::string& header_contents, | |
| const std::string& source_path, | |
| const std::string& source_contents, | |
| std::string* error) { | |
| OutputDirectoryLock lock(output_dir); | |
| if (!lock.acquired()) { | |
| *error = "cannot lock output directory"; | |
| return false; | |
| } | |
| const std::string header_tmp = header_path + ".tmp"; | |
| const std::string source_tmp = source_path + ".tmp"; | |
| const std::string header_backup = header_path + ".bak"; | |
| const std::string source_backup = source_path + ".bak"; | |
| if (!RemoveFile(header_tmp) || | |
| !WriteFile(header_tmp, header_contents, "stage_header")) { | |
| RemoveFile(header_tmp); | |
| *error = "cannot stage header output"; | |
| return false; | |
| } | |
| if (!RemoveFile(source_tmp) || | |
| !WriteFile(source_tmp, source_contents, "stage_source")) { | |
| RemoveFile(header_tmp); | |
| RemoveFile(source_tmp); | |
| *error = "cannot stage source output"; | |
| return false; | |
| } | |
| if (!IsRegularFileOrMissing(header_path) || | |
| !IsRegularFileOrMissing(source_path) || | |
| !RemoveFile(header_backup) || !RemoveFile(source_backup)) { | |
| RemoveFile(header_tmp); | |
| RemoveFile(source_tmp); | |
| *error = "output or backup path is not replaceable"; | |
| return false; | |
| } | |
| bool had_header = false; | |
| bool had_source = false; | |
| if (!MoveExistingFile(header_path, header_backup, | |
| "preserve_header", &had_header)) { | |
| RemoveFile(header_tmp); | |
| RemoveFile(source_tmp); | |
| *error = "cannot preserve previous header"; | |
| return false; | |
| } | |
| if (!MoveExistingFile(source_path, source_backup, | |
| "preserve_source", &had_source)) { | |
| const bool restored = RestoreFile( | |
| header_path, header_backup, "restore_header", had_header); | |
| RemoveFile(header_tmp); | |
| RemoveFile(source_tmp); | |
| if (!restored) { | |
| const bool cleared = RemoveOutputPair(header_path, source_path); | |
| *error = cleared | |
| ? "cannot preserve source or restore header" | |
| : "cannot preserve source; manual cleanup required"; | |
| } else { | |
| *error = "cannot preserve previous source"; | |
| } | |
| return false; | |
| } | |
| if (!RenameFile(header_tmp, header_path, "publish_header") || | |
| !RenameFile(source_tmp, source_path, "publish_source")) { | |
| const bool header_restored = RestoreFile( | |
| header_path, header_backup, "restore_header", had_header); | |
| const bool source_restored = RestoreFile( | |
| source_path, source_backup, "restore_source", had_source); | |
| RemoveFile(header_tmp); | |
| RemoveFile(source_tmp); | |
| if (!header_restored || !source_restored) { | |
| const bool cleared = RemoveOutputPair(header_path, source_path); | |
| *error = cleared | |
| ? "cannot publish or restore output files" | |
| : "cannot publish output files; manual cleanup required"; | |
| } else { | |
| *error = "cannot publish output files"; | |
| } | |
| return false; | |
| } | |
| RemoveFile(header_backup); | |
| RemoveFile(source_backup); | |
| return true; | |
| } | |
| void Usage(std::ostream& out) { | |
| out << "Usage: brpc_flatc [-I include_dir]... " | |
| "[-o existing_output_dir] schema.fbs\n" | |
| << "Run official flatc --cpp separately to produce " | |
| "schema_generated.h.\n"; | |
| } | |
| int Run(int argc, char** argv) { | |
| if (std::string(flatbuffers::FLATBUFFERS_VERSION()) != "25.2.10") { | |
| std::cerr << "brpc_flatc: requires FlatBuffers 25.2.10 library\n"; | |
| return 1; | |
| } | |
| if (argc == 2 && std::string(argv[1]) == "--version") { | |
| std::cout << "brpc_flatc (FlatBuffers 25.2.10)\n"; | |
| return 0; | |
| } | |
| std::string input; | |
| std::string output_dir = "."; | |
| std::vector<std::string> include_dirs; | |
| for (int i = 1; i < argc; ++i) { | |
| const std::string arg = argv[i]; | |
| if (arg == "--help" || arg == "-h") { | |
| Usage(std::cout); | |
| return 0; | |
| } | |
| if (arg == "-o" || arg == "-I") { | |
| if (++i == argc) { | |
| Usage(std::cerr); | |
| return 1; | |
| } | |
| if (arg == "-o") { | |
| output_dir = argv[i]; | |
| } else { | |
| include_dirs.push_back(argv[i]); | |
| } | |
| } else if (arg.compare(0, 2, "-I") == 0 && arg.size() > 2) { | |
| include_dirs.push_back(arg.substr(2)); | |
| } else if (arg.empty() || arg[0] == '-' || !input.empty()) { | |
| Usage(std::cerr); | |
| return 1; | |
| } else { | |
| input = arg; | |
| } | |
| } | |
| if (input.size() < 5 || input.substr(input.size() - 4) != ".fbs" || | |
| output_dir.empty()) { | |
| Usage(std::cerr); | |
| return 1; | |
| } | |
| const size_t slash = input.find_last_of("/\\"); | |
| const std::string basename = input.substr( | |
| slash == std::string::npos ? 0 : slash + 1); | |
| const std::string stem = basename.substr(0, basename.size() - 4); | |
| if (stem.empty() || stem.find_first_not_of( | |
| "abcdefghijklmnopqrstuvwxyz" | |
| "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.-") != | |
| std::string::npos) { | |
| std::cerr << "brpc_flatc: unsupported schema filename\n"; | |
| return 1; | |
| } | |
| include_dirs.push_back(slash == std::string::npos ? "." : | |
| input.substr(0, slash == 0 ? 1 : slash)); | |
| std::vector<const char*> include_paths; | |
| for (const auto& directory : include_dirs) { | |
| include_paths.push_back(directory.c_str()); | |
| } | |
| include_paths.push_back(nullptr); | |
| std::ifstream stream(input.c_str(), std::ios::binary); | |
| if (!stream) { | |
| std::cerr << "brpc_flatc: cannot read " << input << '\n'; | |
| return 1; | |
| } | |
| const std::string schema((std::istreambuf_iterator<char>(stream)), | |
| std::istreambuf_iterator<char>()); | |
| if (stream.bad() || schema.find('\0') != std::string::npos) { | |
| std::cerr << "brpc_flatc: invalid schema input\n"; | |
| return 1; | |
| } | |
| flatbuffers::Parser parser; | |
| if (!parser.Parse(schema.c_str(), include_paths.data(), input.c_str())) { | |
| std::cerr << parser.error_ << '\n'; | |
| return 1; | |
| } | |
| std::vector<Service> services; | |
| std::string error; | |
| if (!CollectServices(parser, input, include_paths.data(), &services, &error)) { | |
| std::cerr << "brpc_flatc: " << error << '\n'; | |
| return 1; | |
| } | |
| // Coexisting service headers cannot define the same qualified service. | |
| // Preserve filename bytes and that service identity, without tying output | |
| // to checkout paths or folding punctuation and case into the same guard. | |
| const std::string guard = | |
| "BRPC_FLATBUFFERS_GENERATED_" + GuardComponent(stem) + "_" + | |
| GuardComponent(Qualified(*services.front().definition)) + "_H_"; | |
| std::ostringstream header; | |
| header << kLicense << "#ifndef " << guard << "\n#define " << guard << "\n\n" | |
| << "#include <cstddef>\n" | |
| << "#include <memory>\n" | |
| << "#include <google/protobuf/service.h>\n" | |
| << "#include \"brpc/flatbuffers/message.h\"\n" | |
| << "#include \"brpc/flatbuffers/service.h\"\n" | |
| << "#include \"" << stem << "_generated.h\"\n\n" | |
| << "#if !BRPC_WITH_FLATBUFFERS\n" | |
| << "#error \"Generated services require BRPC_WITH_FLATBUFFERS\"\n" | |
| << "#endif\n\n"; | |
| std::ostringstream source; | |
| source << kLicense << "#include \"" << stem << ".brpc.fb.h\"\n\n" | |
| << "#include <stdexcept>\n\n"; | |
| for (const auto& service : services) { | |
| GenerateHeader(service, header); | |
| GenerateSource(service, source); | |
| } | |
| header << "#endif // " << guard << '\n'; | |
| if (output_dir.back() != '/') { | |
| output_dir += '/'; | |
| } | |
| if (!PublishFiles(output_dir, | |
| output_dir + stem + ".brpc.fb.h", header.str(), | |
| output_dir + stem + ".brpc.fb.cpp", source.str(), | |
| &error)) { | |
| std::cerr << "brpc_flatc: cannot write output in " << output_dir | |
| << ": " << error << '\n'; | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| } // namespace | |
| int main(int argc, char** argv) { | |
| try { | |
| return Run(argc, argv); | |
| } catch (const std::exception& error) { | |
| std::cerr << "brpc_flatc: " << error.what() << '\n'; | |
| return 1; | |
| } | |
| } |