<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>CMake on kenji.blog</title><link>http://kenji.blog/zh-tw/tags/cmake/</link><description>Recent content in CMake on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>zh-tw</language><copyright>kenjinote</copyright><lastBuildDate>Sat, 12 Sep 2026 09:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/zh-tw/tags/cmake/index.xml" rel="self" type="application/rss+xml"/><item><title>使用CMake建構跨平台C++編譯環境的步驟</title><link>http://kenji.blog/zh-tw/p/cmake-cross-platform-build-environment-guide/</link><pubDate>Sat, 12 Sep 2026 09:00:00 +0900</pubDate><guid>http://kenji.blog/zh-tw/p/cmake-cross-platform-build-environment-guide/</guid><description>&lt;img src="http://kenji.blog/p/cmake-cross-platform-build-environment-guide/img/eyecatch.jpg" alt="Featured image of post 使用CMake建構跨平台C++編譯環境的步驟" />&lt;p>在C++軟體開發中，長久以來讓許多開發者感到頭痛的問題就是「編譯系統 (Build System)」的選擇與建構。由於C++沒有官方的標準套件管理員或編譯系統，因此必須針對不同的平台（Windows、Linux、macOS）使用不同的編譯器與編譯工具（如 MSVC、GCC、Clang、Make、Ninja 等）。&lt;/p>
&lt;p>然而，現在 &lt;strong>CMake&lt;/strong> 已經成為事實上的業界標準（De facto standard），只要正確運用CMake，就能透過單一的 &lt;code>CMakeLists.txt&lt;/code> 優雅地建構出跨平台的編譯環境。&lt;/p>
&lt;p>本文將針對使用CMake的最新（Modern CMake）跨平台C++編譯環境建構步驟，從基礎到進階技巧，進行徹底且詳細的解說。&lt;/p>
&lt;h2 id="1-cmake-是什麼中介編譯系統的概念">1. CMake 是什麼？（中介編譯系統的概念）
&lt;/h2>&lt;p>CMake 本身並不是直接編譯原始碼的工具。CMake 是一個「產生編譯系統的系統」，也就是 &lt;strong>中介編譯系統 (Meta-Build System)&lt;/strong>。&lt;/p>
&lt;p>CMake 的主要角色是讀取不依賴於平台或編譯器的抽象設定檔（&lt;code>CMakeLists.txt&lt;/code>），並自動產生最適合各環境的原生編譯腳本（例如：Linux 的 &lt;code>Makefile&lt;/code>、Windows 的 Visual Studio &lt;code>.sln&lt;/code> 專案檔，或是高速的 &lt;code>build.ninja&lt;/code>）。&lt;/p>
&lt;p>下圖呈現了 CMake 的產生流程。&lt;/p>
&lt;div class="mermaid">graph TD
A["CMakeLists.txt (抽象的編譯定義)"] --> B["CMake (Configure &amp; Generate)"]
B --> C["Unix Makefiles"]
B --> D["Ninja Build Files"]
B --> E["Visual Studio Solutions"]
B --> F["Xcode Projects"]
C --> G["Native Build Tool (make, ninja, MSBuild, xcodebuild)"]
D --> G
E --> G
F --> G
G --> H["Executable / Shared Library / Static Library"]&lt;/div>
&lt;p>透過像這樣將 CMake 夾在中間，開發者就能在管理 C++ 專案時，不需在意各個 OS 之間細微的指令差異。&lt;/p>
&lt;h2 id="2-modern-cmake-的基礎從變數到目標">2. Modern CMake 的基礎：從變數到目標
&lt;/h2>&lt;p>CMake 3.0 以後的寫法被稱為「Modern CMake」，其設計理念與之前的版本（Legacy CMake）有著根本上的差異。在 Legacy CMake 中，主流作法是以目錄為單位改寫全域變數（例如使用 &lt;code>include_directories()&lt;/code> 或 &lt;code>link_libraries()&lt;/code>），但這很容易引發設定意外波及其他模組的嚴重副作用。&lt;/p>
&lt;p>在 Modern CMake 中，所有的東西都會被當作 &lt;strong>目標 (Target)&lt;/strong> 與 &lt;strong>屬性 (Property)&lt;/strong> 來處理。這類似於物件導向程式設計中類別與成員變數的關係。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>目標 (Target)&lt;/strong>: 執行檔（Executable）或函式庫（Library）。&lt;/li>
&lt;li>&lt;strong>屬性 (Property)&lt;/strong>: 編譯該目標所需的原始碼檔案、包含目錄 (include directory)、編譯選項、連結的其他函式庫等。&lt;/li>
&lt;/ul>
&lt;p>透過將設定封裝（侷限）在特定目標內，即使是大型專案，也能實現不會崩潰的安全性編譯定義。&lt;/p>
&lt;h3 id="最小限度的-cmakeliststxt">最小限度的 &lt;code>CMakeLists.txt&lt;/code>
&lt;/h3>&lt;p>首先，讓我們來看看最基本的 &lt;code>CMakeLists.txt&lt;/code>。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 指定 CMake 的最低要求版本
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">cmake_minimum_required&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">VERSION&lt;/span> &lt;span class="s">3.20&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 指定專案名稱與使用的語言
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">project&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">MyAwesomeApp&lt;/span> &lt;span class="s">VERSION&lt;/span> &lt;span class="s">1.0.0&lt;/span> &lt;span class="s">LANGUAGES&lt;/span> &lt;span class="s">CXX&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 要求 C++ 標準規格 (C++20)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">CMAKE_CXX_STANDARD&lt;/span> &lt;span class="s">20&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">CMAKE_CXX_STANDARD_REQUIRED&lt;/span> &lt;span class="s">ON&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">CMAKE_CXX_EXTENSIONS&lt;/span> &lt;span class="s">OFF&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c"># 停用編譯器專屬的擴充功能
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 定義執行檔目標
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">add_executable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">MyAwesomeApp&lt;/span> &lt;span class="s">main.cpp&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>只需這短短幾行，就能完成要求 C++20 且停用編譯器擴充的可攜式執行檔編譯設定。&lt;/p>
&lt;h2 id="3-依賴關係與作用域-public--private--interface">3. 依賴關係與作用域: PUBLIC / PRIVATE / INTERFACE
&lt;/h2>&lt;p>要精通 Modern CMake，最重要且最難懂的就是 &lt;code>target_include_directories&lt;/code> 或 &lt;code>target_link_libraries&lt;/code> 等指令中所使用的 &lt;strong>&lt;code>PUBLIC&lt;/code>、&lt;code>PRIVATE&lt;/code>、&lt;code>INTERFACE&lt;/code>&lt;/strong> 這三個存取修飾詞（作用域）的概念。&lt;/p>
&lt;p>這些修飾詞是用來控制目標的屬性（包含路徑或依賴函式庫）是否「僅為自身的編譯所需？」或是「是否要傳遞給依賴於自己的其他目標？」。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>&lt;code>PRIVATE&lt;/code>&lt;/strong>: 僅為該目標自身的編譯所需。&lt;strong>不會&lt;/strong>傳遞給依賴它的目標。&lt;/li>
&lt;li>&lt;strong>&lt;code>INTERFACE&lt;/code>&lt;/strong>: 該目標自身的編譯不需要，但&lt;strong>會&lt;/strong>傳遞給依賴它的目標（用於 Header-only 函式庫等）。&lt;/li>
&lt;li>&lt;strong>&lt;code>PUBLIC&lt;/code>&lt;/strong>: 該目標自身的編譯需要，且&lt;strong>會&lt;/strong>傳遞給依賴它的目標（&lt;code>PRIVATE&lt;/code> + &lt;code>INTERFACE&lt;/code>）。&lt;/li>
&lt;/ol>
&lt;p>讓我們透過下圖來視覺化依賴關係的傳遞（Usage Requirements 的傳遞）。&lt;/p>
&lt;div class="mermaid">graph TD
subgraph "Libraries"
MathLib["MathLib (Static Library)"]
NetworkLib["NetworkLib (Shared Library)"]
HeaderLib["HeaderLib (Header Only)"]
end
subgraph "Application"
App["Main Application"]
end
App -- "target_link_libraries(App PRIVATE MathLib)" --> MathLib
App -- "target_link_libraries(App PUBLIC NetworkLib)" --> NetworkLib
NetworkLib -- "target_link_libraries(NetworkLib INTERFACE HeaderLib)" --> HeaderLib
note1["App 依賴於 MathLib 的實作，但不向外公開"]
note2["NetworkLib 公開了 HeaderLib 的介面"]&lt;/div>
&lt;h3 id="作用域的具體使用範例">作用域的具體使用範例
&lt;/h3>&lt;p>假設有一個函式庫 &lt;code>MyLib&lt;/code>，它在內部實作中使用了 &lt;code>nlohmann/json&lt;/code>，而在其公開的標頭檔 &lt;code>MyLib.hpp&lt;/code> 中並沒有 include &lt;code>nlohmann/json&lt;/code>。在這種情況下，使用 &lt;code>MyLib&lt;/code> 的一方（應用程式）不需要知道 JSON 函式庫的存在。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 定義函式庫
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">add_library&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">MyLib&lt;/span> &lt;span class="s">src/MyLib.cpp&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 指定專案自身的包含目錄
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># include 目錄因為使用 MyLib 的人也需要，所以設為 PUBLIC
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># src 目錄僅在 MyLib 的實作中使用，所以設為 PRIVATE
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">target_include_directories&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">MyLib&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">PUBLIC&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">$&amp;lt;&lt;/span>&lt;span class="nv">BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">$&amp;lt;&lt;/span>&lt;span class="nv">INSTALL_INTERFACE:include&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">PRIVATE&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">${&lt;/span>&lt;span class="nv">CMAKE_CURRENT_SOURCE_DIR&lt;/span>&lt;span class="o">}&lt;/span>&lt;span class="s">/src&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># json 函式庫僅於內部實作中使用，因此以 PRIVATE 連結
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">target_link_libraries&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">MyLib&lt;/span> &lt;span class="s">PRIVATE&lt;/span> &lt;span class="s">nlohmann_json::nlohmann_json&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>反之，如果在 &lt;code>MyLib.hpp&lt;/code> 中寫了 &lt;code>#include &amp;lt;nlohmann/json.hpp&amp;gt;&lt;/code>，那麼使用 &lt;code>MyLib&lt;/code> 的一方如果不知道 JSON 的標頭檔路徑就會發生編譯錯誤，因此必須以 &lt;code>PUBLIC&lt;/code> 來連結。透過適當地設定此作用域，可以縮短編譯時間並防止不必要的依賴關係洩漏。&lt;/p>
&lt;h2 id="4-原始碼外編譯-out-of-source-build">4. 原始碼外編譯 (Out-of-source Build)
&lt;/h2>&lt;p>在使用 CMake 時，務必遵守的最佳實踐就是 &lt;strong>原始碼外編譯 (Out-of-source Build)&lt;/strong>。
這是一種將編譯產物（目的檔或執行檔）與放置原始碼的目錄（原始碼樹）完全分離，輸出到另一個專用目錄（通常為 &lt;code>build/&lt;/code>）中進行編譯的手法。&lt;/p>
&lt;div class="mermaid">graph TD
Root["Project Root (Git Repository)"]
Root --> Src["src/"]
Root --> Inc["include/"]
Root --> CMake["CMakeLists.txt"]
Root -. "Create build dir" .-> Build["build/ (Out-of-source)"]
Build --> Obj["CMakeFiles/ (Object files, caches)"]
Build --> Bin["Binaries (MyApp.exe)"]
Build --> Gen["Generated Makefile / build.ninja"]&lt;/div>
&lt;p>採用這種架構後，如果想重置編譯環境，只需將整個 &lt;code>build&lt;/code> 目錄刪除即可，而且因為不會弄髒原始碼樹，所以 Git 的管理也會變得非常容易（只需在 &lt;code>.gitignore&lt;/code> 中加入 &lt;code>build/&lt;/code> 即可）。&lt;/p>
&lt;h3 id="編譯執行步驟">編譯執行步驟
&lt;/h3>&lt;p>在 Modern CMake 中，可以使用不依賴作業系統或編譯工具的共通指令來執行編譯。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1. 設置與產生（建立編譯目錄的同時進行設定）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cmake -S . -B build
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 2. 實際的編譯（編譯與連結）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cmake --build build --config Release
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># (選用) 使用多執行緒編譯時加入 -j 選項&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cmake --build build --config Release -j &lt;span class="m">8&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>這裡的 &lt;code>cmake -S . -B build&lt;/code> 代表「將當前目錄（&lt;code>.&lt;/code>）作為原始碼目錄，並將 &lt;code>build&lt;/code> 設定為編譯目錄」。&lt;/p>
&lt;h2 id="5-第三方函式庫的導入方法">5. 第三方函式庫的導入方法
&lt;/h2>&lt;p>在 C++ 開發中，導入外部函式庫（第三方函式庫）向來有很高的門檻。不過在現在，主要有以下三種標準的做法。&lt;/p>
&lt;h3 id="51-find_package-搜尋系統已安裝的函式庫">5.1. find_package (搜尋系統已安裝的函式庫)
&lt;/h3>&lt;p>這是最傳統的方法，用於尋找並連結系統中已安裝的函式庫（例如：OpenSSL 或 Zlib 等）。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">find_package&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">ZLIB&lt;/span> &lt;span class="s">REQUIRED&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">if&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">ZLIB_FOUND&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="nb">target_link_libraries&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">MyAwesomeApp&lt;/span> &lt;span class="s">PRIVATE&lt;/span> &lt;span class="s">ZLIB::ZLIB&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">endif&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="52-fetchcontent-從原始碼下載並整合">5.2. FetchContent (從原始碼下載並整合)
&lt;/h3>&lt;p>這是 CMake 3.11 導入，並在 3.14 之後變得非常強大的模組。它能在編譯時直接從外部的 Git 儲存庫或 URL 下載原始碼，並作為專案的一部分一起編譯。由於能集中管理依賴關係，在跨平台時的重現性極高。&lt;/p>
&lt;p>以下是使用 FetchContent 導入 GoogleTest 的範例。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">include&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">FetchContent&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">FetchContent_Declare&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">googletest&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">GIT_REPOSITORY&lt;/span> &lt;span class="s">https://github.com/google/googletest.git&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">GIT_TAG&lt;/span> &lt;span class="s">v1.14.0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 將函式庫引入專案中
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">FetchContent_MakeAvailable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">googletest&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 建立測試用的執行檔並連結
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">add_executable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">MyTests&lt;/span> &lt;span class="s">test/main.cpp&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">target_link_libraries&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">MyTests&lt;/span> &lt;span class="s">PRIVATE&lt;/span> &lt;span class="s">gtest_main&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="53-與-vcpkg-整合">5.3. 與 vcpkg 整合
&lt;/h3>&lt;p>只要使用微軟主導的 C++ 專用套件管理員 &lt;strong>vcpkg&lt;/strong>，就能輕鬆導入數以千計的函式庫。vcpkg 的設計可與 CMake 無縫整合。&lt;/p>
&lt;p>在執行 CMake 時，只需指定 vcpkg 的 Toolchain 檔案，&lt;code>find_package&lt;/code> 就會自動去尋找 vcpkg 內的函式庫。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE&lt;span class="o">=&lt;/span>/path/to/vcpkg/scripts/buildsystems/vcpkg.cmake
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>此外，只要將 &lt;code>vcpkg.json&lt;/code>（清單模式）放置於專案根目錄，就能完全自動化管理所需函式庫的版本。&lt;/p>
&lt;h2 id="6-跨平台的編譯器旗標-compiler-flags">6. 跨平台的編譯器旗標 (Compiler Flags)
&lt;/h2>&lt;p>為了在 Windows (MSVC)、Linux (GCC/Clang)、macOS (Apple Clang) 任何環境下都能順利編譯，必須適當地設定各編譯器專屬的旗標。&lt;/p>
&lt;p>透過使用 CMake 的 &lt;strong>產生器表達式 (Generator Expressions)&lt;/strong>，就能以宣告式來描述「如果編譯器是 MSVC 就用這個旗標，否則就用那個旗標」這類的條件分支。產生器表達式使用 &lt;code>$&amp;lt;...&amp;gt;&lt;/code> 語法，並在產生編譯系統時（Generate 階段）進行求值。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 在所有平台開啟最高等級警告的範例
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">target_compile_options&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">MyAwesomeApp&lt;/span> &lt;span class="s">PRIVATE&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># MSVC 的情況
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span> &lt;span class="o">$&amp;lt;&lt;/span>&lt;span class="nv">$&amp;lt;CXX_COMPILER_ID:MSVC&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="s">:/W4&lt;/span> &lt;span class="s">/WX&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># GCC 或 Clang 的情況
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span> &lt;span class="o">$&amp;lt;&lt;/span>&lt;span class="nv">$&amp;lt;OR:$&amp;lt;CXX_COMPILER_ID:GNU&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="s">,&lt;/span>&lt;span class="o">$&amp;lt;&lt;/span>&lt;span class="nv">CXX_COMPILER_ID:Clang&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="s">,&lt;/span>&lt;span class="o">$&amp;lt;&lt;/span>&lt;span class="nv">CXX_COMPILER_ID:AppleClang&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="s">&amp;gt;:-Wall&lt;/span> &lt;span class="s">-Wextra&lt;/span> &lt;span class="s">-Wpedantic&lt;/span> &lt;span class="s">-Werror&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>使用這個方法，可以避免大量使用 &lt;code>if(MSVC)&lt;/code> 之類的條件分支導致 &lt;code>CMakeLists.txt&lt;/code> 變得難以閱讀，並能針對每個目標進行彈性的設定。&lt;/p>
&lt;h2 id="7-測試環境的建構-ctest">7. 測試環境的建構 (CTest)
&lt;/h2>&lt;p>為了確保跨平台環境下的品質，導入自動化測試是必須的。CMake 標準內建了一個名為 &lt;strong>CTest&lt;/strong> 的測試執行器 (Test Runner)。&lt;/p>
&lt;p>將前面用 &lt;code>FetchContent&lt;/code> 導入的 GoogleTest 與 CTest 進行整合的步驟如下。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 啟用測試功能（只需在根目錄的 CMakeLists.txt 寫一次）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">enable_testing&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">add_executable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">MyMathTests&lt;/span> &lt;span class="s">test/math_test.cpp&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">target_link_libraries&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">MyMathTests&lt;/span> &lt;span class="s">PRIVATE&lt;/span> &lt;span class="s">gtest_main&lt;/span> &lt;span class="s">MyLib&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 註冊為 CTest 的測試
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">include&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">GoogleTest&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">gtest_discover_tests&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">MyMathTests&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>編譯完成後，只要在編譯目錄內執行 &lt;code>ctest&lt;/code> 指令，就會執行所有的測試並報告結果。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> build
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ctest --output-on-failure -C Release
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="8-編譯系統的理論與數學模型">8. 編譯系統的理論與數學模型
&lt;/h2>&lt;p>在這裡讓我們稍微轉換一下視角，使用數學模型來探討大型專案中編譯系統與平行編譯的效率。&lt;/p>
&lt;p>縮短編譯時間（Compile Time）是 C++ 開發中永遠的課題。透過分割原始碼並進行平行編譯，能夠縮短編譯時間。這種因平行化而帶來的速度提升 (Speedup)，可以透過 &lt;strong>阿姆達爾定律 (Amdahl&amp;rsquo;s Law)&lt;/strong> 來建模。&lt;/p>
&lt;p>假設程式中可平行化部分的比例為 $P$，必須循序執行（無法平行化）部分的比例為 $1-P$，使用的處理器數量為 $N$，那麼整體的理論最大速度提升率 $S(N)$ 可以用以下公式表示：&lt;/p>
$$ S(N) = \frac{1}{(1 - P) + \frac{P}{N}} $$
&lt;p>在 C++ 的編譯過程中，「從各個 &lt;code>.cpp&lt;/code> 檔編譯為 &lt;code>.o&lt;/code> 或 &lt;code>.obj&lt;/code>」是獨立且可平行化的（$P$ 的部分），但「最終由連結器 (Linker) 進行的結合處理」則基本上是循序執行的（$1-P$ 的部分）。&lt;/p>
&lt;p>因此，無論準備了核心數多麼龐大的 CPU（$N \to \infty$），只要連結時間的瓶頸存在，最大的速度提升率就會漸近於以下公式：&lt;/p>
$$ \lim_{N \to \infty} S(N) = \frac{1}{1 - P} $$
&lt;p>這個公式暗示了「單純增加 CPU 核心數對於縮短編譯時間是有極限的」。在 Modern CMake 中適當地劃分 &lt;code>PRIVATE&lt;/code> 與 &lt;code>INTERFACE&lt;/code>，並盡可能減少標頭檔的依賴關係（例如活用前置宣告等），藉此增大 $P$ 的比例，並減少增量編譯 (Incremental Build) 時需重新編譯的目標，這在實務上是最高效的編譯加速策略。&lt;/p>
&lt;p>此外，為了縮短連結時間，將靜態函式庫 (Static Library) 切換為共享函式庫 / DLL (Shared Library)，或是採用 LLD / Mold 等高速連結器，也是非常重要的。&lt;/p>
&lt;p>在 CMake 中，可以很簡單地指定連結器，如下所示：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 在 Clang/GCC 環境中設定使用 lld 連結器
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">if&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">UNIX&lt;/span> &lt;span class="s">AND&lt;/span> &lt;span class="s">NOT&lt;/span> &lt;span class="s">APPLE&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="nb">target_link_options&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">MyAwesomeApp&lt;/span> &lt;span class="s">PRIVATE&lt;/span> &lt;span class="s2">&amp;#34;-fuse-ld=lld&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">endif&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="9-複雜目錄結構的實作範例">9. 複雜目錄結構的實作範例
&lt;/h2>&lt;p>在實際的應用程式開發中，通常是由多個模組組合而成的目錄結構。最後，我們來展示理想中型專案的目錄結構，以及父子 &lt;code>CMakeLists.txt&lt;/code> 之間的關係。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">ProjectRoot/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── CMakeLists.txt (Root: 整個專案的定義)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── vcpkg.json (依賴函式庫的定義)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── external/ (外部模組)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── include/ (公開標頭檔)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── myapp/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── src/ (原始碼與內部編譯定義)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── CMakeLists.txt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── main.cpp
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── math/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ ├── CMakeLists.txt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ ├── Vector3.hpp
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ └── Vector3.cpp
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── network/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── CMakeLists.txt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── NetworkManager.cpp
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── tests/ (測試程式碼)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── CMakeLists.txt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── math_test.cpp
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>根目錄的 &lt;code>CMakeLists.txt&lt;/code> 只進行環境設定與整體的選項定義，並透過 &lt;code>add_subdirectory()&lt;/code> 來加入子目錄。&lt;/p>
&lt;p>&lt;strong>Root &lt;code>CMakeLists.txt&lt;/code>&lt;/strong>:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cmake_minimum_required&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">VERSION&lt;/span> &lt;span class="s">3.20&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">project&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">ComplexApp&lt;/span> &lt;span class="s">LANGUAGES&lt;/span> &lt;span class="s">CXX&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 全域設定
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">CMAKE_CXX_STANDARD&lt;/span> &lt;span class="s">20&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">CMAKE_CXX_STANDARD_REQUIRED&lt;/span> &lt;span class="s">ON&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 啟用測試
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">enable_testing&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 加入子目錄
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">add_subdirectory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">src&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">add_subdirectory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">tests&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>&lt;code>src/CMakeLists.txt&lt;/code>&lt;/strong>:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 加入各個模組
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">add_subdirectory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">math&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">add_subdirectory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">network&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 最終的執行檔
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">add_executable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">ComplexApp&lt;/span> &lt;span class="s">main.cpp&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 連結模組
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">target_link_libraries&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">ComplexApp&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">PRIVATE&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">MathLib&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">NetworkLib&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>像這樣按目錄分割 &lt;code>CMakeLists.txt&lt;/code>，並定義目標之間的依賴關係，可以提高模組的重複使用性，並提升編譯的平行度。這正是 Modern CMake 所提倡的「模組化編譯環境」的精髓。&lt;/p>
&lt;h2 id="10-總結">10. 總結
&lt;/h2>&lt;p>本文解說了使用 CMake 建構跨平台 C++ 編譯環境的步驟。
讓我們來回顧一下重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>理解中介編譯系統&lt;/strong>: CMake 是產生編譯腳本的工具。&lt;/li>
&lt;li>&lt;strong>貫徹 Modern CMake&lt;/strong>: 不使用變數，而是以 &lt;code>add_executable&lt;/code>、&lt;code>target_link_libraries&lt;/code>、&lt;code>target_include_directories&lt;/code> 等&lt;strong>目標導向&lt;/strong>的方式來封裝設定。&lt;/li>
&lt;li>&lt;strong>適當的作用域設定&lt;/strong>: 正確使用 &lt;code>PUBLIC&lt;/code>、&lt;code>PRIVATE&lt;/code>、&lt;code>INTERFACE&lt;/code>，控制依賴關係的波及範圍。&lt;/li>
&lt;li>&lt;strong>貫徹原始碼外編譯&lt;/strong>: 在 &lt;code>build/&lt;/code> 目錄內進行編譯，不弄髒原始碼樹。&lt;/li>
&lt;li>&lt;strong>第三方工具整合&lt;/strong>: 善用 &lt;code>FetchContent&lt;/code> 或 &lt;code>vcpkg&lt;/code>，自動化解決依賴函式庫的問題。&lt;/li>
&lt;li>&lt;strong>活用產生器表達式&lt;/strong>: 聰明地吸收不同編譯器間的旗標差異。&lt;/li>
&lt;li>&lt;strong>數學方法&lt;/strong>: 意識到阿姆達爾定律，減少依賴關係以提升平行編譯的效率。&lt;/li>
&lt;/ol>
&lt;p>雖然一開始會覺得 CMake 很難懂，但只要掌握了目標與屬性的概念，無論是多麼複雜巨大的 C++ 專案，都能維持井然有序的編譯環境。請務必參考本文，用最新的 Modern CMake 寫法來建構您的 C++ 開發環境吧。&lt;/p></description></item></channel></rss>