Linux C++ 開發5 - 一文了解CMake構建

陌尘(MoChen)發表於2024-08-26
  • 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是一個跨平臺的開源構建系統生成器。它能夠生成各種構建系統檔案,如MakefileVisual Studio 專案檔案等。CMake透過讀取一個或多個CMakeLists.txt檔案來配置專案的構建過程。

1.2. CMake有哪些優勢?

相較於Makefile,CMake有以下優勢。

  1. 跨平臺支援: CMake支援多種作業系統和編譯器,使得專案能夠在不同平臺上進行構建。
  2. 簡化構建過程: 透過CMake,開發者可以編寫一次構建指令碼,然後在不同平臺上生成相應的構建檔案。
  3. 模組化: CMake支援模組化開發,可以方便地管理專案的依賴關係。

1.3. CMake 的特點

  1. 跨平臺支援

    • CMake 支援多種作業系統,包括 Linux、Windows、macOS 等。
    • 它能夠生成適用於不同編譯器的構建檔案,如 MakefileNinjaVisual Studio 專案檔案等。
  2. 簡化構建過程

    • 透過 CMake,開發者可以編寫一次構建指令碼(CMakeLists.txt),然後在不同平臺上生成相應的構建檔案,簡化了構建過程。
    • CMake 提供了豐富的命令和選項,使得構建配置更加靈活和高效。
  3. 模組化

    • CMake 支援模組化開發,可以方便地管理專案的依賴關係。
    • 透過 add_subdirectory 命令,可以將大型專案拆分為多個子專案,每個子專案都有自己的 CMakeLists.txt 檔案。
  4. 可擴充套件性

    • CMake 提供了豐富的模組和函式,可以方便地擴充套件其功能。
    • 開發者可以編寫自定義的 CMake 模組和函式,以滿足特定專案的需求。
  5. 依賴管理

    • CMake 支援外部依賴的管理,可以透過 find_package 命令查詢和連結外部庫。
    • 它還支援透過 FetchContent 模組下載和整合第三方庫。

1.4. Cmake 、CMakeLists.txt 、Make 、Makefile 之間的關係

  • CMakeLists.txtCMake 的配置檔案,定義了專案的原始檔、構建規則和依賴關係。
  • CMake 是一個構建系統生成器,負責讀取一個或多個 CMakeLists.txt 檔案並生成相應的構建檔案(如 MakefileVisual Studio 專案檔案等)。
  • MakefileMake 工具的配置檔案,它包含了一系列規則和指令,定義瞭如何編譯和連結原始碼。
  • Make 是一個構建工具,負責讀取 Makefile 檔案並執行編譯和構建過程,生成最終的構建產物。

他們之間的關係可以用下面這張圖來表示。

file

2. 應用案例

Linux C++ 開發4 - 入門makefile一篇文章就夠了》一文中,我們用Makefile編譯了Iterator專案。現在我們任然以這個專案為例,將其改成透過CMake來構建。

2.1. 專案概述

一個公司有多個部門,每個部門有多個人組成,這些人中有開發人員,有測試人員,和與專案相關的其它人員,其結構如下圖片。

file

現在要遍歷這個公司的所有開發人員,遍歷這個公司的所有測試人員。

在專案的原始碼中,我們用迭代器模式實現了這個需求,類的結構圖是這樣的:

file

詳細程式碼參見: 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.txtmessage輸出的日誌級別,如可以使用這個命令來構建: 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,未來還會折騰更多東西,不死不休。

感謝大家的關注,期待與你一起成長。



【SunLogging】
Linux C++ 開發5 - 一文了解CMake構建
掃碼二維碼,關注微信公眾號,閱讀更多精彩內容

相關文章