4、CMake构建系统(上):CMake基础语法、CMakeLists.txt编写、find_package查找Qt模块、add_executable与add_library

说实话,很多Qt初学者一上来就被CMake吓住了。

我当年也是。看着那一堆括号、变量、命令,心里直犯嘀咕:这玩意儿比qmake复杂多了,干嘛要学它?

但后来我在一个跨平台项目里吃了大亏——qmake在Windows上好好的,换到Linux就各种报错。从那以后,我老老实实把CMake啃了下来。今天咱们就聊聊CMake构建系统的核心基础,把这块硬骨头啃掉。

核心观点:CMake不是万能的,但没有CMake的跨平台Qt项目,维护成本会翻倍。

4.1 CMake基础语法:别被括号吓到

CMake的语法其实很简单。说白了就是:命令(参数1 参数2 ...)

每条命令以括号结尾,参数之间用空格隔开。字符串可以用双引号括起来,也可以不括——只要不包含空格。

# 这是注释
cmake_minimum_required(VERSION 3.16)  # 指定最低版本
project(MyApp VERSION 1.0.0 LANGUAGES CXX)  # 项目名称、版本、语言

set(CMAKE_CXX_STANDARD 17)  # 设置C++标准
set(CMAKE_CXX_STANDARD_REQUIRED ON)  # 强制使用该标准

我个人习惯把cmake_minimum_required放在第一行。为什么?因为如果版本不匹配,CMake会直接报错退出,不会继续执行后面的命令。这个坑我踩过——有一次在旧服务器上编译,CMake版本太低,结果一堆莫名其妙的错误,排查了半天才发现是版本问题。

小技巧:Qt6要求CMake最低版本3.16,Qt5要求3.14。建议统一设为3.16以上。

4.2 CMakeLists.txt编写:从零开始搭架子

每个Qt项目都需要一个CMakeLists.txt文件。它就像项目的"施工图纸",告诉CMake怎么编译你的代码。

一个典型的Qt Widgets项目长这样:

cmake_minimum_required(VERSION 3.16)
project(MyQtApp VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)  # 自动处理MOC
set(CMAKE_AUTORCC ON)  # 自动处理资源文件
set(CMAKE_AUTOUIC ON)  # 自动处理UI文件

find_package(Qt6 REQUIRED COMPONENTS Widgets Core Gui)

add_executable(MyQtApp
    main.cpp
    mainwindow.cpp
    mainwindow.h
    mainwindow.ui
    resources.qrc
)

target_link_libraries(MyQtApp PRIVATE
    Qt6::Widgets
    Qt6::Core
    Qt6::Gui
)

嗯,这里要注意三个AUTO开关:

  • AUTOMOC:自动处理Q_OBJECT宏,生成moc文件
  • AUTORCC:自动编译.qrc资源文件
  • AUTOUIC:自动把.ui文件转成C++代码

这三个开关我建议全部打开。你想想看,如果手动管理这些,每次新增一个带Q_OBJECT的类都要手动运行moc,那得多痛苦?

4.3 find_package查找Qt模块:别让CMake找不到Qt

find_package是CMake查找外部库的核心命令。对于Qt来说,它的用法是:

find_package(Qt6 REQUIRED COMPONENTS Widgets Core Gui Network Sql)

这里有几个关键点:

  • Qt6:指定Qt版本。如果你用Qt5,就写Qt5
  • REQUIRED:如果找不到,直接报错。不加这个,找不到只会给个警告
  • COMPONENTS:列出你需要的模块

我曾经遇到过一个情况:同事的电脑上明明装了Qt,但CMake就是找不到。折腾了半天,发现是CMAKE_PREFIX_PATH没设置对。CMake默认会在系统路径里找Qt,但如果你的Qt装在了自定义路径,就得手动告诉它:

# 在命令行里设置
cmake -DCMAKE_PREFIX_PATH=/path/to/Qt/6.5.0/gcc_64 ..

