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

CMake 入门指南:从零开始配置你的第一个项目

目录

一、CMake 是什么,为什么要使用 CMake

二、CMakeLists.txt 文件结构与简单示例

三、进阶的CMake

四、静态库与动态库生成及其使用

五、注释的语法

六、 set、list、message 三个常用的 CMake 函数与命令

七、CMake 的控制语句以及自定义宏/函数

八、为STM32工程进行CMake(嵌套CMake)


一、CMake 是什么,为什么要使用 CMake

在学习 CMake 之前,我们先来思考一个问题:我们为什么需要构建工具?

在 C/C++ 项目中,通常我们写好源代码后,并不是直接能运行的,需要经过编译、链接等步骤才能变成可执行程序。对于简单的项目,手动使用 gccg++ 进行编译也许问题不大,但项目一旦变复杂,手动维护这些编译命令就变得痛苦不堪。这时候,我们就需要一个构建系统来自动化这些操作。

那 Make 和 Makefile 不够用吗?

Make 和 Makefile 确实能解决这个问题,但它们存在一些痛点:

Makefile 语法晦涩,难以维护。

不同平台的差异大,移植性差。

难以和现代 IDE 或构建工具链(如 Ninja、MSVC)协作。

CMake 正是为了解决这些问题而生的

CMake 是一个跨平台的自动化构建系统生成工具。 它本身并不直接构建项目,而是生成本地平台的构建系统(比如 Makefile、Ninja 构建脚本,或者 Visual Studio 的工程文件),然后你再使用这些文件进行实际构建。

简单来说,CMake 的职责是生成工程的“构建说明”。

为什么要使用 CMake?

  • 跨平台:支持 Linux、Windows、macOS 甚至嵌入式系统。

  • 模块化管理项目结构:项目大了以后,管理源文件、库、头文件路径变得容易。

  • 与现代 IDE 兼容良好:如 CLion、Visual Studio、VS Code 等。

  • 支持多种构建工具链:如 Make、Ninja、MSBuild。

  • 便于持续集成:CI/CD 环境中广泛使用。

  • 支持条件编译、可选模块、外部依赖等高级特性

二、CMakeLists.txt 文件结构与简单示例

顶层的 CMakeLists.txt 定义项目的全局配置,

现在有一个main.cpp

// main.cpp
#include <iostream>int main() {std::cout << "Hello, World!" << std::endl;return 0;
}

为了将其进行编译,先进行cmake的版本进行查询

cmake --version  #查询cmake版本,低版本无法兼容高版本

可以看到cmake的版本是3.10.2

在main.cpp同目录下,编写一个简单的CMakeLists.txt,内容如下:

# 顶层 CMakeLists.txt
cmake_minimum_required(VERSION 3.10)      # 指定 CMake 最低版本要求
project(project_name LANGUAGES CXX)         # 定义项目名称(project_name)和语言(CXX->C++)
add_executable(hello main.cpp)            # 添加可执行文件目标(依据main.cpp将生成一个名为hello的可执行文件 )

 再执行

cmake ./        # ”./ “是指CMakeLists.txt的地址,./就是指向本目录,也可以写出cmake .

 执行命令“ls"可以看到cmake指令生成了很多东西:

CMakeCache.txt

作用:
这是 CMake 的缓存文件,用来保存你的配置选项,比如编译器路径、构建选项、库文件路径等。
如果你修改了 CMakeLists.txt 中的配置或外部依赖,建议删除这个文件重新运行 cmake

CMakeFiles/

作用:
这是一个目录,存储 CMake 在配置过程中生成的所有中间文件、依赖信息、目标描述等内容。
比如每个 .cpp 文件会对应一些 .o 文件,链接信息也会写在这里。

建议:
这个目录一般不需要手动修改。你可以用 make clean 或直接删除它来清理构建文件。

cmake_install.cmake

作用:
这个文件由 CMake 自动生成,用于描述如何安装该项目。它是 make install 所依赖的脚本文件。

内容包含:

  • 文件如何被复制到安装路径

  • 安装路径

  • 权限设置等信息

如果你没有写安装规则,这个文件会比较简单

Makefile

作用:
这是最核心的构建文件,CMake 会把你在 CMakeLists.txt 中定义的目标(如 add_executable)转成这个 Makefile,然后通过 GNU Make 编译你的项目。

