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 知识体系总览
为了让你对本章内容有个整体认识,我画了一张图:
这张图把本章的核心内容串起来了。你写CMakeLists.txt的时候,就按这个流程来:先写基础配置,再找Qt模块,最后定义目标文件。
好了,CMake构建系统的上半部分就聊到这儿。记住一句话:CMakeLists.txt就是你的项目说明书,写清楚、写规范,后面能省很多事。
课后练习:试着用CMake搭建一个包含主窗口、对话框和资源文件的Qt项目。编译通过后,再试试把其中一个模块改成动态库。