当前位置: 首页 > news >正文

41. CMake

现在学到了嵌入式 / C 高级开发的一个关键技能 —— CMake

CMake 是现代 C/C++ 工程的灵魂工具,几乎所有大型项目(包括 Linux、ROS、Qt、OpenCV、TensorFlow)都用它。

我会从零开始,系统、深入地教你掌握 CMake ——
让你从 “听说过” → “能看懂” → “能写” → “能设计复杂工程”。


🧩 一、CMake 是什么?为什么要用它?

✅ 一句话解释:

CMake 是一个跨平台的自动化构建系统生成器。


🔍 问题背景:传统方式的痛点

在学 gccMakefile 时我们可能这样写:

gcc main.c add.c sub.c -o calc

或写个 Makefile

calc: main.o add.o sub.ogcc main.o add.o sub.o -o calc

这在小项目没问题,但当工程变大时会出问题:

问题说明
📦 工程文件太多手动维护 Makefile 太累
⚙️ 平台差异Windows、Linux、ARM 参数不一样
🧩 第三方库依赖依赖路径、库版本复杂
🔁 可移植性差切换平台要重新改 Makefile

✅ CMake 解决的痛点:

CMake 不直接编译代码,它会:

自动生成对应平台的 Makefile 或 Visual Studio 工程文件。

然后你再用 make 或 IDE 编译。

流程如下:

CMakeLists.txt ──→ CMake ──→ 生成 Makefile ──→ make ──→ 编译程序

🧱 二、CMake 的基本使用流程

假设项目结构如下:

project/
├── CMakeLists.txt
├── main.c
├── add.c
└── add.h

1️⃣ 写一个最简单的 CMakeLists.txt

# 指定最小 CMake 版本
cmake_minimum_required(VERSION 3.10)# 工程名称
project(MyCalc C)# 指定源文件并生成可执行文件
add_executable(calc main.c add.c)

2️⃣ 编译运行

mkdir build
cd build
cmake ..
make

CMake 会自动生成 Makefile,然后 make 负责编译。

结果:

[100%] Built target calc

生成的可执行文件在 build/calc

✅ 这是最基础的 CMake 使用方式。


🧠 三、CMake 基本语法讲解

指令作用示例
cmake_minimum_required(VERSION 3.10)指定最低 CMake 版本
project(<name> [LANGUAGES])声明项目名project(MyApp C CXX)
add_executable(<name> [sources...])生成可执行文件add_executable(app main.c)
`add_library( [STATICSHARED] [sources…])`生成库文件add_library(math STATIC add.c)
target_link_libraries(target lib1 lib2)链接库文件target_link_libraries(app math)
include_directories(<dir>)添加头文件路径include_directories(include)
link_directories(<dir>)添加库路径link_directories(lib)
set(var value)设置变量set(SRC main.c add.c)
${var}引用变量${SRC}

⚙️ 四、变量与目录结构实战

示例工程:

project/
├── src/
│   ├── main.c
│   ├── add.c
│   └── sub.c
├── include/
│   └── calc.h
└── CMakeLists.txt

CMakeLists.txt:

cmake_minimum_required(VERSION 3.10)
project(MyCalc C)# 添加头文件目录
include_directories(include)# 定义源文件变量
set(SRCsrc/main.csrc/add.csrc/sub.c
)# 生成可执行文件
add_executable(calc ${SRC})

🧩 五、构建静态库与动态库

1️⃣ 生成静态库 .a

add_library(math STATIC add.c sub.c)
add_executable(calc main.c)
target_link_libraries(calc math)

2️⃣ 生成动态库 .so

add_library(math SHARED add.c sub.c)
add_executable(calc main.c)
target_link_libraries(calc math)

区别:

  • STATIC.a
  • SHARED.so

CMake 会自动处理链接。


🧠 六、多级目录工程

结构:

project/
├── CMakeLists.txt
├── src/
│   ├── CMakeLists.txt
│   ├── main.c
│   ├── add.c
│   └── sub.c
└── include/└── calc.h

根目录 CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(MyCalc C)
add_subdirectory(src)

子目录 src/CMakeLists.txt

include_directories(../include)
add_executable(calc main.c add.c sub.c)

