CMake個人理解和使用

2023-06-17 12:00:51

前言

CMake是一個構建工具,通過它可以很容易建立跨平臺的專案。通常使用它構建專案要分兩步,通過原始碼生成工程檔案,通過工程檔案構建目標產物(可能是動態庫,靜態庫,也可能是可執行程式)。使用CMake的一個主要優勢是在多平臺或者多人共同作業的專案中,開發人員可以根據自己的喜好來使選擇IDE,不用受其他人工程設定的影響,它有點像跨平臺的IDE,通過它設定好相關設定之後,可以在多個平臺無縫銜接,提高開發效率。

最簡單的CMake工程

專案搭建

一個用CMake來管理的專案,其專案根目錄通常會包含一個CMakeLists.txt的檔案,當然子目錄可能也有,這種情況我們稍後再說。我們先從最簡單的專案開始。以下就是一個最簡單的工程範例:

CMakeProject
|    CMakeLists.txt
|    main.cpp

這就是完整的可以跑起來的最小專案了。按照順序,我們來看看檔案裡的內容

CMakeLists.txt

# 設定版本號
cmake_minimum_required(VERSION 3.10)
# 設定專案名
project(CMakeProject)
# 設定產物和原始碼的關聯
add_executable(${CMAKE_PROJECT_NAME} main.cpp)

說明:

  • CMake中命令不區分大小寫
  • #開始的是備註
  • 參照變數語法${變數名}

所以檔案中真正的有效內容就三行,

  1. cmake_minimum_required(VERSION 3.10)設定了CMake支援的最低版本,VERSION是引數名,後面是版本號,可以根據自己的需要修改。 注意引數名和引數是以空白符分隔的,不是逗號, 不然會報錯。
  2. project(CMakeProject)CMake中字串可以帶引號或者不帶,效果是一致的,這一行就是設定了專案名,如生成的Visual Studio的工程名就是依據這個名字來的。
  3. add_executable(${CMAKE_PROJECT_NAME} main.cpp)
    才是真正管理原始碼和目標產物的地方,這裡我們使用了參照變數的寫法,而檔案中沒有定義這個變數,說明這個變數存在於CMake中,在CMake還有很多預定義的變數,我們可以直接通過這種方式參照,上面的寫法是將專案名設定為產物的名字,當然也可以直接填字串,取個另外的名字都是可以的。後面的main.cpp則是用來生成產物的原始碼路徑,這就是CMake最靈活的地方。原始碼路徑可以是多樣的,查詢出來的,直接寫的,相對路徑,絕對路徑都可以。 多個原始碼的話就用空白符分隔,依次寫就行了。
    在上面的組態檔中,我們設定了它的原始檔為main.cpp,我們想通過它來生成一個可執行的程式,內容也很簡單:
#include <iostream>
int main()
{
	std::cout<<"hello CMake"<<std::endl;
	return 0;
}

專案編譯與執行

準備工作已經做完,接下來我們就要使用CMake生成可執行檔案了。

第一步當然是要安裝CMake啦,這是下載地址!Download,根據自己的平臺選擇下載即可,安裝完成之後需要把它新增到環境變數中,便於我們在任何地方都能方便使用。
安裝了CMake以後,開啟命令列工具,進入到剛才建立的專案根目錄,也就是進入到存著CMakeLists.txtmain.cpp的目錄,下一步準備生成專案。

通常為了不影響和汙染當前的工作環境,我們會選擇新建一個目錄來存放生成的工程檔案,以下我主要以Windows平臺為主要平臺講解,其他平臺基本一致。

mkdir build                 #建立資料夾,儲存工程檔案;
cd build                    #切換cmake工作目錄;
cmake ..                    #生成專案檔案;

這三步執行完後,我們就可以在build資料夾下看到裡面已經生成了一個Visual Studio的工程,我們可以直接用Visual Studio開啟這個工程,按照我們的習慣執行編譯和偵錯。當然,假如想最快地生成可執行檔案,我還是推薦使用CMake。

使用CMake執行編譯,只需要在上一步的基礎上(也就是已經成功執行了上面的三個步驟)再執行一個命令cmake --build .就可以了。這裡切記不能少第三個英文句號,它代表在當前的工作目錄中執行CMake的編譯。
假如上面的四步都一切順利的話,那麼,我們就可以在build/debug目錄下看到以add_executable的第一個引數命名的可執行檔案(這裡就是CMakeProject.exe),雙擊或者把它拖到命令列就可以執行它了。

