Skip to content

Commit

Permalink
WIP: Support auto generate the Qt code for a dconfig's json
Browse files Browse the repository at this point in the history
  • Loading branch information
zccrs committed Dec 3, 2024
1 parent fc0280e commit 8177083
Show file tree
Hide file tree
Showing 4 changed files with 380 additions and 0 deletions.
35 changes: 35 additions & 0 deletions cmake/DtkDConfig/DtkDConfigConfig.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions tools/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ add_subdirectory(deepin-os-release)
add_subdirectory(qdbusxml2cpp)
add_subdirectory(settings)
add_subdirectory(ch2py)
add_subdirectory(dconfig2cpp)
28 changes: 28 additions & 0 deletions tools/dconfig2cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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}
)
316 changes: 316 additions & 0 deletions tools/dconfig2cpp/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#include <QCoreApplication>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QJsonArray>
#include <QFile>
#include <QDebug>
#include <QCommandLineParser>
#include <QFileInfo>

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<QVariant>{" + 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 <QThread>\n";
headerStream << "#include <QVariant>\n";
headerStream << "#include <DConfig>\n\n";
headerStream << "class " << className << " : public QObject {\n";
headerStream << " Q_OBJECT\n\n";

struct Property {
QString typeName;
QString propertyName;
QString capitalizedPropertyName;
QJsonValue defaultValue;
};
QList<Property> 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<QVariant>";
} 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<DTK_CORE_NAMESPACE::DConfig> 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;
}

0 comments on commit 8177083

Please sign in to comment.