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.mzph.cn/news/902579.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

多线程出bug不知道如何调试?java线程几种常见状态

当你的多线程代码结构很复杂的时候很难找出bug的原因所在&#xff0c;此时我们可以使用getState()方法获取该线程当前的状态&#xff0c;通过观察其状态是阻塞了还是因为没有启动等原因导致的。 状态描述NEW安排了工作&#xff0c;还未开始行动RUNNABLE可工作的&#xff0c;又…

Spark(20)spark和Hadoop的区别

Apache Spark 和 Apache Hadoop 都是广泛使用的开源大数据处理框架&#xff0c;但它们在设计理念、架构、性能和适用场景等方面存在显著区别。以下是它们的主要区别&#xff1a; ### **1. 架构设计** - **Hadoop**&#xff1a; - **HDFS&#xff08;Hadoop Distributed File…

【redis】哨兵模式

Redis主从模式虽然支持数据备份与读写分离&#xff0c;但存在三大核心缺陷&#xff1a;1. 故障切换依赖人工&#xff08;主节点宕机需手动提升从节点&#xff09;&#xff1b;2. 监控能力缺失&#xff08;无法自动检测节点异常&#xff09;&#xff1b;3. 脑裂风险&#xff08;…

Spark-Streaming

找出所有有效数据&#xff0c;要求电话号码为11位&#xff0c;但只要列中没有空值就算有效数据。 按地址分类&#xff0c;输出条数最多的前20个地址及其数据。 代码讲解&#xff1a; 导包和声明对象&#xff0c;设置Spark配置对象和SparkContext对象。 使用Spark SQL语言进行数…

Sentinel源码—9.限流算法的实现对比一

大纲 1.漏桶算法的实现对比 (1)普通思路的漏桶算法实现 (2)节省线程的漏桶算法实现 (3)Sentinel中的漏桶算法实现 (4)Sentinel中的漏桶算法与普通漏桶算法的区别 (5)Sentinel中的漏桶算法存在的问题 2.令牌桶算法的实现对比 (1)普通思路的令牌桶算法实现 (2)节省线程的…

Redis 详解:安装、数据类型、事务、配置、持久化、订阅/发布、主从复制、哨兵机制、缓存

目录 Redis 安装与数据类型 安装指南 Windows Linux 性能测试 基本知识 数据类型 String List&#xff08;双向列表&#xff09; Set&#xff08;集合&#xff09; Hash&#xff08;哈希&#xff09; Zset&#xff08;有序集合&#xff09; 高级功能 地理位置&am…

Docker配置带证书的远程访问监听

一、生成证书和密钥 1、准备证书目录和生成CA证书 # 创建证书目录 mkdir -p /etc/docker/tls cd /etc/docker/tls # 生成CA密钥和证书 openssl req -x509 -newkey rsa:4096 -keyout ca-key.pem \ -out ca-cert.pem -days 365 -nodes -subj "/CNDocker CA" 2、为…

MCP接入方式介绍

上一篇文章&#xff0c;我们介绍了MCP是什么以及MCP的使用。 MCP是什么&#xff0c;MCP的使用 接下来&#xff0c;我们来详细介绍一下MCP的接入 先看官网的架构图 上图的MCP 服务 A、MCP 服务 B、MCP 服务 C是可以运行在你的本地计算机&#xff08;本地服务器方式&#xff…

关于Agent的简单构建和分享

前言&#xff1a;Agent 具备自主性、环境感知能力和决策执行能力&#xff0c;能够根据环境的变化自动调整行为&#xff0c;以实现特定的目标。 一、Agent 的原理 Agent(智能体)被提出时&#xff0c;具有四大能力 感知、分析、决策和执行。是一种能够在特定环境中自主行动、感…

Gitlab runner 安装和注册

Gitlab Runner GitLab Runner是一个用于运行GitLab CI/CD流水线作业的软件包&#xff0c;由GitLab官方开发&#xff0c;完全开源。你可以在很多主流的系统环境或平台上安装它&#xff0c;如Linux、macOS、Windows和Kubernetes。如果你熟悉Jenkins 的话&#xff0c;你可以把它…

