/*
* 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 "src/ipc/protoc_plugin/ipc_generator.h"
#include <functional>
#include <memory>
#include <set>
#include <string>
#include "google/protobuf/compiler/cpp/cpp_options.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/descriptor.pb.h"
#include "google/protobuf/io/printer.h"
#include "google/protobuf/io/zero_copy_stream.h"
#include "google/protobuf/stubs/strutil.h"
namespace perfetto {
namespace ipc {
using google::protobuf::ServiceDescriptor;
using google::protobuf::FileDescriptor;
using google::protobuf::MethodDescriptor;
using google::protobuf::compiler::GeneratorContext;
using google::protobuf::io::Printer;
using google::protobuf::io::ZeroCopyOutputStream;
using google::protobuf::Split;
using google::protobuf::StripString;
using google::protobuf::StripSuffixString;
using google::protobuf::UpperString;
namespace {
static const char kBanner[] = "// DO NOT EDIT. Autogenerated by Perfetto IPC\n";
static const char kHeaderSvcClass[] = R"(
class $c$ : public ::perfetto::ipc::Service {
private:
static ::perfetto::ipc::ServiceDescriptor* NewDescriptor();
public:
~$c$() override;
static const ::perfetto::ipc::ServiceDescriptor& GetDescriptorStatic();
// Service implementation.
const ::perfetto::ipc::ServiceDescriptor& GetDescriptor() override;
// Methods from the .proto file
)";
static const char kHeaderProxyClass[] = R"(
class $c$Proxy : public ::perfetto::ipc::ServiceProxy {
public:
explicit $c$Proxy(::perfetto::ipc::ServiceProxy::EventListener*);
~$c$Proxy() override;
// ServiceProxy implementation.
const ::perfetto::ipc::ServiceDescriptor& GetDescriptor() override;
// Methods from the .proto file
)";
static const char kCppClassDefinitions[] = R"(
const ::perfetto::ipc::ServiceDescriptor& $c$::GetDescriptorStatic() {
static auto* instance = NewDescriptor();
return *instance;
}
// Host-side definitions.
$c$::~$c$() = default;
const ::perfetto::ipc::ServiceDescriptor& $c$::GetDescriptor() {
return GetDescriptorStatic();
}
// Client-side definitions.
$c$Proxy::$c$Proxy(::perfetto::ipc::ServiceProxy::EventListener* event_listener)
: ::perfetto::ipc::ServiceProxy(event_listener) {}
$c$Proxy::~$c$Proxy() = default;
const ::perfetto::ipc::ServiceDescriptor& $c$Proxy::GetDescriptor() {
return $c$::GetDescriptorStatic();
}
)";
static const char kCppMethodDescriptor[] = R"(
desc->methods.emplace_back(::perfetto::ipc::ServiceDescriptor::Method{
"$m$",
&_IPC_Decoder<$i$>,
&_IPC_Decoder<$o$>,
&_IPC_Invoker<$c$, $i$, $o$, &$c$::$m$>});
)";
static const char kCppMethod[] = R"(
void $c$Proxy::$m$(const $i$& request, Deferred$o$ reply, int fd) {
BeginInvoke("$m$", request, ::perfetto::ipc::DeferredBase(std::move(reply)),
fd);
}
)";
std::string StripName(const FileDescriptor& file) {
return StripSuffixString(file.name(), ".proto");
}
std::string GetStubName(const FileDescriptor& file) {
return StripName(file) + ".ipc";
}
void ForEachMethod(const ServiceDescriptor& svc,
std::function<void(const MethodDescriptor&,
const std::string&,
const std::string&)> function) {
for (int i = 0; i < svc.method_count(); i++) {
const MethodDescriptor& method = *svc.method(i);
// TODO if the input or output type are in a different namespace we need to
// emit the ::fully::qualified::name.
std::string input_type = method.input_type()->name();
std::string output_type = method.output_type()->name();
function(method, input_type, output_type);
}
}
void GenerateServiceHeader(const FileDescriptor& file,
const ServiceDescriptor& svc,
Printer* printer) {
printer->Print("\n");
std::vector<std::string> namespaces = Split(file.package(), ".");
for (const std::string& ns : namespaces)
printer->Print("namespace $ns$ {\n", "ns", ns);
// Generate the host-side declarations.
printer->Print(kHeaderSvcClass, "c", svc.name());
std::set<std::string> types_seen;
ForEachMethod(svc, [&types_seen, printer](const MethodDescriptor& method,
const std::string& input_type,
const std::string& output_type) {
if (types_seen.count(output_type) == 0) {
printer->Print(" using Deferred$o$ = ::perfetto::ipc::Deferred<$o$>;\n",
"o", output_type);
types_seen.insert(output_type);
}
printer->Print(" virtual void $m$(const $i$&, Deferred$o$) = 0;\n\n", "m",
method.name(), "i", input_type, "o", output_type);
});
printer->Print("};\n\n");
// Generate the client-side declarations.
printer->Print(kHeaderProxyClass, "c", svc.name());
types_seen.clear();
ForEachMethod(svc, [&types_seen, printer](const MethodDescriptor& method,
const std::string& input_type,
const std::string& output_type) {
if (types_seen.count(output_type) == 0) {
printer->Print(" using Deferred$o$ = ::perfetto::ipc::Deferred<$o$>;\n",
"o", output_type);
types_seen.insert(output_type);
}
printer->Print(" void $m$(const $i$&, Deferred$o$, int fd = -1);\n\n", "m",
method.name(), "i", input_type, "o", output_type);
});
printer->Print("};\n\n");
for (auto it = namespaces.rbegin(); it != namespaces.rend(); it++)
printer->Print("} // namespace $ns$\n", "ns", *it);
printer->Print("\n");
}
void GenerateServiceCpp(const FileDescriptor& file,
const ServiceDescriptor& svc,
Printer* printer) {
printer->Print("\n");
std::vector<std::string> namespaces = Split(file.package(), ".");
for (const std::string& ns : namespaces)
printer->Print("namespace $ns$ {\n", "ns", ns);
printer->Print("::perfetto::ipc::ServiceDescriptor* $c$::NewDescriptor() {\n",
"c", svc.name());
printer->Print(" auto* desc = new ::perfetto::ipc::ServiceDescriptor();\n");
printer->Print(" desc->service_name = \"$c$\";\n", "c", svc.name());
ForEachMethod(svc, [&svc, printer](const MethodDescriptor& method,
const std::string& input_type,
const std::string& output_type) {
printer->Print(kCppMethodDescriptor, "c", svc.name(), "i", input_type, "o",
output_type, "m", method.name());
});
printer->Print(" desc->methods.shrink_to_fit();\n");
printer->Print(" return desc;\n");
printer->Print("}\n\n");
printer->Print(kCppClassDefinitions, "c", svc.name());
ForEachMethod(svc, [&svc, printer](const MethodDescriptor& method,
const std::string& input_type,
const std::string& output_type) {
printer->Print(kCppMethod, "c", svc.name(), "m", method.name(), "i",
input_type, "o", output_type);
});
for (auto it = namespaces.rbegin(); it != namespaces.rend(); it++)
printer->Print("} // namespace $ns$\n", "ns", *it);
}
} // namespace
IPCGenerator::IPCGenerator() = default;
IPCGenerator::~IPCGenerator() = default;
bool IPCGenerator::Generate(const FileDescriptor* file,
const std::string& /*options*/,
GeneratorContext* context,
std::string* error) const {
if (file->options().cc_generic_services()) {
*error = "Please set \"cc_generic_service = false\".";
return false;
}
const std::unique_ptr<ZeroCopyOutputStream> h_fstream(
context->Open(GetStubName(*file) + ".h"));
const std::unique_ptr<ZeroCopyOutputStream> cc_fstream(
context->Open(GetStubName(*file) + ".cc"));
// Variables are delimited by $.
Printer h_printer(h_fstream.get(), '$');
Printer cc_printer(cc_fstream.get(), '$');
std::string guard = file->package() + "_" + file->name() + "_H_";
UpperString(&guard);
StripString(&guard, ".-/\\", '_');
h_printer.Print(kBanner);
h_printer.Print("#ifndef $guard$\n#define $guard$\n\n", "guard", guard);
h_printer.Print("#include \"$h$\"\n", "h", StripName(*file) + ".pb.h");
h_printer.Print("#include \"perfetto/ipc/deferred.h\"\n");
h_printer.Print("#include \"perfetto/ipc/service.h\"\n");
h_printer.Print("#include \"perfetto/ipc/service_descriptor.h\"\n");
h_printer.Print("#include \"perfetto/ipc/service_proxy.h\"\n\n");
cc_printer.Print(kBanner);
cc_printer.Print("#include \"$h$\"\n", "h", GetStubName(*file) + ".h");
cc_printer.Print("#include \"perfetto/ipc/codegen_helpers.h\"\n\n");
cc_printer.Print("#include <memory>\n");
for (int i = 0; i < file->service_count(); i++) {
const ServiceDescriptor* svc = file->service(i);
GenerateServiceHeader(*file, *svc, &h_printer);
GenerateServiceCpp(*file, *svc, &cc_printer);
}
h_printer.Print("#endif // $guard$\n", "guard", guard);
return true;
}
} // namespace ipc
} // namespace perfetto