CMake: QML module with external dependencies
-
If I want to create a QML module that depends on other classes in the project (external to that module) how do I do that in CMake for Qt 6.2?
e.g.
- I have class
Penguin
that I want to expose as a QML component Penguin
is derived fromAnimal
Penguin
uses methods fromSwimming
Here's my starting point:
cmake_minimum_required(VERSION 3.16) project(Silly VERSION 1.0 LANGUAGES CXX) set(CMAKE_AUTOMOC ON) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Qt6 6.2 COMPONENTS Quick REQUIRED) qt_add_executable(appSilly main.cpp) qt_add_qml_module(appSilly URI Silly VERSION 1.0 QML_FILES main.qml SOURCES Animal.h Animal.cpp Mammal.h Mammal.cpp AlsoUsesAnimal.h AlsoUsesAnimal.cpp Swimming.h Swimming.cpp ) qt_add_qml_module(Bird URI Bird VERSION 1.0 SOURCES Penguin.h Penguin.cpp ) target_include_directories(Birdplugin PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_compile_definitions(appSilly PRIVATE $<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:QT_QML_DEBUG>) target_link_libraries(appSilly PRIVATE Qt6::Quick Bird )
When libBird is linked, there are errors for undefined symbols wherever
Penguin
calls anAnimal
orSwimming
method. - I have class
-
I've solved why it failed (by looking through
Qt/6.2.3/macos/lib/cmake/Qt6Qml/Qt6QmlMacros.cmake
for the functionqt6_add_qml_module
).In the
qt6_add_qml_module
statement, ifOUTPUT_DIRECTORY
is not specified then it defaults to the root of the build directory. However there is a check that no two qml modules share the same directory. I think this is a bug and I'll report it. (I think it should default tobuild_root/qml_module_target
instead.)By explicitly setting
OUTPUT_DIRECTORY Bird
for theBird
target, and the equivalent forCommonAnimals
, it runs the CMake configure fine and also, thanks to you @Croitor-Alexandru , builds fine too.Here is the full CMakeList.txt:
cmake_minimum_required(VERSION 3.16) project(Silly VERSION 0.1 LANGUAGES CXX) set(CMAKE_AUTOMOC ON) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Qt6 6.2 COMPONENTS Quick REQUIRED) qt_add_qml_module(CommonAnimals URI CommonAnimals VERSION 1.0 OUTPUT_DIRECTORY CommonAnimals SOURCES Animal.h Animal.cpp Mammal.h Mammal.cpp AlsoUsesAnimal.h AlsoUsesAnimal.cpp Swimming.h Swimming.cpp ) qt_add_qml_module(Bird URI Bird VERSION 1.0 OUTPUT_DIRECTORY Bird SOURCES Penguin.h Penguin.cpp ) target_include_directories(Bird PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(Bird PRIVATE CommonAnimals) qt_add_executable(appSilly main.cpp ) qt_add_qml_module(appSilly URI Silly VERSION 1.0 QML_FILES main.qml ) target_include_directories(appSilly PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_compile_definitions(appSilly PRIVATE $<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:QT_QML_DEBUG>) target_link_libraries(appSilly PRIVATE Qt6::Quick Bird CommonAnimals )
I have updated the git repo with this solution.
Any more comments @Croitor-Alexandru ?
-
This is more of a general C++ and build system question, rather than anything Qt specific.
Note that
qt_add_qml_module
creates 2 targets,Bird
andBirdplugin
.
You wantAnimal.h
to be available to toPenguin.h
, which is part of theBird
target which means you should havetarget_include_directories(Bird PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
Note we use
Bird
as the target, notBirdplugin
.This should make the declarations in
Penguin.h
available, but not the actual definition / implementation of symbols inside that header.To make the implementations available you need to link
Bird
toAppSilly
, becauseAnimal.cpp
is compiled intoAppSilly
. Except you can't link an executable into a library. Which means you need to restructure your project to split Animal.h, .cpp, Swimming.h, .cpp into a separate library.Something like
qt_add_qml_module(CommonAnimals URI CommonAnimals VERSION 1.0 SOURCES Animal.h Animal.cpp Mammal.h Mammal.cpp AlsoUsesAnimal.h AlsoUsesAnimal.cpp Swimming.h Swimming.cpp ) qt_add_qml_module(Bird URI Bird VERSION 1.0 SOURCES Penguin.h Penguin.cpp ) target_include_directories(Bird PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(Bird PRIVATE CommonAnimals) qt_add_executable(appSilly main.cpp) qt_add_qml_module(appSilly URI Silly VERSION 1.0 QML_FILES main.qml) target_include_directories(appSilly PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(appSilly PRIVATE Qt6::Quick Bird CommonAnimals)
Additionally, due to how public symbols are handled in libraries on systems like Windows, you might need to annotate your class with an export macro that conditionally expands to either import or export symbols of that class, so you don't get issues when linking your libraries or application. CMake provides a facility for that, which it documents at https://cmake.org/cmake/help/latest/module/GenerateExportHeader.html
This is similar to how Qt clsases in headers have annotations like Q_MULTIMEDIA_EXPORT or Q_CORE_EXPORT (which under the hood use Q_DECL_IMPORT / Q_DECL_EXPORT)
-
@Croitor-Alexandru It's good to have comments from someone who knows their CMake. Thank you.
I mocked up those classes and tried your CMake. It failed to configure, though, with the error:
CMake Error at /Users/paul.masristone/Qt/6.2.3/macos/lib/cmake/Qt6Qml/Qt6QmlMacros.cmake:305 (message): Output directory for target "Bird" is already used by another QML module (target "CommonAnimals"). Output directory is: /Users/paul.masristone/Code/build-Silly-Qt_6_2_3_for_macOS-Debug Call Stack (most recent call first): /Users/paul.masristone/Qt/6.2.3/macos/lib/cmake/Qt6Qml/Qt6QmlMacros.cmake:615 (qt6_add_qml_module) CMakeLists.txt:20 (qt_add_qml_module)
I have put the full project into a public repo on Github: https://github.com/paulmasri/Silly
-
I've solved why it failed (by looking through
Qt/6.2.3/macos/lib/cmake/Qt6Qml/Qt6QmlMacros.cmake
for the functionqt6_add_qml_module
).In the
qt6_add_qml_module
statement, ifOUTPUT_DIRECTORY
is not specified then it defaults to the root of the build directory. However there is a check that no two qml modules share the same directory. I think this is a bug and I'll report it. (I think it should default tobuild_root/qml_module_target
instead.)By explicitly setting
OUTPUT_DIRECTORY Bird
for theBird
target, and the equivalent forCommonAnimals
, it runs the CMake configure fine and also, thanks to you @Croitor-Alexandru , builds fine too.Here is the full CMakeList.txt:
cmake_minimum_required(VERSION 3.16) project(Silly VERSION 0.1 LANGUAGES CXX) set(CMAKE_AUTOMOC ON) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Qt6 6.2 COMPONENTS Quick REQUIRED) qt_add_qml_module(CommonAnimals URI CommonAnimals VERSION 1.0 OUTPUT_DIRECTORY CommonAnimals SOURCES Animal.h Animal.cpp Mammal.h Mammal.cpp AlsoUsesAnimal.h AlsoUsesAnimal.cpp Swimming.h Swimming.cpp ) qt_add_qml_module(Bird URI Bird VERSION 1.0 OUTPUT_DIRECTORY Bird SOURCES Penguin.h Penguin.cpp ) target_include_directories(Bird PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(Bird PRIVATE CommonAnimals) qt_add_executable(appSilly main.cpp ) qt_add_qml_module(appSilly URI Silly VERSION 1.0 QML_FILES main.qml ) target_include_directories(appSilly PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_compile_definitions(appSilly PRIVATE $<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:QT_QML_DEBUG>) target_link_libraries(appSilly PRIVATE Qt6::Quick Bird CommonAnimals )
I have updated the git repo with this solution.
Any more comments @Croitor-Alexandru ?
-
Having filed this as a bug here: https://bugreports.qt.io/browse/QTBUG-103594, @Croitor-Alexandru responded (to the bug) with some useful comments about structuring a project. As it's relevant, I've taken the liberty of copying a snippet here:
We recommend placing each qt_add_qml_module call in its own $project_src_dir/CommonAnimals/CMakeLists.txt, and add that cmakelists.txt file with add_subdirectory(CommonAnimals). Then the built module is placed in $project_build_dir/CommonAnimals/qmldir and the default qml engine import path is able to find the qml module.
To date I haven't encountered any issues with
import
in QML where all files are in the project source folder and organisation of them is handled in Qt Creator, which generates a virtual folder structure based on QML modules defined in CMake. However I'll file this advice away in case I need to change things in future.