Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. General talk
  3. Qt 6
  4. CMake: QML module with external dependencies
Forum Updated to NodeBB v4.3 + New Features

CMake: QML module with external dependencies

Scheduled Pinned Locked Moved Solved Qt 6
5 Posts 2 Posters 1.9k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • P Offline
    P Offline
    paulmasri
    wrote on last edited by
    #1

    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 from Animal
    • Penguin uses methods from Swimming

    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 an Animal or Swimming method.

    1 Reply Last reply
    0
    • P Offline
      P Offline
      paulmasri
      wrote on last edited by
      #4

      I've solved why it failed (by looking through Qt/6.2.3/macos/lib/cmake/Qt6Qml/Qt6QmlMacros.cmake for the function qt6_add_qml_module).

      In the qt6_add_qml_module statement, if OUTPUT_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 to build_root/qml_module_target instead.)

      By explicitly setting OUTPUT_DIRECTORY Bird for the Bird target, and the equivalent for CommonAnimals, 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 ?

      1 Reply Last reply
      1
      • C Offline
        C Offline
        Croitor Alexandru
        wrote on last edited by Croitor Alexandru
        #2

        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 and Birdplugin.
        You want Animal.h to be available to to Penguin.h, which is part of the Bird target which means you should have

        target_include_directories(Bird PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})

        Note we use Bird as the target, not Birdplugin.

        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 to AppSilly, because Animal.cpp is compiled into AppSilly. 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)

        P 1 Reply Last reply
        2
        • C 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 and Birdplugin.
          You want Animal.h to be available to to Penguin.h, which is part of the Bird target which means you should have

          target_include_directories(Bird PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})

          Note we use Bird as the target, not Birdplugin.

          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 to AppSilly, because Animal.cpp is compiled into AppSilly. 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)

          P Offline
          P Offline
          paulmasri
          wrote on last edited by
          #3

          @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

          1 Reply Last reply
          0
          • P Offline
            P Offline
            paulmasri
            wrote on last edited by
            #4

            I've solved why it failed (by looking through Qt/6.2.3/macos/lib/cmake/Qt6Qml/Qt6QmlMacros.cmake for the function qt6_add_qml_module).

            In the qt6_add_qml_module statement, if OUTPUT_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 to build_root/qml_module_target instead.)

            By explicitly setting OUTPUT_DIRECTORY Bird for the Bird target, and the equivalent for CommonAnimals, 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 ?

            1 Reply Last reply
            1
            • P Offline
              P Offline
              paulmasri
              wrote on last edited by
              #5

              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.

              1 Reply Last reply
              0

              • Login

              • Login or register to search.
              • First post
                Last post
              0
              • Categories
              • Recent
              • Tags
              • Popular
              • Users
              • Groups
              • Search
              • Get Qt Extensions
              • Unsolved