專案擴充套件

在前面的例子中,生成工程檔案,我們使用了兩個命令,其實,這裡可以直接用一個命令就可以完成——cmake build -S . -B build。這個命令的意思是以當前路徑為工作路徑,以build目錄為生成目錄,生成工程檔案,也就是不需要我們手動建立build資料夾了。其中 -S引數設定的是源路徑,-B設定的是生成路徑。

另外,由於CMake沒有清理方法,所以每次修改CMake的設定(也就是新增或者刪除CMakeLists.txt中的程式碼),需要重新生成工程檔案的時候,需要我們手動清理生成目錄,保證它是空目錄,假如不這樣做,那麼專案可能生成失敗或者新設定不起作用。假如只是修改了原始碼的內容的話,則不需要重新生成,直接進行第四步即可。
雖然上面的操作已經足夠簡單,但是考慮到長期的修改和驗證需要,還是太繁瑣枯燥了,尤其是要反覆切換工作目錄,還是比較煩人的。所以我推薦使用批次處理來完成這些操作。結合清理生成目錄和切換工作目錄這幾個步驟,最終的批次檔可能是這樣的


@echo off 
rd /s /q build
mkdir build
cd build
cmake ..
cmake --build .
cd debug
CMakeProject
cd ../..

按順序依次解釋一下:

第一行是關閉了命令列的回顯功能,因為我們不希望它的回顯干擾到CMake的資訊輸出,以造成不必要的混亂,而且通常我們也只關心它最後有沒有完成工作而不是看它在幹什麼。

第二行則是用了Windows上的刪除資料夾命令(Linux,MacOS上對應的是rmdir),/s是設定它清除資料夾中所有的內容,包括子資料夾,不設定命令就會執行失敗,/q則是讓命令直接執行刪除,不需要我們手動確認,這個引數很重要,不然我們需要一個一個地確認刪除,完全失去了自動化的作用。然後後面的四句就是我們上面講的內容了,不再贅述。

一直來到倒數第二句,這裡我直接寫了可執行檔案的名字(需要替換為你自己的名字),為的就是直接在編譯完成之後執行可執行檔案,這對有些會生成檔案的應用來說很有用。

執行結束後,再將目錄切回到專案根目錄,這就是最後一行的作用,由於我們再編譯的時候已經切換了目錄到生成目錄了,而編譯的可執行檔案又是在生成目錄的子目錄中,所以回到根目錄,我們需要回退兩次,這是保證下次我們能勝利執行批次處理的關鍵。

把上面的內容儲存為bat結尾的檔案,然後下次就可以直接在命令列輸入bat檔名來一次性完成生成和構建了,簡直爽歪歪。
以上就是CMake專案我們所需要知道的了。當然實際專案遠比這個複雜得多,接下來我將以我踩過的坑為基礎,逐一增加專案的複雜度,慢慢形成對CMake的工作流程的理解。

多原始碼專案

個人感悟

在開始之前,我先講一講我對CMake專案或者說CMakeLists.txt檔案的理解。我們不能單獨的以某一個設定為理解物件,我們需要對這些命令進行分類甚至提煉出它的核心工作模式。我是以c++檔案的編譯連結為線索梳理的。 我們都知道一個c++原始檔要想生成可執行程式碼,需要分三步

  • 前處理器處理,拷貝標頭檔案的內容到原始檔,宏替換等;
  • 編譯器將原始檔編譯為.o的物件檔案;
  • 連結器以.o檔案和其他庫為輸入,連結生成可執行檔案。

我們按照這個思路來理解CMake就簡單多了。假如CMake報錯,我們就可以根據報錯資訊定位到是哪個階段出了問題,進而快速找到解決辦法。另外我們也可以依據這些資訊對CMake的設定分類,我自己理解的粗略分類如下:

  • 設定CMake基本資訊的:cmake_minimum_required
  • 原始碼管理的:file,aux_source_directory
  • 庫管理的:find_libraray
  • 標頭檔案管理的:include_directories
  • 連結庫管理的:link_directories
  • 子專案管理的:add_subdirectory
  • 生成物管理的:add_executable,add_library

當然,這些只是很少的一部分,但是對我們理解和搜尋問題的解決思路提供了較好的方向。

