Static QML plugins
Open, NormalPublic

Description

This is mostly handled by Qt itself, with some issues.

Statically-compiled QML plugins can be loaded on demand using qt_import_qml_plugins() (Qt6, Qt5).

This macro uses qmlimportscanner tool underneath to scan the source directory for qml files (including ones embedded in qrc) and pull the import calls to build a list of libraries which it then target_link_libraries() against the indicated target (also see note [1] below):

message(STATUS \"Running qmlimportscanner to find used QML plugins. \")
    execute_process(COMMAND
                    \"${tool_path}\" \"${arg_PATH_TO_SCAN}\" -importPath \"${qml_path}\"
                    -cmake-output
                    OUTPUT_FILE \"${qml_imports_file_path}\")

The docs say:

If target was created using qt_add_executable(), projects would not normally need to call qt_import_qml_plugins() directly. When Qt is built statically, the command is called automatically as part of target finalization if target links to the Qml library. By default, this finalization occurs at the end of the same directory scope in which the target was created. If the target was created using the standard CMake add_executable() command instead, the project needs to call qt_import_qml_plugins() itself.

So this excludes ECMAddTests from taking advantage of it, as it uses the generic add_executable() macro, and some extra code with qt_import_qml_plugins() calls is needed.

As an example, to fix issues with some of the frameworks running tests against their own QML plugins, something like this — in theory — should work:

if (TARGET Qt${QT_MAJOR_VERSION}::Qml)
    ecm_add_tests(
        ${kconcatenaterows_qml_SRC}
        ksortfilterproxymodel_qml.cpp
        LINK_LIBRARIES KF5::ItemModels Qt${QT_MAJOR_VERSION}::Test Qt${QT_MAJOR_VERSION}::Qml Qt${QT_MAJOR_VERSION}::Gui
        TARGET_NAMES_VAR qml_tests_targets
    )

    find_package(Qt${QT_MAJOR_VERSION}QmlImportScanner)
    foreach(test_target IN LISTS qml_tests_targets)
        qt_import_qml_plugins(${test_target})
    endforeach()
endif()

(an extension of the code from here: https://invent.kde.org/frameworks/kitemmodels/-/blob/master/autotests/CMakeLists.txt#L42)

Unfortunately, it doesn't. As highlighted in the documentation excerpt above, only the qt_add_executable() automation should be limited to statically compiled Qt. However, the Qt manual is not too precise here, because — quite surprisingly — the complete qt_import_qml_plugins() function becomes a mere stub in a dynamically-compiled Qt:

When Qt is built in a shared library config, the introduced function is a no-op.

(from https://codereview.qt-project.org/c/qt/qtdeclarative/+/268048)

This dictates that we would have to have statically-built Qt seeded into the CI job for this to work.

But this is not the end of it: while qt_import_qml_plugins() takes an (undocumented) PATH_TO_SCAN parameter, which it passes to qmlimportscanner to search for the qml files, it hardcodes the -importPath to ${_qt5Core_install_prefix}/qml/, which is where it performs the actual plugin lookup. This obviously means is that the qt_import_qml_plugins() macro can currently only be used against the previously installed plugins, and not against the in-tree ones. qmlimportscanner tool can take multiple -importPaths, so not adding it as a parameter to qt_import_qml_plugins() seems like a weird omission, to say the least.

So, to summarize, using my local statically-compiled Qt5.15.6, after hacking the qt5_import_qml_plugins() macro and literally hardcoding an extra -importPath "~/Sourcecode/kitemmodels/cmake-build-debug/bin/", and fixing a minor ECMAddTests issue ([1]), I was able to successfully run the QML tests:

/Users/cromo/Documents/Sourcecode/kitemmodels/cmake-build-debug-vcpkg/bin/kconcatenaterows_qml
********* Start testing of tst_KConcatenateRowsQml *********
Config: Using QtTest library 5.15.5, Qt 5.15.5 (arm64-little_endian-lp64 static debug build; by Clang 13.1.6 (clang-1316.0.21.2.5) (Apple)), osx 12.6
PASS   : tst_KConcatenateRowsQml::initTestCase()
PASS   : tst_KConcatenateRowsQml::testQmlLoad()
PASS   : tst_KConcatenateRowsQml::cleanupTestCase()
Totals: 3 passed, 0 failed, 0 skipped, 0 blacklisted, 31ms
********* Finished testing of tst_KConcatenateRowsQml *********

Process finished with exit code 0

PS. It's worth noting that as of 6.2.1 one can link against the plugin target names with target_link_libraries (e.g. Qt6::qmlplugin) instead of the backing lib (Qt6::Qml), and that will also properly link the object file that contains Q_IMPORT_PLUGIN. I would assume this fixes the static consumption as well. (See this comment)

[1] One additional issue is that internally, qt_import_qml_plugins() macro calls target_link_libraries() with PRIVATE modifier, whereas the ECMAddTests doesn't, which results in:

The plain signature for target_link_libraries has already been used with
the target "ksortfilterproxymodel_qml".  All uses of target_link_libraries
with a target must be either all-keyword or all-plain.

Hacking the ECMAddTests to also link PRIVATEly PUBLICly fixes the issue, but I haven't investigated whether making this permanent would have any side-effects — see https://cmake.org/cmake/help/latest/command/target_link_libraries.html#libraries-for-both-a-target-and-its-dependents

wrobelda created this task.Sep 21 2022, 2:43 PM
wrobelda triaged this task as Normal priority.
wrobelda updated the task description. (Show Details)Sep 21 2022, 2:46 PM
wrobelda updated the task description. (Show Details)
wrobelda updated the task description. (Show Details)Sep 21 2022, 2:49 PM
wrobelda updated the task description. (Show Details)
wrobelda updated the task description. (Show Details)
wrobelda updated the task description. (Show Details)Sep 21 2022, 2:51 PM
wrobelda updated the task description. (Show Details)
wrobelda updated the task description. (Show Details)
wrobelda updated the task description. (Show Details)Sep 21 2022, 2:54 PM
wrobelda added a comment.EditedSep 22 2022, 2:26 PM

A relevant QT issue regarding the qt_import_qml_plugins hardcoded importPaths limitation is here: https://bugreports.qt.io/browse/QTBUG-106431

wrobelda updated the task description. (Show Details)Sep 22 2022, 2:29 PM
wrobelda updated the task description. (Show Details)
wrobelda updated the task description. (Show Details)
wrobelda updated the task description. (Show Details)
alex added a comment.Sep 26 2022, 9:26 AM

Hacking the ECMAddTests to also link PRIVATEly PUBLICly fixes the issue

Consumers can just do LINK_LIBRARIES PUBLIC Qt::Test. Without it it is prettier, but it works without side effect.

but I haven't investigated whether making this permanent would have any side-effects

Then all the tests would need to specify if we link PRIVATE/PUBLIC in tests. This is not desirable for most cases.