From 01d84ffc74d4328240e4f242f35f34c2164dbbca Mon Sep 17 00:00:00 2001 From: Sami Shalayel Date: Thu, 22 Dec 2022 17:24:16 +0100 Subject: qmltc: export generated classes Add QMLTC_EXPORT_MACRO_NAME and QMLTC_EXPORT_FILE_NAME arguments to qt_add_qml_module() that allows the user to export qmltc-generated code from its library. The qmltc-generated code will include the header-file specified in QMLTC_EXPORT_FILE_NAME and will be exported with the macro specified by QMLTC_EXPORT_MACRO_NAME. Leave both options unspecified to not export the code generated by qmltc. Describe the options in the documentation and write a test to see if the class really has an export macro in the generated code: 1) tst_qmltc_qprocess will test if the macro and the header are correctly inserted in the generated code. 2) tst_qmltc_{no,}diskcache will test if the generated code can still be used from a static library. Fixes: QTBUG-106840 Task-number: QTBUG-96040 Change-Id: I554f03bcdf043e8114e42f51a7289a5c00de4f89 Reviewed-by: Ulf Hermann --- src/qml/Qt6QmlMacros.cmake | 41 ++++++++++++++++++---- src/qml/doc/src/cmake/qt_add_qml_module.qdoc | 14 ++++++++ tests/auto/qml/qmltc/CMakeLists.txt | 3 ++ .../qml/qmltc/QmltcExportedTests/CMakeLists.txt | 30 ++++++++++++++++ .../QmltcExportedTests/HelloExportedWorld.qml | 5 +++ tests/auto/qml/qmltc/tst_qmltc.cpp | 8 +++++ tests/auto/qml/qmltc/tst_qmltc.h | 1 + .../auto/qml/qmltc_qprocess/tst_qmltc_qprocess.cpp | 34 ++++++++++++++++++ tools/qmltc/main.cpp | 14 ++++++++ tools/qmltc/qmltccodewriter.cpp | 12 ++++--- tools/qmltc/qmltccodewriter.h | 2 +- tools/qmltc/qmltccompiler.cpp | 3 ++ tools/qmltc/qmltccompiler.h | 2 ++ tools/qmltc/qmltcoutputir.h | 2 ++ 14 files changed, 160 insertions(+), 11 deletions(-) create mode 100644 tests/auto/qml/qmltc/QmltcExportedTests/CMakeLists.txt create mode 100644 tests/auto/qml/qmltc/QmltcExportedTests/HelloExportedWorld.qml diff --git a/src/qml/Qt6QmlMacros.cmake b/src/qml/Qt6QmlMacros.cmake index 6b3e8ffb08..70f33d0749 100644 --- a/src/qml/Qt6QmlMacros.cmake +++ b/src/qml/Qt6QmlMacros.cmake @@ -58,6 +58,8 @@ function(qt6_add_qml_module target) INSTALL_DIRECTORY INSTALL_LOCATION TYPE_COMPILER_NAMESPACE + QMLTC_EXPORT_DIRECTIVE + QMLTC_EXPORT_FILE_NAME ) set(args_multi @@ -147,11 +149,32 @@ function(qt6_add_qml_module target) ) endif() - if (DEFINED arg_TYPE_COMPILER_NAMESPACE AND NOT arg_ENABLE_TYPE_COMPILER) - message(WARNING - "TYPE_COMPILER_NAMESPACE is set, but ENABLE_TYPE_COMPILER is not specified. " - "The TYPE_COMPILER_NAMESPACE value will be ignored." - ) + if (NOT arg_ENABLE_TYPE_COMPILER) + if (DEFINED arg_TYPE_COMPILER_NAMESPACE) + message(WARNING + "TYPE_COMPILER_NAMESPACE is set, but ENABLE_TYPE_COMPILER is not specified. " + "The TYPE_COMPILER_NAMESPACE value will be ignored." + ) + endif() + + if (DEFINED arg_QMLTC_EXPORT_DIRECTIVE) + message(WARNING + "QMLTC_EXPORT_DIRECTIVE is set, but ENABLE_TYPE_COMPILER is not specified. " + "The QMLTC_EXPORT_DIRECTIVE value will be ignored." + ) + endif() + if (DEFINED arg_QMLTC_EXPORT_FILE_NAME) + message(WARNING + "QMLTC_EXPORT_FILE_NAME is set, but ENABLE_TYPE_COMPILER is not specified. " + "The QMLTC_EXPORT_FILE_NAME will be ignored." + ) + endif() + else() + if ((DEFINED arg_QMLTC_EXPORT_FILE_NAME) AND (NOT (DEFINED arg_QMLTC_EXPORT_DIRECTIVE))) + message(FATAL_ERROR + "Specifying a value for QMLTC_EXPORT_FILE_NAME also requires one for QMLTC_EXPORT_DIRECTIVE." + ) + endif() endif() set(is_executable FALSE) @@ -670,6 +693,8 @@ Check https://doc.qt.io/qt-6/qt-cmake-policy-qtp0001.html for policy details." QML_FILES ${arg_QML_FILES} IMPORT_PATHS ${arg_IMPORT_PATH} NAMESPACE ${qmltc_namespace} + EXPORT_MACRO_NAME ${arg_QMLTC_EXPORT_DIRECTIVE} + EXPORT_FILE_NAME ${arg_QMLTC_EXPORT_FILE_NAME} ) endif() @@ -1243,7 +1268,7 @@ endfunction() # Compile Qml files (.qml) to C++ source files with Qml Type Compiler (qmltc). function(_qt_internal_target_enable_qmltc target) set(args_option "") - set(args_single NAMESPACE) + set(args_single NAMESPACE EXPORT_MACRO_NAME EXPORT_FILE_NAME) set(args_multi QML_FILES IMPORT_PATHS) cmake_parse_arguments(PARSE_ARGV 1 arg @@ -1276,6 +1301,10 @@ function(_qt_internal_target_enable_qmltc target) if(arg_NAMESPACE) list(APPEND common_args --namespace "${arg_NAMESPACE}") endif() + if(arg_EXPORT_MACRO_NAME) + list(APPEND common_args --export "${arg_EXPORT_MACRO_NAME}") + list(APPEND common_args --exportInclude "${arg_EXPORT_FILE_NAME}") + endif() get_target_property(output_dir ${target} QT_QML_MODULE_OUTPUT_DIRECTORY) set(qmldir_file ${output_dir}/qmldir) diff --git a/src/qml/doc/src/cmake/qt_add_qml_module.qdoc b/src/qml/doc/src/cmake/qt_add_qml_module.qdoc index 857779af2d..e7572163f0 100644 --- a/src/qml/doc/src/cmake/qt_add_qml_module.qdoc +++ b/src/qml/doc/src/cmake/qt_add_qml_module.qdoc @@ -50,6 +50,9 @@ qt_add_qml_module( [NO_IMPORT_SCAN] [ENABLE_TYPE_COMPILER] [TYPE_COMPILER_NAMESPACE namespace] + [QMLTC_EXPORT_DIRECTIVE export_macro] + [QMLTC_EXPORT_FILE_NAME header_defining_export_macro] + ) \endcode @@ -691,4 +694,15 @@ can be put instead in a custom namespace, where different subnamespaces are to be separated by a "::", e.g. "MyNamespace::MySubnamespace" for the namespace MySubnamespace that is inside the MyNamespace. Apart from the "::", C++ namespace naming rules apply. + +\c QMLTC_QMLTC_EXPORT_DIRECTIVE should be used with \c QMLTC_EXPORT_FILE_NAME when +the classes generated by \l{QML Type Compiler}{qmltc} should be exported from +the qml library. By default, classes generated by qmltc are not exported from +their library. +The header defining the export macro for the current library +can be specified as an optional argument to \c QMLTC_EXPORT_FILE_NAME while the +exporting macro name should be specified as an argument to +\c QMLTC_QMLTC_EXPORT_DIRECTIVE. If no additional include is required or wanted, +e.g. when the header of the export macro is already indirectly included by a base +class, then the \c QMLTC_EXPORT_FILE_NAME option can be left out. */ diff --git a/tests/auto/qml/qmltc/CMakeLists.txt b/tests/auto/qml/qmltc/CMakeLists.txt index 4b86a6c018..1b7fbf6ee1 100644 --- a/tests/auto/qml/qmltc/CMakeLists.txt +++ b/tests/auto/qml/qmltc/CMakeLists.txt @@ -3,6 +3,7 @@ add_subdirectory(QmltcTests) add_subdirectory(NamespaceTest/Subfolder) +add_subdirectory(QmltcExportedTests) set(test_sources nameconflict.h nameconflict.cpp @@ -18,6 +19,8 @@ set(qmltc_module_libs # automatic type registration that comes from the plugin) qmltc_test_moduleplugin qmltc_namespace_test_module + qmltc_exported_tests_module + qmltc_exported_tests_moduleplugin ) qt_internal_add_test(tst_qmltc_diskcache SOURCES ${test_sources} diff --git a/tests/auto/qml/qmltc/QmltcExportedTests/CMakeLists.txt b/tests/auto/qml/qmltc/QmltcExportedTests/CMakeLists.txt new file mode 100644 index 0000000000..1e1454baff --- /dev/null +++ b/tests/auto/qml/qmltc/QmltcExportedTests/CMakeLists.txt @@ -0,0 +1,30 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_add_library(qmltc_exported_tests_module STATIC) +qt_autogen_tools_initial_setup(qmltc_exported_tests_module) + +include(GenerateExportHeader) +generate_export_header(qmltc_exported_tests_module) + +set(common_libraries + Qt::QuickPrivate +) + +target_link_libraries(qmltc_exported_tests_module PUBLIC ${common_libraries}) + +qt6_add_qml_module(qmltc_exported_tests_module + URI QmltcExportedTests + AUTO_RESOURCE_PREFIX + QML_FILES + HelloExportedWorld.qml + DEPENDENCIES + Qt::Quick + ENABLE_TYPE_COMPILER + QMLTC_EXPORT_DIRECTIVE "QMLTC_EXPORTED_TESTS_MODULE_EXPORT" + QMLTC_EXPORT_FILE_NAME "qmltc_exported_tests_module_export.h" +) + +target_include_directories(qmltc_exported_tests_module PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) + +qt_autogen_tools_initial_setup(qmltc_exported_tests_moduleplugin) diff --git a/tests/auto/qml/qmltc/QmltcExportedTests/HelloExportedWorld.qml b/tests/auto/qml/qmltc/QmltcExportedTests/HelloExportedWorld.qml new file mode 100644 index 0000000000..5e6886bced --- /dev/null +++ b/tests/auto/qml/qmltc/QmltcExportedTests/HelloExportedWorld.qml @@ -0,0 +1,5 @@ +import QtQuick + +Item { + property string myString: "Hello! I should be exported by qmltc" +} diff --git a/tests/auto/qml/qmltc/tst_qmltc.cpp b/tests/auto/qml/qmltc/tst_qmltc.cpp index 1fa4030642..381701991e 100644 --- a/tests/auto/qml/qmltc/tst_qmltc.cpp +++ b/tests/auto/qml/qmltc/tst_qmltc.cpp @@ -77,6 +77,7 @@ #include "repeatercrash.h" #include "aliases.h" #include "inlinecomponentsfromdifferentfiles.h" +#include "helloexportedworld.h" #include "testprivateproperty.h" #include "singletons.h" @@ -3208,4 +3209,11 @@ void tst_qmltc::namespacedName() Q_UNUSED(t); } +void tst_qmltc::checkExportsAreCompiling() +{ + QQmlEngine e; + QmltcExportedTests::HelloExportedWorld w(&e); + QCOMPARE(w.myString(), u"Hello! I should be exported by qmltc"_s); +} + QTEST_MAIN(tst_qmltc) diff --git a/tests/auto/qml/qmltc/tst_qmltc.h b/tests/auto/qml/qmltc/tst_qmltc.h index af084dcc01..f00a172522 100644 --- a/tests/auto/qml/qmltc/tst_qmltc.h +++ b/tests/auto/qml/qmltc/tst_qmltc.h @@ -93,4 +93,5 @@ private slots: void constSignalParameters(); void cppNamespaces(); void namespacedName(); + void checkExportsAreCompiling(); }; diff --git a/tests/auto/qml/qmltc_qprocess/tst_qmltc_qprocess.cpp b/tests/auto/qml/qmltc_qprocess/tst_qmltc_qprocess.cpp index 7f12f29342..f0b99fcdf0 100644 --- a/tests/auto/qml/qmltc_qprocess/tst_qmltc_qprocess.cpp +++ b/tests/auto/qml/qmltc_qprocess/tst_qmltc_qprocess.cpp @@ -51,6 +51,7 @@ private slots: void topLevelComponent(); void dashesInFilename(); void invalidSignalHandlers(); + void exports(); }; #ifndef TST_QMLTC_QPROCESS_RESOURCES @@ -261,5 +262,38 @@ void tst_qmltc_qprocess::invalidSignalHandlers() } } +static QString fileToString(const QString &path) +{ + QFile f(path); + if (f.open(QIODevice::ReadOnly)) + return QString::fromLatin1(f.readAll()); + return QString(); +} + +void tst_qmltc_qprocess::exports() +{ + const QString fileName = u"dummy.qml"_s; + QStringList extraArgs; + extraArgs << "--export" + << "MYLIB_EXPORT_MACRO" + << "--exportInclude" + << "exportheader.h"; + const auto errors = runQmltc(fileName, true, extraArgs); + + const QString headerName = m_tmpPath + u"/"_s + QFileInfo(fileName).baseName() + u".h"_s; + const QString header = fileToString(headerName); + const QString implementationName = + m_tmpPath + u"/"_s + QFileInfo(fileName).baseName() + u".cpp"_s; + const QString implementation = fileToString(implementationName); + + QCOMPARE(errors.size(), 0); + + QVERIFY(header.contains(u"class MYLIB_EXPORT_MACRO dummy : public QObject\n"_s)); + QVERIFY(!implementation.contains(u"MYLIB_EXPORT_MACRO"_s)); + + QVERIFY(header.contains(u"#include \"exportheader.h\"\n"_s)); + QVERIFY(!implementation.contains(u"exportheader.h"_s)); +} + QTEST_MAIN(tst_qmltc_qprocess) #include "tst_qmltc_qprocess.moc" diff --git a/tools/qmltc/main.cpp b/tools/qmltc/main.cpp index 6ef0afe105..65560c70e1 100644 --- a/tools/qmltc/main.cpp +++ b/tools/qmltc/main.cpp @@ -99,6 +99,18 @@ int main(int argc, char **argv) QCoreApplication::translate("main", "namespace") }; parser.addOption(namespaceOption); + QCommandLineOption exportOption{ u"export"_s, + QCoreApplication::translate( + "main", "Export macro used in the generated C++ code"), + QCoreApplication::translate("main", "export") }; + parser.addOption(exportOption); + QCommandLineOption exportIncludeOption{ + u"exportInclude"_s, + QCoreApplication::translate( + "main", "Header defining the export macro to be used in the generated C++ code"), + QCoreApplication::translate("main", "exportInclude") + }; + parser.addOption(exportIncludeOption); parser.process(app); @@ -224,6 +236,8 @@ int main(int argc, char **argv) info.outputHFile = parser.value(outputHOption); info.resourcePath = firstQml(paths); info.outputNamespace = parser.value(namespaceOption); + info.exportMacro = parser.value(exportOption); + info.exportInclude = parser.value(exportIncludeOption); if (info.outputCppFile.isEmpty()) { fprintf(stderr, "An output C++ file is required. Pass one using --impl"); diff --git a/tools/qmltc/qmltccodewriter.cpp b/tools/qmltc/qmltccodewriter.cpp index 8010f1066c..6996277473 100644 --- a/tools/qmltc/qmltccodewriter.cpp +++ b/tools/qmltc/qmltccodewriter.cpp @@ -209,7 +209,7 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcProgram &progra code.rawAppendToHeader(u"class " + type.cppType + u";"); // write all the types and their content for (const QmltcType &type : std::as_const(program.compiledTypes)) - write(code, type); + write(code, type, program.exportMacro); // add typeCount definitions. after all types have been written down (so // they are now complete types as per C++). practically, this only concerns @@ -251,10 +251,14 @@ static void dumpFunctions(QmltcOutputWrapper &code, const QList &fu } } -void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcType &type) +void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcType &type, + const QString &exportMacro) { const auto constructClassString = [&]() { - QString str = u"class " + type.cppType; + QString str = u"class "_s; + if (!exportMacro.isEmpty()) + str.append(exportMacro).append(u" "_s); + str.append(type.cppType); QStringList nonEmptyBaseClasses; nonEmptyBaseClasses.reserve(type.baseClasses.size()); std::copy_if(type.baseClasses.cbegin(), type.baseClasses.cend(), @@ -340,7 +344,7 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcType &type) // children for (const auto &child : std::as_const(type.children)) - QmltcCodeWriter::write(code, child); + QmltcCodeWriter::write(code, child, exportMacro); // (non-visible) functions dumpFunctions(code, type.functions, std::not_fn(isUserVisibleFunction)); diff --git a/tools/qmltc/qmltccodewriter.h b/tools/qmltc/qmltccodewriter.h index e95777998e..04446f4c4e 100644 --- a/tools/qmltc/qmltccodewriter.h +++ b/tools/qmltc/qmltccodewriter.h @@ -20,7 +20,7 @@ struct QmltcCodeWriter static void writeGlobalFooter(QmltcOutputWrapper &code, const QString &sourcePath, const QString &outNamespace); static void write(QmltcOutputWrapper &code, const QmltcProgram &program); - static void write(QmltcOutputWrapper &code, const QmltcType &type); + static void write(QmltcOutputWrapper &code, const QmltcType &type, const QString &exportMacro); static void write(QmltcOutputWrapper &code, const QmltcEnum &enumeration); static void write(QmltcOutputWrapper &code, const QmltcMethod &method); static void write(QmltcOutputWrapper &code, const QmltcCtor &ctor); diff --git a/tools/qmltc/qmltccompiler.cpp b/tools/qmltc/qmltccompiler.cpp index 4050136ef2..d97fc26ecf 100644 --- a/tools/qmltc/qmltccompiler.cpp +++ b/tools/qmltc/qmltccompiler.cpp @@ -142,8 +142,11 @@ void QmltcCompiler::compile(const QmltcCompilerInfo &info) program.cppPath = m_info.outputCppFile; program.hPath = m_info.outputHFile; program.outNamespace = m_info.outputNamespace; + program.exportMacro = m_info.exportMacro; program.compiledTypes = compiledTypes; program.includes = m_visitor->cppIncludeFiles(); + if (!m_info.exportMacro.isEmpty() && !m_info.exportInclude.isEmpty()) + program.includes += (m_info.exportInclude); program.urlMethod = urlMethod; QmltcOutput out; diff --git a/tools/qmltc/qmltccompiler.h b/tools/qmltc/qmltccompiler.h index d5f07aa3b0..baa5b23256 100644 --- a/tools/qmltc/qmltccompiler.h +++ b/tools/qmltc/qmltccompiler.h @@ -25,6 +25,8 @@ struct QmltcCompilerInfo QString outputHFile; QString outputNamespace; QString resourcePath; + QString exportMacro; + QString exportInclude; }; class QmltcCompiler diff --git a/tools/qmltc/qmltcoutputir.h b/tools/qmltc/qmltcoutputir.h index 8884ddc3a3..cd094ee7b1 100644 --- a/tools/qmltc/qmltcoutputir.h +++ b/tools/qmltc/qmltcoutputir.h @@ -140,6 +140,8 @@ struct QmltcProgram QString cppPath; // C++ output .cpp path QString hPath; // C++ output .h path QString outNamespace; + QString exportMacro; // if not empty, the macro that should be used to export the generated + // classes QSet includes; // non-default C++ include files QmltcMethod urlMethod; // returns QUrl of the QML document -- cgit v1.2.3