CMake管理子目錄

很多時候,我們會引入第三方包來減少重複編碼的工作,通常這種程式碼我們需要放在其他目錄中,於是我新建了一個子目錄,用於模擬存放的第三方程式碼。對於這種情況,我們有兩種包含形式——子模組和子目錄。

先說簡單一些的子目錄吧。子目錄的意思就是將第三方程式碼看作我們程式碼的一部分,一起合併編譯,這種方式可以使我們的專案看起來更緊湊。如以下的專案結構

CMakeProject

|   auto.bat
|   CMakeLists.txt              //修改
|   main.cpp                    //修改
|   

\---3rd                         //新增
        lib.h       

我新建了一個子資料夾,用來模擬第三方程式碼,現在我們把它引入到main.cpp中,編譯,就會發現報錯了,資訊為fatal error C1083: 無法開啟包括檔案: 「lib.h」: No such file or directory,這很正常。結合上面我舉的例子。這個報錯資訊是和標頭檔案相關的,檢視CMake檔案,我發現了CMake有個include_directories的指令,它的意思就是新增檔案頭的目錄,以便讓CMake找到標頭檔案。於是,我在CMakeLists.txt檔案中新增了include_directories(3rd),然後再次執行編譯,專案又正確跑起來了。來看看這時的main.cpp

#include <iostream>
#include <lib.h>

int main()
{
    int a=1,b=1;
	std::cout<<"hello CMake"<<std::endl;
    std::cout<<"a + b = "<<sum(a,b)<<std::endl;
	return 0;
}

注意:這裡的include_directories和cpp中的include是一一對應的,就是說,假如include_directories裡面設定的目錄是.(當前目錄,CMake沒有把當前目錄新增到include路徑),則對應cpp的include要寫成3rd/lib.h這種形式,簡單來說,就是include_directories被設定為了include的根目錄。
另一種情況就是子模組。

CMake管理子模組

子模組的意思是,模組可以單獨編譯,單獨提供給其他庫使用,而不是和主專案共生的,適用於和主模組耦合不大的情況。為了滿足這個條件,我們修改剛才的目錄結構為下面這種

CMakeProject

|   auto.bat
|   CMakeLists.txt                   //修改
|   main.cpp                    

|   
\---3rd
        CMakeLists.txt               //新增
        lib.cpp                      //新增
        lib.h                        //修改

我把lib.h中的函數改為宣告,實現放在了lib.cpp檔案中。最大的變化是新建了3rd目錄下的CMakeLists.txt檔案,用它統一管理3rd目錄下的所有原始檔(假如檔案很多的話,這裡是模擬),使用了add_library3rd目錄下打包成了子模組。

project(sum)
add_library(${PROJECT_NAME} lib.cpp)

add_library在名字和原始碼中間還可以指定構建型別,預設是STATIC,也就是靜態庫,假如想構建動態庫需要手動指定為SHAREDadd_library(${PROJECT_NAME} SHARED lib.cpp))。

重要的改變來自主目錄下的CMakeLists.txt

# 設定版本號
cmake_minimum_required(VERSION 3.10)
# 設定專案名
project(CMakeProject)
# 指定3rd為include的查詢目錄
include_directories(3rd)
# 子模組
add_subdirectory(3rd)
# 設定產物和原始碼的關聯
add_executable(${PROJECT_NAME} main.cpp)
target_link_libraries(${PROJECT_NAME} sum)

新增了add_subdirectory,它的作用是將指定目錄下的原始碼作為一個模組編譯,前提是這個目錄下要有CMakeLists.txt檔案。另一個改變就是target_link_libraries的新增,它的作用是將子模組連結進主模組,假如沒有這一句,在連結的時候會報錯error LNK2019: 無法解析的外部符號。模組的名字需要和子模組中add_library中第一個引數保持一致。

交叉編譯

在前面的範例中,專案的複雜度表現在多目錄,多原始碼,而在使用CMake進行交叉編譯的過程中,專案的主要複雜度表現在環境設定。儘管CMake可以幾乎不修改CMakeLists.txt的情況下,實現交叉編譯,但是對於新手,面對陌生的設定,往往會無從下手,企圖找到一鍵就完成設定的簡便方法。對於CMake,確實沒有這種快捷方法,但是,只要我們理解了交叉編譯就是正確設定屬性值的過程。 這一實質之後,問題就會變得明朗起來。所以,上面的問題就會轉化為我們熟悉的問題了——需要設定哪些屬性,這些屬性有哪些合適的值,這些值怎樣傳遞給CMake等等,這就是交叉編譯的全部了。正如之前提到的一樣,CMake有很多預設的變數,我們需要從這些預設變數中找到一些,設定一些值,然後讓CMake按照這些設定完成工作,這就是我們接下來需要做的事。下面我將以Windows交叉編譯Android為例說明這個過程。

