第 22.5 節

模板擴展與常見問題

0瀏覽次數0訪問次數--跳出率--平均停留

本節整理使用這個 CMake 模板時最常見的擴展方式和問題排查。

添加新的源文件

當前庫目錄使用:

file(GLOB_RECURSE ${PREFIX}_SRC_LIST CONFIGURE_DEPENDS
  "${CMAKE_CURRENT_LIST_DIR}/src/*.c"
  "${CMAKE_CURRENT_LIST_DIR}/src/*.cpp"
)

所以只要把新的 .cpp 放到對應模塊的 src/ 目錄下,CMake 會自動收集。

例如:

src/lib1/src/math_utils.cpp

然後重新構建:

cmake --build --preset linux-debug

如果發現新文件沒有參與編譯,可以手動重新 configure:

cmake --preset linux-debug
cmake --build --preset linux-debug

添加新的頭文件

推薦路徑:

src/lib1/inc/lib1/math_utils.hpp

代碼中包含:

#include "lib1/math_utils.hpp"

不要直接放成:

src/lib1/inc/math_utils.hpp

因為多個庫可能出現同名頭文件。使用 inc/lib1/ 這種結構可以避免衝突。

添加新的庫模塊

假設要添加 lib3

目錄結構:

src/lib3/
├── CMakeLists.txt
├── inc/
│   └── lib3/
│       └── example.hpp
└── src/
    └── example.cpp

src/lib3/CMakeLists.txt

set(PREFIX "lib3")

file(GLOB_RECURSE ${PREFIX}_SRC_LIST CONFIGURE_DEPENDS
  "${CMAKE_CURRENT_LIST_DIR}/src/*.c"
  "${CMAKE_CURRENT_LIST_DIR}/src/*.cpp"
)

add_library(${PREFIX}_src_lib SHARED
  ${${PREFIX}_SRC_LIST}
)

target_include_directories(${PREFIX}_src_lib
  PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/inc>
    $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)

target_link_libraries(${PREFIX}_src_lib
  PUBLIC
    project_options
  PRIVATE
    project_warnings
)

# ========================
# Third-party dependencies
# ========================