👉 这样大型项目就可以模块化管理。


🔍 七、条件控制与平台判断

if(WIN32)message("This is Windows")
elseif(UNIX)message("This is Linux")
endif()

还能根据编译器判断:

if(CMAKE_C_COMPILER_ID STREQUAL "GNU")message("Using GCC")
endif()

🧰 八、调试与信息输出

message(STATUS "Project source dir: ${PROJECT_SOURCE_DIR}")
message(WARNING "This is a warning")
message(FATAL_ERROR "Stop building")

🧩 九、CMake 构建选项

CMake 支持定义选项(类似宏开关):

option(USE_MYMATH "Use custom math library" ON)if(USE_MYMATH)add_subdirectory(math)target_link_libraries(calc mymath)
endif()

启用或禁用:

cmake -DUSE_MYMATH=ON ..

🧠 十、安装与导出

install(TARGETS calc DESTINATION bin)
install(FILES include/calc.h DESTINATION include)

执行:

make install

会将可执行文件复制到 /usr/local/bin


🧩 十一、CMake + Cross Compile(交叉编译)

在嵌入式系统中,CMake 非常方便地支持交叉编译。

创建一个工具链文件 arm-toolchain.cmake

SET(CMAKE_SYSTEM_NAME Linux)
SET(CMAKE_SYSTEM_PROCESSOR arm)SET(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
SET(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)

然后使用:

cmake -DCMAKE_TOOLCHAIN_FILE=arm-toolchain.cmake ..
make

这样生成的二进制就可以在 ARM 板上运行。


📦 十二、CMake 与第三方库(如 OpenCV)

find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
add_executable(demo main.cpp)
target_link_libraries(demo ${OpenCV_LIBS})

🧱 十三、CMake 的现代写法(target思想)

现代 CMake(3.10+)推荐用 target 而非全局命令:

add_library(math STATIC add.c)
target_include_directories(math PUBLIC include)add_executable(calc main.c)
target_link_libraries(calc PRIVATE math)

👉 优点:

  • 更清晰的依赖关系
  • 避免全局污染
  • 支持大型工程与多目标构建

🧠 十四、学习路径总结

阶段学习内容示例
入门单文件项目add_executable
进阶模块化工程add_subdirectory
实战静态库 / 动态库add_library
高级条件控制 + 变量if / set
专业交叉编译 + 外部库toolchain / find_package
现代target-based 写法target_link_libraries

🚀 十五、练习建议

1️⃣ 写一个支持选项开关的程序:

cmake -DUSE_DEBUG=ON ..

2️⃣ 构建一个包含静态库的工程
3️⃣ 尝试交叉编译到 ARM 平台
4️⃣ 用 find_package(OpenCV REQUIRED) 编译图像项目


是否希望我下一步继续教你:

🔧 “手写一个完整的多模块 CMake 工程实战”
包含静态库、头文件、条件宏、可执行文件和交叉编译适配?

非常好 ✅
你已经掌握了 CMake 的基础与理论,接下来我们进入 实战阶段
我们要 从零手写一个完整的多模块 CMake 工程,涵盖:

  • 📦 多目录结构
  • ⚙️ 静态库与头文件管理
  • 🧩 CMake 变量与选项控制
  • 🪛 条件编译
  • 🔧 可移植构建(含交叉编译)

🧱 一、项目目标

我们要写一个简单的计算器程序:

calc = add + sub + main

结构清晰、可扩展,还能打包安装。


🗂️ 二、项目目录结构

CalcProject/
├── CMakeLists.txt        # 顶层配置
├── include/              # 公共头文件
│   └── calc.h
├── src/                  # 主程序
│   ├── main.c
│   └── CMakeLists.txt
├── math/                 # 数学库模块
│   ├── add.c
│   ├── sub.c
│   ├── CMakeLists.txt
│   └── math.h
└── build/                # 构建输出目录(自动生成)

🧩 三、顶层 CMakeLists.txt

# 1. 指定最低版本
cmake_minimum_required(VERSION 3.10)# 2. 声明工程
project(CalcProject C)# 3. 添加子目录(模块)
add_subdirectory(math)
add_subdirectory(src)# 4. 安装规则(可选)
install(DIRECTORY include/ DESTINATION include)

🔢 四、math 模块(生成静态库)

文件:math/CMakeLists.txt

# 指定头文件目录
include_directories(${PROJECT_SOURCE_DIR}/include)# 源文件
set(MATH_SRC add.c sub.c)# 生成静态库 libmath.a
add_library(math STATIC ${MATH_SRC})# 公开头文件(modern CMake 写法)
target_include_directories(math PUBLIC ${PROJECT_SOURCE_DIR}/include)

math.h

#ifndef MATH_H
#define MATH_Hint add(int a, int b);
int sub(int a, int b);#endif

add.c

#include "math.h"
int add(int a, int b) {return a + b;
}

sub.c

#include "math.h"
int sub(int a, int b) {return a - b;
}

💻 五、src 模块(生成可执行文件)

文件:src/CMakeLists.txt

# 指定头文件目录
include_directories(${PROJECT_SOURCE_DIR}/include)# 源文件
set(SRC main.c)# 生成可执行文件
add_executable(calc ${SRC})# 链接 math 库
target_link_libraries(calc PRIVATE math)# 安装规则
install(TARGETS calc DESTINATION bin)

main.c

#include <stdio.h>
#include "math.h"int main() {int a = 8, b = 3;printf("%d + %d = %d\n", a, b, add(a, b));printf("%d - %d = %d\n", a, b, sub(a, b));return 0;
}

