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.pswp.cn/news/902579.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/902579.shtml
英文地址,請注明出處:http://en.pswp.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] 合法密碼 -…