2. CMake基础入门:CMakeLists.txt语法、项目配置、目标定义、库链接

各位同学,咱们今天聊聊CMake。说实话,我刚入行那会儿,对CMake也是又爱又恨。爱的是它跨平台,恨的是它那套语法——嗯,确实有点反直觉。但用久了你会发现,它其实挺有逻辑的。

CMake是什么?说白了,它就是个「项目生成器」。你写一份CMakeLists.txt,它就能帮你生成Visual Studio的.sln、Xcode的.xcodeproj,或者Linux下的Makefile。我当年在Windows上写Qt程序,拿到Linux上一编译就报错,后来用了CMake,世界清净了。

核心思想:CMake不直接编译代码,它负责「描述项目结构」,然后交给本地构建工具去干活。

2.1 CMakeLists.txt 基本语法

每个CMake项目,根目录下必须有一个CMakeLists.txt。这是CMake的「剧本」。我习惯把第一行写成这样:

cmake_minimum_required(VERSION 3.16)

为什么要指定版本?因为不同版本的CMake,有些函数行为不一样。我曾经在3.10上写了个target_sources,结果同事用3.5编译直接报错——嗯,从那以后我每次都写版本号。

接下来是项目名:

project(MyQtApp VERSION 1.0.0 LANGUAGES CXX)

这里LANGUAGES CXX告诉CMake:我们只用C++。如果你混用C和C++,可以写成C CXX。我个人习惯明确指定,避免CMake去猜。

小技巧:项目名不要用中文,也不要用空格。我见过有人写project(我的应用),结果路径解析直接崩了。

2.2 项目配置:变量与缓存

CMake里变量用set()来定义。比如:

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)  # Qt的MOC自动处理

这里CMAKE_AUTOMOC是Qt项目的标配。你想想看,如果不开启这个,每个带Q_OBJECT的头文件都得手动跑moc,那得多痛苦。

还有一种变量叫「缓存变量」,用set(... CACHE ...)定义。比如:

set(BUILD_SHARED_LIBS ON CACHE BOOL "Build shared libraries")

缓存变量会出现在cmake-gui里,用户可以在配置时修改。我记得有一次,客户非要静态链接,我就在CMakeLists.txt里加了个缓存选项,让他们自己勾选——省得我改代码。

2.3 目标定义:add_executable 与 add_library

CMake里最核心的概念是「目标」(target)。目标分两种:可执行文件和库。

可执行文件:

add_executable(MyApp main.cpp mainwindow.cpp)

静态库:

add_library(MyLib STATIC lib.cpp lib.h)

动态库:

add_library(MyLib SHARED lib.cpp lib.h)

这里有个坑:如果你不写STATIC或SHARED,CMake会根据BUILD_SHARED_LIBS变量的值来决定。我建议你显式指定,否则别人看你的代码会一头雾水。

注意:Qt的插件库必须用MODULE类型,不能用SHARED。我当年在这个问题上卡了两天,最后发现是库类型写错了。

2.4 库链接:target_link_libraries

链接库是CMake里最常用的操作。语法很简单:

target_link_libraries(MyApp PRIVATE Qt5::Widgets Qt5::Core)

这里的关键字PRIVATE、PUBLIC、INTERFACE,很多人搞不清楚。我打个比方:

  • PRIVATE:自己用,不传给下游。比如你内部用了log4cpp,但不想让调用你的人知道。
  • PUBLIC:自己用,也传给下游。比如Qt的Widgets,你的用户也要用。
  • INTERFACE:自己不用,只传给下游。比如纯头文件库。

我曾经接手过一个项目,所有链接都写PUBLIC,结果依赖传递得乱七八糟。改了一周才理清楚。所以我的建议是:能用PRIVATE就用PRIVATE,别偷懒。

2.5 查找包:find_package

Qt项目里,find_package是标配。比如:

find_package(Qt5 REQUIRED COMPONENTS Widgets Core Gui)

REQUIRED表示找不到就报错。如果你不想让编译中断,可以去掉REQUIRED,然后自己判断Qt5_FOUND变量。

这里有个细节:CMake找Qt的方式是去系统路径下找Qt5Config.cmake或qt5-config.cmake。如果你装了多个版本的Qt,可以用CMAKE_PREFIX_PATH指定路径:

set(CMAKE_PREFIX_PATH "C:/Qt/5.15.2/msvc2019_64")

嗯,Windows上路径里的反斜杠要转义,或者直接用正斜杠。我习惯用正斜杠,省心。

2.6 完整示例:一个简单的Qt项目

咱们把上面的知识点串起来,写一个完整的CMakeLists.txt:

cmake_minimum_required(VERSION 3.16)
project(SimpleQtApp 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(Qt5 REQUIRED COMPONENTS Widgets Core)

add_executable(SimpleQtApp
    main.cpp
    mainwindow.cpp
    mainwindow.h
    resources.qrc
)

target_link_libraries(SimpleQtApp
    PRIVATE
    Qt5::Widgets
    Qt5::Core
)

这个文件虽然短,但涵盖了CMake的80%常用操作。你想想看,从版本声明到项目名,从C++标准到Qt的自动处理,再到查找包和链接库——一条龙全有了。

2.7 知识体系结构图

下面这张图,是我自己总结的CMake核心知识脉络。你可以把它当成一个「思维导图」来用:

CMakeLists.txt 项目配置 cmake_minimum_required project() set() 变量 目标定义 add_executable() add_library() STATIC / SHARED 库链接 target_link_libraries() PRIVATE / PUBLIC INTERFACE 查找包 find_package() REQUIRED / COMPONENTS CMAKE_PREFIX_PATH Qt 专用设置 CMAKE_AUTOMOC CMAKE_AUTORCC CMAKE_AUTOUIC 核心:描述项目结构,不直接编译 生成构建系统文件(Makefile / .sln / .xcodeproj)

2.8 避坑指南

最后,分享几个我踩过的坑:

  • 路径中的空格:我曾经把项目放在C:\My Projects\下,CMake解析路径时直接炸了。解决方案:要么路径别带空格,要么用file(TO_CMAKE_PATH)转换。
  • Qt版本冲突:系统里装了Qt5和Qt6,find_package找到了错误的版本。我后来用set(CMAKE_PREFIX_PATH)强制指定路径,才搞定。
  • MOC文件未生成:忘了写set(CMAKE_AUTOMOC ON),结果编译报错说找不到moc_mainwindow.cpp。嗯,这个坑我至少帮三个同事填过。

好了,CMake的基础就讲到这里。你把这些练熟了,后面讲Qt界面参数调优的时候,咱们就能直接上手改代码,不用再被构建系统绊住脚。


可可精品资料库