🧰 六、编译与运行

mkdir build
cd build
cmake ..
make
./src/calc

输出:

8 + 3 = 11
8 - 3 = 5

恭喜!🎉
你现在已经构建了一个多模块工程。


🧠 七、添加选项控制(宏开关)

我们希望能通过选项启用调试模式:

修改顶层 CMakeLists.txt

option(USE_DEBUG "Enable debug info" OFF)if(USE_DEBUG)message(STATUS "Debug mode enabled")add_compile_definitions(DEBUG_MODE)
endif()add_subdirectory(math)
add_subdirectory(src)

修改 main.c

#include <stdio.h>
#include "math.h"int main() {int a = 8, b = 3;printf("%d + %d = %d\n", a, b, add(a, b));printf("%d - %d = %d\n", a, b, sub(a, b));#ifdef DEBUG_MODEprintf("[DEBUG] Program finished successfully.\n");
#endifreturn 0;
}

构建时启用:

cmake -DUSE_DEBUG=ON ..
make

⚙️ 八、支持动态库(.so)

math/CMakeLists.txt 中:

add_library(math SHARED ${MATH_SRC})

CMake 会自动生成 libmath.so 并调整链接。


🧩 九、交叉编译支持(嵌入式)

创建工具链文件 toolchain-arm.cmake

SET(CMAKE_SYSTEM_NAME Linux)
SET(CMAKE_SYSTEM_PROCESSOR arm)SET(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
SET(CMAKE_FIND_ROOT_PATH /opt/arm/sysroot)

使用时:

cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain-arm.cmake ..
make

这样你生成的 calc 就可以在 ARM 板(比如嵌入式 Linux)运行。


🧱 十、CMake 输出路径控制(可选)

在顶层添加:

set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)

生成的文件结构:

build/
├── bin/calc
└── lib/libmath.a

🧩 十一、安装与卸载

安装:

sudo make install

默认安装到 /usr/local/bin/usr/local/include

卸载:

sudo xargs rm < install_manifest.txt

📦 十二、总结:你现在掌握的技能

模块内容
✅ 基础结构project / add_executable / add_library
🧩 模块化add_subdirectory、include_directories
⚙️ 控制编译option / if / define
📦 库管理STATIC / SHARED
🔧 可移植性toolchain 跨平台编译
🏗️ 安装install() 导出结构
🧠 现代化target_include_directories / target_link_libraries

🚀 高级 CMake 实战:构建一个嵌入式驱动框架(支持多平台交叉编译 + 动态配置)

我们现在进入 CMake 高级实战篇 ——
一步步教你构建一个可扩展的 嵌入式驱动框架工程

这一部分内容会帮你真正理解:

🔧 “CMake 不只是构建工具,它是跨平台自动化工程系统的核心。”


🧩 一、项目目标:跨平台嵌入式驱动框架