Make 会根据这个文件中的规则编译目标文件(.o)并链接生成最终可执行文件或库。

 再执行:

make   #依据 Makefile规则编译目标文件(.o)并链接生成最终可执行文件或库

可以看到生成了可执行文件”hello",执行它看看:

 

好了,这样就完成了一个简单的cmake过程。

三、进阶的CMake

接下来创建4个文件夹

 mkdir include src build out 

include/ —— 头文件目录 存放项目中需要对外暴露的头文件

src/ —— 源代码目录 存放所有源代码

build/ —— 构建输出目录(中间文件)存放 cmake 生成的中间构建文件

out/ —— 最终输出目录(可执行文件或库) 放置最终生成的产物

之后将在include与scr中创建如下.h文件与.cpp文件:

touch head1.h head2.h head3.h head4.h  #在include目录下执行创建头文件

mv main.cpp ./src #将main.cpp移植到src下

touch app1.cpp app2.cpp app3.cpp  #在src下创建源程序文件

 include/head1.h

#pragma once
void func1();

 include/head2.h

#pragma once
void func2();

 include/head3.h

#pragma once
void func3();

include/head4.h

#pragma once
void func4();

 src/app1.cpp

#include "head1.h"
#include <iostream>void func1() {std::cout << "This is func1()" << std::endl;
}

src/app2.cpp

#include "head2.h"
#include <iostream>void func2() {std::cout << "This is func2()" << std::endl;
}

 src/app3.cpp

#include "head3.h"
#include "head4.h"
#include <iostream>void func3() {std::cout << "This is func3()" << std::endl;
}void func4() {std::cout << "This is func4()" << std::endl;
}

 src/main.cpp

#include "head1.h"
#include "head2.h"
#include "head3.h"
#include "head4.h"int main() {func1();func2();func3();func4();return 0;
}

 编写CMakeLists.txt

cmake_minimum_required(VERSION 3.10)# 项目名称
project(MyApp)# 包含头文件目录
include_directories(${CMAKE_SOURCE_DIR}/include)# 设置源文件路径
file(GLOB_RECURSE SOURCES "${CMAKE_SOURCE_DIR}/src/*.cpp")# 输出路径
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/out)# 生成可执行文件
add_executable(my_app ${SOURCES})

看到这个CMakeLists.txt是不是有点懵。

现在就来 详细讲解上面那个案例中的每一个 CMake 函数

1. cmake_minimum_required

这是每个 CMake 项目的“起点指令”,告诉 CMake 最低支持的版本是多少。

有什么用:

确保 CMake 使用你指定的版本特性来处理脚本。如果用户的 CMake 太旧,就会报错而不是静默失败。

怎么用:

cmake_minimum_required(VERSION 3.10)

小贴士:

  • 如果你用到 target_include_directoriestarget_compile_features 等高级特性,一定要升级版本!

  • 常见版本推荐:3.10 是 Ubuntu 18.04 默认的,3.16+ 支持很多现代功能。


2. project

设置工程的名称、版本号、语言类型等。

有什么用:

告诉 CMake“我这个项目叫啥、用的语言是啥”,并设置一些默认变量。

怎么用:

project(MyApp)

或带语言:

project(MyApp LANGUAGES C CXX)

小贴士:

  • 你可以用 PROJECT_NAMEPROJECT_SOURCE_DIR 等变量,它们是 project() 自动生成的。

  • 可以加上版本号,比如:

    project(MyApp VERSION 1.2.3)
    

 3. include_directories

添加头文件的搜索路径,相当于 g++ -I

有什么用:

让编译器能找到 #include "xxx.h" 所引用的头文件。

怎么用:

include_directories(${CMAKE_SOURCE_DIR}/include)

小贴士:

  • 不推荐全局用太多 include_directories,用 target_include_directories 会更优雅。

  • ${CMAKE_SOURCE_DIR} 是项目根目录路径的变量(后面讲)。


4. file(GLOB_RECURSE ...)

读取目录下所有(符合匹配规则的)文件。
GLOB_RECURSE 代表“递归地查找”。

 有什么用:

让你不用手动一行一行列出源文件名。

 怎么用:

file(GLOB_RECURSE SOURCES "${CMAKE_SOURCE_DIR}/src/*.cpp")

表示把 src/ 目录下所有 .cpp 文件放进变量 SOURCES

