上海网站制作优化,网站建设策划结构,建一个免费网站的流程,东营住建局官网目录 一、CMake 是什么#xff0c;为什么要使用 CMake
二、CMakeLists.txt 文件结构与简单示例
三、进阶的CMake
四、静态库与动态库生成及其使用
五、注释的语法
六、 set、list、message 三个常用的 CMake 函数与命令
七、CMake 的控制语句以及自定义宏/函数
八、为S…目录 一、CMake 是什么为什么要使用 CMake
二、CMakeLists.txt 文件结构与简单示例
三、进阶的CMake
四、静态库与动态库生成及其使用
五、注释的语法
六、 set、list、message 三个常用的 CMake 函数与命令
七、CMake 的控制语句以及自定义宏/函数
八、为STM32工程进行CMake嵌套CMake 一、CMake 是什么为什么要使用 CMake
在学习 CMake 之前我们先来思考一个问题我们为什么需要构建工具
在 C/C 项目中通常我们写好源代码后并不是直接能运行的需要经过编译、链接等步骤才能变成可执行程序。对于简单的项目手动使用 gcc 或 g 进行编译也许问题不大但项目一旦变复杂手动维护这些编译命令就变得痛苦不堪。这时候我们就需要一个构建系统来自动化这些操作。
那 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 iostreamint 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 iostreamvoid func1() {std::cout This is func1() std::endl;
}src/app2.cpp
#include head2.h
#include iostreamvoid func2() {std::cout This is func2() std::endl;
}src/app3.cpp
#include head3.h
#include head4.h
#include iostreamvoid 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.hint 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_directories 或 target_compile_features 等高级特性一定要升级版本 常见版本推荐3.10 是 Ubuntu 18.04 默认的3.16 支持很多现代功能。 2. project
设置工程的名称、版本号、语言类型等。
有什么用
告诉 CMake“我这个项目叫啥、用的语言是啥”并设置一些默认变量。
怎么用
project(MyApp)或带语言
project(MyApp LANGUAGES C CXX)小贴士 你可以用 PROJECT_NAME、PROJECT_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_DIRPROJECT_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 / ReleaseCMAKE_CXX_STANDARD指定 C 标准例如 11、14、17、20CMAKE_SYSTEM_NAME操作系统名如 Linux、Windows、DarwinCMAKE_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 下 .aWindows 是 .lib
链接时机 编译时链接最终 .exe 或 .out 文件包含了所有库代码
部署方式 最终可执行文件独立运行不再依赖外部 .a 文件 举例你编译一个 main.cpp 链接 libmath.a最后 main 程序会包含 math 库代码运行时不需要带上 libmath.a。
什么是动态库Dynamic Library
动态库以 .so 或 .dll 结尾是一种在程序运行时才加载的共享库文件。
生成方式 编译为 .soLinux或 .dllWindows
链接时机编译时生成依赖信息例如 .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);#endifsrc/math_utils.cpp
#include math_utils.hint add(int a, int b) {return a b;
}int square(int a) {return a * a;
}src/main.cpp
#include iostream
#include math_utils.hint 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 标准版本为 C11
# 若系统支持更高版本可以改为 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.aLinux
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就结束了下面进行补充与升级
五、注释的语法
# 单行注释 - 使用 # 符号
#[[多行注释 - 使用 包围
]]
六、 set、list、message 三个常用的 CMake 函数与命令
下面我将详细讲解 set、list、message 三个常用的 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 Alice2. 定义包含多个元素的变量类似列表
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;orange2. 插入元素到指定位置
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致命错误立即停止 CMakeDEPRECATION弃用警告
示例
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-gui、ccmake 或命令行传参使用。
语法
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_LOGON ..四、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 和 RANGEif条件判断支持字符串、文件、正则等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 -mcpucortex-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 -marcharmv7-m -mtunecortex-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 -specsnosys.specs -specsnano.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 result0;__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 result0;__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将极大提升你的开发效率和项目可维护性。