我们要创建一个框架,能:

  1. 在 PC(x86)上编译测试;
  2. 在 ARM 板上交叉编译;
  3. 根据硬件类型(比如 STM32、Raspberry Pi)自动切换驱动;
  4. 输出静态库或动态库;
  5. 一行命令完成构建、安装。

📦 二、目录结构

EmbeddedDriver/
├── CMakeLists.txt                # 顶层配置
├── include/
│   └── driver.h
├── core/
│   ├── driver_core.c
│   ├── CMakeLists.txt
├── drivers/
│   ├── stm32/
│   │   ├── stm32_driver.c
│   │   ├── CMakeLists.txt
│   ├── rpi/
│   │   ├── rpi_driver.c
│   │   ├── CMakeLists.txt
├── app/
│   ├── main.c
│   ├── CMakeLists.txt
└── toolchain-arm.cmake           # 交叉编译配置

⚙️ 三、顶层 CMakeLists.txt

cmake_minimum_required(VERSION 3.15)
project(EmbeddedDriver C)# 输出路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)# 添加 include 目录
include_directories(${PROJECT_SOURCE_DIR}/include)# 自定义选项
option(USE_STM32 "Use STM32 driver" OFF)
option(USE_RPI "Use Raspberry Pi driver" OFF)
option(BUILD_SHARED "Build shared library" OFF)# 条件判断:平台驱动选择
if(USE_STM32)message(STATUS "✅ Using STM32 driver")add_subdirectory(drivers/stm32)
elseif(USE_RPI)message(STATUS "✅ Using Raspberry Pi driver")add_subdirectory(drivers/rpi)
else()message(STATUS "⚙️ Default: generic driver")add_subdirectory(core)
endif()# 主程序
add_subdirectory(app)# 安装公共头文件
install(DIRECTORY include/ DESTINATION include)

🧱 四、核心模块 core/CMakeLists.txt

set(CORE_SRC driver_core.c)if(BUILD_SHARED)add_library(driver_core SHARED ${CORE_SRC})
else()add_library(driver_core STATIC ${CORE_SRC})
endif()target_include_directories(driver_core PUBLIC ${PROJECT_SOURCE_DIR}/include)

driver_core.c

#include "driver.h"
#include <stdio.h>void driver_init() {printf("Core driver initialized.\n");
}

⚙️ 五、STM32 驱动模块

drivers/stm32/CMakeLists.txt

set(STM32_SRC stm32_driver.c)add_library(driver_core STATIC ${STM32_SRC})
target_include_directories(driver_core PUBLIC ${PROJECT_SOURCE_DIR}/include)

stm32_driver.c

#include "driver.h"
#include <stdio.h>void driver_init() {printf("STM32 driver initialized.\n");
}

⚙️ 六、Raspberry Pi 驱动模块

drivers/rpi/CMakeLists.txt

set(RPI_SRC rpi_driver.c)add_library(driver_core STATIC ${RPI_SRC})
target_include_directories(driver_core PUBLIC ${PROJECT_SOURCE_DIR}/include)

rpi_driver.c

#include "driver.h"
#include <stdio.h>void driver_init() {printf("Raspberry Pi driver initialized.\n");
}

💻 七、主程序 app/CMakeLists.txt

set(APP_SRC main.c)add_executable(demo ${APP_SRC})
target_link_libraries(demo PRIVATE driver_core)install(TARGETS demo DESTINATION bin)

main.c

#include "driver.h"int main() {driver_init();return 0;
}

🧰 八、驱动头文件 include/driver.h

#ifndef DRIVER_H
#define DRIVER_Hvoid driver_init(void);#endif

⚙️ 九、编译与运行

🧩 默认编译(核心驱动)

mkdir build && cd build
cmake ..
make
./bin/demo

输出:

Core driver initialized.

🧩 切换为 STM32 驱动

cmake -DUSE_STM32=ON ..
make
./bin/demo

输出:

STM32 driver initialized.

🧩 构建动态库版本

cmake -DBUILD_SHARED=ON ..
make

输出:libdriver_core.so


🔧 十、交叉编译配置

创建 toolchain-arm.cmake