前期準備

在Windows平臺上,預設會使用Visual Studio作為C,C++的編譯器,這對於編譯Android的庫來說可能會報錯。所以在執行cmake命令的時候,需要使用 -G "Unix Makefiles"來改變這一行為。但這還不夠,因為CMake編譯是需要指定編譯器的。而Android上的C,C++編譯器通常以NDK的方式提供,所以,我們需要下載好NDK。在NDK中,會同時為我們提供兩種工具,一種就是編譯器,另一種就是android.toolchain.cmake,這也是CMake命令構成的檔案,裡面為我們交叉編譯指定了很多預設值,能大大減輕我們的工作。

編寫編譯指令碼

前面說了,交叉編譯就是改變CMake預設值,而改變這預設值的方式有兩種,我們要結合起來使用。一種是通過NDK提供的android.toolchain.cmake檔案。 android.toolchain.cmake中以設定了絕大部分的值,但是這些設定也是很靈活的,還有很大的設定空間。因此,根據使用者的需求不同,我們還需要在執行CMake命令時動態傳遞一些值,以使CMake能正確完成工作。這就是另一種方式——選項。傳遞選項會以-D開頭,後面跟著某個CMake的預定義變數由於選項很多,而且大多比較複雜,所以,最好還是通過指令碼檔案來記錄並且修改。以下就是Windows平臺上編譯Android程式碼需要指定的幾個選項,我將逐個介紹這些必要的設定。

  • -DCMAKE_SYSTEM_NAME=Android這個設定是告訴CMake需要生成Android平臺的庫,也就是執行交叉編譯。
  • -DANDROID_ABI=x86這個設定是告訴CMake生成庫適用的架構平臺。熟悉Android開發的讀者應該不會陌生,支援的值會根據NDK的變化而有所變化,如早期的armeabi已經在 NDK r17中移除了,現在主流的還有四種armeabi-v7aarm64-v8ax86x86_64.根據需要把值替換就行。
  • -DANDROID_PLATFORM=android-28,這個值其實不是特別必要,因為有預設值,但是為了可控,還是需要指定一個。它是用來確定庫支援的最低系統版本的。
  • -DCMAKE_TOOLCHAIN_FILE=C:/Users/Leroene/AppData/Local/Android/Sdk/ndk/21.0.6113669/build/cmake/android.toolchain.cmake,這是上面提到的預設檔案。需要注意的是,NDK中有多個以這個名字命名的檔案,假如指定錯誤,可能會導致CMake出錯,所以我的經驗就是,更改版本號(C:/Users/Leroene/AppData/Local/Android/Sdk/ndk/21.0.6113669)及前面的路徑,後面的保持不變。
  • -DCMAKE_MAKE_PROGRAM=C:/Users/Leroene/AppData/Local/Android/Sdk/ndk/21.0.6113669/prebuilt/windows-x86_64/bin/make最後一個引數是指定make程式的路徑,由於我們指定生成了make專案的程式碼,而Windows通常沒有make可執行檔案,所以我們需要讓CMake找到make檔案以完成編譯。這裡我的經驗也是保持後面的不變,修改前面的,並保持版本一致以避免BUG。
  • -DCMAKE_BUILD_TYPE=Release,指定構建型別,這應該很常見了。

至此Windows交叉編譯Android庫的所有設定都講解完了。讓我們來看看它完整的例子

@echo off
rd /s /q build
mkdir build
cd build
cmake -G "Unix Makefiles" ^
-DCMAKE_TOOLCHAIN_FILE=C:/Users/Leroene/AppData/Local/Android/Sdk/ndk/21.0.6113669/build/cmake/android.toolchain.cmake ^
-DCMAKE_MAKE_PROGRAM=C:/Users/Leroene/AppData/Local/Android/Sdk/ndk/21.0.6113669/prebuilt/windows-x86_64/bin/make ^
-DANDROID_PLATFORM=android-28 ^
-DCMAKE_SYSTEM_NAME=Android ^
-DANDROID_ABI=x86 ^
-DCMAKE_BUILD_TYPE=Release ^
../3rd
cmake --build .

