Try   HackMD

CMake 專案建置

tags: NTOU CSE C++ Programming CMake

教學文件和作業說明文件: https://hackmd.io/@kogiokka/ntou-cse-cpp-nav

範例程式和範例專案:Google Drive

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 版本選擇 Latest Release,TDM-GCC 版本選擇 MinGW-w64 based(64位元)。Ninja 壓縮檔內為單一檔案的可執行檔,解壓縮後將ninja.exe放置在C:/dev/bin目錄下。並且將"C:\dev\bin"加入系統環境變數(設定→關於→進階系統設定→環境變數)。

  1. 在終端機輸入指令測試是否正確安裝:
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

macOS

使用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

Linux

Debian/Ubuntu

apt install gcc g++ cmake ninja-build

CentOS/Fedora

dnf install gcc gcc-c++ cmake ninja-build

範例〇、簡單的CMake專案

專案目錄結構

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; }

範例一、多個原始碼檔案的CMake專案

專案目錄結構

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 的使用者介面(官方文件):

安裝

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 專案

檔案→開啟資料夾,開啟 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上可以直接由發行版的套件管理工具安裝:

# 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快取時,用到的參數種類主要有下列四種:

 cmake -S <專案目錄>
       -B <建置目錄>
       -G <產生器>
       -D <變數>=<>
  1. -S,指定原始碼目錄。通常是專案的根目錄。如果沒有此選項,預設是終端機當下的工作目錄
  2. -B,指定建置目錄。通常會在專案根目錄之下多一個 build/out/ 資料夾存放 CMake 快取和建置期產生的檔案(例如可執行檔)。
  3. -G,指定用來生成原生建置系統檔案的 CMake 產生器。常用的是 MakefileNinja,有支援 CMake 專案的 IDE 通常也是搭配上述其中一個。
  4. -D,定義 CMake 變數。例如 CMAKE_BUILD_TYPECMAKE_MAKE_PROGRAM

將參數換行可以增加可讀性,但必須要用跳脫字元來接續。

產生 CMake 快取

Linux

# 類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

# 類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

# 如果電腦上有安裝多個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 變數來改變安裝的路徑。

開發工具設定

Visual Studio

不需額外設定變數,在選取啟動項目的選單中選擇「<可執行檔名稱>.exe (安裝)」的選項即可。

Visual Studio Code

在工作區設定檔(.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/ 資料夾。這時就可以按執行了。

KDevelop

在建立專案時的 Extra arguments 欄位輸入 -DCMAKE_INSTALL_PREFIX=<專案根目錄>/install。若是已經建立的專案,請到專案→設定專案→顯示進階→Extra arguments修改。

在設定啟動器的視窗中,請將上方執行檔的路徑指向install/資料夾中的可執行檔。還有在下方相依性的部分,把動作欄位改成編譯並安裝。

Command Line

產生 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

VCPKG

VSCode

{
    "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>"
    }
}