第 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。
音乐页