322 lines
11 KiB
C++
322 lines
11 KiB
C++
/*
|
|
* Copyright (C) 2017 The Android Open Source Project
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <functional>
|
|
#include <map>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <google/protobuf/descriptor.h>
|
|
#include <google/protobuf/compiler/plugin.h>
|
|
#include <google/protobuf/compiler/code_generator.h>
|
|
#include <google/protobuf/io/printer.h>
|
|
#include <google/protobuf/io/zero_copy_stream.h>
|
|
#include <google/protobuf/stubs/strutil.h>
|
|
|
|
#include "nugget/protobuf/options.pb.h"
|
|
|
|
using ::google::protobuf::FileDescriptor;
|
|
using ::google::protobuf::JoinStrings;
|
|
using ::google::protobuf::MethodDescriptor;
|
|
using ::google::protobuf::ServiceDescriptor;
|
|
using ::google::protobuf::Split;
|
|
using ::google::protobuf::SplitStringUsing;
|
|
using ::google::protobuf::StripSuffixString;
|
|
using ::google::protobuf::compiler::CodeGenerator;
|
|
using ::google::protobuf::compiler::OutputDirectory;
|
|
using ::google::protobuf::io::Printer;
|
|
using ::google::protobuf::io::ZeroCopyOutputStream;
|
|
|
|
using ::nugget::protobuf::app_id;
|
|
using ::nugget::protobuf::request_buffer_size;
|
|
using ::nugget::protobuf::response_buffer_size;
|
|
|
|
namespace {
|
|
|
|
std::string validateServiceOptions(const ServiceDescriptor& service) {
|
|
if (!service.options().HasExtension(app_id)) {
|
|
return "nugget.protobuf.app_id is not defined for service " + service.name();
|
|
}
|
|
if (!service.options().HasExtension(request_buffer_size)) {
|
|
return "nugget.protobuf.request_buffer_size is not defined for service " + service.name();
|
|
}
|
|
if (!service.options().HasExtension(response_buffer_size)) {
|
|
return "nugget.protobuf.response_buffer_size is not defined for service " + service.name();
|
|
}
|
|
return "";
|
|
}
|
|
|
|
template <typename Descriptor>
|
|
std::vector<std::string> Packages(const Descriptor& descriptor) {
|
|
std::vector<std::string> namespaces;
|
|
SplitStringUsing(descriptor.full_name(), ".", &namespaces);
|
|
namespaces.pop_back(); // just take the package
|
|
return namespaces;
|
|
}
|
|
|
|
template <typename Descriptor>
|
|
std::string FullyQualifiedIdentifier(const Descriptor& descriptor) {
|
|
const auto namespaces = Packages(descriptor);
|
|
if (namespaces.empty()) {
|
|
return "::" + descriptor.name();
|
|
} else {
|
|
std::string namespace_path;
|
|
JoinStrings(namespaces, "::", &namespace_path);
|
|
return "::" + namespace_path + "::" + descriptor.name();
|
|
}
|
|
}
|
|
|
|
template <typename Descriptor>
|
|
std::string FullyQualifiedHeader(const Descriptor& descriptor) {
|
|
const auto packages = Packages(descriptor);
|
|
const auto file = Split(descriptor.file()->name(), "/").back();
|
|
const auto header = StripSuffixString(file, ".proto") + ".pb.h";
|
|
if (packages.empty()) {
|
|
return header;
|
|
} else {
|
|
std::string package_path;
|
|
JoinStrings(packages, "/", &package_path);
|
|
return package_path + "/" + header;
|
|
}
|
|
}
|
|
|
|
template <typename Descriptor>
|
|
void OpenNamespaces(Printer& printer, const Descriptor& descriptor) {
|
|
const auto namespaces = Packages(descriptor);
|
|
for (const auto& ns : namespaces) {
|
|
std::map<std::string, std::string> namespaceVars;
|
|
namespaceVars["namespace"] = ns;
|
|
printer.Print(namespaceVars, R"(
|
|
namespace $namespace$ {)");
|
|
}
|
|
}
|
|
|
|
template <typename Descriptor>
|
|
void CloseNamespaces(Printer& printer, const Descriptor& descriptor) {
|
|
const auto namespaces = Packages(descriptor);
|
|
for (auto it = namespaces.crbegin(); it != namespaces.crend(); ++it) {
|
|
std::map<std::string, std::string> namespaceVars;
|
|
namespaceVars["namespace"] = *it;
|
|
printer.Print(namespaceVars, R"(
|
|
} // namespace $namespace$)");
|
|
}
|
|
}
|
|
|
|
void ForEachMethod(const ServiceDescriptor& service,
|
|
std::function<void(std::map<std::string, std::string>)> handler) {
|
|
for (int i = 0; i < service.method_count(); ++i) {
|
|
const MethodDescriptor& method = *service.method(i);
|
|
std::map<std::string, std::string> vars;
|
|
vars["method_id"] = std::to_string(i);
|
|
vars["method_name"] = method.name();
|
|
vars["method_input_type"] = FullyQualifiedIdentifier(*method.input_type());
|
|
vars["method_output_type"] = FullyQualifiedIdentifier(*method.output_type());
|
|
handler(vars);
|
|
}
|
|
}
|
|
|
|
void GenerateMockClient(Printer& printer, const ServiceDescriptor& service) {
|
|
std::map<std::string, std::string> vars;
|
|
vars["include_guard"] = "PROTOC_GENERATED_MOCK_" + service.name() + "_CLIENT_H";
|
|
vars["service_header"] = service.name() + ".client.h";
|
|
vars["mock_class"] = "Mock" + service.name();
|
|
vars["class"] = service.name();
|
|
|
|
printer.Print(vars, R"(
|
|
#ifndef $include_guard$
|
|
#define $include_guard$
|
|
|
|
#include <gmock/gmock.h>
|
|
|
|
#include <$service_header$>)");
|
|
|
|
OpenNamespaces(printer, service);
|
|
|
|
printer.Print(vars, R"(
|
|
struct $mock_class$ : public I$class$ {)");
|
|
|
|
ForEachMethod(service, [&](std::map<std::string, std::string> methodVars) {
|
|
printer.Print(methodVars, R"(
|
|
MOCK_METHOD2($method_name$, uint32_t(const $method_input_type$&, $method_output_type$*));)");
|
|
});
|
|
|
|
printer.Print(vars, R"(
|
|
};)");
|
|
|
|
CloseNamespaces(printer, service);
|
|
|
|
printer.Print(vars, R"(
|
|
#endif)");
|
|
}
|
|
|
|
void GenerateClientHeader(Printer& printer, const ServiceDescriptor& service) {
|
|
std::map<std::string, std::string> vars;
|
|
vars["include_guard"] = "PROTOC_GENERATED_" + service.name() + "_CLIENT_H";
|
|
vars["protobuf_header"] = FullyQualifiedHeader(service);
|
|
vars["class"] = service.name();
|
|
vars["iface_class"] = "I" + service.name();
|
|
vars["app_id"] = "APP_ID_" + service.options().GetExtension(app_id);
|
|
|
|
printer.Print(vars, R"(
|
|
#ifndef $include_guard$
|
|
#define $include_guard$
|
|
|
|
#include <application.h>
|
|
#include <nos/AppClient.h>
|
|
#include <nos/NuggetClientInterface.h>
|
|
|
|
#include "$protobuf_header$")");
|
|
|
|
OpenNamespaces(printer, service);
|
|
|
|
// Pure virtual interface to make testing easier
|
|
printer.Print(vars, R"(
|
|
class $iface_class$ {
|
|
public:
|
|
virtual ~$iface_class$() = default;)");
|
|
|
|
ForEachMethod(service, [&](std::map<std::string, std::string> methodVars) {
|
|
printer.Print(methodVars, R"(
|
|
virtual uint32_t $method_name$(const $method_input_type$&, $method_output_type$*) = 0;)");
|
|
});
|
|
|
|
printer.Print(vars, R"(
|
|
};)");
|
|
|
|
// Implementation of the interface for Nugget
|
|
printer.Print(vars, R"(
|
|
class $class$ : public $iface_class$ {
|
|
::nos::AppClient _app;
|
|
public:
|
|
$class$(::nos::NuggetClientInterface& client) : _app{client, $app_id$} {}
|
|
~$class$() override = default;)");
|
|
|
|
ForEachMethod(service, [&](std::map<std::string, std::string> methodVars) {
|
|
printer.Print(methodVars, R"(
|
|
uint32_t $method_name$(const $method_input_type$&, $method_output_type$*) override;)");
|
|
});
|
|
|
|
printer.Print(vars, R"(
|
|
};)");
|
|
|
|
CloseNamespaces(printer, service);
|
|
|
|
printer.Print(vars, R"(
|
|
#endif)");
|
|
}
|
|
|
|
void GenerateClientSource(Printer& printer, const ServiceDescriptor& service) {
|
|
std::map<std::string, std::string> vars;
|
|
vars["generated_header"] = service.name() + ".client.h";
|
|
vars["class"] = service.name();
|
|
|
|
const uint32_t max_request_size = service.options().GetExtension(request_buffer_size);
|
|
const uint32_t max_response_size = service.options().GetExtension(response_buffer_size);
|
|
vars["max_request_size"] = std::to_string(max_request_size);
|
|
vars["max_response_size"] = std::to_string(max_response_size);
|
|
|
|
printer.Print(vars, R"(
|
|
#include <$generated_header$>
|
|
|
|
#include <application.h>)");
|
|
|
|
OpenNamespaces(printer, service);
|
|
|
|
// Methods
|
|
ForEachMethod(service, [&](std::map<std::string, std::string> methodVars) {
|
|
methodVars.insert(vars.begin(), vars.end());
|
|
printer.Print(methodVars, R"(
|
|
uint32_t $class$::$method_name$(const $method_input_type$& request, $method_output_type$* response) {
|
|
const size_t request_size = request.ByteSize();
|
|
if (request_size > $max_request_size$) {
|
|
return APP_ERROR_TOO_MUCH;
|
|
}
|
|
std::vector<uint8_t> buffer(request_size);
|
|
if (!request.SerializeToArray(buffer.data(), buffer.size())) {
|
|
return APP_ERROR_RPC;
|
|
}
|
|
std::vector<uint8_t> responseBuffer;
|
|
if (response != nullptr) {
|
|
responseBuffer.resize($max_response_size$);
|
|
}
|
|
const uint32_t appStatus = _app.Call($method_id$, buffer,
|
|
(response != nullptr) ? &responseBuffer : nullptr);
|
|
if (appStatus == APP_SUCCESS && response != nullptr) {
|
|
if (!response->ParseFromArray(responseBuffer.data(), responseBuffer.size())) {
|
|
return APP_ERROR_RPC;
|
|
}
|
|
}
|
|
return appStatus;
|
|
})");
|
|
});
|
|
|
|
CloseNamespaces(printer, service);
|
|
}
|
|
|
|
// Generator for C++ Nugget service client
|
|
class CppNuggetServiceClientGenerator : public CodeGenerator {
|
|
public:
|
|
CppNuggetServiceClientGenerator() = default;
|
|
~CppNuggetServiceClientGenerator() override = default;
|
|
|
|
bool Generate(const FileDescriptor* file,
|
|
const std::string& parameter,
|
|
OutputDirectory* output_directory,
|
|
std::string* error) const override {
|
|
for (int i = 0; i < file->service_count(); ++i) {
|
|
const auto& service = *file->service(i);
|
|
|
|
*error = validateServiceOptions(service);
|
|
if (!error->empty()) {
|
|
return false;
|
|
}
|
|
|
|
if (parameter == "mock") {
|
|
std::unique_ptr<ZeroCopyOutputStream> output{
|
|
output_directory->Open("Mock" + service.name() + ".client.h")};
|
|
Printer printer(output.get(), '$');
|
|
GenerateMockClient(printer, service);
|
|
} else if (parameter == "header") {
|
|
std::unique_ptr<ZeroCopyOutputStream> output{
|
|
output_directory->Open(service.name() + ".client.h")};
|
|
Printer printer(output.get(), '$');
|
|
GenerateClientHeader(printer, service);
|
|
} else if (parameter == "source") {
|
|
std::unique_ptr<ZeroCopyOutputStream> output{
|
|
output_directory->Open(service.name() + ".client.cpp")};
|
|
Printer printer(output.get(), '$');
|
|
GenerateClientSource(printer, service);
|
|
} else {
|
|
*error = "Illegal parameter: must be mock|header|source";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(CppNuggetServiceClientGenerator);
|
|
};
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, char* argv[]) {
|
|
GOOGLE_PROTOBUF_VERIFY_VERSION;
|
|
CppNuggetServiceClientGenerator generator;
|
|
return google::protobuf::compiler::PluginMain(argc, argv, &generator);
|
|
}
|