SET(CMAKE_SYSTEM_NAME Linux)
SET(CMAKE_SYSTEM_PROCESSOR arm)SET(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
SET(CMAKE_FIND_ROOT_PATH /opt/arm/sysroot)

构建:

cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain-arm.cmake -DUSE_STM32=ON ..
make

生成的可执行文件可直接拷贝到 ARM 板(如 STM32MP157、Raspberry Pi)运行。


🧠 十一、CMake 宏调试技巧

CMake 脚本中最常见的问题是“变量没生效”。
以下是常用调试命令:

命令用途
message(STATUS "变量X = ${X}")输出变量值
message(WARNING "警告内容")输出黄色警告
message(FATAL_ERROR "中止构建")输出错误并停止
cmake --trace-expand显示宏展开细节
cmake --debug-output显示 CMake 变量解析过程

示例:

message(STATUS "C compiler: ${CMAKE_C_COMPILER}")
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")

🧩 十二、驱动模板自动生成(CMake 脚本高级技巧)

CMake 可以自动生成驱动代码模板:

CMakeLists.txt 中加入:

set(DRIVER_NAME "custom")configure_file(${PROJECT_SOURCE_DIR}/template/driver_template.c.in${PROJECT_BINARY_DIR}/drivers/${DRIVER_NAME}_driver.c
)

模板文件 driver_template.c.in

#include "driver.h"
#include <stdio.h>void driver_init() {printf("${DRIVER_NAME} driver initialized.\n");
}

执行 CMake 后,会自动生成:

drivers/custom_driver.c

其中 ${DRIVER_NAME} 会被替换成 “custom”。


🚀 十三、总结:你现在已经掌握

能力内容
🧱 架构多模块分层工程结构
⚙️ 编译控制静态 / 动态库切换、条件驱动加载
💻 平台适配x86 与 ARM 双平台构建
🧠 宏调试message 与 trace 调试技巧
🔧 自动生成configure_file 模板机制
🪛 实战意义嵌入式系统驱动层统一构建框架

是否希望我下一步带你进入 「CMake + GCC + 交叉编译联合调试(gdbserver + QEMU 实战)」
—— 这部分会讲如何 在 PC 上远程调试 ARM 目标程序

http://www.dtcms.com/a/590130.html

相关文章:

  • 11.string(上)
  • 【开题答辩全过程】以 基于SpringBoot的智慧教育系统的设计与实现为例,包含答辩的问题和答案
  • 360永久免费建网站网站建设及空间
  • 轻松阅读漫画的利器——Kotatsu漫画阅读器
  • 婚纱外贸网站怎么用PS做珠宝网站
  • 新乡网站网站建设网页制作软件是什么
  • C#权威指南第9课:方法
  • fastjson中的原生反序列化漏洞
  • 网站弹屏广告怎么做的如何修改网站后台的用户名和密码
  • Spring中如何使用@Resource注解?
  • 高频面试八股文用法篇(十二)Java 包装类缓存机制
  • 【Envi遥感图像处理】019:影像自动配准操作
  • 杭州网站开发制作公司排名邹平做网站的公司
  • 做家装的网站classplus wordpress
  • IO接口基本结构与内容
  • 亲爱的redis你好
  • php搭建一个简单的网站做网站服装app
  • C++基于websocket的多用户网页五子棋 --- 认识依赖库
  • YOLOv5,YOLOv8替换激活函数
  • STM32外设学习--ADC模数转换器--笔记
  • 深圳网站开发建设服务公司网站推广软件排名
  • ArkTS多维度状态管理机制
  • 广西建设工程质量监督网站南京seo关键词优化资讯
  • 深圳建站公司有推荐的公司吗济南平台公司
  • 夏普比率和最大回撤公式推导及代码实现
  • win32k!xxxKeyEvent函数里面的win32k!xxxDoHotKeyStuff如何确定是CAD键的到来的
  • 网站建设课我要表白网站在线制作
  • 烟台网站建设 烟台网亿网络公司python培训学校
  • 计算机网络自顶向下方法41——网络层 自治系统内部的路由选择:开放最短路优先(OSPF)设置OSPF链路权值
  • HDFS分布式存储“入门教程“:从“文件上传“到“副本管理“,3步理解核心原理