From 25ac971be4e519e9df97b18433db1a5611cd81ba Mon Sep 17 00:00:00 2001 From: deepin-ci-robot Date: Tue, 3 Dec 2024 13:32:39 +0000 Subject: [PATCH] sync: from linuxdeepin/dtkcore Synchronize source files from linuxdeepin/dtkcore. Source-pull-request: https://github.com/linuxdeepin/dtkcore/pull/448 --- cmake/DtkDConfig/DtkDConfigConfig.cmake.in | 35 +++ tools/CMakeLists.txt | 1 + tools/dconfig2cpp/CMakeLists.txt | 28 ++ tools/dconfig2cpp/main.cpp | 316 +++++++++++++++++++++ 4 files changed, 380 insertions(+) create mode 100644 tools/dconfig2cpp/CMakeLists.txt create mode 100644 tools/dconfig2cpp/main.cpp diff --git a/cmake/DtkDConfig/DtkDConfigConfig.cmake.in b/cmake/DtkDConfig/DtkDConfigConfig.cmake.in index 689c1b5..54f52c8 100644 --- a/cmake/DtkDConfig/DtkDConfigConfig.cmake.in +++ b/cmake/DtkDConfig/DtkDConfigConfig.cmake.in @@ -22,6 +22,41 @@ if(NOT DEFINED DSG_DATA_DIR) endif() add_definitions(-DDSG_DATA_DIR=\"${DSG_DATA_DIR}\") + +# Define the helper function +function(dtk_config_to_cpp JSON_FILE OUTPUT_VAR) + if(NOT EXISTS ${JSON_FILE}) + message(FATAL_ERROR "JSON file ${JSON_FILE} does not exist.") + endif() + + # Generate the output header file name + get_filename_component(FILE_NAME_WE ${JSON_FILE} NAME_WE) + if(DEFINED OUTPUT_FILE_NAME) + set(OUTPUT_HEADER "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_FILE_NAME}") + else() + set(OUTPUT_HEADER "${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME_WE}.hpp") + endif() + + # Check if CLASS_NAME is set + if(DEFINED CLASS_NAME) + set(CLASS_NAME_ARG -c ${CLASS_NAME}) + else() + set(CLASS_NAME_ARG "") + endif() + + # Add a custom command to run dconfig2cpp + add_custom_command( + OUTPUT ${OUTPUT_HEADER} + COMMAND ${TOOL_INSTALL_DIR}/dconfig2cpp -o ${OUTPUT_HEADER} ${CLASS_NAME_ARG} ${JSON_FILE} + DEPENDS ${JSON_FILE} + COMMENT "Generating ${OUTPUT_HEADER} from ${JSON_FILE}" + VERBATIM + ) + + # Add the generated header to the specified output variable + set(${OUTPUT_VAR} ${${OUTPUT_VAR}} ${OUTPUT_HEADER} PARENT_SCOPE) +endfunction() + # deploy some `meta` 's configure. # # FILES - deployed files. diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index f3ef2a5..63b8fc9 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -3,3 +3,4 @@ add_subdirectory(deepin-os-release) add_subdirectory(qdbusxml2cpp) add_subdirectory(settings) add_subdirectory(ch2py) +add_subdirectory(dconfig2cpp) diff --git a/tools/dconfig2cpp/CMakeLists.txt b/tools/dconfig2cpp/CMakeLists.txt new file mode 100644 index 0000000..6f5f8f9 --- /dev/null +++ b/tools/dconfig2cpp/CMakeLists.txt @@ -0,0 +1,28 @@ +set(TARGET_NAME dconfig2cpp) +set(BIN_NAME ${TARGET_NAME}${DTK_VERSION_MAJOR}) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core) +find_package(Dtk REQUIRED COMPONENTS Core) + +add_executable(${BIN_NAME} + main.cpp +) + +target_link_libraries( + ${BIN_NAME} PRIVATE + Qt${QT_VERSION_MAJOR}::Core + Dtk::Core +) + +set_target_properties( + ${BIN_NAME} PROPERTIES + OUTPUT_NAME ${TARGET_NAME} + EXPORT_NAME dconfig2cpp +) + +install( + TARGETS ${BIN_NAME} + EXPORT Dtk${DTK_VERSION_MAJOR}ToolsTargets + DESTINATION ${TOOL_INSTALL_DIR} +) \ No newline at end of file diff --git a/tools/dconfig2cpp/main.cpp b/tools/dconfig2cpp/main.cpp new file mode 100644 index 0000000..450b3a9 --- /dev/null +++ b/tools/dconfig2cpp/main.cpp @@ -0,0 +1,316 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static QString jsonValueToCppCode(const QJsonValue &value){ + if (value.isBool()) { + return value.toBool() ? "true" : "false"; + } else if (value.isDouble()) { + return QString::number(value.toDouble()); + } else if (value.isString()) { + return QString("QStringLiteral(\"%1\")").arg(value.toString()); + } else if (value.isNull()) { + return "QVariant::fromValue(nullptr)"; + } else if (value.isArray()) { + QStringList elements; + const auto array = value.toArray(); + for (const QJsonValue &element : array) { + elements << "QVariant(" + jsonValueToCppCode(element) + ")"; + } + return "QList{" + elements.join(", ") + "}"; + } else if (value.isObject()) { + QStringList elements; + QJsonObject obj = value.toObject(); + for (auto it = obj.begin(); it != obj.end(); ++it) { + elements << QString("{QStringLiteral(\"%1\"), QVariant(%2)}").arg(it.key(), jsonValueToCppCode(it.value())); + } + return "QVariantMap{" + elements.join(", ") + "}"; + } else { + return "QVariant()"; + } +} + +namespace DTK_CORE_NAMESPACE { +class DConfig; +} + +int main(int argc, char *argv[]) { + QCoreApplication app(argc, argv); + QCommandLineParser parser; + parser.setApplicationDescription(QStringLiteral("DConfig to C++ class generator")); + parser.addHelpOption(); + + QCommandLineOption classNameOption(QStringList() << QStringLiteral("c") << QStringLiteral("class-name"), + QStringLiteral("Name of the generated class"), + QStringLiteral("className")); + parser.addOption(classNameOption); + + QCommandLineOption sourceFileOption(QStringList() << QStringLiteral("o") << QStringLiteral("output"), + QStringLiteral("Path to the output source(header only) file"), + QStringLiteral("sourceFile")); + parser.addOption(sourceFileOption); + + parser.addPositionalArgument(QStringLiteral("json-file"), QStringLiteral("Path to the input JSON file")); + parser.process(app); + + const QStringList args = parser.positionalArguments(); + if (args.size() != 1) { + parser.showHelp(-1); + } + + QString className = parser.value(classNameOption); + if (className.isEmpty()) { + QString jsonFileName = QFileInfo(args.first()).completeBaseName(); + className = jsonFileName.replace('.', '_'); + } + + QString sourceFilePath = parser.value(sourceFileOption); + if (sourceFilePath.isEmpty()) { + sourceFilePath = className.toLower() + QStringLiteral(".hpp"); + } + + QFile file(args.first()); + if (!file.open(QIODevice::ReadOnly)) { + qWarning() << QStringLiteral("Failed to open file:") << args.first(); + return -1; + } + + QByteArray data = file.readAll(); + QJsonDocument doc = QJsonDocument::fromJson(data); + QJsonObject root = doc.object(); + + // Check magic value + if (root[QStringLiteral("magic")].toString() != QStringLiteral("dsg.config.meta")) { + qWarning() << QStringLiteral("Invalid magic value in JSON file"); + return -1; + } + + // Generate header and source files + QFile headerFile(sourceFilePath); + if (!headerFile.open(QIODevice::WriteOnly | QIODevice::Text)) { + qWarning() << QStringLiteral("Failed to open file for writing:") << sourceFilePath; + return -1; + } + + QTextStream headerStream(&headerFile); + + // Extract version and add it as a comment in the generated code + QString version = root[QStringLiteral("version")].toString(); + + // Generate header and source file comments + QString commandLineArgs = QCoreApplication::arguments().join(QStringLiteral(" ")); + QString generationTime = QDateTime::currentDateTime().toString(Qt::ISODate); + + QString headerComment = QString( + QStringLiteral("/**\n" + " * This file is generated by dconfig2cpp.\n" + " * Command line arguments: %1\n" + " * Generation time: %2\n" + " * JSON file version: %3\n" + " * \n" + " * WARNING: DO NOT MODIFY THIS FILE MANUALLY.\n" + " * If you need to change the content, please modify the dconfig2cpp tool.\n" + " */\n\n") + ).arg(commandLineArgs, generationTime, version); + + headerStream << headerComment; + QJsonObject contents = root[QStringLiteral("contents")].toObject(); + + // Write header file content + headerStream << "#ifndef " << className.toUpper() << "_H\n"; + headerStream << "#define " << className.toUpper() << "_H\n\n"; + headerStream << "#include \n"; + headerStream << "#include \n"; + headerStream << "#include \n\n"; + headerStream << "class " << className << " : public QObject {\n"; + headerStream << " Q_OBJECT\n\n"; + + struct Property { + QString typeName; + QString propertyName; + QString capitalizedPropertyName; + QJsonValue defaultValue; + }; + QList properties; + + for (auto it = contents.begin(); it != contents.end(); ++it) { + QJsonObject obj = it.value().toObject(); + QString propertyName = it.key(); + QString typeName; + if (obj[QStringLiteral("value")].type() == QJsonValue::Bool) { + typeName = "bool"; + } else if (obj[QStringLiteral("value")].type() == QJsonValue::Array) { + typeName = "QList"; + } else if (obj[QStringLiteral("value")].type() == QJsonValue::Object) { + typeName = "QVariantMap"; + } else if (obj[QStringLiteral("value")].type() == QJsonValue::Double) { + typeName = "double"; + } else if (obj[QStringLiteral("value")].type() == QJsonValue::String) { + typeName = "QString"; + } else { + typeName = "QVariant"; + } + + QString capitalizedPropertyName = propertyName; + if (!capitalizedPropertyName.isEmpty() && capitalizedPropertyName[0].isLower()) { + capitalizedPropertyName[0] = capitalizedPropertyName[0].toUpper(); + } + + properties.append(Property({ + typeName, + propertyName, + capitalizedPropertyName, + obj[QStringLiteral("value")] + })); + + headerStream << " Q_PROPERTY(" << typeName << " " << propertyName << " READ " << propertyName + << " WRITE set" << capitalizedPropertyName << " NOTIFY " << propertyName << "Changed)\n"; + } + headerStream << "public:\n"; + headerStream << " explicit " << className << R"((QThread *thread, const QString &appId, const QString &name, const QString &subpath, QObject *parent = nullptr) + : QObject(parent) { + if (!thread->isRunning()) { + qWarning() << QStringLiteral("Warning: The provided thread is not running."); + } + Q_ASSERT(QThread::currentThread() != thread); + auto worker = new QObject(); + worker->moveToThread(thread); + QMetaObject::invokeMethod(worker, [this, worker, appId, name, subpath]() { + auto config = DTK_CORE_NAMESPACE::DConfig::create(appId, name, subpath, nullptr); + if (!config) { + qWarning() << QStringLiteral("Failed to create DConfig instance."); + worker->deleteLater(); + return; + } + config->moveToThread(QThread::currentThread()); + initialize(config); + worker->deleteLater(); + }, Qt::QueuedConnection); + } + explicit )" << className << R"((QThread *thread, DTK_CORE_NAMESPACE::DConfigBackend *backend, const QString &appId, const QString &name, const QString &subpath, + QObject *parent = nullptr) + : QObject(parent) { + if (!thread->isRunning()) { + qWarning() << QStringLiteral("Warning: The provided thread is not running."); + } + Q_ASSERT(QThread::currentThread() != thread); + auto worker = new QObject(); + worker->moveToThread(thread); + QMetaObject::invokeMethod(worker, [this, worker, backend, appId, name, subpath]() { + auto config = DTK_CORE_NAMESPACE::DConfig::create(backend, appId, name, subpath, nullptr); + if (!config) { + qWarning() << QStringLiteral("Failed to create DConfig instance."); + worker->deleteLater(); + return; + } + config->moveToThread(QThread::currentThread()); + initialize(config); + worker->deleteLater(); + }, Qt::QueuedConnection); + } + explicit )" << className << R"((QThread *thread, const QString &name, const QString &subpath, + QObject *parent = nullptr) + : QObject(parent) { + if (!thread->isRunning()) { + qWarning() << QStringLiteral("Warning: The provided thread is not running."); + } + Q_ASSERT(QThread::currentThread() != thread); + auto worker = new QObject(); + worker->moveToThread(thread); + QMetaObject::invokeMethod(worker, [this, worker, name, subpath]() { + auto config = DTK_CORE_NAMESPACE::DConfig::createGeneric(name, subpath, nullptr); + if (!config) { + qWarning() << QStringLiteral("Failed to create DConfig instance."); + worker->deleteLater(); + return; + } + config->moveToThread(QThread::currentThread()); + initialize(config); + worker->deleteLater(); + }, Qt::QueuedConnection); + } + explicit )" << className << R"((QThread *thread, DTK_CORE_NAMESPACE::DConfigBackend *backend, const QString &name, const QString &subpath = QString(), + QObject *parent = nullptr) + : QObject(parent) { + if (!thread->isRunning()) { + qWarning() << QStringLiteral("Warning: The provided thread is not running."); + } + Q_ASSERT(QThread::currentThread() != thread); + auto worker = new QObject(); + worker->moveToThread(thread); + QMetaObject::invokeMethod(worker, [this, worker, backend, name, subpath]() { + auto config = DTK_CORE_NAMESPACE::DConfig::createGeneric(backend, name, subpath, nullptr); + if (!config) { + qWarning() << QStringLiteral("Failed to create DConfig instance."); + worker->deleteLater(); + return; + } + config->moveToThread(QThread::currentThread()); + initialize(config); + worker->deleteLater(); + }, Qt::QueuedConnection); + } + + )"; + + for (const Property &property : std::as_const(properties)) { + headerStream << " " << property.typeName << " " << property.propertyName << "() const {\n" + << " return p_" << property.propertyName << ";\n }\n"; + headerStream << " void set" << property.capitalizedPropertyName << "(const " << property.typeName << " &value) {\n" + << " m_config->setValue(QStringLiteral(\"" << property.propertyName << "\"), value);\n }\n"; + } + headerStream << "Q_SIGNALS:\n"; + for (const Property &property : std::as_const(properties)) { + headerStream << " void " << property.propertyName << "Changed();\n"; + } + headerStream << "private:\n"; + headerStream << " void initialize(DTK_CORE_NAMESPACE::DConfig *config) {\n"; + headerStream << " m_config.reset(config);\n"; + for (const Property &property : std::as_const(properties)) { + headerStream << " {\n"; + headerStream << " QVariant variantValue = m_config->value(QStringLiteral(\"" << property.propertyName << "\"), QVariant::fromValue(p_" << property.propertyName << "));\n"; + headerStream << " auto newValue = qvariant_cast<" << property.typeName << ">(variantValue);\n"; + headerStream << " QMetaObject::invokeMethod(this, [this, newValue]() {\n"; + headerStream << " if (p_" << property.propertyName << " != newValue) {\n"; + headerStream << " p_" << property.propertyName << " = newValue;\n"; + headerStream << " Q_EMIT " << property.propertyName << "Changed();\n"; + headerStream << " }\n"; + headerStream << " });\n"; + headerStream << " }\n"; + } + headerStream << " connect(config, &DTK_CORE_NAMESPACE::DConfig::valueChanged, this, [this](const QString &key) {\n"; + headerStream << " const QVariant &value = m_config->value(key);\n"; + for (const Property &property : std::as_const(properties)) { + headerStream << " if (key == QStringLiteral(\"" << property.propertyName << "\")) {\n"; + headerStream << " auto newValue = qvariant_cast<" << property.typeName << ">(value);\n"; + headerStream << " QMetaObject::invokeMethod(this, [this, newValue]() {\n"; + headerStream << " if (p_" << property.propertyName << " != newValue) {\n"; + headerStream << " p_" << property.propertyName << " = newValue;\n"; + headerStream << " Q_EMIT " << property.propertyName << "Changed();\n"; + headerStream << " }\n"; + headerStream << " });\n"; + headerStream << " }\n"; + } + headerStream << " });\n"; + headerStream << " }\n"; + headerStream << " std::unique_ptr m_config;\n\n"; + + for (const Property &property : std::as_const(properties)) { + headerStream << " " << property.typeName << " p_" << property.propertyName << " { "; + headerStream << jsonValueToCppCode(property.defaultValue) << " };\n"; + } + headerStream << "};\n\n"; + headerStream << "#endif // " << className.toUpper() << "_H\n"; + + return 0; +}