# 或者在CMakeLists.txt里设置(不推荐,会写死路径)
set(CMAKE_PREFIX_PATH "/path/to/Qt/6.5.0/gcc_64")

注意:不要把路径写死在CMakeLists.txt里。用命令行参数或者环境变量更灵活。我一般用Qt6_DIR环境变量来指定Qt安装路径。

4.4 add_executable与add_library:生成目标文件

这两个命令是CMake的核心——它们定义了最终要生成什么。

add_executable:生成可执行文件

add_executable(MyApp
    main.cpp
    mainwindow.cpp
    mainwindow.h
    mainwindow.ui
)

第一个参数是目标名称,后面是源文件列表。注意:头文件也要列出来,这样CMake才能跟踪依赖关系。虽然不列也能编译,但IDE的代码补全和跳转可能会出问题。

add_library:生成库文件

如果你要写一个Qt库(比如封装一些通用组件),用add_library:

# 静态库
add_library(MyLib STATIC
    mylib.cpp
    mylib.h
)

# 动态库
add_library(MySharedLib SHARED
    myshared.cpp
    myshared.h
)

# 模块库(Qt插件专用)
add_library(MyPlugin MODULE
    plugin.cpp
    plugin.h
)

三种类型有什么区别?

类型 生成文件 适用场景
STATIC .a / .lib 小型工具库,直接嵌入可执行文件
SHARED .so / .dll / .dylib 大型库,动态加载,节省内存
MODULE .so / .dll Qt插件,用QPluginLoader加载

我个人建议:如果你的库会被多个项目使用,用SHARED;如果只是自己项目内部用,用STATIC更省事——不用操心动态库的路径问题。

4.5 实战:一个完整的CMake项目结构

说了这么多,咱们来个完整的例子。假设你要写一个计算器应用,目录结构如下:

Calculator/
├── CMakeLists.txt
├── src/
│   ├── main.cpp
│   ├── calculator.cpp
│   └── calculator.h
├── ui/
│   └── calculator.ui
└── resources/
    └── icons.qrc

对应的CMakeLists.txt:

cmake_minimum_required(VERSION 3.16)
project(Calculator VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)

find_package(Qt6 REQUIRED COMPONENTS Widgets Core Gui)

add_executable(Calculator
    src/main.cpp
    src/calculator.cpp
    src/calculator.h
    ui/calculator.ui
    resources/icons.qrc
)

target_include_directories(Calculator PRIVATE src)

target_link_libraries(Calculator PRIVATE
    Qt6::Widgets
    Qt6::Core
    Qt6::Gui
)

这里我用了target_include_directories来指定头文件搜索路径。这样在calculator.cpp里就可以直接用#include "calculator.h",不用写相对路径。

经验之谈:把源文件按目录组织好,CMakeLists.txt里用相对路径引用。这样项目大了以后,维护起来特别清爽。

4.6 知识体系总览

为了让你对本章内容有个整体认识,我画了一张图:

CMake构建系统知识体系 CMakeLists.txt 基础语法 cmake_minimum_required project / set 注释与变量 查找Qt模块 find_package(Qt6) COMPONENTS 列表 CMAKE_PREFIX_PATH 生成可执行文件 add_executable 源文件 + 头文件 target_link_libraries 生成库文件 STATIC / SHARED / MODULE target_include_directories 链接Qt库 核心:CMakeLists.txt → 配置 → 生成 → 编译 → 运行

这张图把本章的核心内容串起来了。你写CMakeLists.txt的时候,就按这个流程来:先写基础配置,再找Qt模块,最后定义目标文件。

好了,CMake构建系统的上半部分就聊到这儿。记住一句话:CMakeLists.txt就是你的项目说明书,写清楚、写规范,后面能省很多事。

课后练习:试着用CMake搭建一个包含主窗口、对话框和资源文件的Qt项目。编译通过后,再试试把其中一个模块改成动态库。

可可精品资料库