CMake 编译工程入门教程
1. CMake 是什么
CMake 不是编译器,它是一个“工程构建配置工具”。
简单理解:
- GCC、Clang、MSVC 才是真正负责把代码编译成可执行文件的工具。
- Make、Ninja、Visual Studio 工程文件负责执行具体编译过程。
- CMake 负责根据
CMakeLists.txt自动生成这些构建文件。
也就是说,CMake 的作用是:
C/C++ 源码
↓
CMakeLists.txt
↓
cmake 生成构建系统
↓
make / ninja / msbuild 编译
↓
生成可执行文件或库文件
2. 最简单的 CMake 工程结构
假设我们要创建一个最简单的 C 工程:
hello_cmake/
├── CMakeLists.txt
└── main.c
其中:
CMakeLists.txt:CMake 工程配置文件main.c:程序源码文件
3. 编写 main.c
在工程目录下创建 main.c:
#include <stdio.h>
int main(void)
{
printf("Hello CMake!\n");
return 0;
}
4. 编写 CMakeLists.txt
在工程目录下创建 CMakeLists.txt:
cmake_minimum_required(VERSION 3.16)
project(hello_cmake C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
add_executable(hello_cmake main.c)
说明:
cmake_minimum_required(VERSION 3.16)
表示当前工程最低需要 CMake 3.16。
project(hello_cmake C)
表示工程名叫 hello_cmake,使用 C 语言。
set(CMAKE_C_STANDARD 11)
表示使用 C11 标准。
add_executable(hello_cmake main.c)
表示把 main.c 编译成一个可执行文件,名字叫 hello_cmake。
5. 编译工程
进入工程目录:
cd hello_cmake
创建构建目录:
mkdir build
cd build
执行 CMake 配置:
cmake ..
开始编译:
cmake --build .
运行程序:
./hello_cmake
如果看到:
Hello CMake!
说明工程编译成功。
6. 为什么要新建 build 目录
推荐使用这种方式:
hello_cmake/
├── CMakeLists.txt
├── main.c
└── build/
这样做叫“外部构建”。
好处是:
- 源码目录保持干净。
- 编译生成的中间文件都在
build目录。 - 删除
build目录就可以重新开始编译。 - 不会污染源码文件夹。
如果想清理工程,直接删除 build 文件夹即可:
rm -rf build
7. 多文件工程
实际项目通常不止一个 .c 文件。
例如:
multi_file_project/
├── CMakeLists.txt
├── main.c
├── led.c
└── led.h
7.1 main.c
#include <stdio.h>
#include "led.h"
int main(void)
{
led_init();
led_on();
printf("LED is on\n");
return 0;
}
7.2 led.h
#ifndef LED_H
#define LED_H
void led_init(void);
void led_on(void);
void led_off(void);
#endif
7.3 led.c
#include <stdio.h>
#include "led.h"
void led_init(void)
{
printf("LED init\n");
}
void led_on(void)
{
printf("LED on\n");
}
void led_off(void)
{
printf("LED off\n");
}
7.4 CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(multi_file_project C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
add_executable(multi_file_project
main.c
led.c
)
这里需要把所有参与编译的 .c 文件都写进去。
.h 文件一般不需要写进 add_executable(),因为头文件会通过 #include 被源文件引用。
8. 使用 include 目录
如果头文件单独放在 include 文件夹中,工程结构可以这样:
project/
├── CMakeLists.txt
├── src/
│ ├── main.c
│ └── led.c
└── include/
└── led.h
这时 CMakeLists.txt 可以写成:
cmake_minimum_required(VERSION 3.16)
project(my_project C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
add_executable(my_project
src/main.c
src/led.c
)
target_include_directories(my_project PRIVATE
include
)
说明:
target_include_directories(my_project PRIVATE include)
表示告诉编译器:
编译 my_project 的时候,到 include 目录里找头文件。
这样源码里就可以写:
#include "led.h"
而不需要写:
#include "../include/led.h"
9. 生成静态库
有时候我们希望把某些模块编译成库文件。
例如:
library_project/
├── CMakeLists.txt
├── src/
│ ├── main.c
│ └── led.c
└── include/
└── led.h
可以把 led.c 编译成静态库:
cmake_minimum_required(VERSION 3.16)
project(library_project C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
add_library(led STATIC
src/led.c
)
target_include_directories(led PUBLIC
include
)
add_executable(app
src/main.c
)
target_link_libraries(app PRIVATE
led
)
说明:
add_library(led STATIC src/led.c)
生成一个名为 led 的静态库。
target_link_libraries(app PRIVATE led)
表示可执行程序 app 需要链接 led 这个库。
10. Debug 和 Release 编译
CMake 常见构建类型有:
- Debug:调试版本,包含调试信息,优化较少。
- Release:发布版本,优化较高,运行速度更快。
10.1 Debug 编译
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build
10.2 Release 编译
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
其中:
-S .
表示源码目录是当前目录。
-B build
表示构建目录是 build。
这种写法比先 cd build 更清晰。
11. 指定编译器
有时候系统里有多个编译器,可以手动指定。
11.1 指定 GCC
cmake -S . -B build -DCMAKE_C_COMPILER=gcc
cmake --build build
11.2 指定 Clang
cmake -S . -B build -DCMAKE_C_COMPILER=clang
cmake --build build
C++ 工程还可以指定:
-DCMAKE_CXX_COMPILER=g++
或者:
-DCMAKE_CXX_COMPILER=clang++
12. C++ 工程示例
工程结构:
cpp_project/
├── CMakeLists.txt
└── main.cpp
12.1 main.cpp
#include <iostream>
int main()
{
std::cout << "Hello CMake C++!" << std::endl;
return 0;
}
12.2 CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(cpp_project CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(cpp_project main.cpp)
编译:
cmake -S . -B build
cmake --build build
运行:
./build/cpp_project
13. 常用 CMake 命令总结
13.1 配置工程
cmake -S . -B build
13.2 编译工程
cmake --build build
13.3 指定 Debug 模式
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
13.4 指定 Release 模式
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
13.5 清理重新编译
rm -rf build
cmake -S . -B build
cmake --build build
13.6 并行编译
cmake --build build -j
或者指定线程数:
cmake --build build -j8
14. 在 VS Code 中使用 CMake
推荐安装插件:
- C/C++
- CMake Tools
基本流程:
- 打开工程文件夹。
- 安装 CMake Tools 插件。
- 按
Ctrl + Shift + P。 - 输入
CMake: Configure。 - 选择编译器。
- 再执行
CMake: Build。 - 生成的程序一般在
build目录中。
如果 VS Code 提示选择 Kit,可以选择:
GCC
Clang
MinGW
Visual Studio
根据你电脑上安装的编译器决定。
15. Windows 下使用 CMake
Windows 下常见编译器有:
- MinGW-w64
- MSVC,也就是 Visual Studio 自带编译器
- Clang
15.1 使用 MinGW
cmake -S . -B build -G "MinGW Makefiles"
cmake --build build
15.2 使用 Visual Studio
cmake -S . -B build -G "Visual Studio 17 2022"
cmake --build build
生成的 .exe 文件一般在:
build/Debug/
或者:
build/Release/
16. Linux / Ubuntu 下使用 CMake
安装工具:
sudo apt update
sudo apt install build-essential cmake
检查版本:
gcc --version
cmake --version
编译:
cmake -S . -B build
cmake --build build
./build/hello_cmake
17. 嵌入式工程中的 CMake 思路
嵌入式项目也可以使用 CMake,例如 STM32、MSPM0、ESP32 等。
不过嵌入式工程通常需要额外配置:
- 交叉编译器,例如
arm-none-eabi-gcc - 链接脚本,例如
STM32F103C8Tx_FLASH.ld - 启动文件,例如
startup_stm32f103xb.s - 编译选项,例如 CPU 内核、FPU、优化等级
- 生成
.elf、.hex、.bin文件
嵌入式 CMake 的流程大概是:
源码
↓
CMakeLists.txt
↓
arm-none-eabi-gcc 编译
↓
生成 .elf
↓
objcopy 转换
↓
生成 .hex / .bin
↓
下载到单片机
18. STM32 CMake 示例思路
一个 STM32 工程结构可以类似这样:
stm32_project/
├── CMakeLists.txt
├── STM32F103C8Tx_FLASH.ld
├── startup_stm32f103xb.s
├── Core/
│ ├── Inc/
│ │ └── main.h
│ └── Src/
│ └── main.c
└── Drivers/
└── STM32F1xx_HAL_Driver/
CMake 中通常需要配置:
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
set(CMAKE_OBJCOPY arm-none-eabi-objcopy)
然后设置 MCU 参数:
set(MCU_FLAGS
-mcpu=cortex-m3
-mthumb
)
再设置链接脚本:
set(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/STM32F103C8Tx_FLASH.ld)
链接参数示例:
target_link_options(app.elf PRIVATE
${MCU_FLAGS}
-T${LINKER_SCRIPT}
-Wl,-Map=${PROJECT_NAME}.map
-Wl,--gc-sections
)
最后生成 .hex 和 .bin:
add_custom_command(TARGET app.elf POST_BUILD
COMMAND ${CMAKE_OBJCOPY} -O ihex app.elf ${PROJECT_NAME}.hex
COMMAND ${CMAKE_OBJCOPY} -O binary app.elf ${PROJECT_NAME}.bin
)
19. 常见错误和解决方法
19.1 cmake: command not found
说明没有安装 CMake。
Ubuntu:
sudo apt install cmake
Windows:
需要安装 CMake,并把 CMake 加入环境变量。
19.2 No CMAKE_C_COMPILER could be found
说明没有找到 C 编译器。
Ubuntu:
sudo apt install build-essential
Windows:
需要安装 MinGW 或 Visual Studio。
19.3 CMakeLists.txt 找不到
如果执行:
cmake ..
报错找不到 CMakeLists.txt,说明你当前目录位置不对。
正确结构应该是:
project/
├── CMakeLists.txt
└── build/
然后在 build 目录中执行:
cmake ..
或者直接在工程根目录执行:
cmake -S . -B build
19.4 头文件找不到
错误类似:
fatal error: led.h: No such file or directory
解决方法是在 CMakeLists.txt 中添加头文件路径:
target_include_directories(app PRIVATE
include
)
19.5 undefined reference to xxx
这个错误一般表示:
- 函数声明了,但是没有实现。
- 对应的
.c文件没有加入编译。 - 库文件没有链接。
例如 led_on() 在 led.c 中实现了,但是 CMakeLists.txt 只写了:
add_executable(app main.c)
这时应该改成:
add_executable(app
main.c
led.c
)
20. 推荐的 CMake 工程模板
普通 C 工程可以使用这个结构:
my_project/
├── CMakeLists.txt
├── README.md
├── include/
│ └── led.h
├── src/
│ ├── main.c
│ └── led.c
└── build/
推荐 CMakeLists.txt:
cmake_minimum_required(VERSION 3.16)
project(my_project C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
add_executable(${PROJECT_NAME}
src/main.c
src/led.c
)
target_include_directories(${PROJECT_NAME} PRIVATE
include
)
编译命令:
cmake -S . -B build
cmake --build build
./build/my_project
21. 学习建议
初学 CMake 时,不要一开始就写复杂工程。
建议按这个顺序学习:
- 单文件编译。
- 多
.c文件编译。 - 添加头文件目录。
- 生成静态库。
- 链接第三方库。
- 区分 Debug 和 Release。
- 再学习交叉编译和嵌入式 CMake。
对于普通 C/C++ 项目,掌握下面几个命令基本就够用:
cmake_minimum_required()
project()
add_executable()
add_library()
target_include_directories()
target_link_libraries()
target_compile_options()
target_link_options()
22. 总结
CMake 的核心思想是:
用 CMakeLists.txt 描述工程结构,
再让 CMake 自动生成对应平台的构建文件。
最常用的编译流程是:
cmake -S . -B build
cmake --build build
最常用的工程配置是:
cmake_minimum_required(VERSION 3.16)
project(my_project C)
add_executable(my_project
src/main.c
)
如果工程有头文件目录,就加:
target_include_directories(my_project PRIVATE
include
)
如果工程有库,就加:
target_link_libraries(my_project PRIVATE
library_name
)
视频教程请观看:Cmake管理工程_哔哩哔哩_bilibili
掌握这些内容后,就可以独立搭建大部分 C/C++ 的 CMake 编译工程。