小贴士:

  • 如果你新增了 .cpp 文件,CMake 不会自动重新扫描,必须手动 cmake .. 一次。

  • 你也可以用 file(GLOB ...) 只查当前目录。


 5. set

设置变量的值。

有什么用:

定义变量路径、开关等,后面都可以复用。

怎么用:

set(EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/out)

表示将编译生成的可执行文件放在 out/ 目录。

小贴士:

  • 你可以用 message(STATUS "路径:${EXECUTABLE_OUTPUT_PATH}") 打印变量。

  • set(VAR "value" CACHE TYPE "描述") 可以让变量出现在 GUI 配置器中。


6. add_executable

创建一个可执行程序。

有什么用:

把你写的 .cpp 文件编译链接成可执行文件。

怎么用:

add_executable(my_app ${SOURCES})

这里 my_app 是目标名称,也是生成的程序名;${SOURCES} 是所有源文件。

小贴士:

  • 如果你只写一两个文件,也可以直接列出:

    add_executable(my_app main.cpp app1.cpp)
    
  • my_app 后续可以用在其他函数里,比如加头文件、库文件。


 7. CMake内置变量

CMAKE_SOURCE_DIR是一个CMake内置变量

是 CMake 的顶层项目根目录

 有什么用:

配合 include_directories()file() 等引用项目路径。

怎么用:

include_directories(${CMAKE_SOURCE_DIR}/include)

小贴士:

  • 如果用多级 CMake,可以注意还有个变量 CMAKE_CURRENT_SOURCE_DIR 表示“当前目录”。

CMake内置变量有很多,如下:

路径相关内置变量