從上面可以看到,這些選項後面都跟著一個^符號,這不是cmake的一部分,只是為了我們閱讀方便,特意書寫成這樣的,這是在Windows平臺上批次處理使用的命令換行符,它的作用就是告訴命令解析器,這個命令還沒有結束,接著往下面解析,該功能在Linux,MacOS上對應於\。現在有了這些設定之後,該怎麼使用呢?其實也很簡單,只需要將這些命令儲存在android.bat檔案中,在CMD中切換到當前目錄,執行這個檔案就能在build目錄中找到以libsum.a命名的靜態庫檔案了。下一步,我們試著用這個庫檔案執行在模擬器中。

在Android專案中使用CMake

在Android平臺中,也使用CMake來管理jni的專案,配合Gradle一起完成構建工作。這和普通的CMake專案最大的不同是,我們通常需要參照多個Android相關的庫,如log,android等.這些庫通常是由NDK提供的,我們仿照預設生成的CMakeLists.txt檔案編寫就可。

目錄結構

接下來,為了描述方便,我們先來看一下現在的目錄結構(為了避免混亂,這裡只列出比較有代表性的檔案)

CMakeProject
│  android.bat
│  CMakeLists.txt
│  main.cpp
│  
├─3rd
│      CMakeLists.txt
│      lib.cpp
│      lib.h
│      
└─Android
    │  build.gradle
    │       
    ├─app
    │  │  build.gradle
    │  │                   
    │  ├─libs
    │  └─src                     
    │      ├─main
    │      │  │  AndroidManifest.xml
    │      │  │  
    │      │  ├─cpp
    │      │  │      CMakeLists.txt
    │      │  │      native-lib.cpp
    │      │  │      
    │      │  ├─java
    │      │  │  └─me
    │      │  │      └─hongui
    │      │  │          └─cmakesum
    │      │  │                  MainActivity.kt
    │      │  │                  
    │      │  ├─jniLibs
    │      │  │  └─x86
    │      │  │          libsum.a

在原來的目錄根目錄下新建了Android子目錄,該目錄是一個Android C++工程,所以相比其他普通Android工程,它多了個cpp目錄,後面我們主要的修改都是發生在該目錄下。

原來的根目錄,為了不增加複雜度,我們只作為生成靜態庫的功能存在,所以和上面的範例相比,沒有任何修改。

構建靜態庫

首先,我們回到根目錄。使用根目錄下的android.bat批次處理生成Android上可用的靜態庫,也可以修改android.bat檔案中的-DANDROID_ABI選項的值,生成其他架構的靜態庫,但這需要和jniLibs目錄下的目錄要一一對應,否則可能連結失敗。如我生成的libsum.a檔案是x86的架構。那麼就需要在jniLibs目錄下新建x86的目錄下,然後再把libsum.a放到該目錄下。至此,靜態庫的構建工作就算結束了。

使用靜態庫

把靜態庫放到合適的位置後,我們需要設定app目錄下的build.gradlecpp目錄下的CMakeLists.txt檔案,完成靜態庫的引入。

設定Gradle

首先說build.gradle,該檔案主要涉及到修改ABI的問題,因為不指定的話,Gradle預設生成的ABI可能找不到對應的靜態庫檔案來連結,從而導致連結失敗。該檔案主要的修改如下

android {
    defaultConfig {
        externalNativeBuild {
                cmake {
                    cppFlags ""
                    abiFilters "x86"
                }
            }
    }
}

也就是把abiFilters的值指定為剛才構建的靜態庫相同的值。

設定CMake

CMakeLists.txt檔案就複雜一些了,它需要完成兩個工作,找到靜態庫和靜態庫的標頭檔案,連結靜態庫。

找到標頭檔案

在文章的第二部分我們已經知道了讓CMake找到標頭檔案的include_directories命令,把引數設定為3rd目錄就行了。值得注意的是,CMake是以當前的CMakeLists.txt檔案為工作目錄的,所以,要指定到3rd檔案,我們需要一直回退目錄到根專案,最終就有了include_directories(../../../../../3rd)這樣的設定。儘量使用相對路徑,可以在多人協同的情況下,不用修改設定。

