blob: aaf52b33f763cb60e7959aa344735f3efe9ab531 [file]
// 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;
}
}