qtgrpc/src/tools/qtprotobufgen/qprotobufgenerator.cpp

252 lines
10 KiB
C++
Raw Normal View History

// Copyright (C) 2022 The Qt Company Ltd.
// Copyright (C) 2019 Alexey Edelev <semlanik@gmail.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "qprotobufgenerator.h"
#include "enumdeclarationprinter.h"
#include "enumdefinitionprinter.h"
#include "messagedeclarationprinter.h"
#include "messagedefinitionprinter.h"
#include "commontemplates.h"
#include "utils.h"
#include "options.h"
#include <cassert>
#include <unordered_set>
#include <google/protobuf/stubs/common.h>
#include <google/protobuf/io/printer.h>
#include <google/protobuf/io/zero_copy_stream.h>
#include <google/protobuf/descriptor.h>
using namespace ::QtProtobuf;
using namespace ::qtprotoccommon;
using namespace ::google::protobuf;
using namespace ::google::protobuf::io;
using namespace ::google::protobuf::compiler;
QProtobufGenerator::QProtobufGenerator() : GeneratorBase()
{}
QProtobufGenerator::~QProtobufGenerator() = default;
bool QProtobufGenerator::Generate(const FileDescriptor *file,
[[maybe_unused]] const std::string &parameter,
GeneratorContext *generatorContext,
[[maybe_unused]] std::string *error) const
{
assert(file != nullptr);
assert(generatorContext != nullptr);
return GenerateMessages(file, generatorContext);
}
void QProtobufGenerator::GenerateSources(const FileDescriptor *file,
GeneratorContext *generatorContext) const
{
assert(file != nullptr);
assert(generatorContext != nullptr);
std::string basename = utils::extractFileBasename(file->name());
std::string relativePath = common::generateRelativeFilePath(file, basename);
std::unique_ptr<io::ZeroCopyOutputStream> sourceStream(
generatorContext->Open(relativePath + CommonTemplates::ProtoFileSuffix() + ".cpp"));
std::unique_ptr<io::ZeroCopyOutputStream> registrationStream(
generatorContext->Open(relativePath + "_protobuftyperegistrations.cpp"));
std::shared_ptr<Printer> sourcePrinter(new Printer(sourceStream.get(), '$'));
std::shared_ptr<Printer> registrationPrinter(new Printer(registrationStream.get(), '$'));
printDisclaimer(sourcePrinter.get());
utils::ExternalIncludesOrderedSet externalIncludes{ "QtProtobuf/qprotobufregistration.h" };
std::set<std::string> internalIncludes{ relativePath + CommonTemplates::ProtoFileSuffix()
+ CommonTemplates::HeaderSuffix() };
printIncludes(registrationPrinter.get(), internalIncludes, externalIncludes, {});
bool generateWellknownTimestamp = false;
common::iterateMessages(file, [&](const Descriptor *message) {
if (message->full_name() == "google.protobuf.Timestamp") {
generateWellknownTimestamp = true;
return;
}
});
if (generateWellknownTimestamp)
externalIncludes.insert("QtCore/QTimeZone");
printIncludes(sourcePrinter.get(), internalIncludes, externalIncludes, { "cmath" });
OpenFileNamespaces(file, sourcePrinter.get());
OpenFileNamespaces(file, registrationPrinter.get());
for (int i = 0; i < file->enum_type_count(); ++i) {
EnumDefinitionPrinter enumSourceDef(file->enum_type(i), sourcePrinter);
enumSourceDef.run();
}
common::iterateMessages(
file,
[&sourcePrinter, &registrationPrinter](const Descriptor *message) {
MessageDefinitionPrinter messageDef(message, sourcePrinter);
messageDef.printClassDefinition();
messageDef.printClassRegistration(registrationPrinter.get());
});
registrationPrinter->Print({{"proto_name", utils::capitalizeAsciiName(basename)}},
CommonTemplates::ProtobufTypeRegistrarTemplate());
CloseFileNamespaces(file, registrationPrinter.get());
CloseFileNamespaces(file, sourcePrinter.get());
// Include the moc file:
sourcePrinter->Print({{"source_file",
"moc_" + basename + CommonTemplates::ProtoFileSuffix() + ".cpp"}},
CommonTemplates::MocIncludeTemplate());
}
void QProtobufGenerator::GenerateHeader(const FileDescriptor *file,
GeneratorContext *generatorContext) const
{
assert(file != nullptr);
assert(generatorContext != nullptr);
const std::string basename = utils::extractFileBasename(file->name()) +
CommonTemplates::ProtoFileSuffix();
std::string relativePath = common::generateRelativeFilePath(file, basename);
std::unique_ptr<io::ZeroCopyOutputStream>
headerStream(generatorContext->Open(relativePath + CommonTemplates::HeaderSuffix()));
std::shared_ptr<Printer> headerPrinter(new Printer(headerStream.get(), '$'));
printDisclaimer(headerPrinter.get());
std::set<std::string> internalIncludes;
utils::ExternalIncludesOrderedSet externalIncludes;
std::set<std::string> systemIncludes;
const std::string
headerGuard = common::headerGuardFromFilename(basename + CommonTemplates::HeaderSuffix());
QProtobufGenerator::printHeaderGuardBegin(headerPrinter.get(), headerGuard);
Fix the generation of the export macro Consider the EXPORT_MACRO CMake argument of qt_add_<protobuf|grpc> calls. Add the support for the EXPORT_MACRO option extras to the qt<protobuf|grpc>gen generators. The extras now allow setting: - export file name - boolean flag that indicates if export file needs to be generated The EXPORT_MACRO option of the generators now has the following format: EXPORT_MACRO=<export_name>[:export_filename[:<true|false>]] If export_filename is not set, then generators fall back to the previos behavior and use the export_name as the export filename base, the file will be generated unconditionally. If export_filename is set and the follow boolean flag is not set or is set to false, generators skip the generating of the export file. [ChangeLog][Protobuf][qtprotobufgen] EXPORT_MACRO option now has the following format: EXPORT_MACRO=<export_name>[:export_filename[:<true|false>]] New option extras allow setting the generated export filename and control if it should be generated at the generator run. [ChangeLog][GRPC][qtgrpcgen] EXPORT_MACRO option now has the following format: EXPORT_MACRO=<export_name>[:export_filename[:<true|false>]] New option extras allow setting the generated export filename and control if it should be generated at the generator run. Pick-to: 6.7 Fixes: QTBUG-121854 Change-Id: Ifff6506ab363d18dc417f222e9929d7eba135d8a Reviewed-by: Tatiana Borisova <tatiana.borisova@qt.io> Reviewed-by: Alexey Edelev <alexey.edelev@qt.io> Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
2024-02-03 10:25:33 +00:00
if (!Options::instance().exportMacroFilename().empty()) {
std::string exportMacroFilename = Options::instance().exportMacroFilename();
internalIncludes.insert(exportMacroFilename);
}
externalIncludes.insert("QtCore/qbytearray.h");
externalIncludes.insert("QtCore/qlist.h");
externalIncludes.insert("QtCore/qmetatype.h");
externalIncludes.insert("QtCore/qshareddata.h");
externalIncludes.insert("QtCore/qstring.h");
externalIncludes.insert("QtProtobuf/qprotobuflazymessagepointer.h");
externalIncludes.insert("QtProtobuf/qprotobufmessage.h");
externalIncludes.insert("QtProtobuf/qprotobufobject.h");
externalIncludes.insert("QtProtobuf/qtprotobuftypes.h");
if (Options::instance().hasQml()) {
externalIncludes.insert("QtQml/qqmlregistration.h");
externalIncludes.insert("QtQml/qqmllist.h");
}
Add support for the 'oneof' type This patch adds support for 'oneof', a union-like type in protobuf, that need not contain a value, with the following features: - Setting a oneof field will automatically clear all other members of the oneof. - If the parser encounters multiple members of the same oneof on the wire, only the last member seen is used in the parsed message. - A oneof cannot be repeated. - A oneof cannot contain repeated or map fields. - If you set a oneof field to the default value (even the default, such as 0 for an int32 oneof field), the "case" of that oneof field will be set, and the value will be serialized on the wire. Unlike the reference C++ implementation, the Qt implementation of 'oneof' fields adds the 'has<PropertyName>' functions for every 'oneof' member. The protobuf generator generates a Q_PROPERTY for each member of the 'oneof'; these properties are collectively implemented as a single class member. 'oneof' fields are represented by the QtProtobufPrivate::QProtobufOptional class. The class holds the 'oneof' field number and the value that it contains as QVariant. A QExplicitSharedDataPointer is used as reference counter, and to copy and to free the memory of protobuf messages that are stored inside the nested QVariant as pointers. The class could also be used to hold 'optional' fields in follow up commits, but has small overhead since it holds field number, that is not used by optional fields. The generated classes also allows reading field number that is currently stored in the 'oneof' field. The QtProtobuf::InvalidFieldNumber constant indicates that oneof fields is uninitialized. The serialization and deserialization tests use the expected values from the results of the reference C++ serialization(protobuf version 3.19). Task-number: QTBUG-103981 Change-Id: Ie4053cb164bba6bc5f14f8cedb34bad62a638c43 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
2022-12-27 14:23:14 +00:00
bool hasOneofFields = false;
bool hasOptionalFields = false;
std::unordered_set<std::string> qtTypesSet;
Add support for the 'oneof' type This patch adds support for 'oneof', a union-like type in protobuf, that need not contain a value, with the following features: - Setting a oneof field will automatically clear all other members of the oneof. - If the parser encounters multiple members of the same oneof on the wire, only the last member seen is used in the parsed message. - A oneof cannot be repeated. - A oneof cannot contain repeated or map fields. - If you set a oneof field to the default value (even the default, such as 0 for an int32 oneof field), the "case" of that oneof field will be set, and the value will be serialized on the wire. Unlike the reference C++ implementation, the Qt implementation of 'oneof' fields adds the 'has<PropertyName>' functions for every 'oneof' member. The protobuf generator generates a Q_PROPERTY for each member of the 'oneof'; these properties are collectively implemented as a single class member. 'oneof' fields are represented by the QtProtobufPrivate::QProtobufOptional class. The class holds the 'oneof' field number and the value that it contains as QVariant. A QExplicitSharedDataPointer is used as reference counter, and to copy and to free the memory of protobuf messages that are stored inside the nested QVariant as pointers. The class could also be used to hold 'optional' fields in follow up commits, but has small overhead since it holds field number, that is not used by optional fields. The generated classes also allows reading field number that is currently stored in the 'oneof' field. The QtProtobuf::InvalidFieldNumber constant indicates that oneof fields is uninitialized. The serialization and deserialization tests use the expected values from the results of the reference C++ serialization(protobuf version 3.19). Task-number: QTBUG-103981 Change-Id: Ie4053cb164bba6bc5f14f8cedb34bad62a638c43 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
2022-12-27 14:23:14 +00:00
common::iterateMessages(
file, [&](const Descriptor *message) {
Add support for the 'oneof' type This patch adds support for 'oneof', a union-like type in protobuf, that need not contain a value, with the following features: - Setting a oneof field will automatically clear all other members of the oneof. - If the parser encounters multiple members of the same oneof on the wire, only the last member seen is used in the parsed message. - A oneof cannot be repeated. - A oneof cannot contain repeated or map fields. - If you set a oneof field to the default value (even the default, such as 0 for an int32 oneof field), the "case" of that oneof field will be set, and the value will be serialized on the wire. Unlike the reference C++ implementation, the Qt implementation of 'oneof' fields adds the 'has<PropertyName>' functions for every 'oneof' member. The protobuf generator generates a Q_PROPERTY for each member of the 'oneof'; these properties are collectively implemented as a single class member. 'oneof' fields are represented by the QtProtobufPrivate::QProtobufOptional class. The class holds the 'oneof' field number and the value that it contains as QVariant. A QExplicitSharedDataPointer is used as reference counter, and to copy and to free the memory of protobuf messages that are stored inside the nested QVariant as pointers. The class could also be used to hold 'optional' fields in follow up commits, but has small overhead since it holds field number, that is not used by optional fields. The generated classes also allows reading field number that is currently stored in the 'oneof' field. The QtProtobuf::InvalidFieldNumber constant indicates that oneof fields is uninitialized. The serialization and deserialization tests use the expected values from the results of the reference C++ serialization(protobuf version 3.19). Task-number: QTBUG-103981 Change-Id: Ie4053cb164bba6bc5f14f8cedb34bad62a638c43 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
2022-12-27 14:23:14 +00:00
if (message->oneof_decl_count() > 0)
hasOneofFields = true;
if (message->full_name() == "google.protobuf.Timestamp") {
externalIncludes.insert("QtCore/QDateTime");
Add support for the 'oneof' type This patch adds support for 'oneof', a union-like type in protobuf, that need not contain a value, with the following features: - Setting a oneof field will automatically clear all other members of the oneof. - If the parser encounters multiple members of the same oneof on the wire, only the last member seen is used in the parsed message. - A oneof cannot be repeated. - A oneof cannot contain repeated or map fields. - If you set a oneof field to the default value (even the default, such as 0 for an int32 oneof field), the "case" of that oneof field will be set, and the value will be serialized on the wire. Unlike the reference C++ implementation, the Qt implementation of 'oneof' fields adds the 'has<PropertyName>' functions for every 'oneof' member. The protobuf generator generates a Q_PROPERTY for each member of the 'oneof'; these properties are collectively implemented as a single class member. 'oneof' fields are represented by the QtProtobufPrivate::QProtobufOptional class. The class holds the 'oneof' field number and the value that it contains as QVariant. A QExplicitSharedDataPointer is used as reference counter, and to copy and to free the memory of protobuf messages that are stored inside the nested QVariant as pointers. The class could also be used to hold 'optional' fields in follow up commits, but has small overhead since it holds field number, that is not used by optional fields. The generated classes also allows reading field number that is currently stored in the 'oneof' field. The QtProtobuf::InvalidFieldNumber constant indicates that oneof fields is uninitialized. The serialization and deserialization tests use the expected values from the results of the reference C++ serialization(protobuf version 3.19). Task-number: QTBUG-103981 Change-Id: Ie4053cb164bba6bc5f14f8cedb34bad62a638c43 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
2022-12-27 14:23:14 +00:00
}
if (message->full_name() == "google.protobuf.Any")
externalIncludes.insert("QtProtobufWellKnownTypes/qprotobufanysupport.h");
for (int i = 0; i < message->field_count(); ++i) {
const auto *field = message->field(i);
if (field->type() == FieldDescriptor::TYPE_MESSAGE && !field->is_map()
&& !field->is_repeated() && common::isQtType(field)) {
externalIncludes.insert(field->message_type()->file()->package()
+ "/" + field->message_type()->name());
qtTypesSet.insert(field->message_type()->file()->package());
}
if (common::isOptionalField(field))
hasOptionalFields = true;
}
Add support for the 'oneof' type This patch adds support for 'oneof', a union-like type in protobuf, that need not contain a value, with the following features: - Setting a oneof field will automatically clear all other members of the oneof. - If the parser encounters multiple members of the same oneof on the wire, only the last member seen is used in the parsed message. - A oneof cannot be repeated. - A oneof cannot contain repeated or map fields. - If you set a oneof field to the default value (even the default, such as 0 for an int32 oneof field), the "case" of that oneof field will be set, and the value will be serialized on the wire. Unlike the reference C++ implementation, the Qt implementation of 'oneof' fields adds the 'has<PropertyName>' functions for every 'oneof' member. The protobuf generator generates a Q_PROPERTY for each member of the 'oneof'; these properties are collectively implemented as a single class member. 'oneof' fields are represented by the QtProtobufPrivate::QProtobufOptional class. The class holds the 'oneof' field number and the value that it contains as QVariant. A QExplicitSharedDataPointer is used as reference counter, and to copy and to free the memory of protobuf messages that are stored inside the nested QVariant as pointers. The class could also be used to hold 'optional' fields in follow up commits, but has small overhead since it holds field number, that is not used by optional fields. The generated classes also allows reading field number that is currently stored in the 'oneof' field. The QtProtobuf::InvalidFieldNumber constant indicates that oneof fields is uninitialized. The serialization and deserialization tests use the expected values from the results of the reference C++ serialization(protobuf version 3.19). Task-number: QTBUG-103981 Change-Id: Ie4053cb164bba6bc5f14f8cedb34bad62a638c43 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
2022-12-27 14:23:14 +00:00
});
if (hasOneofFields)
externalIncludes.insert("QtProtobuf/qprotobufoneof.h");
if (hasOptionalFields)
systemIncludes.insert("optional");
for (const auto &qtTypeInclude: qtTypesSet) {
std::string qtTypeLower = qtTypeInclude;
std::transform(qtTypeLower.begin(), qtTypeLower.end(),
qtTypeLower.begin(), utils::toAsciiLower);
externalIncludes.insert("QtProtobuf" + qtTypeInclude
+ "Types/qtprotobuf" + qtTypeLower + "types.h");
}
for (int i = 0; i < file->dependency_count(); ++i) {
if (file->dependency(i)->name() == "QtCore/QtCore.proto"
|| file->dependency(i)->name() == "QtGui/QtGui.proto") {
continue;
}
2022-10-05 17:11:22 +00:00
// Override the any.proto include with our own specific support
if (file->dependency(i)->name() == "google/protobuf/any.proto") {
externalIncludes.insert("QtProtobufWellKnownTypes/qprotobufanysupport.h");
continue;
}
internalIncludes.insert(utils::removeFileSuffix(file->dependency(i)->name())
+ CommonTemplates::ProtoFileSuffix()
+ CommonTemplates::HeaderSuffix());
}
printIncludes(headerPrinter.get(), internalIncludes, externalIncludes, systemIncludes);
OpenFileNamespaces(file, headerPrinter.get());
for (int i = 0; i < file->enum_type_count(); ++i) {
EnumDeclarationPrinter enumDecl(file->enum_type(i), headerPrinter);
enumDecl.run();
}
common::iterateMessages(file, [&headerPrinter](const Descriptor *message) {
MessageDeclarationPrinter messageDecl(message, headerPrinter);
messageDecl.printClassForwardDeclaration();
});
common::iterateMessages(
file,
[&headerPrinter](const Descriptor *message) {
MessageDeclarationPrinter messageDecl(message, headerPrinter);
messageDecl.printClassDeclaration();
});
CloseFileNamespaces(file, headerPrinter.get());
common::iterateMessages(file, [&headerPrinter](const Descriptor *message) {
MessageDeclarationPrinter messageDef(message, headerPrinter);
});
QProtobufGenerator::printHeaderGuardEnd(headerPrinter.get(), headerGuard);
}
bool QProtobufGenerator::GenerateMessages(const FileDescriptor *file,
GeneratorContext *generatorContext) const
{
assert(file != nullptr);
assert(generatorContext != nullptr);
if (file->message_type_count() <= 0 && file->enum_type_count() <= 0)
return true;
GenerateHeader(file, generatorContext);
GenerateSources(file, generatorContext);
return true;
}