找到靜態庫

下一步要讓CMake找到我們的靜態庫。說到庫,都是和add_library相關的,不同的只是引數。使用原始碼新增庫的時候,我們需要指定庫的名稱和原始碼位置,而參照第三方庫,則是需要指定庫的名稱和型別,外加一個IMPORTED的指示引數,告訴CMake這個庫是匯入的。所以就有了add_library(addSum STATIC IMPORTED)這樣的設定。

但是,這裡我們只告訴了CMake庫的名字,庫儲存在哪裡,還不知道,所以我們還需要另一個命令告訴CMake庫的儲存位置。涉及到設定引數的,通常就是set_target_properties命令了,可以多次呼叫這個命令設定多種設定。set_target_properties(addSum PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libsum.a),第一個引數和上一條的第一個引數是一一對應的,可以隨便取。其實add_library相當於生成了一種目標產物,用第一個引數來指代這種產物,所以才讓我們的set_target_properties找得到合適的目標設定屬性。第二個引數則是設定屬性的標準寫法,第三個代表屬性變數,第四個是屬性值,設定庫路徑的變數就是IMPORTED_LOCATION,而值這裡就有個坑了,Android下的CMake限定值必須是絕對路徑,不能是相對路徑。而這與使用CMake的初衷背道而馳,幸好,我們有幾個預設值可以用,CMAKE_CURRENT_SOURCE_DIR就是其中之一,它代表著當前這個CMakeLIsts.txt檔案的絕對路徑,有了這個,再加上目錄的回退功能,我們就能找到任何合適的目錄了。至此,又出現了第二個問題,當有多個架構的靜態庫需要設定時,我們引入的目錄是不一樣的,而且會出現很多重複的設定。還好有ANDROID_ABI的幫助,它指代了當前編譯的某個架構,隨著編譯的進行,這個值會被設定為合適的值,並且是和正在編譯的架構是一一對應的。所以,儘管它們有點奇怪,但是這給我帶來了靈活和簡單。

連結靜態庫

現在標頭檔案有了,庫也有了,但是C++的編譯是分成兩步的,目前為止,我們的工作只做完了編譯的事情,還沒涉及到連結的事情,當然,相比前面的設定,這就簡單多了,無疑就是在target_link_libraries命令裡新增一個引數就可,如

target_link_libraries( 
                       native-lib
                       ${log-lib}
                       addSum
        )

只需喲注意名字和add_library時設定的名字一一對應就可。

在原始碼中使用

經過漫長的等待,現在我們終於能在native-lib.cpp檔案中引入addsum的標頭檔案,並且使用裡面的函數完成工作了。我打算讓函數返回一個包含加法運算結果的字串。最終實現如下

#include <jni.h>
#include <string>
#include <lib.h>

extern "C" JNIEXPORT jstring JNICALL
Java_me_hongui_cmakesum_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = std::to_string(sum(1,1));
    return env->NewStringUTF(hello.c_str());
}

至此,點選工具列上的run按鈕,我們終於可以在Android的模擬器上看到我們的靜態庫工作的成果啦。

擴充套件

其實除了參照靜態庫的方式之外,我們還可以直接通過設定CMakeLists.txt檔案來參照原始碼,這樣可以隨時隨地對原始碼進行客製化,但是也降低了編譯速度,而且可能會增加CMakeLists.txt的複雜度。所以我還是推薦直接使用靜態庫的方式。

總結

CMake其實還有很多很多命令,我們這裡涉及到的只是很少的一部分。但是,我覺得理解CMake有這些內容差不多就可以了,後續有需要再針對性學習就行了。學習一門技術,切忌不能貪多,貪細。先要抓住主幹,理清脈絡,後面的細節就是水到渠成的事。對於CMake,我覺得就是以C++程式碼編譯為二進位制的過程為主幹就夠了。原始碼從哪裡來,標頭檔案在那裡,庫檔案在哪裡,怎麼組織編譯,參與連結的庫有哪些,生成什麼產物,還有一些完成這些工作的通用操作,複製檔案啊,目錄資訊啊等,這些操作的集合就構成了CMake的主體。另外,CMake其實只是一種構建工具,它本身不是編譯器和連結器,有些問題可能不僅僅會涉及到cmake,還可能會涉及到編譯器和聯結器。當然,這些都是後面深入瞭解之後才可能碰到的問題了。