[QML]Qt6项目CMakeLists.txt详解

1,451 阅读5分钟

前言

CMake是一个跨平台的、开源的构建工具,主要用于管理和生成项目的构建文件。CMake允许开发者以一种平台无关的方式定义项目的构建过程,然后生成特定于平台的构建文件。而Qt-CMake不是一个单独的工具,而是指在使用CMake构建Qt项目时所需要的配置和支持,涉及将Qt特性集成到CMake构建系统中。

一般在Linux平台,Qt-CMake会生成Makefile文件,而在Windows平台,默认是生成build.ninja文件。

正文

这里我们先来简单介绍一下编译代码的过程,再来详细说明CMakeLists.txt文件的内容。

一般情况下,为了不让编译产生的中间文件污染我们的工程,我们可以创建一个cmake-build-debug目录,然后进入该目录,执行cmake ..,这时的CMake工具会去上一级目录中查找CMakeLists.txt文件,当然最好不要在源码目录这样干,在源码上一层目录新建构建目录最好。

然后会生成Makefile文件,执行cmake --build .进行编译代码,便可以生成可执行文件。

下面便是常见的Qt项目的CMakeLists.txt的分析:

  • cmake_minimum_required(VERSION 3.21.1):设置cmake需要的最低版本。

  • option(<option_variable> "<option_description>" <initial_value>):用于定义一个用户可配置的选项,其中<option_variable>定义一个CMake变量,用于存储该选项的状态,<option_description>是选项的描述信息,<initial_value>是选项的初始值,一般为ON或者OFF

    说白了就是定义一个变量,在CMakeLists.txt中使用。比如QML默认的2个配置:

    option(LINK_INSIGHT "Link Qt Insight Tracker library" ON)
    option(BUILD_QDS_COMPONENTS "Build design studio components" ON)
    

    第一个是是否需要链接Qt Insight Tracker库,该库是一个应用程序和嵌入式设备的智能分析工具,然后该变量的后面的使用是:

    if (LINK_INSIGHT)
        include(${CMAKE_CURRENT_SOURCE_DIR}/insight)
    endif ()
    

    第二个是是否构建Qt Design Studio的组件,该工具可以为设计师和开发人员快速构建UI的工具,后面的使用是:

    if (BUILD_QDS_COMPONENTS)
        include(${CMAKE_CURRENT_SOURCE_DIR}/qmlcomponents)
    endif()
    

    合理使用option进行合理化的构建,可以节省构建资源,加快速度。

  • project(HelloQMLApp LANGUAGES CXX):设置项目名称、编程语言等,如果不设置编程语言,CMake会根据项目中的文件类型自动推断出所使用的编程语言。

  • set(CMAKE_AUTOMOC ON):告诉CMake在构建过程中自动运行moc(Meta-Object Compiler)工具来处理包含了Q_OBJECT宏的C++源文件。当C++类中包含有Q_OBJECT宏时,这意味着该类涉及了信号槽机制,需要通过moc工具生成额外代码。

    这里的set的作用就是定义或者修改CMake变量的值,CMake变量用于存储和传递构建配置的各种信息,比如文件路径、编译选项、库版本等。

  • find_package()是CMake中用于查找并且加载外部软件包的一个命令,通常用于引入和管理项目所需的第三方库或者模块。

    基本语法是:

    find_package(<package_name> [version] [EXACT] [QUIET] [MODULE] [REQUIRED]
                 [COMPONENTS [components...]]
                 [OPTIONAL_COMPONENTS components...]
                 [NO_POLICY_SCOPE])
    ​
    

    其中参数说明:

    • <package_name>:要查找的软件包的名称;
    • [version]:可选项,指定软件包的版本号;
    • [EXACT]:可选项,要求精确匹配指定版本的软件包;
    • [QUIT]:可选项,安静模式,不输出查找过程的详细信息;
    • [REQUIRED]:可选项,要求必须找到指定的软件包,否则会引发错误;
    • [COMPONENTS] [components ...]:指定要查找的软件包的组件。

    在QML项目中,默认有find_package(Qt6 6.2 REQUIRED COMPONENTS Core Gui Qml Quick)表示需要加载Qt6.2,并且要求CoreGui等几个组件。

    这里值得关注的点是如果使用交叉编译的qt-cmake工具来构建项目,这里就会寻找到交叉编译的库。

  • qt_add_executable(HelloQMLApp src/main.cpp):该指令和add_executable不同,它是Qt6中用于添加一个可执行文件的函数调用。该语句的作用是将名为HelloQMLApp的可执行文件与指定的源文件src/main.cpp相关联。功能包括如下几点:

    • 创建可执行文件,在项目中创建一个名为HelloQMLApp的可执行文件。
    • 指定源文件,源文件src/main.cpp就是入口源文件。
    • 与Qt相关联,由于是使用Qt6构建项目,qt_add_executable()函数会自动处理与Qt相关的配置,例如生成moc文件,以确保Qt功能正常使用。
    • 简化配置,有助于简化项目的配置过程,特别是涉及到Qt功能的部分。
  • qt_add_resources的作用是将二进制资源编译为源代码,这个比较有意思,该指令的使用是:

    qt_add_resources(<TARGET> <RESOURCE_NAME>
                      [PREFIX <PATH>]
                      [LANG <LANGUAGE>]
                      [BASE <PATH>]
                      [BIG_RESOURCES]
                      [OUTPUT_TARGETS <VARIABLE_NAME>]
                      [FILES ...] [OPTIONS ...])
    ​
    

    其中<TARGET>是目标,即往哪个程序或者库中添加资源;<Resource_Name>是资源名,自定义;PREFIX PATH是可选的,指定路径资源,即访问该资源需要的前缀,默认是/FILES是可选的,指需要添加的二进制资源文件。

    比如代码:

    qt_add_resources(${PROJECT_NAME} "txt"
        PREFIX
            "txt"
        FILES
            in.txt
    )
    

    如果要访问in.txt,则需要file.setFileName(":txt/in.txt");

    同理,在默认项目中有如下代码:

    qt_add_resources(HelloQMLApp "configuration"
        PREFIX "/"
        FILES
        qtquickcontrols2.conf
    )
    

    即把qtquickcontrols2.conf当作资源文件,添加进项目中。

  • target_link_librariestarget_include_directories的作用是用于配置目标文件(可执行文件或者库)的编译与链接选项。比如在项目中有代码:

    target_link_libraries(HelloQMLApp PRIVATE
        Qt6::Core
        Qt6::Gui
        Qt6::Qml
        Qt6::Quick
    )
    

    Qt6模块链接到名为HelloQMLApp上,以便在构建目标文件时可以使用这些模块提供的功能和库。同时,使用私有链接,意味着只有HelloQMLApp可以访问这些链接的内容,其他目标无法直接访问。 这里的参数可以是静态库、动态库或者其他目标的库,这个指令告诉链接器在生成目标文件时需要链接哪些库。

    再比如下面代码:

    target_include_directories(${target_name} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
    

    就是去规定的目录中查找头文件,头文件包含了原文件编译所需的接口定义与声明。

  • target_compile_definitions命令用于为指定的目标添加预处理的宏定义,类似在编译命令行中使用-D选项。比如下面代码:

    target_compile_definitions(${target_name} PRIVATE CONFIG_LIBRARY)
    

    就是定义了一个宏,可以在C++代码中判断该宏有没有定义。

  • include命令用于包含外部文件或者脚本文件到当前CMakeLists.txt文件中,包括其他的CMakeLists.txt、模块文件、函数文件、宏文件等,以便在当前的构建过程中使用他们。

    include(GNUInstallDirs)用于包含GNU安装目录相关的定义和变量,主要是获取一些GNU安装目录相关的变量在后面可以使用,比如${CMAKE_INSTALL_LIBDIR}

  • install命令用于配置安装规则,指定在构建完成后,如何将生成的文件安装到指定的位置。安装规则通常用于将构建生成的文件(如可执行文件、库文件、头文件等)安装到系统的特定目录,以便用户可以访问这些文件。

    比如在项目中:

    install(TARGETS HelloQMLApp
        BUNDLE DESTINATION .
        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
        RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
    )
    

    作用就是用于安装HelloQMLApp的目标文件到不同的目录中,具体来说就是将目标文件安装为一个包(bundle)时,将其安装到当前构建目录下。当将HelloQMLApp安装为一个库文件时,将其安装到${CMAKE_INSTALL_LIBDIR}目录中。当将HelloQMLApp安装为一个运行时文件时,安装到${CMAKE_INSTALL_BINDIR}目录中。

  • set命令用于设置变量的值,可以用于创建新变量、更新已存在的变量或者删除变量。基本语法是:

    set(<variable> <value> [CACHE <type> <docstring> [FORCE]])
    

    各个选项的作用:

    • variable表示要设置的变量名,可以是已存在的变量,也可以是一个新的变量。
    • value是要设置的变量,可以是简单的字符串或列表,也可以是一个表达式或者函数调用。
    • CACHE <type> <docstring>将变量设置为一个缓存变量,以便用户可以通过命令行或者配置工具将其修改。type是变量类型,docstring为对变量的描述。

    比如在项目中有:

    set(QML_IMPORT_PATH ${PROJECT_BINARY_DIR}/qml CACHE PATH
        "Path to the custom QML components defined by the project")
    

    作用就是定义QML组件的导入路径,其中PATH表示这个缓存变量是路径。

    总结

    关于CMake还有更多的知识需要学习,这里简单地介绍一下Qt项目的CMakeLists.txt,后续持续更新。