精益数据分析(18/126):权衡数据运用,精准把握创业方向

精益数据分析&#xff08;18/126&#xff09;&#xff1a;权衡数据运用&#xff0c;精准把握创业方向 大家好&#xff01;一直以来&#xff0c;我都希望能和大家在创业与数据分析的领域共同探索、共同进步。今天&#xff0c;我们继续深入研读《精益数据分析》&#xff0c;探讨…

Git技术详解:从核心原理到实际应用

Git技术详解&#xff1a;从核心原理到实际应用 一、Git的本质与核心价值 Git是由Linux之父Linus Torvalds在2005年开发的分布式版本控制系统&#xff0c;其核心功能是通过记录文件变更历史&#xff0c;帮助开发者实现以下目标&#xff1a; 版本回溯&#xff1a;随时恢复到项…

Java从入门到“放弃”(精通)之旅——String类⑩

Java从入门到“放弃”&#xff08;精通&#xff09;之旅&#x1f680;——String类⑩ 前言 在Java编程中&#xff0c;String类是最常用也是最重要的类之一。无论是日常开发还是面试&#xff0c;对String类的深入理解都是必不可少的。 1. String类的重要性 在C语言中&#xf…

抓取淘宝数据RPA--影刀

最近用了一下RPA软件&#xff0c;挑了影刀&#xff0c;发现很无脑也很简单&#xff0c;其语法大概是JAVA和PYTHON的混合体&#xff0c;如果懂爬虫的话&#xff0c;学这个软件就快的很&#xff0c;看了一下官方的教程&#xff0c;对于有基础的人来说很有点枯燥&#xff0c;但又不…

docker部署seafile修改默认端口并安装配置onlyoffice实现在线编辑

背景 有很多场景会用到类似seafile功能的需求&#xff0c;比如&#xff1a; 在内网中传输和共享文件个人部署私人网盘文档协同在线编辑写笔记… 这些功能seafile均有实现&#xff0c;并且社区版提供的功能基本可以满足个人或者小型团队的日常需求 问题 由于主机的80和443端…

计算机视觉cv2入门之视频处理

在我们进行计算机视觉任务时&#xff0c;经常会对视频中的图像进行操作&#xff0c;这里我来给大家分享一下&#xff0c;cv2对视频文件的操作方法。这里我们主要介绍cv2.VideoCapture函数的基本使用方法。 cv2.VideoCapture函数 当我们在使用cv2.VideoCapture函数时&#xff…

Linux之彻底掌握防火墙-----安全管理详解

—— 小 峰 编 程 目录&#xff1a; 一、防火墙作用 二、防火墙分类 1、逻辑上划分&#xff1a;大体分为 主机防火墙 和 网络防火墙 2、物理上划分&#xff1a; 硬件防火墙 和 软件防火墙 三、硬件防火墙 四、软件防火墙 五、iptables 1、iptables的介绍 2、netfilter/…

python项目实战-后端个人博客系统

本文分享一个基于 Flask 框架开发的个人博客系统后端项目&#xff0c;涵盖用户注册登录、文章发布、分类管理、评论功能等核心模块。适合初学者学习和中小型博客系统开发。 一、项目结构 blog │ app.py │ forms.py │ models.py │ ├───instance │ blog.d…

Unity 接入阿里的全模态大模型Qwen2.5-Omni

1 参考 根据B站up主阴沉的怪咖 开源的项目的基础上修改接入 AI二次元老婆开源项目地址(unity-AI-Chat-Toolkit): Github地址&#xff1a;https://github.com/zhangliwei7758/unity-AI-Chat-Toolkit Gitee地址&#xff1a;https://gitee.com/DammonSpace/unity-ai-chat-too…

第十五届蓝桥杯 2024 C/C++组 合法密码

目录 题目&#xff1a; 题目描述&#xff1a; 题目链接&#xff1a; 思路&#xff1a; substr函数&#xff1a; 思路详解&#xff1a; 代码&#xff1a; 代码详解; 题目&#xff1a; 题目描述&#xff1a; 题目链接&#xff1a; P10906 [蓝桥杯 2024 国 B] 合法密码 -…