提到的:
“THE MOST COMMON C++ TOOLSET”
- VERSION CONTROL SYSTEM
- BUILDING
- PACKAGE MANAGEMENT
這些是 C++ 項目開發中最核心的工具鏈組成部分。下面我將逐一解釋每部分的作用、常見工具,以及它們如何協同構建現代 C++ 項目。
1. VERSION CONTROL SYSTEM(版本控制系統)
用來追蹤文件變化、管理協作開發、備份代碼歷史。
常用工具:
工具 | 簡介 |
---|---|
Git | 最主流的分布式版本控制系統,用于本地和遠程協作(如 GitHub、GitLab) |
SVN | 老牌集中式版本控制系統,已不太常見 |
最佳實踐:
- 用 Git 管理源代碼、構建腳本、配置文件
- 使用
.gitignore
排除臨時文件、構建目錄 - 每個新功能用一個分支(如
feature/foo
) - 使用 Pull Request(PR)做代碼審查
2. BUILDING(構建系統)
將
.cpp
、.h
等源文件編譯鏈接成可執行程序或庫。
常用工具:
工具 | 簡介 |
---|---|
CMake | 最主流跨平臺構建系統,支持多編譯器、自動生成構建文件(Makefile / Ninja / Visual Studio) |
Make / GNU Make | 老牌 UNIX 構建工具,依賴 Makefile |
Ninja | 更快的現代構建工具,常和 CMake 搭配 |
MSBuild | Windows Visual Studio 的默認構建系統 |
示例:CMake 構建流程
mkdir build
cd build
cmake ..
cmake --build .
3. PACKAGE MANAGEMENT(包管理器)
用來下載、安裝、管理 C++ 庫和依賴項。解決“手動下載庫、設置 include/lib”的麻煩。
常用工具:
工具 | 簡介 |
---|---|
vcpkg | 微軟出品,支持跨平臺,最主流之一,支持 CMake 集成 |
Conan | 更強大,支持版本控制、構建配置、包上傳等功能,適合大型項目 |
Hunter / CPM.cmake | CMake 模塊級集成的包管理器(更輕量) |
示例:用 vcpkg 安裝庫
./vcpkg install fmt
cmake .. -DCMAKE_TOOLCHAIN_FILE=path/to/vcpkg.cmake
結合起來:現代 C++ 項目的推薦結構
my_project/
├── CMakeLists.txt # 構建定義
├── src/ # 源文件
├── include/ # 頭文件
├── tests/ # 單元測試
├── build/ # 構建輸出目錄
├── .git/ # Git 倉庫
├── .gitignore
└── vcpkg.json / conanfile.txt # 包依賴聲明
總結:C++ 常用開發工具集
模塊 | 工具 | 說明 |
---|---|---|
版本控制 | Git | 管理代碼版本、團隊協作 |
構建 | CMake + Ninja/Make/MSBuild | 自動構建項目 |
包管理 | vcpkg / Conan | 管理第三方庫依賴 |
編譯器 | GCC / Clang / MSVC | 實際進行編譯 |
這些工具協同使用,構成了現代 C++ 工程開發的基礎工具鏈。 |
現代 C++ 最常用的開發工具集(THE MOST COMMON C++ TOOLSET)
目前業界主流推薦組合是:
類別 | 工具 | 說明 |
---|---|---|
Version Control | Git | 無可爭議的行業標準 |
Building | CMake | 最廣泛支持、跨平臺、集成多構建系統 |
Package Manager | (暫時無標準) | 生態分裂,但 Conan 是強有力的候選者 |
主要觀點解釋(你列出的那些 bullet points)
“Please do not partition our C++ development environment even more”
- 意思是 C++ 社區的工具鏈已經過于碎片化(很多人用不同工具,比如 Makefile、Bazel、vcpkg、手動管理等),不希望再加劇這種分裂。
- 建議盡量統一使用主流工具(Git + CMake + 一個主力包管理器)來提高協作效率和生態整合。
“Other tools may sometimes be better at one aspect but probably are worse at others”
- 意思是:雖然一些構建系統、包管理器在某些方面可能比 CMake/Conan 強(比如速度、配置簡潔等),但它們在其它方面(生態支持、可維護性、跨平臺能力)可能較差。
- 例如:
Bazel
構建很快,語法簡潔,但不易上手、缺少通用庫支持。
“Until a strong game-changer appears on the market please use above tools”
- 當前沒有出現足夠強大的新工具能全面替代 Git、CMake 等。
- 所以除非出現“顛覆性”工具,建議繼續使用 目前最成熟、最兼容、最通用的工具集。
Conan:被視為最有希望成為 C++ 的主流包管理器
“Conan is a strong contender to become the C++ Package Manager”
這是對 C++ 生態中長期缺乏官方標準包管理器的回應。
Conan 的優勢:
特性 | Conan 說明 |
---|---|
支持版本控制 | 明確依賴版本與構建配置 |
跨平臺 / 跨編譯器 | 支持 MSVC, GCC, Clang 等多種配置 |
與 CMake 深度集成 | 可以自動生成 CMake 工具鏈 |
支持私有倉庫 | 適合公司/企業內部使用 |
但目前還有一些阻力,比如: |
- 語法復雜度偏高
- 社區生態還不如 npm / pip 成熟
- 學習曲線相對陡峭
總結:你的筆記理解如下
C++ 最主流的開發工具集(當前最佳實踐):
- 版本控制:Git
- 構建系統:CMake
- 包管理器:暫無統一標準,但 Conan 是最有力候選
社區呼吁統一工具鏈,避免碎片化,除非有革命性工具出現,建議使用上述工具。
列出的是 C++ 項目中管理依賴(第三方庫)的典型方式,出現在許多大型項目。
這些方法各有優缺點,理解它們可以幫助你選擇適合你項目的依賴管理策略。
TYPICAL WAYS TO HANDLE DEPENDENCIES IN C++ PROJECTS
1. external/
或 3rdparty/
目錄 + add_subdirectory()
把第三方庫的源代碼直接復制到你的項目中,然后用
add_subdirectory()
加進 CMake 構建圖。
優點:
- 簡單、可控,離線也能構建
- 不依賴外部工具
缺點: - 手動更新很麻煩
- 難以維護多個版本
- 侵入式(把別人的代碼塞你項目里)
適用: - 小項目
- 學習/教學用途
- 穩定依賴,不常更新
2. Git Submodules + add_subdirectory()
用 Git submodule 管理外部庫倉庫,引入源碼,再通過 CMake 構建。
優點:
- 外部庫保持原始結構
- 可以獨立更新、checkout 版本
缺點: - Git submodule 本身復雜(init、update、版本同步)
- 依賴項目的 CMake 配置必須支持被嵌入(有些不支持)
適用: - 中等規模項目
- 對版本有控制要求的團隊開發
3. ExternalProject_Add()
+ add_subdirectory()
用 CMake 的
ExternalProject_Add()
指定外部源碼的下載方式,然后在構建時拉取并集成。
優點:
- 支持自動拉取、構建第三方項目
- 不需要在源碼里維護外部庫
缺點: - 編譯/構建步驟復雜
- 與主項目構建不共享 target,難以鏈接
- 外部項目的安裝方式要求統一
適用: - 大型項目
- 需要自動化集成但不想內嵌源碼
4. 預先下載并安裝依賴 + find_package()
要求開發者/CI 先安裝所有依賴(系統或自定義路徑),然后通過
find_package()
找到它們。
優點:
- 符合 CMake 模式,清晰且傳統
- 依賴項目和主項目解耦
缺點: - 需要你手動安裝依賴(容易出錯)
- 跨平臺兼容性差(每個平臺裝法不同)
適用: - 系統庫(如 Boost)
- 公司/團隊統一開發環境
5. 使用其他語言生態工具(如 Maven、NuGet 等)
使用 Java、.NET 等語言的包管理器(如 NuGet for MSVC,vcpkg 也類似),間接管理 C++ 依賴。
優點:
- 如果已有現成工具鏈,方便復用
- 在某些平臺上更友好(如 NuGet + MSVC)
缺點: - 不通用,跨平臺麻煩
- 與 C++ 本身生態割裂
適用: - 混合項目(如 C++/CLI、C++ + .NET)
- Windows-only 項目
6. 使用專門的 C++ 包管理器(如 Conan)
使用 Conan(或 vcpkg 等)來拉取、構建、管理依賴。
優點:
- 管理靈活、支持多版本、平臺和編譯器
- 可與 CMake 深度集成(Conan + CMake Toolchain)
- 適合企業級使用,支持私有倉庫
缺點: - 有學習成本
- 有時包缺失,需要自己構建
適用: - 現代項目
- 多平臺構建
- CI/CD 集成
總結對比表
# | 方法 | 控制性 | 易用性 | 可維護性 | 推薦場景 |
---|---|---|---|---|---|
1 | 手動源碼 + add_subdirectory() | 高 | 簡單 | 差 | 教學、小項目 |
2 | Git submodule + add_subdirectory() | 高 | 中等 | 一般 | 中小型項目 |
3 | ExternalProject_Add() | 中 | 復雜 | 一般 | 自動拉庫,隔離構建 |
4 | 系統安裝 + find_package() | 低 | 中等 | 差 | 老項目、公司環境固定 |
5 | Maven / NuGet | 低 | 特定平臺好用 | 差 | 混合語言項目 |
6 | Conan / vcpkg | 高 | 現代 | 強 | 現代企業級開發 |
問題是關于 使用 add_subdirectory()
管理 C++ 依賴(deps),并配合一個 Mermaid 圖 來表達依賴關系:
classDiagramclass LibA class Boost_1_66 class GTest_1_8_0LibA o-- Boost_1_66 : usesLibA ..> GTest_1_8_0 : tests with
你這個 Mermaid 類圖很清晰地表達了:
LibA
聚合(o--
)了Boost 1.66
(即LibA
使用 Boost,但不擁有生命周期)LibA
通過實現/依賴(..>
)了GTest 1.8.0
(即LibA
依賴 GTest 來做測試)
關于 add_subdirectory()
用于依賴管理的理解:
1. add_subdirectory()
是 CMake 中的一個命令,用來:
- 把一個子目錄(通常包含一個
CMakeLists.txt
)加入當前構建樹 - 這樣子目錄的目標(target)、變量等都會被納入當前項目的構建管理
2. 對于第三方依賴的處理:
- 如果你把第三方庫的源碼(比如 GTest)放在你項目某個子目錄下(
external/gtest
),并且它有自己的CMakeLists.txt
,你就可以通過add_subdirectory(external/gtest)
來把它編譯進來。 - 對于像 GTest 這樣原生支持 CMake 的庫,
add_subdirectory()
是一種很方便的集成方式。 - 但是對于 Boost 這種庫(尤其老版本 1.66),它并不是完整 CMake 項目,通常不適合用
add_subdirectory()
,更常用的是系統安裝或通過find_package(Boost)
來使用。
3. 結合你的類圖和依賴關系:
LibA
對Boost
是聚合依賴,建議用find_package()
或包管理工具(Conan、vcpkg)來集成 Boost。LibA
對GTest
是測試依賴,如果你放了 GTest 的源碼,可以用add_subdirectory()
。
總結
依賴庫 | 用 add_subdirectory() 管理嗎? | 推薦做法 |
---|---|---|
Boost 1.66 | 否 | 使用 find_package() 或包管理 |
GTest 1.8.0 | 是 | 直接 add_subdirectory() |
以下是基于您提供的圖片生成的 Mermaid 類圖代碼,展示 LibA
和 LibB
及其依賴關系:
這張類圖很好地表達了兩個庫 LibA
和 LibB
與它們依賴的第三方庫版本之間的關系。針對你問的“add_subdirectory() FOR DEPS?”,我幫你理解和總結:
你的 Mermaid 類圖解釋
classDiagramclass LibA class LibB LibA *--> Boost 1.66LibA ..> GTest 1.8.0LibB *--> Boost 1.57LibB --> OpenSSL 1.0.2LibB ..> GTest 1.7.0
classDiagramclass LibA class LibB LibA *--> Boost 1.66LibA ..> GTest 1.8.0LibB *--> Boost 1.57LibB --> OpenSSL 1.0.2LibB ..> GTest 1.7.0
*-->
表示 組合(強擁有),即LibA
和LibB
都強依賴特定版本的 Boost。-->
表示 關聯/依賴,LibB
依賴 OpenSSL。..>
表示 實現/測試依賴,LibA
和LibB
都用不同版本的 GTest 測試。
add_subdirectory() 用于依賴管理的理解
什么時候用 add_subdirectory()
?
- 你擁有第三方庫的源碼,并且該源碼是 CMake 項目(帶有
CMakeLists.txt
), - 你希望直接把第三方庫編譯到你的項目里,不用單獨安裝或包管理,
- 這時可以用
add_subdirectory()
把依賴庫源碼目錄加入構建。
針對你的依賴
依賴 | 是否適合用 add_subdirectory() | 理由 |
---|---|---|
Boost 1.66 | 通常不適合 | Boost 通常是預編譯庫或系統庫,用 find_package() 更合適 |
Boost 1.57 | 同上 | 同上 |
OpenSSL 1.0.2 | 通常不適合 | 也是系統庫,或者用包管理安裝更方便 |
GTest 1.8.0 | 適合 | GTest 官方支持 CMake,源碼常用 add_subdirectory() |
GTest 1.7.0 | 適合 | 同上 |
推薦的依賴管理方式結合你的圖
- Boost 和 OpenSSL 用系統安裝的庫或者包管理器(Conan、vcpkg等),然后用
find_package()
找到它們。 - GTest 放源碼子目錄,用
add_subdirectory()
編譯。
舉例說明
# CMakeLists.txt
# 找系統或包管理的 Boost
find_package(Boost 1.66 REQUIRED) # for LibA
find_package(Boost 1.57 REQUIRED) # for LibB
find_package(OpenSSL 1.0.2 REQUIRED)
# 添加 GTest 源碼子目錄(假設源碼在 external/gtest)
add_subdirectory(external/gtest)
# 定義 LibA
add_library(LibA ...)
target_link_libraries(LibA PRIVATE Boost::boost gtest)
# 定義 LibB
add_library(LibB ...)
target_link_libraries(LibB PRIVATE Boost::boost OpenSSL::SSL OpenSSL::Crypto gtest)
總結
- add_subdirectory() 適合用來管理支持 CMake 的第三方庫源碼(如 GTest)。
- 對于系統或預編譯的庫(Boost、OpenSSL),用
find_package()
+ 包管理更常見。 - 你的圖很好地表達了依賴關系和庫版本,結合合適的 以下是基于您提供的圖片生成的 Mermaid 類圖代碼,展示
App
及其依賴關系:
classDiagramclass App class LibA class GTest_Latestclass OpenSSL_1_1_0class LibBApp --> LibAApp --> GTest_LatestApp --> OpenSSL_1_1_0App --> LibBLibA o--> Boost 1.66LibA ..> GTest 1.8.0LibB o--> Boost 1.57LibB --> OpenSSL 1.0.2LibB ..> GTest 1.7.0
完整的示例 CMakeLists.txt
,重點展示 LibA、LibB 和它們的依賴如何鏈接,以及 App 如何鏈接 LibA、LibB 和第三方庫。
假設目錄結構如下:
/project/LibACMakeLists.txt.../LibBCMakeLists.txt.../external/gtestCMakeLists.txtCMakeLists.txt <-- 頂層文件
頂層 CMakeLists.txt 示例
cmake_minimum_required(VERSION 3.15)
project(MyApp)
# 查找系統/包管理的 Boost 和 OpenSSL
find_package(Boost 1.66 REQUIRED) # 用于 LibA
find_package(Boost 1.57 REQUIRED) # 用于 LibB
find_package(OpenSSL 1.1 REQUIRED) # 用于 LibB 和 App
# 添加子目錄,構建 LibA, LibB 和 GTest
add_subdirectory(LibA)
add_subdirectory(LibB)
add_subdirectory(external/gtest)
# 構建應用程序
add_executable(App src/main.cpp)
# 鏈接 App 依賴
target_link_libraries(App PRIVATE LibA LibB OpenSSL::SSL OpenSSL::Crypto gtest)
LibA/CMakeLists.txt 示例
add_library(LibA src/liba.cpp)
# 鏈接 Boost 1.66(假設 find_package 找到的 target 是 Boost::boost)
target_link_libraries(LibA PUBLIC Boost::boost)
# 添加測試依賴 GTest(假設 GTest target 是 gtest)
target_link_libraries(LibA PRIVATE gtest)
LibB/CMakeLists.txt 示例
add_library(LibB src/libb.cpp)
# 鏈接 Boost 1.57 和 OpenSSL
target_link_libraries(LibB PUBLIC Boost::boost OpenSSL::SSL OpenSSL::Crypto)
# 添加測試依賴 GTest
target_link_libraries(LibB PRIVATE gtest)
說明
target_link_libraries(LibA PUBLIC Boost::boost)
表示 LibA 對 Boost 的依賴對它的用戶(比如 App)也是可見的。target_link_libraries(LibA PRIVATE gtest)
表示 GTest 只在 LibA 內部用于測試,不傳播給 App。- 頂層 App 鏈接了 LibA, LibB,以及 OpenSSL 和 gtest(測試用庫)。
這樣寫,整個依賴鏈和第三方庫都清晰管理,避免重復鏈接問題。
CMake 不是構建系統,而是構建系統生成器
- CMake 的任務是根據你的配置文件(
CMakeLists.txt
)生成適合你平臺的構建系統文件。 - 例如:
- 在 Linux 下,它生成 Makefile 或 Ninja 文件
- 在 Windows 下,它生成 Visual Studio 工程文件
跨平臺 C++ 構建生成工具
- 你寫的 CMake 配置可以在多平臺、多種編譯器間復用
- 只需要改動少量參數,方便跨平臺開發和構建
CMake 的典型工作流程示例
Linux 下用 GCC
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=~/.local # 配置項目
cmake --build . # 編譯
ctest -VV # 運行測試
cmake --build . --target install # 安裝
Windows 下用 Visual Studio
cmake .. -G "Visual Studio 15 2017 Win64" -DCMAKE_INSTALL_PREFIX=~/.local # 生成 VS 工程
cmake --build . --config Release # 編譯 Release 版本
ctest -VV -C Release # 運行測試
cmake --build . --target install --config Release # 安裝 Release 版本
總結
- 先用
cmake
命令生成構建系統 - 用
cmake --build
或相應平臺的構建工具去構建項目 - 用
ctest
運行測試 - 再用
cmake --build --target install
安裝項目
傳統用法中管理編譯選項(build flags)遇到的問題,并引出現代 CMake 的核心思想。幫你總結理解一下:
傳統管理編譯選項的問題
- Build flags 不可擴展:
你對某個庫或組件設置的公共編譯選項(比如-Wall
、-O2
、-std=c++17
)必須一層層向上層傳遞給依賴它的所有組件和最終應用。
這在大型項目里會很難維護。 - 沖突難解決:
不同目標(target)可能需要不同甚至沖突的編譯選項,統一管理非常麻煩。
Modern CMake 的解決方案
- 以 Target(目標)為中心管理屬性:
- 目標(target)是指
add_library()
或add_executable()
創建的具體模塊。 - 現代 CMake 建議把所有編譯選項、包含目錄、鏈接庫等信息綁定到對應 target 的屬性上。
- 目標(target)是指
- 屬性(Properties)管理依賴和編譯設置:
- 每個 target 可以獨立定義自己的編譯選項、包含目錄、依賴庫等,且可以控制傳遞規則(
PRIVATE
、PUBLIC
、INTERFACE
)。 - 這樣更靈活,也方便依賴管理和維護。
- 每個 target 可以獨立定義自己的編譯選項、包含目錄、依賴庫等,且可以控制傳遞規則(
總結
- 不要用全局變量(如
CMAKE_CXX_FLAGS
)來設置編譯選項。 - 要給每個 target 設置自己的編譯選項和屬性。
- 這樣能讓項目更模塊化、可維護,解決沖突問題。
介紹 CMake 中的 目標類型(target types),也就是 add_executable
和 add_library
支持創建的不同目標類型。幫你梳理理解:
CMake 常見的目標類型(Target Types)
1. 可執行文件 (EXECUTABLE)
add_executable(MyApp main.cpp)
- 生成可執行程序。
- 最終產物是一個程序文件,比如 Linux 下的
MyApp
。
2. 共享庫 (SHARED LIBRARIES)
add_library(MySharedLib SHARED lib.cpp)
- 動態鏈接庫,運行時加載。
- Linux 下生成
.so
,Windows 下生成.dll
。
3. 靜態庫 (STATIC LIBRARIES)
add_library(MyStaticLib STATIC lib.cpp)
- 靜態鏈接庫,編譯時直接打包進可執行文件。
- Linux 下生成
.a
,Windows 下生成.lib
。
4. 對象庫 (OBJECT LIBRARIES)
add_library(MyObjLib OBJECT obj1.cpp obj2.cpp)
- 不生成庫文件,生成目標文件集合 (
.o
或.obj
)。 - 這些對象文件可以被其他目標(靜態庫、共享庫、可執行文件)重用。
- 方便代碼復用,但不會直接鏈接。
5. 接口庫 (INTERFACE LIBRARIES)
add_library(MyInterfaceLib INTERFACE)
- 不產生任何編譯產物(沒有源文件)。
- 主要用來傳遞編譯選項、包含目錄、編譯定義等給依賴它的目標。
- 類似“虛擬目標”,用來管理接口依賴。
6. 別名庫 (ALIAS LIBRARIES)
add_library(MyAliasLib ALIAS RealLib)
- 給已有目標起一個別名。
- 方便引用,特別是在大型項目或跨目錄使用時。
- 不生成新的目標,只是引用已有的。
7. 導入庫 (IMPORTED LIBRARIES)
add_library(ExternalLib IMPORTED GLOBAL)
- 用來聲明外部預編譯好的庫(比如第三方庫)。
- 不由本項目構建,而是外部已有。
- 你可以給它設置位置、包含目錄、鏈接選項等,供項目引用。
總結
目標類型 | 是否生成產物 | 用途 |
---|---|---|
EXECUTABLE | 是 | 生成可執行程序 |
SHARED | 是 | 生成動態庫 (.so/.dll) |
STATIC | 是 | 生成靜態庫 (.a/.lib) |
OBJECT | 否 | 生成目標文件集合,用于復用 |
INTERFACE | 否 | 用于傳遞接口信息,無代碼 |
ALIAS | 否 | 給目標起別名 |
IMPORTED | 否 | 聲明外部已有的庫 |
CMake 中 add_library()
沒有指定庫類型時的默認行為。我們來詳細拆解理解:
add_library()
沒有指定類型時怎么辦?
如果你寫:
add_library(MyLib lib.cpp)
你沒有寫 STATIC
、SHARED
、OBJECT
、INTERFACE
這些類型,這時 CMake 怎么辦?
關鍵點:取決于變量 BUILD_SHARED_LIBS
- CMake 有一個變量叫
BUILD_SHARED_LIBS
,默認是OFF
。 - 如果你沒有顯式指定類型,CMake 會這樣判斷:
|BUILD_SHARED_LIBS
值 | 默認類型 |
| --------------------- | ------------- |
|ON
|SHARED
(共享庫) |
|OFF
(默認) |STATIC
(靜態庫) |
示例
默認情況下(靜態庫):
# 沒有設置 BUILD_SHARED_LIBS
add_library(MyLib lib.cpp) # 實際等價于 add_library(MyLib STATIC lib.cpp)
如果你這樣設置:
set(BUILD_SHARED_LIBS ON) # 全局開啟共享庫構建
add_library(MyLib lib.cpp) # 實際等價于 add_library(MyLib SHARED lib.cpp)
誤區
這個行為 只影響沒有明確指定類型的庫,一旦你寫了:
add_library(MyLib STATIC lib.cpp)
那 BUILD_SHARED_LIBS
就不會影響這個庫了。
總結
add_library(MyLib lib.cpp)
沒有指定類型時,CMake 會參考BUILD_SHARED_LIBS
的值:ON
→ 構建為動態庫OFF
→ 構建為靜態庫(默認)
- 推薦做法:
- 對于每個庫都明確指定類型,避免構建行為不一致或依賴外部變量。
CMake 現代用法講解,核心是理解 target_link_libraries()
的作用域關鍵字(PRIVATE / PUBLIC / INTERFACE) 以及它們在依賴傳播中的含義。
我幫你詳細解釋一下,確保你理解 現代 CMake 的依賴傳播模型(MODULAR DESIGN)。
現代 CMake 的依賴傳播方式(自 CMake 2.8.12 起)
target_link_libraries(<target> PRIVATE|PUBLIC|INTERFACE ...)
這是現代 CMake 的核心命令,用于將依賴項添加到一個目標上。它不僅是“鏈接庫”,還能控制依賴是否傳遞(transitive)。
三種依賴作用域的區別
作用域 | 對我有用?() | 對依賴我的人有用?(dependers) | 用于什么場景? |
---|---|---|---|
PRIVATE | 是的 | 否 | 只在當前目標中使用,比如實現細節庫、內部工具 |
PUBLIC | 是的 | 是的 | 當前目標用,依賴它的人也要用,比如頭文件庫 |
INTERFACE | 否 | 是的 | 自己不使用,僅是為別人傳遞依賴,比如純頭文件庫 |
示例 1:普通庫依賴實現細節
target_link_libraries(MyLib PRIVATE zlib)
- MyLib 使用 zlib
- 依賴 MyLib 的其他目標不會知道或需要 zlib
示例 2:MyLib 使用 Boost 的頭文件庫,且用戶也必須包含
target_link_libraries(MyLib PUBLIC Boost::headers)
- MyLib 需要 Boost
- 依賴 MyLib 的其他目標也會自動獲得 Boost 的包含路徑和編譯選項
示例 3:MyHeaderOnlyLib 是一個純頭文件庫,自己不編譯但別人需要
add_library(MyHeaderOnlyLib INTERFACE)
target_include_directories(MyHeaderOnlyLib INTERFACE include/)
- INTERFACE 庫不能編譯,但可以給別人傳遞包含路徑
- 所以它適用于 header-only 項目
總結重點句式
- PRIVATE:我用,你別管(不向下傳播)
- PUBLIC:我用,你也得用(向下傳播)
- INTERFACE:我不用,你用就行(傳給 dependers)
最重要的一句話
Modern CMake 是關于 target 和 property 的管理,PUBLIC 和 INTERFACE 是 可傳遞依賴(transitive dependencies),而 PRIVATE 不是。
現代 CMake 在物理設計(physical design)層面帶來的轉變,以下是逐條解釋,幫助你理解:
PHYSICAL DESIGN: MODERN CMAKE
“More than a linking diagram”
- 現代 CMake 不只是“誰鏈接誰”的圖(如早期 Makefile 那種)
- 它捕捉的是邏輯依賴:誰真的需要誰的頭文件、編譯選項、定義等
“Logical dependencies included”
- CMake 中的
target_link_libraries()
不只是“鏈接” - 它還傳播頭文件路徑、宏定義、編譯選項等(如果用
PUBLIC
或INTERFACE
)
“PRIVATE and PUBLIC dependencies make a difference”
- 如前所述:
PRIVATE
: 自己用,但不傳下去(不會污染依賴方)PUBLIC
: 自己用,也傳下去(接口依賴)- 它體現了清晰的模塊邊界,避免“編譯污染”
“Now we see real design problems…”
- 當你采用 Modern CMake 模式后,依賴鏈就變成了設計表達
- 如果你的模塊之間有過度耦合、不清晰的接口,會在構建結構中直接暴露出來
NO CYCLIC PHYSICAL DEPENDENCIES!
物理設計中不允許循環依賴!
- CMake 的依賴結構是有向無環圖(DAG)
- A → B → C → A 是非法的
- 如果你在物理層面(模塊間)形成循環,說明你的設計本身就有問題
現代 CMake 依賴結構本身能揭示架構缺陷(模塊濫用、設計不清晰)
ALIAS TARGETS(別名目標)
add_library(MyLibrary lib_source.cpp)
add_library(MyCompany::MyLibrary ALIAS MyLibrary)
作用:
MyLibrary
是一個普通目標MyCompany::MyLibrary
是一個命名空間別名- 讓你的依賴更像現代語言的模塊導入,例如 C++ 的
std::vector
好處:
好處 | 說明 |
---|---|
命名空間結構 | 避免全局名字沖突,體現所屬(如公司/庫) |
更易維護 | 可統一更換內部實現,只要別名不變 |
跨項目清晰 | 適合安裝導出的庫(比如用 install(EXPORT) 時) |
總結:現代 CMake 帶來什么?
特性 | 意義 |
---|---|
目標是核心(Targets) | 不再是全局變量控制一切 |
依賴是語義化的(PUBLIC/PRIVATE/INTERFACE) | 表達真實的邏輯接口依賴 |
沒有循環依賴 | 構建系統暴露架構問題 |
別名目標 | 體現模塊設計與命名規范 |
現代 CMake 變成了一個模塊化構建表達語言,不僅是構建工具,它讓你的構建結構直接反映了你的設計結構。 |
內容集中講的是 Modern CMake 的兩大核心高級特性:
一、ALIAS TARGETS(別名目標)
示例:
add_library(MyLibrary lib_source.cpp)
add_library(MyCompany::MyLibrary ALIAS MyLibrary)
target_link_libraries(MyLibraryTestPUBLICMyCompany::MyLibraryGTest::Main
)
目的和優點:
點 | 解釋 |
---|---|
模塊命名空間 | MyCompany::MyLibrary 表達清晰層級結構,像 std::vector 一樣規范 |
統一接口 | 和 find_package() 兼容:像 find_package(fmt) 給你 fmt::fmt ,你也可以提供 MyCompany::MyLibrary |
防止拼寫錯誤 | :: 強制語法,不能用 MyCompany:: 作為目標,這種結構只能別名已有 target,錯了就編譯錯誤 |
適用于安裝和導出目標 | 更適合跨項目復用,如通過 install(EXPORT ...) 導出別名給下游用 |
二、GENERATOR EXPRESSIONS(生成器表達式)
概念:
- 寫在
$<...>
里的表達式,不是立即求值 - 是 在生成構建系統時 由 CMake 評估的
- 常用于按平臺、配置、變量值等條件切換行為
BAD 示例(傳統方式):
if(CMAKE_BUILD_TYPE STREQUAL DEBUG)target_sources(hello PRIVATE helper_debug.cpp)
else()target_sources(hello PRIVATE helper_release.cpp)
endif()
問題:
CMAKE_BUILD_TYPE
不是可靠來源(不適用于多配置生成器如 Visual Studio)- 無法切換構建類型后自動改變行為(必須重新配置)
GOOD 示例(生成器表達式):
add_executable(hello main.cpp$<IF:$<CONFIG:Debug>:helper_debug.cpp,helper_release.cpp>
)
優點:
CONFIG:Debug
檢測構建配置(支持 VS/Xcode 多配置構建)- 表達式在構建系統生成時才判斷,不影響配置時的 CMake 腳本流程
- 更可移植、更聲明式、更適合現代項目
示例:使用 VERBOSE 控制宏定義
target_compile_definitions(foo PRIVATE"VERBOSITY=$<IF:$<BOOL:${VERBOSE}>,30,10>"
)
- 如果變量
VERBOSE
為真,則定義VERBOSITY=30
,否則是10
- 這樣可以在不寫 if() 的情況下控制條件編譯宏
三、BUILD_INTERFACE vs INSTALL_INTERFACE
用法示例:
target_include_directories(mylibINTERFACE$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>$<INSTALL_INTERFACE:include>
)
表達式 | 用途 |
---|---|
BUILD_INTERFACE | 當前項目被構建時用的路徑(如源代碼 include/) |
INSTALL_INTERFACE | 安裝后導出給別人的路徑(如 CMAKE_INSTALL_PREFIX/include ) |
意義: |
- 避免安裝目標引用本地源碼路徑
- 讓構建時和安裝后的 include 目錄各自獨立、正確
總結
特性 | 說明 |
---|---|
ALIAS TARGETS | 定義命名空間別名,統一目標命名,便于導出 |
GENERATOR EXPRESSIONS | 生成階段判斷條件,替代 if(),適配多構建配置 |
NEVER USE CMAKE_BUILD_TYPE in if() | 多配置構建器無法使用該變量正確判斷 |
BUILD/INSTALL INTERFACE | 精準區分構建時 vs 安裝后的接口信息,防止路徑污染 |
Modern CMake 的庫設計最佳實踐(MODERN LIBRARY EXAMPLE) 的詳細說明,重點在于:
1. GENERATOR EXPRESSIONS 用于接口路徑管理
target_include_directories(Foo PUBLIC$<BUILD_INTERFACE:${Foo_BINARY_DIR}/include>$<BUILD_INTERFACE:${Foo_SOURCE_DIR}/include>$<INSTALL_INTERFACE:include>)
理解:
表達式 | 含義 |
---|---|
$<BUILD_INTERFACE:...> | 表示:構建當前項目時 應該使用的 include 路徑(源碼路徑、構建目錄等) |
$<INSTALL_INTERFACE:...> | 表示:安裝之后被別人使用時 暴露的路徑(一般是 install/include ) |
這是為了解決一個關鍵問題: |
構建時我可以包含本地源文件夾,但安裝后不應該暴露它!
2. MODERN CMake 庫項目結構
這是一套推薦寫法,是現代 CMake 項目模板的基礎。
示例完整解釋:
cmake_minimum_required(VERSION 3.8)
project(MyLibrary VERSION 0.0.1) # 避免自定義變量在這里
- 避免在
project()
中添加自定義變量,比如不要寫LANGUAGES CXX C
冗余參數。 - 推薦只寫名稱和版本,簡潔明了。
依賴查找:
find_package(Foo 1.0 REQUIRED)
- 使用
Foo::Foo
的方式鏈接依賴庫 - 與
target_link_libraries()
兼容,便于封裝模塊化依賴
添加庫:
add_library(MyLibrary lib_source.cpp lib_source.h include/MyLibrary/lib_header.h)
- 一個真實的源文件庫
- 包含
.cpp
和.h
,同時列出頭文件方便 IDE 識別
設置 C++ 標準:
target_compile_features(MyLibrary PUBLIC cxx_std_17)
- 設置為 C++17
PUBLIC
表示:依賴MyLibrary
的項目也要使用 C++17
設置頭文件路徑:
target_include_directories(MyLibrary PUBLIC$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>$<INSTALL_INTERFACE:include>)
- 構建時用項目自身路徑
- 安裝后只暴露
include/
路徑 - 避免把私有源碼路徑“泄露”出去
鏈接依賴:
target_link_libraries(MyLibrary PRIVATE Foo::Foo)
PRIVATE
表示:MyLibrary 自己用 Foo,但依賴 MyLibrary 的人不需要關心 Foo
創建別名目標(ALIAS):
add_library(MyCompany::MyLibrary ALIAS MyLibrary)
- 給庫設置命名空間別名
- 為安裝導出、find_package 使用提供統一接口
3. 避免在 project()
中加自定義變量(推薦寫法)
project(MyLibrary VERSION 0.0.1)
- 推薦只傳名稱 + 版本號
- 不建議在
project()
中混入路徑、自定義宏、toolchain 等 - 現代 CMake 項目建議用 目標屬性 控制項目行為,而非全局變量
總結:現代 CMake 庫設計最佳實踐
原則 | 說明 |
---|---|
使用 Targets 管理構建 | 不再用全局變量 |
分清 PRIVATE / PUBLIC / INTERFACE | 明確依賴的傳播性 |
用 Generator Expressions 控制構建/安裝路徑 | 避免路徑污染 |
使用 命名空間 ALIAS | 模塊化設計,統一接口命名 |
安裝友好,find_package 兼容 | 便于重用和分發 |
避免 if(CMAKE_BUILD_TYPE) | 用 $<CONFIG:...> 替代 |
現代 CMake 項目的 文件組織結構(FILES ORGANIZATION),目的是讓項目結構清晰、模塊解耦、易于維護和安裝。它強調將源碼、測試和頂層配置分開處理,每一部分職責明確。
我來幫你完整地理解和總結這一結構的設計思想:
典型目錄結構(現代 CMake 項目)
MyLibrary/
├── CMakeLists.txt <-- 項目入口(頂層)
├── src/
│ ├── CMakeLists.txt <-- 只負責庫構建和安裝
│ └── my_library.cpp/hpp <-- 庫源碼
├── test/
│ ├── CMakeLists.txt <-- 只負責測試目標
│ └── unit_tests.cpp
└── include/└── MyLibrary/└── lib_header.h <-- 公共頭文件
各部分職責說明
頂層 CMakeLists.txt
(項目入口)
“Simple project wrapper / Entry point for development”
- 設置項目名稱和版本
- 添加編譯選項(比如
CMAKE_CXX_STANDARD
、BUILD_SHARED_LIBS
) - 添加子目錄:
add_subdirectory(src)
、add_subdirectory(test)
- 不直接定義庫或測試邏輯
src/CMakeLists.txt
(只負責庫)
“Standalone library definition and installation”
“Does not change compiler’s warnings!”
- 添加庫(
add_library(...)
) - 設置接口、包含路徑、版本信息、安裝規則
- 不負責控制編譯警告(應由頂層或 toolchain 設置)
保持純凈,這個 CMake 文件應是可以被其他項目add_subdirectory()
或add_external_project()
使用的!
test/CMakeLists.txt
(只負責測試)
“Standalone unit tests definition”
- 查找或引入測試框架(如 GTest)
- 添加測試目標:
add_executable(MyTests ...)
- 鏈接庫:
target_link_libraries(MyTests MyLibrary GTest::Main)
- 使用
enable_testing()
+add_test()
注冊測試
示例:頂層 CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(MyLibrary VERSION 1.0 LANGUAGES CXX)
add_subdirectory(src)
add_subdirectory(test)
示例:src/CMakeLists.txt
add_library(MyLibrary my_library.cpp)
target_include_directories(MyLibraryPUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include>$<INSTALL_INTERFACE:include>
)
target_compile_features(MyLibrary PUBLIC cxx_std_17)
# 安裝規則
install(TARGETS MyLibrary EXPORT MyLibraryTargets)
install(DIRECTORY ../include/ DESTINATION include)
# 可選:命名空間別名
add_library(MyCompany::MyLibrary ALIAS MyLibrary)
示例:test/CMakeLists.txt
enable_testing()
add_executable(MyLibraryTests unit_tests.cpp)
target_link_libraries(MyLibraryTests PRIVATE MyLibrary GTest::Main)
add_test(NAME UnitTests COMMAND MyLibraryTests)
總結
模塊位置 | 作用與原則 |
---|---|
/src | 構建庫本身,專注模塊定義和安裝,不修改全局選項 |
/test | 定義測試目標,只負責測試 |
頂層 CMakeLists | 項目入口,只做配置和引導,不定義目標 |
現代 CMake 鼓勵模塊化、清晰分工、無全局污染 —— 這也是可維護性和可復用性的關鍵。
Modern CMake 項目中如何組織“開發入口(wrapper)”與“測試目標”。下面是完整的理解與總結:
WRAPPER: LOCAL DEVELOPMENT ENTRY POINT
這是指頂層 CMakeLists.txt
的職責,它作為 項目開發的入口點,方便本地構建、運行測試、使用 IDE 等。
示例:
cmake_minimum_required(VERSION 3.5)
project(MyLibrary)
# 添加主庫代碼(不直接定義庫)
add_subdirectory(src)
# 添加單元測試(可選)
enable_testing()
add_subdirectory(test)
理解要點:
項目頂層職責 | 描述 |
---|---|
項目入口 | 提供給開發者(或 IDE)一個統一的構建入口 |
集成 src/test 子模塊 | add_subdirectory() 將各模塊構建圖統一 |
不需要安裝庫也能測試 | 本地開發時不需要 make install ,直接構建測試 |
IDE 兼容性好 | CLion、Visual Studio 可識別該項目入口作為工作空間 |
MODERN LIBRARY USAGE: TESTS
這是你在 /test/CMakeLists.txt
中組織測試的方式。即使庫被安裝到系統中,測試代碼也能獨立存在并運行。
示例:test/CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(MyLibraryTests)
enable_testing()
find_package(GTest MODULE REQUIRED)
# 如果本地開發,MyLibrary 已存在;如果只在測試項目中構建,需要通過 find_package 找到安裝版本
if(NOT TARGET MyCompany::MyLibrary)find_package(MyLibrary CONFIG REQUIRED)
endif()
add_executable(MyLibraryTests tests_source.cpp)
target_link_libraries(MyLibraryTests PRIVATEMyCompany::MyLibraryGTest::Main
)
add_test(NAME MyLibrary.UnitTests COMMAND MyLibraryTests)
理解要點:
點 | 解釋 |
---|---|
if(NOT TARGET ...) | 支持兩種方式:本地源構建(直接 add_subdirectory(src) )或使用安裝包(find_package ) |
add_test() | 讓 ctest 可以自動執行測試用例 |
使用命名空間 MyCompany::MyLibrary | 與 find_package() 的 alias 兼容,保持一致接口 |
總結一句話:
現代 CMake 項目將“庫構建”、“測試邏輯”和“開發入口”清晰分離,通過頂層 wrapper 匯總,而測試模塊具備獨立運行能力,無需依賴安裝。
構建 & 測試流程簡要回顧
mkdir build && cd build
cmake ..
cmake --build .
ctest -V # 運行測試
COMPILED, BUILT, TESTED — Done?
是的,這是現代 CMake 項目開發的基本完整流程:
- 本地構建:
- 測試驗證:
- 不需要安裝庫就能測試:
- 可遷移到 CI/CD:
- 可被
find_package()
使用:
現在正在學習 如何使用 CMake 正確導出(export)你的庫接口,也就是說:
讓別人可以通過
find_package(MyLibrary)
使用你的庫
這正是現代 CMake 項目發布和復用的關鍵部分。
目標:EXPORT YOUR LIBRARY INTERFACE
你希望做什么?
把 MyLibrary
變成一個可以被別的項目使用的“包”(package):
- 安裝
.so
/.a
/.dll
文件 - 安裝頭文件
- 安裝導出目標信息(
MyLibraryTargets.cmake
) - 提供
MyLibraryConfig.cmake
(+版本) - 支持
find_package(MyLibrary CONFIG REQUIRED)
調用
分步解釋你筆記中的 CMake 腳本
基礎定義
cmake_minimum_required(VERSION 3.8)
project(MyLibrary VERSION 0.0.1)
find_package(Foo 1.0 REQUIRED)
add_library(MyLibrary lib_source.cpp lib_source.h include/MyLibrary/lib_header.h)
這是定義你的庫,并鏈接依賴 Foo(可能是別人的庫)
安裝目標(庫)
install(TARGETS MyLibrary EXPORT MyLibraryTargetsLIBRARY DESTINATION libARCHIVE DESTINATION libRUNTIME DESTINATION binINCLUDES DESTINATION include
)
這一段的意思是安裝你的庫產物(
.a
,.so
,.dll
)以及包含頭文件路徑信息。
安裝目標導出文件(targets.cmake)
install(EXPORT MyLibraryTargetsDESTINATION lib/cmake/MyLibraryFILE MyLibraryTargets.cmakeNAMESPACE MyCompany::
)
這會生成
MyLibraryTargets.cmake
,它保存MyCompany::MyLibrary
這個 target 的元信息。
依賴方可以通過
find_package(MyLibrary)
加載它。
安裝頭文件目錄
install(DIRECTORY include/MyLibrary DESTINATION include)
把你的公共頭文件安裝到系統或指定安裝目錄。
創建 MyLibraryConfigVersion.cmake
include(CMakePackageConfigHelpers)
write_basic_package_version_file(MyLibraryConfigVersion.cmakeCOMPATIBILITY SameMajorVersion
)
自動生成
MyLibraryConfigVersion.cmake
文件,用于支持find_package(MyLibrary VERSION ...)
時的版本檢查。
安裝配置文件 + 版本文件
install(FILESMyLibraryConfig.cmake${CMAKE_CURRENT_BINARY_DIR}/MyLibraryConfigVersion.cmakeDESTINATION lib/cmake/MyLibrary
)
這一段把你的
*.cmake
文件(配置+版本)放到安裝路徑下,讓find_package()
能找到。
MyLibraryConfig.cmake
的內容(必須手寫!)
include(CMakeFindDependencyMacro)
find_dependency(Foo 1.0)
include("${CMAKE_CURRENT_LIST_DIR}/MyLibraryTargets.cmake")
解釋:
- 告訴
find_package(MyLibrary)
時:- 先找
Foo
(你的依賴) - 然后加載導出的
MyLibraryTargets.cmake
,即注冊MyCompany::MyLibrary
- 先找
總結:完整導出流程
步驟 | 作用 |
---|---|
install(TARGETS ...) | 安裝庫文件和頭文件 |
install(EXPORT ...) | 導出 target 接口 |
write_basic_package_version_file() | 生成版本控制文件 |
install(FILES ...) | 安裝配置與版本文件 |
手寫 MyLibraryConfig.cmake | 定義依賴和目標注冊 |
安裝后的 find_package 工作方式
其他項目只需這樣:
find_package(MyLibrary CONFIG REQUIRED)
target_link_libraries(app PRIVATE MyCompany::MyLibrary)
即可安全鏈接、復用你的庫(包括傳遞依賴、編譯特性等)。
下面是一個 完整導出式 CMake 項目模板,包含:
- 模塊化的項目結構(
src/
、include/
、test/
) - 安裝導出支持(
install() + export()
) - 可被
find_package()
使用(包含MyLibraryConfig.cmake
與版本控制) - Modern CMake 實踐(target-based,命名空間、INTERFACE 編譯特性等)
目錄結構
MyLibrary/
├── CMakeLists.txt # 頂層入口(wrapper)
├── include/
│ └── MyLibrary/
│ └── MyLibrary.hpp # 公共頭文件
├── src/
│ ├── CMakeLists.txt
│ └── MyLibrary.cpp # 庫實現
├── test/
│ ├── CMakeLists.txt
│ └── MyLibraryTests.cpp # 單元測試
├── MyLibraryConfig.cmake # 手寫的 config 文件
頂層 CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(MyLibrary VERSION 1.0 LANGUAGES CXX)
# 設置 C++ 標準
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 添加模塊
add_subdirectory(src)
add_subdirectory(test)
src/CMakeLists.txt
(定義庫 + 安裝 + 導出)
add_library(MyLibraryMyLibrary.cpp${CMAKE_CURRENT_SOURCE_DIR}/../include/MyLibrary/MyLibrary.hpp
)
target_include_directories(MyLibraryPUBLIC$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include>$<INSTALL_INTERFACE:include>
)
target_compile_features(MyLibrary PUBLIC cxx_std_17)
# 安裝目標
install(TARGETS MyLibraryEXPORT MyLibraryTargetsARCHIVE DESTINATION libLIBRARY DESTINATION libRUNTIME DESTINATION binINCLUDES DESTINATION include
)
# 安裝頭文件
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../include/DESTINATION include)
# 導出 CMake targets
install(EXPORT MyLibraryTargetsFILE MyLibraryTargets.cmakeNAMESPACE MyCompany::DESTINATION lib/cmake/MyLibrary
)
# 生成版本文件
include(CMakePackageConfigHelpers)
write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/MyLibraryConfigVersion.cmake"VERSION ${PROJECT_VERSION}COMPATIBILITY SameMajorVersion
)
# 安裝 config & version
install(FILES${CMAKE_CURRENT_LIST_DIR}/../MyLibraryConfig.cmake${CMAKE_CURRENT_BINARY_DIR}/MyLibraryConfigVersion.cmakeDESTINATION lib/cmake/MyLibrary
)
# 添加別名 target
add_library(MyCompany::MyLibrary ALIAS MyLibrary)
MyLibraryConfig.cmake
(手寫)
# Optional dependencies here:
# include(CMakeFindDependencyMacro)
# find_dependency(SomeDep REQUIRED)
include("${CMAKE_CURRENT_LIST_DIR}/MyLibraryTargets.cmake")
test/CMakeLists.txt
(本地測試)
enable_testing()
find_package(GTest REQUIRED)
add_executable(MyLibraryTests MyLibraryTests.cpp)
target_link_libraries(MyLibraryTestsPRIVATEMyCompany::MyLibraryGTest::Main
)
add_test(NAME MyLibrary.UnitTests COMMAND MyLibraryTests)
include/MyLibrary/MyLibrary.hpp
#pragma once
namespace MyLibrary {int add(int a, int b);
}
src/MyLibrary.cpp
#include "MyLibrary/MyLibrary.hpp"
namespace MyLibrary {int add(int a, int b) { return a + b; }
}
xiaqiu@xz:~/test/CppCon/day205/code$ ls
CMakeLists.txt MyLibraryConfig.cmake include main.cpp src test
xiaqiu@xz:~/test/CppCon/day205/code$ tree
.
├── CMakeLists.txt
├── MyLibraryConfig.cmake
├── include
│ └── MyLibrary
│ └── MyLibrary.hpp
├── main.cpp
├── src
│ ├── CMakeLists.txt
│ └── MyLibrary.cpp
└── test├── CMakeLists.txt└── MyLibraryTests.cpp
5 directories, 8 files
xiaqiu@xz:~/test/CppCon/day205/code$
構建 + 安裝
mkdir build && cd build
cmake .. -DCMAKE_INSTALL_PREFIX=../install
cmake --build .
cmake --install .
下面流程
xiaqiu@xz:~/test/CppCon/day205/code$ mkdir build && cd build
xiaqiu@xz:~/test/CppCon/day205/code/build$ cmake … -DCMAKE_INSTALL_PREFIX=…/install
– The CXX compiler identification is GNU 13.3.0
– Detecting CXX compiler ABI info
– Detecting CXX compiler ABI info - done
– Check for working CXX compiler: /usr/bin/c++ - skipped
– Detecting CXX compile features
– Detecting CXX compile features - done
– Found GTest: /usr/lib/x86_64-linux-gnu/cmake/GTest/GTestConfig.cmake (found version “1.14.0”)
– Configuring done (1.0s)
– Generating done (0.0s)
– Build files have been written to: /home/xiaqiu/test/CppCon/day205/code/build
xiaqiu@xz:~/test/CppCon/day205/code/build$
xiaqiu@xz:~/test/CppCon/day205/code/build$ cmake --build .
[ 25%] Building CXX object src/CMakeFiles/MyLibrary.dir/MyLibrary.cpp.o
[ 50%] Linking CXX static library libMyLibrary.a
[ 50%] Built target MyLibrary
[ 75%] Building CXX object test/CMakeFiles/MyLibraryTests.dir/MyLibraryTests.cpp.o
[100%] Linking CXX executable MyLibraryTests
[100%] Built target MyLibraryTests
xiaqiu@xz:~/test/CppCon/day205/code/build$ cmake --install .
– Install configuration: “”
– Installing: /home/xiaqiu/test/CppCon/day205/code/install/lib/libMyLibrary.a
– Installing: /home/xiaqiu/test/CppCon/day205/code/install/include
– Installing: /home/xiaqiu/test/CppCon/day205/code/install/include/MyLibrary
– Installing: /home/xiaqiu/test/CppCon/day205/code/install/include/MyLibrary/MyLibrary.hpp
– Installing: /home/xiaqiu/test/CppCon/day205/code/install/lib/cmake/MyLibrary/MyLibraryTargets.cmake
– Installing: /home/xiaqiu/test/CppCon/day205/code/install/lib/cmake/MyLibrary/MyLibraryTargets-noconfig.cmake
– Installing: /home/xiaqiu/test/CppCon/day205/code/install/lib/cmake/MyLibrary/MyLibraryConfig.cmake
– Installing: /home/xiaqiu/test/CppCon/day205/code/install/lib/cmake/MyLibrary/MyLibraryConfigVersion.cmake
xiaqiu@xz:~/test/CppCon/day205/code/build$
xiaqiu@xz:~/test/CppCon/day205/code/build$ ls …/install/
include lib
xiaqiu@xz:~/test/CppCon/day205/code/build$ tree …/install/
…/install/
├── include
│ └── MyLibrary
│ └── MyLibrary.hpp
└── lib
├── cmake
│ └── MyLibrary
│ ├── MyLibraryConfig.cmake
│ ├── MyLibraryConfigVersion.cmake
│ ├── MyLibraryTargets-noconfig.cmake
│ └── MyLibraryTargets.cmake
└── libMyLibrary.a
6 directories, 6 files
使用這個庫(示例項目)
find_package(MyLibrary CONFIG REQUIRED)
add_executable(MyApp main.cpp)
target_link_libraries(MyApp PRIVATE MyCompany::MyLibrary)
關于 CMake 和現代 C++ 項目管理的經典思路,尤其是針對依賴管理和構建流程的討論。總結一下核心要點:
1. Package Testing Workflow
- 先在源碼目錄單獨構建并安裝(
cmake .. -DCMAKE_INSTALL_PREFIX=...
+cmake --build . --target install
) - 再在測試目錄單獨構建,使用已安裝的庫,運行測試(
ctest
) - 這種方式是純 CMake 的“安裝-再使用”模式
2. Pure CMake Dependencies — The Wrong Way
- 每個庫單獨構建安裝,生成自己的配置文件
- 其他項目用
find_package()
查找已安裝的庫再鏈接 - 缺點:
- 多層依賴時,更新很麻煩,要重復手動重新構建和安裝
- 多配置(Release/Debug)、多平臺支持復雜,易出錯
- 依賴版本沖突難解決
- 不能自動下載和管理依賴
3. What We Want?
- 一鍵構建:一次構建完成所有依賴,避免手動管理
- 智能重用:只重建變更部分,其他部分用緩存或預編譯庫
- 自動化依賴管理:無需手動下載安裝依賴
- 自定義版本支持:可以用自定義版本替代系統版本(比如指定特定版本的 Boost、ZLib)
現代 C++ 依賴管理的解決方案示例
方案 | 特點 | 適用場景 |
---|---|---|
純 CMake + find_package() | 簡單但不適合復雜依賴 | 小型項目 |
FetchContent / ExternalProject | 下載源碼,統一構建,易集成 | 中小型項目 |
包管理工具(Conan / vcpkg) | 專業依賴管理,版本控制,二進制緩存 | 大型跨平臺項目和團隊協作 |
你可以考慮用: |
- FetchContent 實現統一構建依賴,減少手動步驟
- Conan 做專業依賴版本管理,構建緩存和跨項目共享
這兩種方法比純find_package()
+ 安裝更適合現代 C++ 項目。
后面Conan包管理可以參考ppt后面內容和Conan官網
https://github.com/CppCon/CppCon2018/blob/master/Presentations/git_cmake_conan_how_to_ship_and_reuse_our_cpp_projects/git_cmake_conan_how_to_ship_and_reuse_our_cpp_projects__mateusz_pusz__cppcon_2018.pdf
https://docs.conan.io/2/introduction.html