NTOU CSE C++ Programming
CMake
教學文件和作業說明文件: https://hackmd.io/@kogiokka/ntou-cse-cpp-nav
範例程式和範例專案:Google Drive
CMake 是一個跨平台的建置自動化軟體,用於軟體的建置、測試和包裝。CMake 會依照你撰寫的腳本產生平台上原生建置系統的設定檔(例如:Makefile),再使用原生建置系統編譯你的專案。
CMake專案的建置流程:
參考資料:https://cgold.readthedocs.io/en/latest/tutorials/cmake-stages.html
如果你想在 Visual Studio 中使用 CMake 設定專案,請直接到「搭配 Visual Studio」的章節。
CMake 版本選擇 Latest Release,TDM-GCC 版本選擇 MinGW-w64 based(64位元)。Ninja 壓縮檔內為單一檔案的可執行檔,解壓縮後將ninja.exe
放置在C:/dev/bin
目錄下。並且將"C:\dev\bin"
加入系統環境變數(設定→關於→進階系統設定→環境變數)。
使用Homebrew套件管理工具(必須先下載XCode)。開啟終端機後執行下列指令安裝Homebrew:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
腳本跑完後請依照最後的提示訊息把 brew
指令加到 PATH
環境變數中。
安裝所需軟體:
brew install cmake
brew install gcc
brew install ninja
Debian/Ubuntu
apt install gcc g++ cmake ninja-build
CentOS/Fedora
dnf install gcc gcc-c++ cmake ninja-build
專案目錄結構
Project0
├── CMakeLists.txt
└── main.cpp
CMakeLists.txt
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
#include <iostream>
int main()
{
std::cout << "Hello, world!" << std::endl;
return 0;
}
專案目錄結構
Project1
├── CMakeLists.txt
├── f1.cpp
├── f1.h
├── f2.cpp
├── f2.h
└── main.cpp
CMakeLists.txt
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"
# ...
)
專案目錄結構
Project2
├── CMakeLists.txt
├── libstatic1
│ ├── CMakeLists.txt
│ ├── f1.cpp
│ ├── f1.h
│ ├── f2.cpp
│ └── f2.h
└── main.cpp
CMakeLists.txt
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 目標,目標型態為靜態函式庫。
add_library(libstatic1 STATIC)
target_sources(libstatic1
PRIVATE
"f1.cpp"
"f2.cpp"
)
Visual Studio Code 的使用者介面(官方文件):
Visual Studio Code下載頁面:https://code.visualstudio.com/download
使用到的延伸模組:
在左側活動列點選延伸模組按鈕,在 Marketplace 中搜尋上面列出來的模組並安裝。
在 CMake 專案的根目錄建立.vscode
資料夾,並在裡面新增工作區設定檔,settings.json
。開啟設定檔中請將cmake.generator
設成 Ninja
。如下列原始碼:
.vscode/settings.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 專案的根目錄。
VSCode 會提示是否要執行 CMake 配置,選擇 Yes。
在上方出現的選單中選擇GCC。VSCode會自動開始CMake配置,配置完成後你會在輸出的方框內看到完成配置和完成產生的訊息。
完成配置後,按下方狀態列的播放鍵編譯並執行。
近年來微軟已經把 CMake 整合進 Visual Studio 的 C++ 工作負載中,現在在 Visual Studio 中建立和開啟 CMake 專案已經是再簡單不過的事。
在開始視窗點選開啟本機資料夾,或是在主視窗的選單點擊檔案→開啟→資料夾。在開啟的對話視窗選擇 CMake 專案的根目錄,也就是最上層的CMakeLists.txt
資料夾。開啟後請稍微等一下,Visual Studio會自動開始配置CMake的快取。如果在配置期沒有出現問題,Visual Studio會在輸出面板回報「已完成 CMake 的產生」。
在選取啟動項目的下拉選單選取 CMake 目標就可以編譯並執行。
在開始視窗點選建立新的專案→CMake 專案,或是在主視窗的選單點擊檔案→新增→專案→CMake 專案。一樣勾選「將解決方案與專案置於相同目錄中」簡化目錄結構。
Visual Studio 2017
檔案→新增→專案→已安裝→Visual C++→跨平台→CMake→CMake 專案
取消勾選「為方案建立目錄」。
Windows和macOS使用者請到此下載:https://www.kdevelop.org/download
Linux上可以直接由發行版的套件管理工具安裝:
# 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:
將終端機的工作目錄切換到專案的根目錄,再用 cmake
產生 CMake 快取,最後執行建置。在命令列使用 cmake
指令產生CMake快取時,用到的參數種類主要有下列四種:
cmake -S <專案目錄>
-B <建置目錄>
-G <產生器>
-D <變數>=<值>
-S
,指定原始碼目錄。通常是專案的根目錄。如果沒有此選項,預設是終端機當下的工作目錄。-B
,指定建置目錄。通常會在專案根目錄之下多一個 build/
或 out/
資料夾存放 CMake 快取和建置期產生的檔案(例如可執行檔)。-G
,指定用來生成原生建置系統檔案的 CMake 產生器。常用的是 Makefile
或 Ninja
,有支援 CMake 專案的 IDE 通常也是搭配上述其中一個。-D
,定義 CMake 變數。例如 CMAKE_BUILD_TYPE
和 CMAKE_MAKE_PROGRAM
。將參數換行可以增加可讀性,但必須要用跳脫字元來接續。
# 類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
# 類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 Terminal 使用。
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(命令提示字元)
:: 如果電腦上有安裝多個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 產生快取的位置。
# 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
變數來改變安裝的路徑。
不需額外設定變數,在選取啟動項目的選單中選擇「<可執行檔名稱>.exe (安裝)
」的選項即可。
在工作區設定檔(.vscode/settings.json
)中的 cmake.configureSettings
加入CMAKE_INSTALL_PREFIX
變數。並加入cmake.debugConfig
的設定,如下:
.vscode/settings.json
{
"cmake.generator": "Ninja",
"cmake.debugConfig": {
"cwd": "${workspaceFolder}/installed"
},
"cmake.configureSettings": {
"CMAKE_INSTALL_PREFIX": "${workspaceFolder}/installed"
}
}
請在 CMake Tools 下方的工具列把預設的建置目標從[all]
改成[install]
,再執行 Build
,你就會看到專案根目錄多了一個 install/
資料夾。這時就可以按執行了。
在建立專案時的 Extra arguments
欄位輸入 -DCMAKE_INSTALL_PREFIX=<專案根目錄>/install
。若是已經建立的專案,請到專案→設定專案→顯示進階→Extra arguments修改。
在設定啟動器的視窗中,請將上方執行檔的路徑指向install/
資料夾中的可執行檔。還有在下方相依性的部分,把動作欄位改成編譯並安裝。
產生 CMake 快取時,在 cmake
的參數的部分加上 -D CMAKE_INSTALL_PREFIX=<專案根目錄>/install
。例如:
cmake -B build/Debug -G Ninja \
-D CMAKE_BUILD_TYPE=Debug \
-D CMAKE_INSTALL_PREFIX=<專案根目錄>/install
編譯並安裝
cmake --build build/Debug --target install
進到 install/
資料夾,讓工作目錄位於 <專案根目錄>/install/
再執行。
cd install/
./<target-name>.exe
{
"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>"
}
}