CppCon 2018 學習:GIT, CMAKE, CONAN

提到的:

“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 搭配
MSBuildWindows Visual Studio 的默認構建系統

示例:CMake 構建流程

mkdir build
cd build
cmake ..
cmake --build .

3. PACKAGE MANAGEMENT(包管理器)

用來下載、安裝、管理 C++ 庫和依賴項。解決“手動下載庫、設置 include/lib”的麻煩。

常用工具:

工具簡介
vcpkg微軟出品,支持跨平臺,最主流之一,支持 CMake 集成
Conan更強大,支持版本控制、構建配置、包上傳等功能,適合大型項目
Hunter / CPM.cmakeCMake 模塊級集成的包管理器(更輕量)

示例:用 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 ControlGit無可爭議的行業標準
BuildingCMake最廣泛支持、跨平臺、集成多構建系統
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()簡單教學、小項目
2Git submodule + add_subdirectory()中等一般中小型項目
3ExternalProject_Add()復雜一般自動拉庫,隔離構建
4系統安裝 + find_package()中等老項目、公司環境固定
5Maven / NuGet特定平臺好用混合語言項目
6Conan / vcpkg現代現代企業級開發

問題是關于 使用 add_subdirectory() 管理 C++ 依賴(deps),并配合一個 Mermaid 圖 來表達依賴關系:

uses
tests with
LibA
Boost_1_66
GTest_1_8_0
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. 結合你的類圖和依賴關系:

  • LibABoost聚合依賴,建議用 find_package() 或包管理工具(Conan、vcpkg)來集成 Boost。
  • LibAGTest測試依賴,如果你放了 GTest 的源碼,可以用 add_subdirectory()

總結

依賴庫add_subdirectory() 管理嗎?推薦做法
Boost 1.66使用 find_package() 或包管理
GTest 1.8.0直接 add_subdirectory()

以下是基于您提供的圖片生成的 Mermaid 類圖代碼,展示 LibALibB 及其依賴關系:

這張類圖很好地表達了兩個庫 LibALibB 與它們依賴的第三方庫版本之間的關系。針對你問的“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
  • *--> 表示 組合(強擁有),即 LibALibB 都強依賴特定版本的 Boost。
  • --> 表示 關聯/依賴LibB 依賴 OpenSSL。
  • ..> 表示 實現/測試依賴LibALibB 都用不同版本的 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 的屬性上。
  • 屬性(Properties)管理依賴和編譯設置
    • 每個 target 可以獨立定義自己的編譯選項、包含目錄、依賴庫等,且可以控制傳遞規則(PRIVATEPUBLICINTERFACE)。
    • 這樣更靈活,也方便依賴管理和維護。

總結

  • 不要用全局變量(如 CMAKE_CXX_FLAGS)來設置編譯選項。
  • 要給每個 target 設置自己的編譯選項和屬性。
  • 這樣能讓項目更模塊化、可維護,解決沖突問題。

介紹 CMake 中的 目標類型(target types),也就是 add_executableadd_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)

