- 1. 什麼是CMake?
- 1.1. CMake的定義
- 1.2. CMake有哪些優勢?
- 1.3. CMake 的特點
- 1.4. Cmake 、CMakeLists.txt 、Make 、Makefile 之間的關係
- 2. 應用案例
- 2.1. 專案概述
- 2.2. CMakeLists.txt
- 2.2.1. 基本用法
- 2.2.2. 完整內容
- 2.2.3. 構建執行
上一篇《Linux C++ 開發4 - 入門makefile一篇文章就夠了》我們講解了透過Makefile
來編譯 包含多個.cpp
和多個.h
檔案 的複雜C++專案。這種方式用來構建中小型的Linux(或類Unix系統)C++專案,是沒有問題的。但如果是跨平臺專案或者大型專案,Makefile
就顯得力不從心了;因為Makefile
不具備良好的跨平臺性,大型專案的編譯規則和依賴項也是比較複雜的,Makefile
的編寫和維護成本都比較高。這時,CMake
就可以派上用場了。
1. 什麼是CMake?
1.1. CMake的定義
CMake是一個跨平臺的開源構建系統生成器。它能夠生成各種構建系統檔案,如Makefile
、Visual Studio 專案檔案
等。CMake
透過讀取一個或多個CMakeLists.txt檔案來配置專案的構建過程。
1.2. CMake有哪些優勢?
相較於Makefile
,CMake有以下優勢。
- 跨平臺支援: CMake支援多種作業系統和編譯器,使得專案能夠在不同平臺上進行構建。
- 簡化構建過程: 透過CMake,開發者可以編寫一次構建指令碼,然後在不同平臺上生成相應的構建檔案。
- 模組化: CMake支援模組化開發,可以方便地管理專案的依賴關係。
1.3. CMake 的特點
-
跨平臺支援:
CMake
支援多種作業系統,包括 Linux、Windows、macOS 等。- 它能夠生成適用於不同編譯器的構建檔案,如
Makefile
、Ninja
、Visual Studio 專案檔案
等。
-
簡化構建過程:
- 透過 CMake,開發者可以編寫一次構建指令碼(CMakeLists.txt),然後在不同平臺上生成相應的構建檔案,簡化了構建過程。
CMake
提供了豐富的命令和選項,使得構建配置更加靈活和高效。
-
模組化:
CMake
支援模組化開發,可以方便地管理專案的依賴關係。- 透過
add_subdirectory
命令,可以將大型專案拆分為多個子專案,每個子專案都有自己的 CMakeLists.txt 檔案。
-
可擴充套件性:
CMake
提供了豐富的模組和函式,可以方便地擴充套件其功能。- 開發者可以編寫自定義的
CMake
模組和函式,以滿足特定專案的需求。
-
依賴管理:
CMake
支援外部依賴的管理,可以透過find_package
命令查詢和連結外部庫。- 它還支援透過
FetchContent
模組下載和整合第三方庫。
1.4. Cmake 、CMakeLists.txt 、Make 、Makefile 之間的關係
- CMakeLists.txt 是
CMake
的配置檔案,定義了專案的原始檔、構建規則和依賴關係。 - CMake 是一個構建系統生成器,負責讀取一個或多個 CMakeLists.txt 檔案並生成相應的構建檔案(如
Makefile
、Visual Studio 專案檔案
等)。 - Makefile 是
Make
工具的配置檔案,它包含了一系列規則和指令,定義瞭如何編譯和連結原始碼。 - Make 是一個構建工具,負責讀取 Makefile 檔案並執行編譯和構建過程,生成最終的構建產物。
他們之間的關係可以用下面這張圖來表示。
2. 應用案例
《Linux C++ 開發4 - 入門makefile一篇文章就夠了》一文中,我們用Makefile
編譯了Iterator
專案。現在我們任然以這個專案為例,將其改成透過CMake來構建。
2.1. 專案概述
一個公司有多個部門,每個部門有多個人組成,這些人中有開發人員,有測試人員,和與專案相關的其它人員,其結構如下圖片。
現在要遍歷這個公司的所有開發人員,遍歷這個公司的所有測試人員。
在專案的原始碼中,我們用迭代器模式實現了這個需求,類的結構圖是這樣的:
詳細程式碼參見: https://gitee.com/spencer_luo/iterator/tree/cmake/
現在我們就以這個專案為例,看看這個專案的CMakeLists.txt
需要怎麼寫?
2.2. CMakeLists.txt
2.2.1. 基本用法
設定 cmake的最低版本號:
cmake_minimum_required(VERSION 3.28.3)
注意:這一項要放在CMakeLists.txt
的第一行,否則可能會報錯。
設定 專案名稱、版本、語言:
project(Iterator VERSION 1.0.0 LANGUAGES CXX)
這裡CXX
表示C++
語言。
設定 C/C++ 的標準:
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 11)
可以根據自己的需求設定編譯時使用的C++版本,如:98/11/14/17/20。(注意:你的編譯也要能支援你設定的C++版本)
查詢要編譯的.cpp檔案:
file(GLOB SRC_FILES
${CMAKE_CURRENT_SOURCE_DIR}/*.cpp
)
message(DEBUG "COMMON_UTIL_SRC:" ${SRC_FILES})
CMAKE_CURRENT_SOURCE_DIR
是CMake的內建變數,表示當前CMakeLists.txt
檔案所在的目錄,透過$(CMAKE_CURRENT_SOURCE_DIR)
方式來使用該變數,更多內建變數參見官方文件《cmake-variables》。GLOB
命令會搜尋當前目錄下所有.cpp
檔案,並將它們新增到SRC_FILES
變數中。你也可以使用GLOB_RECURSE
,與GLOB
相比,它不僅會搜尋當前目錄,還會遞迴搜尋所有子目錄。message
是CMake的內建命令,用於輸出構建相關的資訊。第一個參數列示訊息的型別,可以是以下這些值(按優先順序大小排序):FATAL_ERROR
(致命錯誤訊息) >SEND_ERROR
(錯誤訊息) >AUTHOR_WARNING
(警告訊息) >NOTICE
(重要訊息) >STATUS
(狀態訊息) >DEBUG
(除錯訊息) >TRACE
(跟蹤訊息)。
構建可執行檔案:
add_executable(${PROJECT_NAME} ${SRC_FILES})
- 表示:要將所有
.cpp
檔案(${SRC_FILES}
)編譯並連結成可執行的二進位制檔案,可執行檔名為專案名。 - 如果要編譯連結成靜態庫,可以替換成
add_library(${PROJECT_NAME} STATIC ${SRC_FILES})
。 - 如果要編譯連結成動態庫,可以替換成
add_library(${PROJECT_NAME} SHARED ${SRC_FILES})
。
根據不同的編譯模式新增不同的編譯選項:
# 設定構建型別: Debug/Release
set(CMAKE_BUILD_TYPE Debug)
# 設定編譯選項
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
# Debug模式,編譯是需要保留除錯符號表
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -pg")
elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
# Release模式,編譯時最佳化程式碼,最佳化選項:-O2
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
endif()
這裡透過 if(CMAKE_BUILD_TYPE STREQUAL "Debug")
來判斷是Debug
模式還是Release
模式,然後設定不同的編譯選項,Debug
模式下編譯時保留除錯符號表,Release
模式下編譯時會最佳化程式碼。
2.2.2. 完整內容
# 要求的cmake的最低版本號
cmake_minimum_required(VERSION 3.28.3)
# 專案名稱、版本、語言
project(Iterator VERSION 1.0.0 LANGUAGES CXX)
# C/C++ 的標準: C11/C++11
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 11)
# 查詢要編譯的.cpp檔案
file(GLOB SRC_FILES
${CMAKE_CURRENT_SOURCE_DIR}/*.cpp
)
message(DEBUG "COMMON_UTIL_SRC:" ${SRC_FILES})
# 構建可執行檔案
add_executable(${PROJECT_NAME} ${SRC_FILES})
2.2.3. 構建執行
構建專案:
# 開始構建專案,生成Makefile構建系統檔案
cmake -B ./build -S ./
-- The CXX compiler identification is GNU 13.2.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (1.5s)
-- Generating done (0.1s)
-- Build files have been written to: /mnt/d/workspace/iterator/build
➜ iterator git:(cmake) ✗
cmake -B ./build -S ./
說明:
-B
: 指定構建目錄,這裡是./build
。-S
: 指定原始碼目錄,這裡是./
。 這一引數可以不寫,不寫時表示:原始碼目錄就是當前目錄。--log-level
: 可以指定CMakeLists.txt
中message
輸出的日誌級別,如可以使用這個命令來構建:cmake -B ./build --log-level DEBUG
開始編譯連結:
# 進入 build 目錄
cd build
# 檢視 build 目錄有哪些檔案
ls
CMakeCache.txt CMakeFiles Makefile cmake_install.cmake
# 執行make,開始編譯連結
make
[ 25%] Building CXX object CMakeFiles/Iterator.dir/Company.cpp.o
[ 50%] Building CXX object CMakeFiles/Iterator.dir/Iterator.cpp.o
[ 75%] Building CXX object CMakeFiles/Iterator.dir/Person.cpp.o
[100%] Linking CXX executable Iterator
[100%] Built target Iterator
執行編譯結果:
# 進入 build 目錄。此時會發現多了一個可執行檔案 Iterator
ls
CMakeCache.txt CMakeFiles Iterator Makefile cmake_install.cmake
# 執行 Iterator 可執行檔案,檢視輸出結果
./Iterator
遍歷所有開發者:
員工:1-Developer11 開發工程師,擅長語言:C++,負責專案:智慧城市
員工:2-Developer12 開發工程師,擅長語言:Java,負責專案:智慧城市
員工:3-Developer13 開發工程師,擅長語言:JavaScript,負責專案:智慧城市
員工:6-Developer21 開發工程師,擅長語言:IOS,負責專案:智慧語音
員工:7-Developer22 開發工程師,擅長語言:Android,負責專案:智慧語音
員工:9-Developer31 開發工程師,擅長語言:C++,負責專案:電子書核心
遍歷所有測試人員:
員工:4-Tester15 測試工程師,測試型別:LoadRunner
員工:5-Tester16 測試工程師,測試型別:黑盒測試
員工:8-Tester24 測試工程師,測試型別:TestIn
員工:10-Tester35 測試工程師,測試型別:LoadRunner
遍歷公司所有員工:
員工:1-Developer11 開發工程師,擅長語言:C++,負責專案:智慧城市
員工:2-Developer12 開發工程師,擅長語言:Java,負責專案:智慧城市
員工:3-Developer13 開發工程師,擅長語言:JavaScript,負責專案:智慧城市
員工:4-Tester15 測試工程師,測試型別:LoadRunner
員工:5-Tester16 測試工程師,測試型別:黑盒測試
員工:6-Developer21 開發工程師,擅長語言:IOS,負責專案:智慧語音
員工:7-Developer22 開發工程師,擅長語言:Android,負責專案:智慧語音
員工:8-Tester24 測試工程師,測試型別:TestIn
員工:9-Developer31 開發工程師,擅長語言:C++,負責專案:電子書核心
員工:10-Tester35 測試工程師,測試型別:LoadRunner
大家好,我是陌塵。
IT從業10年+, 北漂過也深漂過,目前暫定居於杭州,未來不知還會飄向何方。
搞了8年C++,也幹過2年前端;用Python寫過書,也玩過一點PHP,未來還會折騰更多東西,不死不休。
感謝大家的關注,期待與你一起成長。