第一个CMake项目:从零开始搭建你的构建系统

说实话,很多嵌入式工程师第一次接触CMake时,都觉得它很神秘。我当年也是这样——看着一堆CMakeLists.txt文件,完全不知道从哪下手。但等你真正动手创建一个最简单的项目后,你会发现,嗯,其实核心就那么几个命令。

今天我们就来亲手搭建第一个CMake项目。我会带着你一步步写CMakeLists.txt,然后编译、运行。整个过程不会超过10分钟。

1. 项目结构:先想清楚怎么放文件

我个人习惯,嵌入式项目一般这样组织:

my_first_project/
├── CMakeLists.txt
└── main.c

对,就两个文件。一个CMakeLists.txt,一个main.c。简单到不能再简单。

main.c的内容也很朴素:

#include <stdio.h>

int main(void) {
    printf("Hello, CMake!\n");
    return 0;
}

这里我用的是printf,而不是嵌入式里常见的串口打印。为什么?因为咱们先跑通桌面环境,把CMake的流程搞明白。等后面讲到交叉编译,再切到真正的嵌入式目标板。

2. 编写CMakeLists.txt:三个核心命令

打开CMakeLists.txt,写入以下内容:

cmake_minimum_required(VERSION 3.10)
project(MyFirstProject)
add_executable(my_app main.c)

就三行。我刚开始学的时候也觉得太简单了,但实际项目里,90%的CMakeLists.txt都是这三行的变体。

咱们逐行拆解一下:

2.1 cmake_minimum_required()

这行指定了CMake的最低版本。比如我写VERSION 3.10,意思是如果用户电脑上的CMake版本低于3.10,就会报错退出。

注意:版本号别写太高。我曾经在客户现场遇到过,他们服务器上装的是CMake 3.5,我写了3.16,结果编译不过去。后来我学乖了,一般写3.10,兼容性最好。

2.2 project()

这行定义了项目名称。比如project(MyFirstProject),项目名就叫MyFirstProject。

这个命令还会隐式地设置一些变量,比如:

  • PROJECT_NAME — 项目名
  • PROJECT_SOURCE_DIR — 源码根目录
  • PROJECT_BINARY_DIR — 构建目录

这些变量后面会经常用到。你想想看,如果没有project(),CMake连项目叫啥都不知道,后面很多功能就没法用了。

2.3 add_executable()

这行告诉CMake:我要生成一个可执行文件。第一个参数是目标名(比如my_app),后面跟的是源文件列表。

说白了,add_executable(my_app main.c) 就是告诉CMake:把main.c编译链接成my_app这个可执行文件。

小技巧:如果源文件很多,可以用变量来管理。比如:
set(SRC_FILES main.c uart.c gpio.c)
add_executable(my_app ${SRC_FILES})
这样后面加文件时,只改set那一行就行。

3. 构建与运行:三步走

写好了CMakeLists.txt,接下来就是构建。我习惯在项目根目录下创建一个build文件夹,所有编译产物都放里面,保持源码目录干净。

mkdir build
cd build
cmake ..
make

这三条命令干了什么?

  1. mkdir build && cd build — 创建并进入构建目录
  2. cmake .. — 解析上一级目录的CMakeLists.txt,生成Makefile
  3. make — 根据Makefile编译链接,生成可执行文件

执行完cmake后,你会看到类似这样的输出:

-- The C compiler identification is GNU 9.3.0
-- The CXX compiler identification is GNU 9.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/user/my_first_project/build

看到"Configuring done"和"Generating done"就说明CMake配置成功了。然后make一下:

Scanning dependencies of target my_app
[ 50%] Building C object CMakeFiles/my_app.dir/main.c.o
[100%] Linking C executable my_app

最后运行:

./my_app
Hello, CMake!

看到"Hello, CMake!"了吗?恭喜你,第一个CMake项目跑通了!

4. 避坑指南:我踩过的三个坑

做嵌入式这么多年,CMake的坑我踩过不少。这里分享三个最常见的:

坑一:忘记创建build目录

直接在源码目录下运行cmake,会把生成的Makefile和中间文件跟源码混在一起。清理起来非常麻烦。我曾经有一次不小心把build目录删错了,结果源码也被误删了……从那以后,我坚持用out-of-source build。

坑二:cmake_minimum_required版本写错

如果你用了某个CMake特性,但版本写低了,CMake不会报错,但行为可能不符合预期。反过来,版本写高了,老环境的用户就没法用。我建议:先用你本地CMake版本,等确认项目稳定后,再适当降低版本号。

坑三:add_executable里漏了源文件

这个错误很隐蔽。比如你新增了一个uart.c,但忘了加到add_executable里。编译不会报错,但链接时会提示"undefined reference"。排查起来很费时间。我的习惯是:每加一个源文件,立刻更新CMakeLists.txt。

5. 知识体系总览

下面这张图,把本章的核心逻辑串起来了:

第一个CMake项目知识体系 cmake_minimum_required 指定CMake最低版本 project() 定义项目名称和语言 add_executable() 生成可执行文件 CMakeLists.txt → CMake配置 → Makefile → make编译 可执行文件 my_app 常见坑:忘记build目录 | 版本写错 | 漏源文件

从图上可以清楚看到:三个核心命令定义了项目的基本信息,CMake根据这些信息生成Makefile,最后make编译出可执行文件。整个过程环环相扣,缺一不可。

6. 总结

今天咱们做了三件事:

  • 写了一个最简单的CMakeLists.txt,用了三个命令
  • 用cmake + make完成了构建
  • 运行了生成的可执行文件

别看现在项目小,这套流程跟大型嵌入式项目完全一样。后面章节我们会逐步加入库管理、交叉编译、单元测试等高级功能,但核心骨架就是今天这三行命令。

嗯,动手试试吧。把代码敲一遍,感受一下CMake的工作流。遇到问题别慌,对照着避坑指南检查一下,多半能解决。

公众号:蓝海资料掘金营,微信deep3321