# =======
# Install
# =======
install(TARGETS ${PREFIX}_src_lib
  LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
  ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
  RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

install(DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/inc/"
  DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

然後在 src/CMakeLists.txt 中添加:

add_subdirectory(lib3)

並鏈接到主程序:

target_link_libraries(${PROJECT_NAME}
  PRIVATE
    lib1_src_lib
    lib2_src_lib
    lib3_src_lib
)

添加新的可執行文件

如果一個項目有多個程序,例如:

src/main.cpp
src/tools/calibrate_camera.cpp

可以在 src/CMakeLists.txt 裏添加:

add_executable(calibrate_camera
  ${CMAKE_CURRENT_SOURCE_DIR}/tools/calibrate_camera.cpp
)

target_link_libraries(calibrate_camera
  PRIVATE
    project_options
    project_warnings
    lib1_src_lib
    lib2_src_lib
)

set_target_properties(calibrate_camera PROPERTIES
  INSTALL_RPATH "$ORIGIN/../${CMAKE_INSTALL_LIBDIR}"
)

install(TARGETS calibrate_camera
  RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

安裝後:

install/linux-debug/bin/calibrate_camera

改項目名

頂層:

project(cmake_template VERSION 1.0.0 LANGUAGES C CXX)

改成:

project(robot_app VERSION 1.0.0 LANGUAGES C CXX)

因為主程序使用:

add_executable(${PROJECT_NAME}
  ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
)

所以可執行文件會從:

cmake_template

變成:

robot_app

README 裏的運行命令也要同步改:

./install/linux-debug/bin/robot_app

改 C++ 標準

當前:

set(CMAKE_CXX_STANDARD 17)
target_compile_features(project_options INTERFACE cxx_std_17)

如果要改 C++20:

set(CMAKE_CXX_STANDARD 20)
target_compile_features(project_options INTERFACE cxx_std_20)

如果要改 C++23:

set(CMAKE_CXX_STANDARD 23)
target_compile_features(project_options INTERFACE cxx_std_23)

同時確認編譯器支持對應標準。

改動態庫為靜態庫

當前:

add_library(${PREFIX}_src_lib SHARED
  ${${PREFIX}_SRC_LIST}
)

改成靜態庫:

add_library(${PREFIX}_src_lib STATIC
  ${${PREFIX}_SRC_LIST}
)

動態庫和靜態庫對比:

類型優點缺點
SHARED可執行文件較小,庫可獨立更新運行時要能找到 .so
STATIC部署簡單,很多代碼打進可執行文件可執行文件更大,更新庫要重新鏈接

模板默認 SHARED,是為了演示安裝動態庫和 RPATH。

BUILD_SHARED_LIBS 控制庫類型

也可以不寫 SHARED

add_library(${PREFIX}_src_lib
  ${${PREFIX}_SRC_LIST}
)

然後在 preset 中控制:

"BUILD_SHARED_LIBS": "ON"

或:

"BUILD_SHARED_LIBS": "OFF"

這樣同一個模板可以通過配置切換動態庫或靜態庫。

添加宏定義

給某個 target 添加宏:

target_compile_definitions(${PREFIX}_src_lib
  PRIVATE
    LIB1_ENABLE_LOG
)

C++ 中使用:

#ifdef LIB1_ENABLE_LOG
// log code
#endif

帶值宏:

target_compile_definitions(${PREFIX}_src_lib
  PRIVATE
    LIB1_VERSION="1.0.0"
)

可見性選擇:

關鍵字場景
PRIVATE隻影響本庫源碼
PUBLIC本庫源碼和使用者都需要這個宏
INTERFACE本 target 自己不用,只傳給使用者

添加編譯選項

給某個庫單獨加選項:

target_compile_options(${PREFIX}_src_lib
  PRIVATE
    -Wshadow
)

不建議用全局:

add_compile_options(-Wshadow)

因為全局選項會影響所有 target,不利於排查。

添加 include 路徑

推薦:

target_include_directories(${PREFIX}_src_lib
  PRIVATE
    ${CMAKE_CURRENT_LIST_DIR}/some_private_include
)

不推薦:

include_directories(some_private_include)

原因:

  1. include_directories 是目錄級影響,範圍更大。
  2. target_include_directories 能明確説明哪個 target 需要這個路徑。

添加測試目錄

如果以後要加測試,可以新增:

test/
└── CMakeLists.txt

頂層添加:

include(CTest)

if(BUILD_TESTING)
  add_subdirectory(test)
endif()

preset 中可以控制:

"BUILD_TESTING": "ON"

不過當前模板要求不新增 examples,測試目錄是否添加要看項目需求。

清理構建和安裝目錄

因為模板已經有 .gitignore 忽略:

build/
install/
log/

所以這些目錄都是生成物。

清理 Debug:

rm -rf build/linux-debug install/linux-debug

清理 Release:

rm -rf build/linux-release install/linux-release

重新構建:

cmake --preset linux-debug
cmake --build --preset linux-debug
cmake --install build/linux-debug

為什麼安裝目錄裏可能是 lib64

本模板使用:

include(GNUInstallDirs)

庫安裝時使用:

LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}

在某些 Linux 發行版上:

CMAKE_INSTALL_LIBDIR = lib64

所以安裝結果是:

install/linux-debug/lib64/

這不是錯誤,而是系統慣例。

不要手寫:

LIBRARY DESTINATION lib

否則會繞開 CMake 對系統目錄的判斷。

運行時報找不到 .so

錯誤類似:

error while loading shared libraries: liblib1_src_lib.so: cannot open shared object file

常見原因:

  1. 沒有執行 cmake --install ...
  2. 可執行文件不是從安裝目錄運行的。
  3. INSTALL_RPATH 沒設置對。
  4. 手動移動了 bin/lib64/ 的相對位置。

本模板設置:

set_target_properties(${PROJECT_NAME} PROPERTIES
  INSTALL_RPATH "$ORIGIN/../${CMAKE_INSTALL_LIBDIR}"
)

所以保持這種結構即可:

install/linux-debug/
├── bin/
│   └── cmake_template
└── lib64/
    ├── liblib1_src_lib.so
    └── liblib2_src_lib.so

CMake 找不到 Eigen 或 OpenCV

先確認安裝開發包。

Eigen:

sudo apt install libeigen3-dev
sudo dnf install eigen3-devel

OpenCV:

sudo apt install libopencv-dev
sudo dnf install opencv-devel

再重新 configure:

cmake --preset linux-debug

如果庫安裝在非標準路徑,添加:

cmake --preset linux-debug -DCMAKE_PREFIX_PATH=/opt/my_library

或者寫入 preset:

"CMAKE_PREFIX_PATH": "/opt/my_library"

ccache 導致 configure 失敗

有些環境中,gccg++ 可能指向 ccache 包裝器。如果 ccache 目錄不可寫,CMake 檢查編譯器時可能失敗。

臨時解決:

CC=/usr/bin/gcc CXX=/usr/bin/g++ cmake --fresh --preset linux-debug

這只是驗證或特殊環境下的處理,不建議寫死到模板中。

如果你確實想在 preset 裏指定編譯器,可以加:

"cacheVariables": {
  "CMAKE_C_COMPILER": "/usr/bin/gcc",
  "CMAKE_CXX_COMPILER": "/usr/bin/g++"
}

但這會降低模板通用性,所以默認不寫。

clangd 找不到項目頭文件或第三方庫

如果工程能夠正常編譯,但 VSCode 仍然提示找不到項目頭文件、Eigen 或 OpenCV,通常不是 target_include_directories 寫錯了,而是 clangd 沒有讀取 CMake 生成的編譯數據庫。

先確認 Debug 編譯數據庫存在:

cmake --preset linux-debug
test -f build/linux-debug/compile_commands.json && echo "找到了编译数据库"

然後在項目根目錄創建 .clangd

CompileFlags:
  CompilationDatabase: build/linux-debug

在 VSCode 命令面板執行:

clangd: Restart language server

再到 查看 -> 输出 -> clangd 檢查日誌。正常日誌應包含:

Loaded compilation database from .../build/linux-debug/compile_commands.json

如果日誌出現:

Failed to find compilation database
command clangd fallback

説明 clangd 還在使用 fallback 參數,項目的 include 路徑、第三方庫 路徑和 C++ 標準都可能識別錯誤。

關於 compile_commands.json.clangd、Debug/Release 數據庫切換和 命令行驗證的完整説明,見 CMakePresets與構建安裝

VSCode 裏沒有識別 preset

檢查:

  1. 是否安裝 CMake Tools 擴展。
  2. 打開的目錄是否是項目根目錄。
  3. CMakePresets.json 是否在項目根目錄。
  4. JSON 是否合法。

命令行檢查:

cmake --list-presets

如果命令行能看到:

linux-debug
linux-release

説明 preset 文件本身沒問題。

VSCode CMake Tools 不能運行或調試

CMake Tools 運行或調試需要幾件事同時正常:

  1. CMake Tools 能識別 preset。
  2. 工程已經 Configure 和 Build。
  3. CMake Tools 已經選擇可執行 target。
  4. 如果要 Debug,GDB 能正常啓動。

檢查 preset

先確認 VSCode 打開的是項目根目錄,並且能識別 CMakePresets.json

命令行可以這樣檢查:

cmake --list-presets

如果能看到:

linux-debug
linux-release

説明 preset 文件本身沒問題。

VSCode 裏則需要選擇 Configure Preset,例如:

Linux Debug

然後執行 Configure。

檢查 target

CMake Tools 需要知道你要運行或調試哪個可執行 target。

模板裏的可執行 target 是:

cmake_template

如果運行或調試按鈕不可用,先確認 CMake Tools 已經選擇了 cmake_template

檢查是否已經構建

CMake Tools 運行或調試的通常是 build 目錄裏的可執行文件,例如:

build/linux-debug/src/cmake_template

如果還沒有 Build,這個文件不存在,運行或調試就無法啓動。

先執行:

cmake --preset linux-debug
cmake --build --preset linux-debug

或者在 VSCode CMake Tools 中執行 Configure 和 Build。

檢查 gdb

如果只是普通運行程序,不一定需要 GDB。如果要 Debug,就需要確認 GDB 已安裝。

命令行檢查:

gdb --version

沒有安裝就執行:

sudo apt install gdb

或:

sudo dnf install gdb

運行和調試不需要每次 install

日常開發時,CMake Tools 通常運行的是 build 目錄產物:

./build/linux-debug/src/cmake_template

不是安裝目錄產物:

./install/linux-debug/bin/cmake_template

install 主要用於驗證安裝佈局、頭文件安裝、動態庫 RPATH 等是否正確。

.gitignore 怎樣處理 VSCode 配置

這個模板不依賴額外的 VSCode 調試配置文件,因為 CMake Tools 可以直接運行或調試當前 CMake target。

如果不想把個人 VSCode 配置提交到倉庫,可以直接忽略 .vscode/

.vscode/

也可以只忽略常見的本地配置:

.vscode/settings.json
.vscode/tasks.json

如果團隊確實想共享某些 VSCode 配置,比如推薦擴展或格式化設置,可以單獨討論哪些文件進入 Git。運行和調試本身可以交給 CMake Tools。

模板的核心規則

最後總結一下這個模板最重要的幾條規則:

  1. 頂層 CMakeLists.txt 只做總控。
  2. 公共編譯規則放在 cmake/ProjectOptions.cmake
  3. 主程序由 src/CMakeLists.txt 管理。
  4. 每個庫目錄自己管理自己的源碼、頭文件、安裝和第三方依賴。
  5. 頭文件路徑帶模塊名,例如 lib1/eigen3_test.hpp
  6. 第三方庫放在使用者自己的 Third-party dependencies 區塊。
  7. 構建參數放在 CMakePresets.json,不要寫死在頂層 CMake。
  8. 生成目錄 build/install/log/ 不進入 Git。
  9. VSCode 裏運行和調試交給 CMake Tools,命令行構建仍然交給 CMakePresets。
音乐页