Qt中qt_add_qml_module引发依赖循环的解决方案求助
qt_add_qml_module置于子目录引发的依赖循环问题 问题背景
在Qt Creator中使用CMake构建项目时,希望将QML模块的配置(qt_add_qml_module)放在src_gui子目录的CMakeLists.txt中以保持目录结构整洁,但构建时出现以下依赖循环错误:
ninja: error: dependency cycle: appRecipeManager_autogen/timestamp -> src_gui/appRecipeManager_tooling -> src_gui/.rcc/qmlcache/appRecipeManager_Main_qml.cpp -> src_gui/GUI/appRecipeManager.qmltypes -> meta_types/qt6apprecipemanager_debug_metatypes.json -> meta_types/appRecipeManager_json_file_list.txt -> appRecipeManager_autogen/timestamp
将qt_add_qml_module移至主CMakeLists.txt时可正常构建,但希望保留子目录的结构划分。
原因分析
Qt6的qt_add_qml_module命令在为可执行目标配置QML模块时,会生成元类型文件、QML缓存代码等构建产物。当在为主可执行目标添加C++源文件(通过target_sources)之后再调用该命令,主目标的自动生成文件(autogen)与QML模块的构建目标会形成循环依赖链,导致构建系统(如Ninja)报错。
解决方案
方案1:调整子目录的加载顺序
将src_gui子目录的加载顺序提前,确保qt_add_qml_module在为主可执行目标添加任何C++源文件之前执行,避免循环依赖。
修改主CMakeLists.txt中的add_subdirectory顺序:
cmake_minimum_required(VERSION 3.16) project(RecipeManager VERSION 1.0.0 LANGUAGES CXX) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Qt6 6.4 REQUIRED COMPONENTS Quick Sql) qt_standard_project_setup() qt_add_executable(appRecipeManager) # 先加载src_gui,确保qt_add_qml_module先执行 add_subdirectory(src_gui) add_subdirectory(src) add_subdirectory(include) target_include_directories(appRecipeManager PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ) target_link_libraries(appRecipeManager PRIVATE Qt6::Quick PRIVATE Qt${QT_VERSION_MAJOR}::Sql ) set_target_properties(appRecipeManager PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} MACOSX_BUNDLE TRUE WIN32_EXECUTABLE TRUE ) install(TARGETS appRecipeManager BUNDLE DESTINATION . LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} )
src、include、src_gui子目录的CMakeLists.txt保持原内容不变。
方案2:将QML模块封装为单独的库目标
将QML模块作为独立的静态库目标,主可执行目标通过链接该库来使用QML资源,这种方式更符合模块化设计,彻底避免依赖循环。
修改src_gui/CMakeLists.txt:
# 创建静态库目标 add_library(GuiModule STATIC) # 为库添加QML模块 qt_add_qml_module(GuiModule URI RecipeManager VERSION 1.0 QML_FILES Main.qml ) # 链接Qt依赖 target_link_libraries(GuiModule PRIVATE Qt6::Quick)
修改主CMakeLists.txt:
移除src_gui子目录中对主可执行目标的直接操作,改为链接新的库目标:
cmake_minimum_required(VERSION 3.16) project(RecipeManager VERSION 1.0.0 LANGUAGES CXX) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Qt6 6.4 REQUIRED COMPONENTS Quick Sql) qt_standard_project_setup() qt_add_executable(appRecipeManager) add_subdirectory(src) add_subdirectory(include) add_subdirectory(src_gui) target_include_directories(appRecipeManager PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ) target_link_libraries(appRecipeManager PRIVATE Qt6::Quick PRIVATE Qt${QT_VERSION_MAJOR}::Sql PRIVATE GuiModule # 链接QML模块库 ) set_target_properties(appRecipeManager PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} MACOSX_BUNDLE TRUE WIN32_EXECUTABLE TRUE ) install(TARGETS appRecipeManager BUNDLE DESTINATION . LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) # 安装QML模块(如果需要部署) install(TARGETS GuiModule BUNDLE DESTINATION . LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} )
src和include子目录的CMakeLists.txt保持原内容不变。
验证
两种方案均可解决依赖循环问题,同时保持目录结构的整洁。方案1修改量小,适合小型项目;方案2更适合大型项目的模块化扩展。
内容的提问来源于stack exchange,提问作者Michael21

