2022-11-21 17:31:49 +00:00
|
|
|
// 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 <array>
|
2023-06-22 13:30:20 +00:00
|
|
|
#include <cassert>
|
2022-11-21 17:31:49 +00:00
|
|
|
#include <numeric>
|
2023-02-27 07:18:17 +00:00
|
|
|
#include <unordered_set>
|
2023-06-22 13:30:20 +00:00
|
|
|
|
2022-11-21 17:31:49 +00:00
|
|
|
#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 ¶meter,
|
|
|
|
|
GeneratorContext *generatorContext,
|
2023-06-29 11:33:09 +00:00
|
|
|
[[maybe_unused]] std::string *error) const
|
2022-11-21 17:31:49 +00:00
|
|
|
{
|
2023-04-06 14:02:29 +00:00
|
|
|
assert(file != nullptr);
|
|
|
|
|
assert(generatorContext != nullptr);
|
2022-11-21 17:31:49 +00:00
|
|
|
|
|
|
|
|
return GenerateMessages(file, generatorContext);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QProtobufGenerator::GenerateSources(const FileDescriptor *file,
|
|
|
|
|
GeneratorContext *generatorContext) const
|
|
|
|
|
{
|
2023-04-06 14:02:29 +00:00
|
|
|
assert(file != nullptr);
|
|
|
|
|
assert(generatorContext != nullptr);
|
2022-11-21 17:31:49 +00:00
|
|
|
|
|
|
|
|
std::string filename = utils::extractFileBasename(file->name());
|
|
|
|
|
std::string basename = generateBaseName(file, filename);
|
|
|
|
|
std::unique_ptr<io::ZeroCopyOutputStream> sourceStream(
|
|
|
|
|
generatorContext->Open(basename + CommonTemplates::ProtoFileSuffix() + ".cpp"));
|
|
|
|
|
std::unique_ptr<io::ZeroCopyOutputStream> registrationStream(
|
|
|
|
|
generatorContext->Open(basename + "_protobuftyperegistrations.cpp"));
|
|
|
|
|
|
|
|
|
|
std::shared_ptr<Printer> sourcePrinter(new Printer(sourceStream.get(), '$'));
|
|
|
|
|
std::shared_ptr<Printer> registrationPrinter(new Printer(registrationStream.get(), '$'));
|
|
|
|
|
|
|
|
|
|
printDisclaimer(sourcePrinter.get());
|
|
|
|
|
sourcePrinter->Print({{"include", basename + CommonTemplates::ProtoFileSuffix()}},
|
|
|
|
|
CommonTemplates::InternalIncludeTemplate());
|
|
|
|
|
|
2023-03-09 15:49:08 +00:00
|
|
|
registrationPrinter->Print({{"include", "QtProtobuf/qprotobufserializer.h"}},
|
|
|
|
|
CommonTemplates::ExternalIncludeTemplate());
|
|
|
|
|
|
2022-11-21 17:31:49 +00:00
|
|
|
registrationPrinter->Print({{"include", basename + CommonTemplates::ProtoFileSuffix()}},
|
|
|
|
|
CommonTemplates::InternalIncludeTemplate());
|
|
|
|
|
|
2023-06-30 12:03:36 +00:00
|
|
|
bool generateWellknownTimestamp = false;
|
|
|
|
|
common::iterateMessages(file, [&](const Descriptor *message) {
|
|
|
|
|
if (message->full_name() == "google.protobuf.Timestamp") {
|
|
|
|
|
generateWellknownTimestamp = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
if (generateWellknownTimestamp) {
|
|
|
|
|
sourcePrinter->Print({ { "include", "QtCore/QTimeZone" } },
|
|
|
|
|
CommonTemplates::ExternalIncludeTemplate());
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-09 15:49:08 +00:00
|
|
|
sourcePrinter->Print({{"include", "QtProtobuf/qprotobufserializer.h"}},
|
2022-11-21 17:31:49 +00:00
|
|
|
CommonTemplates::ExternalIncludeTemplate());
|
|
|
|
|
if (Options::instance().hasQml()) {
|
2023-04-06 14:02:29 +00:00
|
|
|
sourcePrinter->Print({{"include", "QtQml/qqmlengine.h"}},
|
2022-11-21 17:31:49 +00:00
|
|
|
CommonTemplates::ExternalIncludeTemplate());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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, ®istrationPrinter](const Descriptor *message) {
|
|
|
|
|
MessageDefinitionPrinter messageDef(message, sourcePrinter);
|
|
|
|
|
messageDef.printClassDefinition();
|
|
|
|
|
messageDef.printClassRegistration(registrationPrinter.get());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
CloseFileNamespaces(file, registrationPrinter.get());
|
|
|
|
|
CloseFileNamespaces(file, sourcePrinter.get());
|
|
|
|
|
|
|
|
|
|
// Include the moc file:
|
2023-04-06 14:02:29 +00:00
|
|
|
sourcePrinter->Print({{"source_file",
|
|
|
|
|
"moc_" + filename + CommonTemplates::ProtoFileSuffix() + ".cpp"}},
|
|
|
|
|
CommonTemplates::MocIncludeTemplate());
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-11 10:36:38 +00:00
|
|
|
bool QProtobufGenerator::GenerateAll(const std::vector<const FileDescriptor *> &files,
|
|
|
|
|
const std::string ¶meter, GeneratorContext *generatorContext,
|
|
|
|
|
std::string *error) const
|
2023-04-06 14:02:29 +00:00
|
|
|
{
|
2023-05-11 10:36:38 +00:00
|
|
|
assert(!files.empty());
|
|
|
|
|
assert(generatorContext != nullptr);
|
|
|
|
|
|
|
|
|
|
Options::setFromString(parameter);
|
|
|
|
|
if (Options::instance().hasQml()) {
|
|
|
|
|
std::set<std::string> headersContainer;
|
|
|
|
|
// Collect all required includes
|
|
|
|
|
std::transform(files.begin(), files.end(), std::inserter(headersContainer,
|
|
|
|
|
headersContainer.begin()),
|
|
|
|
|
[](const auto &it) {
|
|
|
|
|
std::string filename = utils::extractFileBasename(it->name());
|
|
|
|
|
return generateBaseName(it, filename);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
std::string qmlPackageUri;
|
|
|
|
|
for (const FileDescriptor *file: files) {
|
|
|
|
|
assert(file != nullptr);
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
if (file->dependency(i)->name() == "google/protobuf/any.proto") {
|
|
|
|
|
headersContainer.insert("QtProtobufWellKnownTypes/qprotobufanysupport.h");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
std::string filename = utils::extractFileBasename(file->dependency(i)->name());
|
|
|
|
|
headersContainer.insert(utils::removeFileSuffix(generateBaseName(file, filename)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
qmlPackageUri = Options::instance().qmlUri();
|
|
|
|
|
|
|
|
|
|
// Fill QML plugin openning
|
|
|
|
|
std::string pluginFileName = utils::replace(qmlPackageUri, ".", "_");
|
|
|
|
|
std::unique_ptr<io::ZeroCopyOutputStream> qmlPluginStream(
|
|
|
|
|
generatorContext->Open(pluginFileName + "plugin.cpp"));
|
|
|
|
|
std::shared_ptr<Printer> registrationPluginPrinter(new Printer(qmlPluginStream.get(),
|
|
|
|
|
'$'));
|
|
|
|
|
GenerateQmlPluginIntro(registrationPluginPrinter.get(), headersContainer, qmlPackageUri);
|
|
|
|
|
|
|
|
|
|
for (const FileDescriptor *file: files) {
|
|
|
|
|
GenerateQmlPluginSource(file, registrationPluginPrinter);
|
|
|
|
|
}
|
2023-06-22 12:23:48 +00:00
|
|
|
registrationPluginPrinter->Print(CommonTemplates::QmlRegisterProtobufTypes());
|
2023-05-11 10:36:38 +00:00
|
|
|
registrationPluginPrinter->Indent();
|
|
|
|
|
registrationPluginPrinter->Indent();
|
|
|
|
|
registrationPluginPrinter->Print(CommonTemplates::SimpleBlockEnclosureTemplate());
|
|
|
|
|
registrationPluginPrinter->Outdent();
|
|
|
|
|
registrationPluginPrinter->Outdent();
|
|
|
|
|
registrationPluginPrinter->Print(CommonTemplates::SemicolonBlockEnclosureTemplate());
|
|
|
|
|
// Include the moc file:
|
|
|
|
|
registrationPluginPrinter->Print({{"source_file", pluginFileName + "plugin.moc"}},
|
|
|
|
|
CommonTemplates::MocIncludeTemplate());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return CodeGenerator::GenerateAll(files, parameter, generatorContext, error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QProtobufGenerator::GenerateQmlPluginIntro(Printer *printer,
|
|
|
|
|
const std::set<std::string> &headersContainer,
|
|
|
|
|
const std::string &qmlPackageUri) const
|
|
|
|
|
{
|
|
|
|
|
std::vector<std::string> packageNameList = utils::split(qmlPackageUri, ".");
|
|
|
|
|
std::string pluginClassName;
|
|
|
|
|
for (std::string name: packageNameList) {
|
|
|
|
|
pluginClassName += utils::capitalizeAsciiName(name);
|
|
|
|
|
}
|
|
|
|
|
printDisclaimer(printer);
|
|
|
|
|
|
2023-06-22 12:23:48 +00:00
|
|
|
const std::array<std::string, 4> qmlHeaders = {"QtQml/qqmlextensionplugin.h",
|
2023-05-11 10:36:38 +00:00
|
|
|
"QtQml/qqml.h",
|
2023-06-22 12:23:48 +00:00
|
|
|
"QtQml/qqmlengine.h",
|
|
|
|
|
"QtProtobuf/qtprotobuftypes.h"};
|
2023-05-11 10:36:38 +00:00
|
|
|
for (const auto &header : qmlHeaders) {
|
|
|
|
|
printer->Print({{"include", header}},
|
|
|
|
|
CommonTemplates::ExternalIncludeTemplate());
|
|
|
|
|
}
|
|
|
|
|
for (std::string basename: headersContainer) {
|
|
|
|
|
printer->Print({{"include", basename + CommonTemplates::ProtoFileSuffix()}},
|
|
|
|
|
CommonTemplates::InternalIncludeTemplate());
|
|
|
|
|
}
|
2023-07-21 13:40:37 +00:00
|
|
|
printer->Print("\n");
|
|
|
|
|
printer->Print(CommonTemplates::QmlPluginExportMacroTemplate());
|
2023-07-19 17:26:47 +00:00
|
|
|
|
|
|
|
|
std::string qmlPackageEscaped = utils::escapedQmlUri(qmlPackageUri);
|
|
|
|
|
printer->Print({{"qml_package", qmlPackageUri},
|
|
|
|
|
{"qml_package_escaped", qmlPackageEscaped}},
|
|
|
|
|
CommonTemplates::QmlExtensionPluginPreamble());
|
|
|
|
|
|
2023-07-21 13:40:37 +00:00
|
|
|
printer->Print({{"plugin_name", pluginClassName}},
|
|
|
|
|
CommonTemplates::QmlExtensionPluginClass());
|
|
|
|
|
|
2023-05-11 10:36:38 +00:00
|
|
|
printer->Print({{"plugin_name", pluginClassName},
|
2023-07-19 17:26:47 +00:00
|
|
|
{"qml_package", qmlPackageUri},
|
|
|
|
|
{"qml_package_escaped", qmlPackageEscaped}},
|
2023-05-11 10:36:38 +00:00
|
|
|
CommonTemplates::QmlExtensionPluginClassBody());
|
|
|
|
|
}
|
2023-04-06 14:02:29 +00:00
|
|
|
|
2023-05-11 10:36:38 +00:00
|
|
|
void QProtobufGenerator::GenerateQmlPluginSource(const FileDescriptor *file,
|
|
|
|
|
std::shared_ptr<Printer> printer) const
|
|
|
|
|
{
|
|
|
|
|
assert(file != nullptr);
|
|
|
|
|
|
|
|
|
|
common::iterateMessages(file, [&printer](const Descriptor *message) {
|
2023-04-06 14:02:29 +00:00
|
|
|
if (message->enum_type_count() > 0) {
|
2023-05-11 10:36:38 +00:00
|
|
|
MessageDefinitionPrinter messageDefinition(message, printer);
|
2023-04-06 14:02:29 +00:00
|
|
|
messageDefinition.printQmlPluginClassRegistration();
|
|
|
|
|
}
|
|
|
|
|
});
|
2022-11-21 17:31:49 +00:00
|
|
|
|
2023-04-06 14:02:29 +00:00
|
|
|
for (int i = 0; i < file->enum_type_count(); ++i) {
|
|
|
|
|
EnumDefinitionPrinter enumSourceDefinition(file->enum_type(i),
|
2023-05-11 10:36:38 +00:00
|
|
|
printer);
|
2023-04-06 14:02:29 +00:00
|
|
|
enumSourceDefinition.printQmlPluginRegisterBody();
|
|
|
|
|
}
|
2022-11-21 17:31:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QProtobufGenerator::GenerateHeader(const FileDescriptor *file,
|
|
|
|
|
GeneratorContext *generatorContext) const
|
|
|
|
|
{
|
2023-04-06 14:02:29 +00:00
|
|
|
assert(file != nullptr);
|
|
|
|
|
assert(generatorContext != nullptr);
|
2022-11-21 17:31:49 +00:00
|
|
|
|
|
|
|
|
std::string filename = utils::extractFileBasename(file->name());
|
|
|
|
|
std::string basename = generateBaseName(file, filename);
|
|
|
|
|
std::set<std::string> internalIncludes;
|
|
|
|
|
std::set<std::string> externalIncludes;
|
|
|
|
|
|
|
|
|
|
std::unique_ptr<io::ZeroCopyOutputStream> headerStream(
|
|
|
|
|
generatorContext->Open(basename
|
|
|
|
|
+ CommonTemplates::ProtoFileSuffix() + ".h"));
|
|
|
|
|
std::shared_ptr<Printer> headerPrinter(new Printer(headerStream.get(), '$'));
|
|
|
|
|
|
|
|
|
|
printDisclaimer(headerPrinter.get());
|
2023-03-09 15:49:08 +00:00
|
|
|
|
|
|
|
|
std::string fileNameToUpper = filename;
|
|
|
|
|
std::transform(fileNameToUpper.begin(), fileNameToUpper.end(),
|
|
|
|
|
fileNameToUpper.begin(), utils::toAsciiUpper);
|
|
|
|
|
|
|
|
|
|
headerPrinter->Print({{"filename", fileNameToUpper}}, CommonTemplates::PreambleTemplate());
|
2022-11-21 17:31:49 +00:00
|
|
|
|
|
|
|
|
headerPrinter->Print(CommonTemplates::DefaultProtobufIncludesTemplate());
|
|
|
|
|
if (Options::instance().hasQml()) {
|
|
|
|
|
headerPrinter->Print(CommonTemplates::QmlProtobufIncludesTemplate());
|
|
|
|
|
}
|
|
|
|
|
|
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;
|
2022-12-15 18:05:01 +00:00
|
|
|
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(
|
2022-12-15 18:05:01 +00:00
|
|
|
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") {
|
2023-03-09 15:49:08 +00:00
|
|
|
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");
|
|
|
|
|
|
2023-03-09 15:49:08 +00:00
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
}
|
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)
|
2023-03-09 15:49:08 +00:00
|
|
|
externalIncludes.insert("QtProtobuf/qprotobufoneof.h");
|
2022-11-21 17:31:49 +00:00
|
|
|
|
2022-12-15 18:05:01 +00:00
|
|
|
for (const auto &qtTypeInclude: qtTypesSet) {
|
2023-03-09 15:49:08 +00:00
|
|
|
std::string qtTypeLower = qtTypeInclude;
|
|
|
|
|
std::transform(qtTypeLower.begin(), qtTypeLower.end(),
|
|
|
|
|
qtTypeLower.begin(), utils::toAsciiLower);
|
|
|
|
|
externalIncludes.insert("QtProtobuf" + qtTypeInclude
|
|
|
|
|
+ "Types/qtprotobuf" + qtTypeLower + "types.h");
|
2022-11-21 17:31:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < file->dependency_count(); ++i) {
|
2022-12-15 18:05:01 +00:00
|
|
|
if (file->dependency(i)->name() == "QtCore/QtCore.proto"
|
|
|
|
|
|| file->dependency(i)->name() == "QtGui/QtGui.proto") {
|
2022-11-21 17:31:49 +00:00
|
|
|
continue;
|
|
|
|
|
}
|
Add support for the google.protobuf.Any type
This also means adding another library; the Protobuf Well-Known types
library. No other types added from the collection at the moment.
The Any type is like QVariant or std::any in the sense that it can hold
any type. A small string is embedded along with the Any type upon
serializing. This corresponds to some URL, that can be used for
dynamically looking up definitions of types if wanted
(we don't support this), plus the object name.
We use the embedded object name to verify, upon attempting to
deserialize, that the objects are the same. The function is called
as<T>.
Some users may want a as_unchecked<T>, in the future, where we don't do
this, since different messages may be binary compatible but miss some
fields. And this is fine.
Further, the class also contains the raw bytes of its contained message.
We, quite simply, need to deserialize it to some type when requested.
Implementation consists of a helper-class which uses the "real class" we
generate when serializing or deserializing. At some point they could
potentially be collapsed together to avoid extra overhead. But then
the metadata fields need to be dynamically created or manually
maintained.
When asked to serialize/deserialize the class simply proxies calls to
the "real class" underneath
Fixes: QTBUG-103982
Change-Id: Ie9e833cd30aa58afbd5bdd1a9e9c83bdece6f685
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
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;
|
|
|
|
|
}
|
2022-11-21 17:31:49 +00:00
|
|
|
internalIncludes.insert(utils::removeFileSuffix(file->dependency(i)->name())
|
|
|
|
|
+ CommonTemplates::ProtoFileSuffix());
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-09 15:49:08 +00:00
|
|
|
externalIncludes.insert("QtCore/qbytearray.h");
|
|
|
|
|
externalIncludes.insert("QtCore/qstring.h");
|
|
|
|
|
|
2022-11-21 17:31:49 +00:00
|
|
|
for (const auto &include : externalIncludes) {
|
|
|
|
|
headerPrinter->Print({{"include", include}}, CommonTemplates::ExternalIncludeTemplate());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const auto &include : internalIncludes) {
|
|
|
|
|
headerPrinter->Print({{"include", include}}, CommonTemplates::InternalIncludeTemplate());
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-09 15:49:08 +00:00
|
|
|
headerPrinter->Print(CommonTemplates::DefaultQtIncludesTemplate());
|
|
|
|
|
headerPrinter->Print(CommonTemplates::DefaultSystemIncludesTemplate());
|
|
|
|
|
|
2022-11-21 17:31:49 +00:00
|
|
|
headerPrinter->PrintRaw("\n");
|
|
|
|
|
if (!Options::instance().exportMacro().empty()) {
|
|
|
|
|
headerPrinter->Print({ { "export_macro", Options::instance().exportMacro() } },
|
|
|
|
|
CommonTemplates::ExportMacroTemplate());
|
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
|
messageDef.printMetaTypesDeclaration();
|
|
|
|
|
});
|
|
|
|
|
|
2023-03-09 15:49:08 +00:00
|
|
|
headerPrinter->Print({{"filename", fileNameToUpper}}, CommonTemplates::FooterTemplate());
|
2022-11-21 17:31:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool QProtobufGenerator::GenerateMessages(const FileDescriptor *file,
|
|
|
|
|
GeneratorContext *generatorContext) const
|
|
|
|
|
{
|
2023-04-06 14:02:29 +00:00
|
|
|
assert(file != nullptr);
|
|
|
|
|
assert(generatorContext != nullptr);
|
2022-11-21 17:31:49 +00:00
|
|
|
|
|
|
|
|
if (file->message_type_count() <= 0 && file->enum_type_count() <= 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
GenerateHeader(file, generatorContext);
|
|
|
|
|
GenerateSources(file, generatorContext);
|
|
|
|
|
return true;
|
|
|
|
|
}
|