你沒有寫 STATICSHAREDOBJECTINTERFACE 這些類型,這時 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() 不只是“鏈接”
  • 它還傳播頭文件路徑、宏定義、編譯選項等(如果用 PUBLICINTERFACE

“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_STANDARDBUILD_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::MyLibraryfind_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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/87682.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/87682.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/87682.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

使用tensorflow的線性回歸的例子(五)

我們使用Iris數據&#xff0c;Sepal length為y值而Petal width為x值。import matplotlib.pyplot as pltimport numpy as npimport tensorflow as tffrom sklearn import datasetsfrom tensorflow.python.framework import opsops.reset_default_graph()# Load the data# iris.d…

虛幻基礎:動作——蒙太奇

能幫到你的話&#xff0c;就給個贊吧 &#x1f618; 文章目錄 動作——蒙太奇如果動作被打斷&#xff0c;則后續的動畫通知不會執行 動作——蒙太奇 如果動作被打斷&#xff0c;則后續的動畫通知不會執行

[工具系列] 開源的 API 調試工具 Postwoman

介紹 隨著 Web 應用的復雜性增加&#xff0c;API 測試已成為開發中不可或缺的一部分&#xff0c;無論是前端還是后端開發&#xff0c;確保 API 正常運行至關重要。 Postman 長期以來是開發者進行 API 測試的首選工具&#xff0c;但是很多基本功能都需要登陸才能使用&#xff…

【力扣 簡單 C】746. 使用最小花費爬樓梯

目錄 題目 解法一 題目 解法一 int min(int a, int b) {return a < b ? a : b; }int minCostClimbingStairs(int* cost, int costSize) {const int n costSize; // 樓頂&#xff0c;第n階// 爬到第n階的最小花費 // 爬到第n-1階的最小花費從第n-1階爬上第n階的花費…

python+django開發帶auth接口

pythondjango開發帶auth接口 # coding utf-8 import base64 from django.contrib import auth as django_authfrom django.core.exceptions import ObjectDoesNotExist from django.http import JsonResponsefrom sign.models import Eventdef user_auth(request):"&quo…

RBAC權限模型如何讓API訪問控制既安全又靈活?

url: /posts/9f01e838545ae8d34016c759ef461423/ title: RBAC權限模型如何讓API訪問控制既安全又靈活? date: 2025-07-01T04:52:07+08:00 lastmod: 2025-07-01T04:52:07+08:00 author: cmdragon summary: RBAC權限模型通過用戶、角色和權限的關聯實現訪問控制,核心組件包括用…

安達發|告別低效排產:APS高級排程如何助力電池企業智造升級?

在全球能源轉型的背景下&#xff0c;動力電池、儲能電池等市場需求快速增長&#xff0c;電池制造企業面臨著訂單波動大、工藝復雜、交期嚴格等挑戰。傳統的手工排產或基于ERP的簡單計劃模式已難以滿足高效、精準的生產需求。APS高級排程通過智能算法優化生產計劃&#xff0c;實…

數據結構20250620_數據結構考試

試卷01 天津金海通軟件筆試題 選擇題(4*416) 對于雙向循環鏈表,在p指針所指的結點之后插入s指針所指結點的操作應為 p->nexts; s->prip; p->next->pris; s->nextp->nextp->nexts; p->next->pris; s->prip; s->nextp->nexts->pri …

4. 尋找正序數組的中位數

題目&#xff1a; 給定兩個大小分別為 m 和 n 的正序&#xff08;從小到大&#xff09;數組 nums1 和 nums2。請你找出并返回這兩個正序數組的 中位數 。 算法的時間復雜度應該為 O(log (mn)) 。 示例&#xff1a; 輸入&#xff1a;nums1 [1,3], nums2 [2] 輸出&#xff1a…

DeepSeek飛機大戰小游戲HTML5(附源碼)

用DeepSeek幫忙生成的飛機大戰小游戲網頁版&#xff0c;基于HTML5。 提示詞prompt 幫我做一個網頁版的飛機大戰游戲 html5的游戲功能說明 玩家控制&#xff1a; 使用鍵盤方向鍵或WASD移動飛機 空格鍵發射子彈 移動設備支持觸摸控制 游戲機制&#xff1a; 敵機會從屏幕頂部隨機位…

全素山藥開發指南:從防癢處理到高可用食譜架構

摘要&#xff1a;本文系統性解析山藥的化學特性&#xff08;黏液蛋白/皂苷致癢機制&#xff09;及全素場景下的烹飪解決方案&#xff0c;提供6種高內聚低耦合的食譜實現&#xff0c;附完整防氧化與黏液控制技術方案。一、核心問題分析&#xff1a;山藥處理中的“痛點”致癢物質…

OpenLayers 入門指南:序言

本專欄旨在幫助零GIS基礎的開發人員系統掌握OpenLayers這一強大的開源Web地圖庫&#xff0c;通過 “理論實戰” 結合的方式&#xff0c;逐步實現從創建地圖到構建一個基礎地圖應用模版。無論你是前端開發者、GIS愛好者&#xff0c;都可以通過此專欄零基礎開始用OpenLayers開發一…

WebRTC輕量學習 libdatachannel

最近想了解一些在瀏覽器中推送音視頻流&#xff0c;尋找很多版本的代碼&#xff0c;C、Go、Python等語言實現的webRTC協議。 按照搭建難度和快速實現首選Python版本的WebRTC&#xff0c;這種是最適合原型開發的。 選型&#xff1a;C的開源庫libdatachannel Python的開源庫Ai…

Vue2中的keep-alive:組件狀態緩存與性能優化實戰指南

目錄 一、什么是keep-alive&#xff1f; 與普通組件切換的對比 二、核心用法詳解 1. 基礎用法&#xff1a;動態組件緩存 2. 路由視圖緩存 3. 生命周期鉤子 三、進階配置與優化 1. 精準控制緩存組件 &#xff08;1&#xff09;include/exclude屬性 &#xff08;2&…

FastAPI安全加固:密鑰輪換、限流策略與安全頭部如何實現三重防護?

url: /posts/f96ba438de34dc197fd2598f91ae133d/ title: FastAPI安全加固:密鑰輪換、限流策略與安全頭部如何實現三重防護? date: 2025-07-02T22:05:04+08:00 lastmod: 2025-07-02T22:05:04+08:00 author: cmdragon summary: FastAPI框架安全加固方案包括密鑰輪換自動化、請…

NeighborGeo:基于鄰居的IP地理定位(五)

NeighborGeo:基于neighbors的IP地理定位 X. Wang, D. Zhao, X. Liu, Z. Zhang, T. Zhao, NeighborGeo: IP geolocation based on neighbors, Comput. Netw. 257 (2025) 110896, 5. Case analysis 為了說明NeighborGeo在優化圖結構和利用鄰居信息進行預測方面的優勢,將目標I…

Ethernet IP與Profinet共舞:網關驅動綠色工業的智慧脈動

Ethernet IP與Profinet共舞&#xff1a;驅動綠色工業的智慧脈動 光伏建筑一體化&#xff0c;建筑碳中和&#xff0c;在全球氣候變化、國家碳達峰碳中和戰略大背景下&#xff0c;敬畏生活、生產與自然和諧共處&#xff0c;確立自身資源循環高效利用的倒計時和路線圖。 在全球氣…

衡石科技破解指標管理技術難題:語義層建模如何實現業務與技術語言對齊?

在數字化轉型的深水區&#xff0c;企業指標管理體系普遍面臨一個核心矛盾&#xff1a;業務部門需要敏捷的數據洞察支撐決策&#xff0c;而IT部門卻受困于復雜的數據架構和冗長的需求響應周期。這種矛盾的本質&#xff0c;是傳統指標管理體系中“技術語言”與“業務語言”的割裂…

正品庫拍照PWA應用的實現與性能優化|得物技術

一、 背景與難點 背景 目前得物ERP主要鑒別流程&#xff0c;是通過鑒別師鑒別提需到倉庫&#xff0c;倉庫庫工去進行商品補圖拍照&#xff0c;現有正品庫59%的人力投入在線下商品借取/歸還業務的操作端&#xff0c;目前&#xff0c;線下借取的方式會占用商品資源&#xff0c…

如何使用python識別出文件夾中全是圖片合成的的PDF,并將其移動到指定文件夾

引言 在現代數字化工作流程中&#xff0c;無論是為機器學習模型處理數據&#xff0c;還是進行數字歸檔&#xff0c;區分原生文本 PDF&#xff08;例如&#xff0c;由文字處理器生成的報告&#xff09;和基于圖像的 PDF&#xff08;例如&#xff0c;掃描的發票、檔案文件&#…