目錄
一、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 <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_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_DIR | CMake 執行構建命令所在目錄(一般是 build/ ) |
CMAKE_CURRENT_BINARY_DIR | 當前處理的 CMakeLists.txt 對應的構建輸出目錄 |
PROJECT_SOURCE_DIR | project() 函數所在 CMakeLists.txt 的目錄,通常等同于 CMAKE_SOURCE_DIR |
PROJECT_BINARY_DIR | project() 執行后對應的構建輸出目錄,通常等同于 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_COMPILER | C 編譯器路徑 |
CMAKE_CXX_COMPILER | C++ 編譯器路徑 |
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_NAME | project() 指定的項目名稱 |
PROJECT_VERSION | project(... 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.so
,main
程序運行時會查找并加載它,若 .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就結束了,下面進行補充與升級:
五、注釋的語法
# 單行注釋 - 使用 # 符號
#[[多行注釋 - 使用 包圍
]]
六、?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 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-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_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,將極大提升你的開發效率和項目可維護性。