# CMake 專案建置 ###### tags: `NTOU CSE C++ Programming` `CMake` > 教學文件和作業說明文件: https://hackmd.io/@kogiokka/ntou-cse-cpp-nav > 範例程式和範例專案:[Google Drive]( https://drive.google.com/drive/folders/100YvcqccLgY_27mJnubRSuPALrMAghJQ?usp=sharing) CMake 是一個跨平台的建置自動化軟體,用於軟體的建置、測試和包裝。CMake 會依照你撰寫的腳本產生平台上原生建置系統的設定檔(例如:Makefile),再使用原生建置系統編譯你的專案。 CMake專案的建置流程: 1. 配置CMake快取(Configure Step) 2. 產生原生建置系統的設定檔(Generate Step) 3. 編譯並執行(Compile & Run) 參考資料:https://cgold.readthedocs.io/en/latest/tutorials/cmake-stages.html ## 環境配置 ### Windows > 如果你想在 Visual Studio 中使用 CMake 設定專案,請直接到「**搭配 Visual Studio**」的章節。 * CMake:https://cmake.org/download/ * TDM-GCC:https://jmeubank.github.io/tdm-gcc/ * Ninja: https://github.com/ninja-build/ninja/releases CMake 版本選擇 **Latest Release**,TDM-GCC 版本選擇 **MinGW-w64 based**(64位元)。Ninja 壓縮檔內為單一檔案的可執行檔,解壓縮後將`ninja.exe`放置在`C:/dev/bin`目錄下。並且將`"C:\dev\bin"`加入系統環境變數(**設定→關於→進階系統設定→環境變數**)。 1. 在終端機輸入指令測試是否正確安裝: ![](https://i.imgur.com/m920Wiv.png) ### macOS 使用[Homebrew](https://brew.sh/)套件管理工具(必須先下載XCode)。開啟終端機後執行下列指令安裝Homebrew: ```zsh /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" ``` 腳本跑完後請依照最後的提示訊息把 `brew` 指令加到 `PATH` 環境變數中。 安裝所需軟體: ```sh brew install cmake brew install gcc brew install ninja ``` ### Linux Debian/Ubuntu ```text apt install gcc g++ cmake ninja-build ``` CentOS/Fedora ```text dnf install gcc gcc-c++ cmake ninja-build ``` ## 範例〇、簡單的CMake專案 專案目錄結構 ```text Project0 ├── CMakeLists.txt └── main.cpp ``` CMakeLists.txt ```cmake= cmake_minimum_required(VERSION 3.16) # CMake_最低版本_需求 project("Hello World") # 建立一個CMake專案,並給予專案名稱 add_executable(hello-world) # 建立一個CMake目標,目標為可執行檔 target_sources(hello-world PRIVATE "main.cpp") # 指定建置該CMake目標時所使用的來源檔案 ``` main.cpp ```cxx= #include <iostream> int main() { std::cout << "Hello, world!" << std::endl; return 0; } ``` ## 範例一、多個原始碼檔案的CMake專案 專案目錄結構 ```text Project1 ├── CMakeLists.txt ├── f1.cpp ├── f1.h ├── f2.cpp ├── f2.h └── main.cpp ``` CMakeLists.txt ```cmake= cmake_minimum_required(VERSION 3.16) project("Project 01") # 新增一個 CMake 目標,目標型態為可執行檔。 add_executable(project-01) # 指定建置該 CMake 目標時所使用的來源檔案,不必包含標頭檔。 # PRIVATE之後列出來的檔案路徑是從 CMakeLists.txt 所在的目錄起始的相對 # 路徑。以空格區隔即可,換行加縮排是為了可讀性和方便編輯。 target_sources(project-01 PRIVATE "main.cpp" "f1.cpp" "f2.cpp" # "f3.cpp" # "f4.cpp" # ... ) ``` ## 範例二、建立和使用靜態函式庫 專案目錄結構 ```text Project2 ├── CMakeLists.txt ├── libstatic1 │ ├── CMakeLists.txt │ ├── f1.cpp │ ├── f1.h │ ├── f2.cpp │ └── f2.h └── main.cpp ``` CMakeLists.txt ```cmake= cmake_minimum_required(VERSION 3.16) project("Project 02") # 新增一個 CMake 目標,目標型態為可執行檔。 add_executable(project-02) target_sources(project-02 PRIVATE "main.cpp" ) # 新增目標 project-02 的 Include 目錄 target_include_directories(project-02 PRIVATE "libstatic1/" ) # 將指定資料夾的 CMake 專案(含有 CMakeLists.txt)一起加入建置。 add_subdirectory("libstatic1/") # 新增目標 project-02 所連結的函式庫。函式庫名稱為其他 CMake 專案的目標名稱。通常來自 # find_package 或 add_subdirectory。以這個範例來說,libstatic1 函式庫是來自 # add_subdirectory 指令所加入的 CMake 專案。 target_link_libraries(project-02 PRIVATE libstatic1 ) ``` libstatic1/CMakeLists.txt ```cmake= # 新增一個 CMake 目標,目標型態為靜態函式庫。 add_library(libstatic1 STATIC) target_sources(libstatic1 PRIVATE "f1.cpp" "f2.cpp" ) ``` ## 搭配 Visual Studio Code Visual Studio Code 的使用者介面([官方文件](https://code.visualstudio.com/docs/getstarted/userinterface)): ![](https://i.imgur.com/82rl4ST.png) ### 安裝 Visual Studio Code下載頁面:https://code.visualstudio.com/download 使用到的延伸模組: * [C/C++](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) * [CMake Tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools) 在左側**活動列**點選**延伸模組**按鈕,在 Marketplace 中搜尋上面列出來的模組並安裝。 ### 設定檔 在 CMake 專案的根目錄建立`.vscode`資料夾,並在裡面新增[工作區設定檔](https://code.visualstudio.com/docs/getstarted/settings#_workspace-settings),`settings.json`。開啟設定檔中請將`cmake.generator` 設成 `Ninja`。如下列原始碼: `.vscode/settings.json` ```json= { // 設定CMake產生器 "cmake.generator": "Ninja", // 設定CMake變數 "cmake.configureSettings": { // 例: // CMAKE_C_COMPILER: "C:/TDM-GCC-64/bin/gcc.exe" // CMAKE_CXX_COMPILER: "C:/TDM-GCC-64/bin/g++.exe" // CMAKE_PREFIX_PATH: "<list-of-search-paths>" // CMAKE_INSTALL_PREFIX: "<prefix-of-installation-path>" // ...... }, } ``` `cmake.configureSettings` 是設定 CMake 的變數,可以指定C/C++編譯器路徑,可執行檔的安裝路徑等等。可以先留空。 ### 開啟 CMake 專案 **檔案→開啟資料夾**,開啟 CMake 專案的根目錄。 VSCode 會提示是否要執行 CMake 配置,選擇 Yes。 ![](https://i.imgur.com/abb6jO5.png) 在上方出現的選單中選擇GCC。VSCode會自動開始CMake配置,配置完成後你會在輸出的方框內看到完成配置和完成產生的訊息。 ![](https://i.imgur.com/nopfDVb.png) 完成配置後,按下方狀態列的播放鍵編譯並執行。 ## 搭配 Visual Studio 近年來微軟已經把 CMake 整合進 Visual Studio 的 C++ 工作負載中,現在在 Visual Studio 中建立和開啟 CMake 專案已經是再簡單不過的事。 ### 開啟 CMake 專案 在**開始視窗**點選**開啟本機資料夾**,或是在主視窗的選單點擊**檔案→開啟→資料夾**。在開啟的對話視窗選擇 CMake 專案的根目錄,也就是最上層的`CMakeLists.txt`資料夾。開啟後請稍微等一下,Visual Studio會自動開始配置CMake的快取。如果在配置期沒有出現問題,Visual Studio會在輸出面板回報「已完成 CMake 的產生」。 ![](https://i.imgur.com/kmPkOv7.png) 在選取啟動項目的下拉選單選取 CMake 目標就可以編譯並執行。 ![](https://i.imgur.com/diaWwOM.png) ### 建立 CMake 專案 在**開始視窗**點選**建立新的專案→CMake 專案**,或是在主視窗的選單點擊**檔案→新增→專案→CMake 專案**。一樣勾選「將解決方案與專案置於相同目錄中」簡化目錄結構。 ![](https://i.imgur.com/9aovMUM.png) > Visual Studio 2017 > > 檔案→新增→專案→已安裝→Visual C++→跨平台→CMake→CMake 專案 > 取消勾選「為方案建立目錄」。 ## 搭配 KDevelop ![](https://i.imgur.com/AnDDl0q.png) ### 安裝 Windows和macOS使用者請到此下載:https://www.kdevelop.org/download Linux上可以直接由發行版的套件管理工具安裝: ```bash # Debian/Ubuntu apt install kdevelop # Fedora dnf install kdevelop # openSUSE zypper install kdevelop5 ``` ### 建立專案 **專案 → 由樣本建立...** 開啟「建立新專案」的視窗。選擇專案模板:**Standard → Terminal**,右欄選單選擇`CMake C++`。 ### 開啟專案 **專案 → 開啟/匯入專案...**,選擇 CMake 專案的根目錄(`CMakeListst.txt`所在的位置)。開啟後你會看見 Project Manager 欄位顯示為 `CMakeListst.txt (CMake Project Manager)`,按完成。 ![](https://i.imgur.com/hfQtc7h.png) 接下來進到專案的初始設定。KDevelop把建置目錄(Build directory)預設在`<CMake專案根目錄>/build`,但我們希望編譯產生的檔案會依照編譯型態(Debug 或 Release)分成不同資料夾。所以請在建置目錄的`.../build`之後接一個依照編譯型態命名的資料夾。簡而言之,Debug組態的建置目錄變成`<CMake專案根目錄>/build/Debug`,而待會提到的Release組態的建置目錄會變成`<CMake專案根目錄>/build/Release`。 ![](https://i.imgur.com/B6V1mIN.png) 開啟專案後,等待 CMake 執行配置。如果看到下方輸出訊息顯示「Configuring done」和「Generating done」表示 CMake 成功產生快取。在側邊欄點擊專案分頁就可以開始瀏覽和撰寫程式碼囉! ![](https://i.imgur.com/xx9w5Qj.png) ### 編譯 如果你的 CMake 專案依照上述的步驟有成功產生快取的話,就可以開始編譯了。按左上角工作列的「編譯」,或是按`F8`。 ### 執行 按下工作列的執行,或是 `Shift+F9`。 #### 設定啟動器 當編譯完成後你按下工作列上的執行,KDevelop 會跳出一個**啟動設定**的視窗,尋問你應該如何啟動你的專案。在左上角**新增**選單中選擇我們專案中的 **CMake 目標**。以 `Project02` 為例,在選單中你會看到透過 `add_executable` 加入的 `project-02`,還有 `add_library` 加入的 `libstatic1`。因為我們想要「啟動」的是 `project-02` 這個可執行檔,所以選擇它。 ![](https://i.imgur.com/Ra4rwo1.png) 新增完成後,你會看到右側面板有一欄**專案目標**顯示「Project02/project-02」。這就是你預設要啟動的目標了,按 OK。 > 為何要多一個這樣的設定而不是直接執行?假設未來在開發大型應用程式時,一個專案可能包含好幾個部件,有主程式相依於其他可執行檔或函式庫。那這時你就會需要指定每個部件的執行順序。 #### 多個啟動器 上方「執行選單 → 設定啟動器...」可以再新增啟動方式。啟動器一次只能選擇一個,也就是當你按下`Shift+F9`時會代入的設定。到「執行選單 → 目前的啟動設定」做切換。 ### 新增編譯組態 在開啟專案時,我們設定了Debug組態。現在來新增Release組態。在主選單點擊**專案 → 開啟設定...**,進到「設定專案」的頁面後點擊右上角的「+」按鈕。 ![](https://i.imgur.com/PUq8AfX.png) 又是剛剛設定建置目錄的頁面。編譯型態選擇`Release`,建置目錄請記得設為`<CMake專案根目錄>/build/Release`。 ![](https://i.imgur.com/FBjI3wB.png) 上述設定完成後按 OK → Apply,你就可以在設定專案的頁面選擇 Debug 或 Release: ![](https://i.imgur.com/6uhOpGw.png) ## 使用 Command Line 將終端機的工作目錄切換到專案的根目錄,再用 `cmake` 產生 CMake 快取,最後執行建置。在命令列使用 `cmake` 指令產生CMake快取時,用到的參數種類主要有下列四種: ```text cmake -S <專案目錄> -B <建置目錄> -G <產生器> -D <變數>=<值> ``` 1. `-S`,指定原始碼目錄。通常是專案的根目錄。如果沒有此選項,預設是終端機當下的**工作目錄**。 2. `-B`,指定建置目錄。通常會在專案根目錄之下多一個 `build/` 或 `out/` 資料夾存放 CMake 快取和建置期產生的檔案(例如可執行檔)。 4. `-G`,指定用來生成原生建置系統檔案的 [CMake 產生器](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html)。常用的是 `Makefile` 或 `Ninja`,有支援 CMake 專案的 IDE 通常也是搭配上述其中一個。 3. `-D`,定義 CMake 變數。例如 [`CMAKE_BUILD_TYPE`](https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html) 和 [`CMAKE_MAKE_PROGRAM`](https://cmake.org/cmake/help/latest/variable/CMAKE_MAKE_PROGRAM.html)。 將參數換行可以增加可讀性,但必須要用跳脫字元來接續。 ### 產生 CMake 快取 #### Linux ```zsh= # 類Unix系統的殼層(shell)中跳脫字元為反斜線(backslash):「\」。 # Debug cmake -B build/Debug -G Ninja \ -D CMAKE_BUILD_TYPE=Debug # Release cmake -B build/Release -G Ninja \ -D CMAKE_BUILD_TYPE=Release ``` #### macOS ```zsh= # 類Unix系統的殼層(shell)中跳脫字元為反斜線(backslash):「\」。 # Debug cmake -B build/Debug -G Ninja \ -D CMAKE_BUILD_TYPE=Debug # Release cmake -B build/Release -G Ninja \ -D CMAKE_BUILD_TYPE=Release ``` #### Windows 終端機建議到微軟商店下載 Windows Terminal 使用。 PowerShell ```powershell # 如果電腦上有安裝多個C/C++編譯器,可以透過設定CC/CXX環境變數指定編譯器。 $Env:CC = "C:/TDM-GCC-64/bin/gcc.exe"; $Env:CXX = "C:/TDM-GCC-64/bin/g++.exe"; # PowerShell的跳脫字元為反引號(backtick):「`」 # Debug cmake -B build/Debug -G Ninja ` -D CMAKE_MAKE_PROGRAM="C:/dev/bin/ninja.exe" ` -D CMAKE_BUILD_TYPE=Debug # Release cmake -B build/Release -G Ninja ` -D CMAKE_MAKE_PROGRAM="C:/dev/bin/ninja.exe" ` -D CMAKE_BUILD_TYPE=Release ``` CMD(命令提示字元) ```cmd :: 如果電腦上有安裝多個C/C++編譯器,可以透過設定CC/CXX環境變數指定編譯器。 set CC="C:/TDM-GCC-64/bin/gcc.exe"; set CXX="C:/TDM-GCC-64/bin/g++.exe"; :: CMD的跳脫字元為脫字符(caret):「^」。 :: Debug cmake -B build/Debug -G Ninja ^ -D CMAKE_MAKE_PROGRAM="C:/dev/bin/ninja.exe" ^ -D CMAKE_BUILD_TYPE=Debug :: Release cmake -B build/Release -G Ninja ^ -D CMAKE_MAKE_PROGRAM="C:/dev/bin/ninja.exe" ^ -D CMAKE_BUILD_TYPE=Release ``` ### 編譯 `cmake --build` 後面接剛才 CMake 產生快取的位置。 ```sh # Debug cmake --build build/Debug # Release cmake --build build/Release ``` ### 執行 產生出的可執行檔會放在 CMake 快取資料夾中,名稱會是 CMake 目標名稱。例如 `build/Debug/<target-name>.exe`。可以在終端機用相對路徑的方式執行它,如果終端機的目錄在專案根目錄,執行的方式就是`./build/Debug/<target-name>.exe`。 ## 安裝 CMake 提供 `install` 指令來指示一個專案應該怎麼安裝。簡單來說,是把專案編譯後產生的執行檔、函式庫和各式資源複製到每個作業系統上預設的安裝路徑,例如 Windows 上的 `C:\Program Files` 或 Linux 上的 `/usr`。開發者也能夠藉由設定 CMake 變數來指定安裝路徑。 ### 為何需要安裝專案 程式開啟時都有所謂的「**工作目錄(Working directory)**」,通常就是可執行檔所在的位置。當程式在讀取外部檔案時,找尋檔案的路徑是從工作目錄開始算起。所以如果程式碼中你要讀取一個路徑為`TestData/hello.txt`的測試檔案,當程式編譯完成要執行時,`hello.txt` 就必須放在可執行檔所在資料夾之下的`TestData/`資料夾。 工作目錄不一定都是可執行檔所在的資料夾,如果你是從終端機(例:命令提示字元)執行程式,則工作目錄會是你終端機所在的目錄。假設現在有一個可執行檔 `a.exe` 位於`<專案根目錄>/build/Debug/`資料夾中。如果你終端機進到`<專案根目錄>`並透過輸入`./build/Debug/a.exe` 執行程式,那麼依剛才的範例,`a.exe`找的檔案路徑就會是`<專案根目錄>/TestData/hello.txt`。 一般的整合開發環境(IDE)在執行你的程式時預設的工作目錄會是可執行檔的目錄。因此,若`hello.txt`放在專案的`TestData` 資料夾之下(即`<專案根目錄>/TestData/hello.txt`),那麼位於 `<專案根目錄>/build/Debug/` 的`a.exe`在執行時就會找不到它。CMake的`install`指令可把建置出的 CMake 目標(可執行檔)和程式的資源(例如範例的`hello.txt`)安裝到同一個資料夾下。但這個資料夾的預設路徑會是系統安裝應用程式的地方,例如`C:/Program Files/`。如果我們不希望讓自己測試的東西就這樣安裝在系統上,就可以透過定義 `CMAKE_INSTALL_PREFIX` 變數來改變安裝的路徑。 ### 開發工具設定 #### Visual Studio 不需額外設定變數,在**選取啟動項目**的選單中選擇「`<可執行檔名稱>.exe (安裝)`」的選項即可。 #### Visual Studio Code 在工作區設定檔(`.vscode/settings.json`)中的 `cmake.configureSettings` 加入`CMAKE_INSTALL_PREFIX` 變數。並加入`cmake.debugConfig`的設定,如下: `.vscode/settings.json` ```json= { "cmake.generator": "Ninja", "cmake.debugConfig": { "cwd": "${workspaceFolder}/installed" }, "cmake.configureSettings": { "CMAKE_INSTALL_PREFIX": "${workspaceFolder}/installed" } } ``` 請在 CMake Tools 下方的工具列把預設的建置目標從`[all]`改成`[install]`,再執行 `Build`,你就會看到專案根目錄多了一個 `install/` 資料夾。這時就可以按執行了。 ![](https://i.imgur.com/8FzrrGN.png) #### KDevelop 在建立專案時的 `Extra arguments` 欄位輸入 `-DCMAKE_INSTALL_PREFIX=<專案根目錄>/install`。若是已經建立的專案,請到**專案→設定專案→顯示進階→Extra arguments**修改。 在設定啟動器的視窗中,請將上方執行檔的路徑指向`install/`資料夾中的可執行檔。還有在下方相依性的部分,把動作欄位改成編譯並安裝。 #### Command Line 產生 CMake 快取時,在 `cmake` 的參數的部分加上 `-D CMAKE_INSTALL_PREFIX=<專案根目錄>/install`。例如: ```sh cmake -B build/Debug -G Ninja \ -D CMAKE_BUILD_TYPE=Debug \ -D CMAKE_INSTALL_PREFIX=<專案根目錄>/install ``` 編譯並安裝 ```sh cmake --build build/Debug --target install ``` 進到 `install/` 資料夾,讓工作目錄位於 `<專案根目錄>/install/` 再執行。 ```sh cd install/ ./<target-name>.exe ``` ## VCPKG ### VSCode ```json { "cmake.buildDirectory": "${workspaceFolder}/build", "cmake.generator": "Ninja", "cmake.configureSettings": { "CMAKE_TOOLCHAIN_FILE": "<path-to-vcpkg.cmake>", "VCPKG_TARGET_TRIPLET": "x64-windows", "CMAKE_RC_COMPILER": "<path-to-windres.exe>" } } ```