变量名说明
CMAKE_SOURCE_DIR最顶层 CMakeLists.txt 所在目录(源代码根目录)
CMAKE_CURRENT_SOURCE_DIR当前处理的 CMakeLists.txt 所在目录
CMAKE_BINARY_DIRCMake 执行构建命令所在目录(一般是 build/
CMAKE_CURRENT_BINARY_DIR当前处理的 CMakeLists.txt 对应的构建输出目录
PROJECT_SOURCE_DIRproject() 函数所在 CMakeLists.txt 的目录,通常等同于 CMAKE_SOURCE_DIR
PROJECT_BINARY_DIRproject() 执行后对应的构建输出目录,通常等同于 CMAKE_BINARY_DIR

输出目录相关变量(现代推荐)

变量名说明
CMAKE_RUNTIME_OUTPUT_DIRECTORY可执行文件的输出目录(比 EXECUTABLE_OUTPUT_PATH 更推荐)
CMAKE_LIBRARY_OUTPUT_DIRECTORY动态库(.so/.dll)的输出目录
CMAKE_ARCHIVE_OUTPUT_DIRECTORY静态库(.a/.lib)的输出目录
EXECUTABLE_OUTPUT_PATH老版本可执行文件输出目录
LIBRARY_OUTPUT_PATH老版本库文件输出目录

编译器相关变量

变量名说明
CMAKE_C_COMPILERC 编译器路径
CMAKE_CXX_COMPILERC++ 编译器路径
CMAKE_C_FLAGS传递给 C 编译器的选项
CMAKE_CXX_FLAGS传递给 C++ 编译器的选项
CMAKE_BUILD_TYPE编译类型(如:Debug / Release)
CMAKE_CXX_STANDARD指定 C++ 标准,例如 11、14、17、20
CMAKE_SYSTEM_NAME操作系统名,如 Linux、Windows、Darwin
CMAKE_SYSTEM_PROCESSOR架构,如 x86_64、ARM、aarch64

项目和目标相关

变量名说明
PROJECT_NAMEproject() 指定的项目名称
PROJECT_VERSIONproject(... VERSION 1.2.3) 设置的版本
CMAKE_PROJECT_NAME根项目名称(区别于子项目)
CMAKE_VERBOSE_MAKEFILE如果为 ON,生成详细的 Makefile,编译时能看到完整命令
CMAKE_INSTALL_PREFIX安装的根目录,默认为 /usr/local

测试 & 环境变量(进阶)

变量名说明
CMAKE_TESTING_ENABLED是否启用了 enable_testing()
CMAKE_INSTALL_RPATH动态链接库的运行路径设置
CMAKE_MODULE_PATH查找自定义 CMake 模块的路径
CMAKE_EXPORT_COMPILE_COMMANDS是否导出 compile_commands.json(给 clangd / LSP 用)

常用内置变量推荐(你能马上用的)

类别建议使用变量
路径CMAKE_SOURCE_DIR, CMAKE_BINARY_DIR, CMAKE_CURRENT_SOURCE_DIR
输出目录CMAKE_RUNTIME_OUTPUT_DIRECTORY(可执行)
CMAKE_LIBRARY_OUTPUT_DIRECTORY(动态库)
编译控制CMAKE_CXX_STANDARD, CMAKE_BUILD_TYPE, CMAKE_CXX_FLAGS
安装路径CMAKE_INSTALL_PREFIX
编译器路径CMAKE_C_COMPILER, CMAKE_CXX_COMPILER

好了,继续吧

进入到build下执行cmake与make

cd build && cmake .. && make

可以看到cmake生成的中间文件都放在了build里面,再到out下可以看到生成的my_app文件,执行它:

cd out 

./my_app

可以看到main.cpp成功调用了app1~3的代码。

四、静态库与动态库生成及其使用

好了,接下来继续静态库与动态库的生成吧


什么是静态库(Static Library)

静态库(以 .a.lib 结尾)是一种在编译阶段就被打包进最终可执行文件的库文件

生成方式:ar 工具打包 .o 文件(Linux 下 .a,Windows 是 .lib

链接时机: 编译时链接,最终 .exe.out 文件包含了所有库代码

部署方式: 最终可执行文件独立运行,不再依赖外部 .a 文件

 举例:你编译一个 main.cpp 链接 libmath.a,最后 main 程序会包含 math 库代码,运行时不需要带上 libmath.a

什么是动态库(Dynamic Library)

动态库(以 .so.dll 结尾)是一种在程序运行时才加载的共享库文件

生成方式: 编译为 .so(Linux)或 .dll(Windows)

链接时机:编译时生成依赖信息(例如 .so 名字)运行时由操作系统动态加载

部署方式: 可执行程序运行时必须能访问 .so 文件,否则会报错

 举例:你编译 main.cpp 链接 libmath.somain 程序运行时会查找并加载它,若 .so 不在路径中就会失败。


区别总结对比

常见使用场景对比

使用静态库的场景:

嵌入式设备开发(如 STM32、树莓派):不方便部署 .so

编译时确保功能完整

对启动速度有极致要求

程序发布不希望依赖任何额外文件

使用动态库的场景:

多个程序共享同一个库(节省空间)

希望随时升级功能模块(比如:游戏引擎插件、浏览器)

需要插件系统或模块热更新

构建跨平台框架(Qt、OpenCV、Python 模块等)


总结一句话:

静态库 = 程序自带所有代码,运行独立;
动态库 = 程序轻巧灵活,运行依赖共享模块。


好了,项目开始把

首先,在out下创建lib与bin

 mkdir lib bin #lib由于存放库文件,bin用于存放链接的可执行文件

然后再创建并编写include/math_utils.h

#ifndef MATH_UTILS_H
#define MATH_UTILS_Hint add(int a, int b);
int square(int a);#endif

src/math_utils.cpp

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

src/main.cpp

#include <iostream>
#include "math_utils.h"int main() {std::cout << "3 + 4 = " << add(3, 4) << std::endl;std::cout << "5 squared = " << square(5) << std::endl;return 0;
}

math_utils.h/cpp:封装一些数学函数(加法、平方等)

编译生成:

静态库:libmath_utils.a

动态库:libmath_utils.so

可执行文件:main(链接静态库或动态库)

CMakeLists.txt

# 指定所需的最低 CMake 版本
cmake_minimum_required(VERSION 3.10)# 定义当前工程的名称为 MyMathProject
project(MyMathProject)# 设置使用的 C++ 标准版本为 C++11
# 若系统支持更高版本,可以改为 17 或 20
set(CMAKE_CXX_STANDARD 11)# 设置可执行文件输出目录为 out/bin(运行时的二进制文件)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/out/bin)# 设置库文件输出目录(包括静态库和动态库)为 out/lib
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/out/lib)   # .so 文件输出目录
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/out/lib)   # .a 文件输出目录# 添加头文件搜索路径(让编译器能找到 include/ 目录下的头文件)
include_directories(${CMAKE_SOURCE_DIR}/include)# 使用通配符收集 src/ 目录下所有 .cpp 源文件,保存到变量 SRC_FILES
# 虽然后面我们没有用到这个变量,它演示了如何批量获取源码文件
file(GLOB SRC_FILES src/*.cpp)#  静态库构建部分 --------------------------------------# 生成一个名为 math_utils_static 的静态库(libmath_utils.a)
# 只包含 src/math_utils.cpp,一个 cpp 文件也可以构成一个库
add_library(math_utils_static STATIC src/math_utils.cpp)# 设置该库的输出文件名为 math_utils
# 注意:这里设置的是逻辑输出名,最终输出是 libmath_utils.a(Linux)
set_target_properties(math_utils_static PROPERTIES OUTPUT_NAME "math_utils")# 动态库构建部分 --------------------------------------# 生成一个名为 math_utils_shared 的动态库(libmath_utils.so)
add_library(math_utils_shared SHARED src/math_utils.cpp)# 设置该库的输出文件名为 math_utils
# 同样最终输出为 libmath_utils.so
set_target_properties(math_utils_shared PROPERTIES OUTPUT_NAME "math_utils")#  可执行文件构建部分 -----------------------------------# 构建可执行文件 main_static,使用 main.cpp
add_executable(main_static src/main.cpp)# 链接静态库 math_utils_static 到 main_static 中
# 也就是说 math_utils.a 的代码会打包进 main_static 中
target_link_libraries(main_static math_utils_static)# 构建另一个可执行文件 main_shared,使用相同的 main.cpp
add_executable(main_shared src/main.cpp)# 链接动态库 math_utils_shared 到 main_shared
# 程序运行时需要找到 libmath_utils.so 才能启动成功
target_link_libraries(main_shared math_utils_shared)

在build下执行如下指令:

make clean  #清除生成make结果

cmake ..    

make

再到out下查看:

可以看到生成了这四个文件:

/out
├── bin/
│   ├── main_static     # 静态库链接的可执行文件
│   └── main_shared     # 动态库链接的可执行文件
└── lib/
    ├── libmath_utils.a   # 静态库文件
    └── libmath_utils.so  # 动态库文件

再对可执行文件继续执行:

好了,到这里,基本的cmake就结束了,下面进行补充与升级:

五、注释的语法

# 单行注释 - 使用 # 符号
#[[多行注释 - 使用  包围
]]

六、 setlistmessage 三个常用的 CMake 函数与命令

下面我将详细讲解 setlistmessage 三个常用的 CMake 函数与命令

message("这是一条很重要的消息")  # 普通消息(白色)
message(STATUS "这是状态消息")   # 状态消息(前缀带 --,绿色)
message(WARNING "这是警告消息") # 警告消息(黄色)
#message(FATAL_ERROR "致命错误") # 错误消息(红色,会终止构建)

( 一)、set() 命令

 作用:

set() 是 CMake 中最常用的命令之一,用于定义或修改变量的值。

基本语法:

set(<variable> <value>... [CACHE <type> <docstring> [FORCE]])

常见用法:

1. 定义普通变量
set(MY_NAME "Alice")
message(STATUS "Name is ${MY_NAME}")  # 输出:Name is Alice
2. 定义包含多个元素的变量(类似列表)
set(SOURCES main.cpp util.cpp helper.cpp)
3. 定义缓存变量(适合用户配置的变量)
set(USE_MATH_LIB ON CACHE BOOL "Use math library")
4. 修改已有变量的值
set(MY_NAME "Bob")  # 重新赋值
5. 设置环境变量
set(ENV{MY_PATH} "/usr/local/bin")

(二)、list() 命令

作用:

对 CMake 中的列表变量(空格分隔)进行各种操作,如添加、删除、搜索等。

 基本语法:

list(<COMMAND> <list_variable> [ARGS...])

 常用操作示例:

1. 添加元素到列表尾部
set(FRUITS apple banana)
list(APPEND FRUITS orange)
message(STATUS "Fruits: ${FRUITS}")  # 输出:apple;banana;orange
2. 插入元素到指定位置
list(INSERT FRUITS 1 mango)  # 插入 mango 到第2个位置
3. 移除指定元素
list(REMOVE_ITEM FRUITS banana)
4. 获取列表长度
list(LENGTH FRUITS FRUITS_COUNT)
message(STATUS "Fruits count: ${FRUITS_COUNT}")
5. 访问指定索引元素
list(GET FRUITS 0 FIRST_FRUIT)
message(STATUS "First fruit: ${FIRST_FRUIT}")

(三)、message() 命令

作用:

用于在配置阶段向终端输出信息,便于调试或传递信息给用户。

基本语法:

message([<mode>] "message to display")

 常用模式:

模式说明
STATUS正常信息(推荐调试输出)
WARNING警告信息
AUTHOR_WARNING针对开发者的警告
SEND_ERROR错误信息,但继续执行
FATAL_ERROR致命错误,立即停止 CMake
DEPRECATION弃用警告

示例:

1. 普通信息输出
message(STATUS "Compiling project...")
2. 警告信息
message(WARNING "This feature is experimental.")
3. 错误信息并中断
if(NOT DEFINED USER_OPTION)message(FATAL_ERROR "USER_OPTION is not defined!")
endif()

 示例:综合使用

cmake_minimum_required(VERSION 3.10)
project(DemoSetListMessage)# set 一个变量
set(MY_LIST "one" "two" "three")# list 增加一个元素
list(APPEND MY_LIST "four")# 获取长度
list(LENGTH MY_LIST LEN)
message(STATUS "List length: ${LEN}")  # 输出长度# 获取第一个元素
list(GET MY_LIST 0 FIRST_ITEM)
message(STATUS "First item: ${FIRST_ITEM}")# 输出整个列表
message(STATUS "Current list: ${MY_LIST}")# 检查某个条件
if(LEN GREATER 3)message(WARNING "List has more than 3 items")
endif()

 总结

命令用途
set()设置变量、缓存值、环境变量等
list()操作字符串列表
message()输出信息、调试、报错等

七、CMake 的控制语句以及自定义宏/函数


(一)、foreach 循环

用途:

用于遍历列表或范围,类似其他语言中的 for 循环。

语法:

foreach(var IN LISTS mylist)message(STATUS "Item: ${var}")
endforeach()

 示例:

遍历列表:
set(FRUITS apple banana orange)
foreach(fruit IN LISTS FRUITS)message(STATUS "Fruit: ${fruit}")
endforeach()
遍历整数区间:
foreach(i RANGE 1 5)message(STATUS "Number: ${i}")
endforeach()

(二)、if 条件语句

用途:

条件判断,控制执行逻辑。

 常见条件:

if(DEFINED VAR):是否定义

if(VAR STREQUAL "abc"):字符串比较

if(VAR MATCHES ".*test.*"):正则匹配

if(NOT ...):取反

if(EXISTS path):文件是否存在

示例:

set(MODE debug)if(MODE STREQUAL "debug")message(STATUS "Debug mode")
else()message(STATUS "Release mode")
endif()

(三)、option 配置选项

用途:

为用户提供编译选项,配合 cmake-guiccmake 或命令行传参使用。

语法:

option(USE_MY_LIB "Enable my library" ON)

示例:

option(ENABLE_LOG "Enable logging" OFF)if(ENABLE_LOG)add_definitions(-DENABLE_LOGGING)message(STATUS "Logging is enabled")
endif()

用户可用:

cmake -DENABLE_LOG=ON ..

(四)、macro 宏定义

用途:

定义一段可复用的 CMake 脚本片段,参数替换更简单(不支持局部作用域)。

📌 语法:

macro(print_var var)message(STATUS "Value of ${var}: [${${var}}]")
endmacro()

🔍 示例:

set(NAME "CMake")
print_var(NAME)

输出:Value of NAME: [CMake]


(五)、function 函数定义

用途:

功能与 macro 类似,但有局部作用域(更安全),推荐用函数替代宏

 语法:

function(print_var var)message(STATUS "Value: ${${var}}")
endfunction()

支持 return:

function(double_input INPUT OUTPUT)math(EXPR result "${INPUT} * 2")set(${OUTPUT} ${result} PARENT_SCOPE)
endfunction()double_input(5 MY_RESULT)
message(STATUS "Result: ${MY_RESULT}")

总结表

指令功能特点/备注
foreach遍历列表、范围支持 LISTS 和 RANGE
if条件判断支持字符串、文件、正则等
option用户可设置的布尔开关可用于条件编译
macro定义重复逻辑变量为全局作用域
function定义函数逻辑(推荐)支持局部作用域,推荐使用

八、为STM32工程进行CMake(嵌套CMake)

准备好stm32完整工程文件,

去 Keil / STM32CubeMX / IAR 工程里找 .ld 文件

 

将其拷贝到STM32中

安装gcc-arm-none-eabi工具链版

sudo apt-get update
sudo apt-get install gcc-arm-none-eabi

创建并编写CMakelists.txt

project/CMakeLists.txt

cmake_minimum_required(VERSION 3.5)project(STM32_Project LANGUAGES C ASM)# 强制设置交叉编译器
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
set(CMAKE_OBJCOPY arm-none-eabi-objcopy)
set(CMAKE_OBJDUMP arm-none-eabi-objdump)# 设置系统类型(必须放在project命令后)
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR ARM)
# Set the toolchain file for ARM cross-compilation
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_SOURCE_DIR}/STM32/gcc-arm-none-eabi.cmake)# Add the STM32 subdirectory
add_subdirectory(STM32)

project/STM32/CMakeLists.txt

# 设置MCU特定编译选项
set(CPU "-mcpu=cortex-m3")
set(FPU "")
set(FLOAT_ABI "")# 设置全局编译选项
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${CPU} ${FPU} ${FLOAT_ABI} -mthumb -ffunction-sections -fdata-sections -Wall -DSTM32F10X_HD -DUSE_STDPERIPH_DRIVER -march=armv7-m -mtune=cortex-m3 -fno-exceptions")# 设置汇编文件的编译选项
set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} ${CPU} ${FPU} ${FLOAT_ABI} -mthumb")# 设置链接选项
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${CPU} ${FPU} ${FLOAT_ABI} -mthumb -specs=nosys.specs -specs=nano.specs -T${CMAKE_CURRENT_SOURCE_DIR}/STM32F103ZETX_FLASH.ld -Wl,--gc-sections -static -Wl,-Map=${PROJECT_NAME}.map")# 包含路径
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/CORE${CMAKE_CURRENT_SOURCE_DIR}/STM32F10x_FWLib/inc${CMAKE_CURRENT_SOURCE_DIR}/SYSTEM/delay${CMAKE_CURRENT_SOURCE_DIR}/SYSTEM/sys${CMAKE_CURRENT_SOURCE_DIR}/SYSTEM/usart${CMAKE_CURRENT_SOURCE_DIR}/USER
)# 收集所有源文件
file(GLOB_RECURSE SOURCES"${CMAKE_CURRENT_SOURCE_DIR}/CORE/*.c""${CMAKE_CURRENT_SOURCE_DIR}/STM32F10x_FWLib/src/*.c""${CMAKE_CURRENT_SOURCE_DIR}/SYSTEM/*/*.c""${CMAKE_CURRENT_SOURCE_DIR}/USER/*.c"
)# 创建可执行文件
add_executable(${PROJECT_NAME}.elf ${SOURCES})
target_compile_options(${PROJECT_NAME}.elf PRIVATE -O2)# 为特定目标设置编译选项
target_compile_options(${PROJECT_NAME}.elf PRIVATE -O0 -fno-builtin)# 生成二进制文件
add_custom_command(TARGET ${PROJECT_NAME}.elf POST_BUILDCOMMAND ${CMAKE_OBJCOPY} -Obinary ${PROJECT_NAME}.elf ${PROJECT_NAME}.binCOMMENT "Generating binary file ${PROJECT_NAME}.bin"
)

由于需要进行交叉编译(简单说就是需要同时进行X86与ARM指令),所以要进行创建并编写一个.cmake文件,

project/STM32/gcc-arm-none-eabi.cmake

# 设置系统类型
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR ARM)# 指定交叉编译器
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
set(CMAKE_OBJCOPY arm-none-eabi-objcopy)
# 禁止尝试编译运行测试程序
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
set(CMAKE_OBJDUMP arm-none-eabi-objdump)
set(CMAKE_SIZE arm-none-eabi-size)# 设置查找路径规则
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

创建build并进行cmake与make操作,当出现如下报错是因为CMSIS内联汇编与编译器不兼容,

所以需要对core_cm3.c进行更改如下:

/*** @brief  STR Exclusive (8 bit)** @param  value  value to store* @param  *addr  address pointer* @return        successful / failed** Exclusive STR command for 8 bit values*/
uint32_t __STREXB(uint8_t value, uint8_t *addr)
{uint32_t result=0;__ASM volatile ("strexb %0, %2, [%1]" : "=&r" (result) : "r" (addr), "r" (value) );return(result);
}/*** @brief  STR Exclusive (16 bit)** @param  value  value to store* @param  *addr  address pointer* @return        successful / failed** Exclusive STR command for 16 bit values*/
uint32_t __STREXH(uint16_t value, uint16_t *addr)
{uint32_t result=0;__ASM volatile ("strexh %0, %2, [%1]" : "=&r" (result) : "r" (addr), "r" (value) );return(result);
}

当使用的是正点原子的代码,会出现如下报错: 

这段代码用的是 Keil 风格的 __asm void,而现在使用的是 GCC 工具链(arm-none-eabi-gcc),所以会报错。

对sys.c文件更改成如下:

#include "sys.h"// 执行 WFI 指令
void WFI_SET(void)
{__asm volatile("wfi");
}// 关闭所有中断(设置 PRIMASK)
void INTX_DISABLE(void)
{__asm volatile("cpsid i");
}// 开启所有中断(清除 PRIMASK)
void INTX_ENABLE(void)
{__asm volatile("cpsie i");
}// 设置主堆栈指针(MSP)
void MSR_MSP(u32 addr)
{__asm volatile ("msr msp, %0\n":: "r" (addr):);
}

最后,在build再进行

cmak ..

make

可以在build/STM32目录下找到生成的ELF与BIN文件:

至此完成。

使用例程链接:【免费】学习CMake的例程-学习CMake的例程资源-CSDN文库

结语:用 CMake 掌控跨平台构建的力量

本文从 “CMake 是什么” 的基础出发,逐步深入讲解了 CMakeLists.txt 的结构与常用命令,展示了从构建可执行文件到静态库、动态库的完整流程。同时也涵盖了项目实战示例、嵌套使用、以及与 STM32 工程结合的案例,帮助你建立起对 CMake 的全面认知。

CMake 不仅是一个跨平台构建工具,更是现代 C++ 工程组织不可或缺的一部分。与嵌入式、Linux 系统深度集成。

如果你是一位正在开发嵌入式项目、跨平台应用、或者希望将工程进行自动化构建的开发者,那么深入掌握 CMake,将极大提升你的开发效率和项目可维护性。

http://www.xdnf.cn/news/1299.html

相关文章:

  • 动态贴纸+美颜SDK的融合实现:底层架构与性能优化技术全解析
  • Redis-cli常用参数及功能的详细说明
  • 基于Flask与Ngrok实现Pycharm本地项目公网访问:从零部署
  • Redis常见命令
  • 【C/S通信仿真】
  • uniapp 处理app video组件各种问题
  • vue+flask+lstm高校舆情分析系统 | 可获取最新数据!
  • 蓝桥杯17. 机器人塔
  • gem5-gpu教程04 高速缓存一致性协议和缓存拓扑
  • 服务器配置环境-condapytorch_20250422
  • Java从入门到“放弃”(精通)之旅——String类⑩
  • C#多线程访问资源
  • Node.js 开发用户登录功能(使用mysql实现)
  • 【AI应用】免费代码仓构建定制版本的ComfyUI应用镜像
  • 【Linux应用】RADXA ZERO 3快速上手:镜像烧录、串口shell、外设挂载、WiFi配置、SSH连接、文件交互
  • Zookeeper是什么?基于zookeeper实现分布式锁
  • 软件黑盒与白盒测试详解
  • 同样的接口用postman/apifox能跑通,用jmeter跑就报错500
  • 【MCP】第二篇:IDE革命——用MCP构建下一代智能工具链
  • 【Linux】冯诺依曼体系结构及操作系统架构图的具体剖析
  • 【Ubuntu】关于系统分区、挂载点、安装位置的一些基本信息
  • 【算法笔记】动态规划基础(一):dp思想、基础线性dp
  • 【k8s】docker、k8s、虚拟机的区别以及使用场景
  • sentinel
  • CATBOOST算法总结
  • vscode如何多行同时编辑,vscode快速选中多行快捷键
  • 使用 JUnit 4在 Spring 中进行单元测试的完整步骤
  • 【数据结构入门训练DAY-21】信息学奥赛一本通T1334-围圈报数
  • 深入剖析TCP协议(内容二):从OSI与TCP/IP网络模型到三次握手、四次挥手、状态管理、性能优化及Linux内核源码实现的全面技术指南
  • 基于cubeMX的hal库STM32实现MQ2烟雾浓度检测