# 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. 在終端機輸入指令測試是否正確安裝:

### 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)):

### 安裝
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。

在上方出現的選單中選擇GCC。VSCode會自動開始CMake配置,配置完成後你會在輸出的方框內看到完成配置和完成產生的訊息。

完成配置後,按下方狀態列的播放鍵編譯並執行。
## 搭配 Visual Studio
近年來微軟已經把 CMake 整合進 Visual Studio 的 C++ 工作負載中,現在在 Visual Studio 中建立和開啟 CMake 專案已經是再簡單不過的事。
### 開啟 CMake 專案
在**開始視窗**點選**開啟本機資料夾**,或是在主視窗的選單點擊**檔案→開啟→資料夾**。在開啟的對話視窗選擇 CMake 專案的根目錄,也就是最上層的`CMakeLists.txt`資料夾。開啟後請稍微等一下,Visual Studio會自動開始配置CMake的快取。如果在配置期沒有出現問題,Visual Studio會在輸出面板回報「已完成 CMake 的產生」。

在選取啟動項目的下拉選單選取 CMake 目標就可以編譯並執行。

### 建立 CMake 專案
在**開始視窗**點選**建立新的專案→CMake 專案**,或是在主視窗的選單點擊**檔案→新增→專案→CMake 專案**。一樣勾選「將解決方案與專案置於相同目錄中」簡化目錄結構。

> Visual Studio 2017
>
> 檔案→新增→專案→已安裝→Visual C++→跨平台→CMake→CMake 專案
> 取消勾選「為方案建立目錄」。
## 搭配 KDevelop

### 安裝
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)`,按完成。

接下來進到專案的初始設定。KDevelop把建置目錄(Build directory)預設在`<CMake專案根目錄>/build`,但我們希望編譯產生的檔案會依照編譯型態(Debug 或 Release)分成不同資料夾。所以請在建置目錄的`.../build`之後接一個依照編譯型態命名的資料夾。簡而言之,Debug組態的建置目錄變成`<CMake專案根目錄>/build/Debug`,而待會提到的Release組態的建置目錄會變成`<CMake專案根目錄>/build/Release`。

開啟專案後,等待 CMake 執行配置。如果看到下方輸出訊息顯示「Configuring done」和「Generating done」表示 CMake 成功產生快取。在側邊欄點擊專案分頁就可以開始瀏覽和撰寫程式碼囉!

### 編譯
如果你的 CMake 專案依照上述的步驟有成功產生快取的話,就可以開始編譯了。按左上角工作列的「編譯」,或是按`F8`。
### 執行
按下工作列的執行,或是 `Shift+F9`。
#### 設定啟動器
當編譯完成後你按下工作列上的執行,KDevelop 會跳出一個**啟動設定**的視窗,尋問你應該如何啟動你的專案。在左上角**新增**選單中選擇我們專案中的 **CMake 目標**。以 `Project02` 為例,在選單中你會看到透過 `add_executable` 加入的 `project-02`,還有 `add_library` 加入的 `libstatic1`。因為我們想要「啟動」的是 `project-02` 這個可執行檔,所以選擇它。

新增完成後,你會看到右側面板有一欄**專案目標**顯示「Project02/project-02」。這就是你預設要啟動的目標了,按 OK。
> 為何要多一個這樣的設定而不是直接執行?假設未來在開發大型應用程式時,一個專案可能包含好幾個部件,有主程式相依於其他可執行檔或函式庫。那這時你就會需要指定每個部件的執行順序。
#### 多個啟動器
上方「執行選單 → 設定啟動器...」可以再新增啟動方式。啟動器一次只能選擇一個,也就是當你按下`Shift+F9`時會代入的設定。到「執行選單 → 目前的啟動設定」做切換。
### 新增編譯組態
在開啟專案時,我們設定了Debug組態。現在來新增Release組態。在主選單點擊**專案 → 開啟設定...**,進到「設定專案」的頁面後點擊右上角的「+」按鈕。

又是剛剛設定建置目錄的頁面。編譯型態選擇`Release`,建置目錄請記得設為`<CMake專案根目錄>/build/Release`。

上述設定完成後按 OK → Apply,你就可以在設定專案的頁面選擇 Debug 或 Release:

## 使用 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/` 資料夾。這時就可以按執行了。

#### 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>"
}
}
```