看完這篇文章,我終於搞懂了 CMake,真香!-基礎篇-
今天我們就從頭到尾,用最簡單的大白話,把 CMake 的用法講清楚!看完這篇文章,你一定會覺得:“CMake 原來這麼簡單!”
在正式開始之前,先通過一張思維導圖,幫你快速瞭解 CMake 的全貌,這樣你心裏會更有底:
基礎篇
1. 什麼是 CMake?爲什麼用它?
CMake 是一個跨平臺的項目構建工具,通俗點說,它會幫你生成 Makefile 或其他編譯系統需要的構建文件。
用 CMake 的好處:
-
不用手寫複雜的 Makefile:你只需要寫一個簡單的
CMakeLists.txt
,CMake 會自動幫你生成 Makefile。 -
支持跨平臺開發:不管是 Linux、Windows 還是 MacOS,CMake 都能輕鬆搞定。
-
自動化依賴管理:只要源文件有改動,它會自動處理文件依賴,不用你手動維護。
-
支持大項目:幾百個源文件的項目,CMake 比你還細心,編譯起來穩得一批。
簡單來說,CMake = 省心 + 高效。
2. CMake 的基本使用流程
2.1 基本流程
學習 CMake,就像學做飯,只有幾個簡單步驟:
-
寫菜單:創建一個
CMakeLists.txt
文件,告訴 CMake 要編譯哪些源文件。 -
配置廚房:運行 CMake,生成構建文件(比如 Makefile)。
-
開火做飯:用生成的構建文件開始編譯,得到程序。
以下是CMake
與Makefile
的構建流程,幫助你更直觀地理解這些步驟:
項目源碼
│
└──► [CMakeLists.txt] # 編寫 CMake 構建文件
│
▼
cmake (命令) # 運行 CMake 命令生成 Makefile
│
▼
[Makefile] # 自動生成的 Makefile
│
▼
make (命令) # 使用 Makefile 構建目標
│
▼
目標文件 # 編譯生成的目標文件(如可執行程序或庫)
2.2 關鍵命令
假設你的代碼放在項目目錄my_project/
下,使用 CMake 的流程如下:
mkdir build # 創建編譯目錄(推薦在項目外部)
cd build # 進入編譯目錄
cmake .. # 生成 Makefile
make # 開始編譯
./hello # 運行可執行文件
3. 第一個簡單的 CMake 項目
3.1 文件結構
我們寫一個最簡單的項目,只有一個main.cpp
文件:
my_project/
├── main.cpp
├── CMakeLists.txt
main.cpp
:
#include <iostream>
int main() {
std::cout << "Hello, CMake!" << std::endl;
return 0;
}
3.2 編寫 CMakeLists.txt
在項目根目錄下,新建一個名爲CMakeLists.txt
的文件,內容如下:
# 指定 CMake 的最低版本
cmake_minimum_required(VERSION 3.10)
# 設置項目名稱
project(HelloCMake)
# 添加可執行文件
add_executable(hello main.cpp)
解釋:
-
cmake_minimum_required()
:告訴 CMake 使用的最低版本,避免版本兼容問題。 -
project()
:設置項目名稱。 -
add_executable()
:告訴 CMake,我們要生成一個名字叫hello
的可執行文件,它的源文件是main.cpp
。
3.3 編譯運行
1、在項目根目錄下新建build
文件夾,並進入:
mkdir build
cd build
- 運行 CMake 配置命令:
cmake ..
此時,CMake 會生成Makefile
。
- 執行
make
開始編譯:
make
- 運行生成的程序:
./hello
你會看到輸出:
Hello, CMake!
恭喜你,第一個簡單的CMake
項目完成了!
4. 支持多源文件和目錄
當你的項目越來越大,源代碼文件越來越多時,你一定會覺得直接在CMakeLists.txt
中寫一堆文件名特別麻煩。別擔心,CMake 給你提供了更優雅的解決方案,讓代碼管理更簡單!
4.1 添加多個源文件
如果你的項目中有多個源文件,像main.cpp
和utils.cpp
,你可以直接把它們放到add_executable()
中:
add_executable(hello main.cpp utils.cpp)
這樣,CMake 就會把這些文件一起編譯成一個可執行文件hello
。簡單明瞭,對吧?
4.2 使用變量管理源文件
但當源文件越來越多時,手動列出每個文件就顯得很麻煩。這時候,我們可以用變量來管理這些文件,CMake 會幫你搞定。
set(SOURCES main.cpp utils.cpp)
add_executable(hello ${SOURCES})
通過這種方式,你可以輕鬆管理文件列表,未來如果需要增加或刪除文件,只需要修改這個變量,就能一勞永逸。
是不是很方便?
除了自己定義的變量,CMake 還可以訪問系統環境變量。例如,獲取當前用戶的HOME
目錄:
set(MY_PATH $ENV{HOME})
CMake 提供了$ENV{ }
語法,用於訪問系統的環境變量,通過$ENV{HOME}
,CMake 會自動讀取你係統中的HOME
環境變量,把它的值賦給MY_PATH
變量。
4.3 多目錄管理
隨着項目逐漸變大,你的代碼可能會分散到多個目錄中。比如,你可能會有一個src/
目錄存放核心代碼,一個include/
目錄存放頭文件,還有一個libs/
目錄存放外部庫的文件。
到了這個階段,你可能不希望將所有文件都寫在一個 CMakeLists.txt 文件裏了。沒錯,CMake 完全支持這種方式,允許你輕鬆管理多個目錄的代碼。
使用 cmake 來管理項目,目錄結構一般是這樣:
my_project/
├── CMakeLists.txt # 根目錄的 CMake 配置
├── src/
│ ├── CMakeLists.txt # src 目錄下的 CMake 配置
│ ├── main.cpp
│ ├── utils.cpp
│ └── helper.cpp
├── libs/
│ ├── CMakeLists.txt # libs 目錄下的 CMake 配置
│ ├── lib.cpp
│ └── lib.h
├── include/
│ ├── utils.h
│ └── helper.h
└── build/ # 構建目錄(自動創建)
在這個結構中,根目錄下有一個CMakeLists.txt
文件,它負責管理整個項目。而src/
和libs/
目錄下分別有各自的CMakeLists.txt
文件,用來管理這些目錄中的源文件和庫。這樣,不僅讓項目更有條理,而且也方便管理。
1、根目錄的 CMakeLists.txt
在根目錄的CMakeLists.txt
文件中,我們通常做一些全局的配置,比如設置項目的名字、指定 CMake 最低版本要求,以及引入子目錄等。
# 根目錄的 CMakeLists.txt
# 設置項目名和 CMake 最低版本
cmake_minimum_required(VERSION 3.10)
project(MyProject)
# 添加 src 和 libs 子目錄
add_subdirectory(src)
add_subdirectory(libs)
這裏做了兩件事:
-
使用
add_subdirectory()
告訴 CMake 去處理src/
和libs/
子目錄的CMakeLists.txt
文件。 -
設置項目名爲
MyProject
,並指定了最低版本要求(3.10
)。
2、src/ 目錄的 CMakeLists.txt
接下來,在src/
目錄下的CMakeLists.txt
文件中,負責編譯src/
目錄下的源文件(比如main.cpp
和utils.cpp
)。如果有多個源文件,你可以通過變量來管理這些文件。
# src/CMakeLists.txt
# 設置源文件
set(SOURCES
main.cpp
utils.cpp
helper.cpp
)
# 創建可執行文件
add_executable(hello ${SOURCES})
# 設置頭文件路徑
target_include_directories(hello PRIVATE ${PROJECT_SOURCE_DIR}/include)
# PROJECT_SOURCE_DIR:這是一個 CMake 內置的變量,表示項目的源代碼根目錄。
# 無論你在哪個子目錄中,它都會指向項目的最外層目錄:即 my_project/
# 鏈接庫
target_link_libraries(hello PRIVATE mylib)
這段代碼做了幾件事:
-
使用
set(SOURCES ...)
將src/
目錄下的源文件名賦值給變量 SOURCES,方便下面使用。 -
使用
add_executable()
創建可執行文件hello
。 -
使用
target_include_directories()
設置頭文件路徑,指向根目錄下的include/
目錄,這樣編譯器就能找到頭文件。 -
使用
target_link_libraries()
鏈接一個外部庫mylib
(假設它在libs/
目錄下)。
3、libs/ 目錄的 CMakeLists.txt
libs/
目錄下的CMakeLists.txt
文件類似地負責編譯庫文件。
# libs/CMakeLists.txt
# 設置庫文件源代碼
set(LIB_SOURCES
lib.cpp
)
# 創建靜態庫
add_library(mylib STATIC ${LIB_SOURCES}) # STATIC 可改爲 SHARED 生成動態庫
# 設置頭文件路徑
target_include_directories(mylib PUBLIC ${PROJECT_SOURCE_DIR}/include)
在這裏:
-
使用
set(LIB_SOURCES ...)
設置變量 LIB_SOURCES,將libs/
目錄下的源文件名賦值給它。 -
使用
add_library()
創建了一個靜態庫mylib
。 -
使用
target_include_directories()
設置庫的頭文件路徑,指向根目錄下的include/
目錄,通過 PUBLIC 關鍵字,確保其他需要引用mylib
的目標能夠找到頭文件。
4、項目根目錄的頭文件(include/)
在include/
目錄下存放了項目的頭文件。需要注意的是,你不需要在src/
和libs/
的 CMakeLists.txt 文件中列出這些頭文件。
CMake 會自動知道你從include/
目錄中包含頭文件,只要你確保src/
和libs/
目錄下的 CMakeLists.txt 文件中正確配置了target_include_directories()
。
my_project/
├── include/
│ ├── utils.h
│ └── helper.h
5、添加build/
目錄
到這裏,你的代碼管理已經非常清晰了,但接下來,我們需要談一談build/
目錄。build/
目錄是構建過程中的輸出目錄,它將存放所有編譯產生的中間文件、目標文件和最終的可執行文件。
通常,在 CMake 構建項目時,我們不會直接在源碼目錄下運行構建命令,而是會創建一個單獨的build/
目錄,這樣可以保持源碼目錄的整潔,同時也可以更方便地管理構建文件。你可以這樣做:
- 創建
build/
目錄:
在項目根目錄下創建一個build/
目錄:
mkdir build
cd build
- 配置項目:
然後在build/
目錄下運行cmake
命令,CMake 會自動查找根目錄下的CMakeLists.txt
文件並配置整個項目。
cmake ..
這樣,CMake 會根據根目錄的CMakeLists.txt
配置生成必要的構建文件。
- 開始構建:
配置完成後,你可以運行make
或其他構建工具來進行編譯:
make
- 構建輸出:
編譯完成後,所有生成的文件(比如目標文件、可執行文件等)都會被放到build/
目錄裏。源代碼目錄就不會被任何臨時文件污染了,保持整潔。
小結:
通過這種結構和配置方式,CMake 可以非常有效地管理多個目錄下的源代碼和庫:
-
根目錄的
CMakeLists.txt
負責引入子目錄和進行全局配置。 -
每個子目錄(如
src/
和libs/
)都有自己的CMakeLists.txt
來管理該目錄下的源文件和庫。 -
target_include_directories()
確保頭文件可以被正確引用。 -
build/
目錄用來存放所有構建過程中生成的中間文件和最終的可執行文件,保持源代碼目錄乾淨整潔。
這種多目錄的管理方式,會讓你的項目變得更加模塊化、易於擴展,同時也方便多人協作開發。如果以後需要添加新的庫或源文件,只需在對應子目錄的CMakeLists.txt
中做調整,而根目錄只需要引入子目錄即可。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/pREDMNf5Uz489giDey4AJQ