模板扩展与常见问题
本节整理使用这个 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)
原因:
include_directories是目录级影响,范围更大。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
常见原因:
- 没有执行
cmake --install ...。 - 可执行文件不是从安装目录运行的。
INSTALL_RPATH没设置对。- 手动移动了
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 失败
有些环境中,gcc、g++ 可能指向 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
检查:
- 是否安装 CMake Tools 扩展。
- 打开的目录是否是项目根目录。
CMakePresets.json是否在项目根目录。- JSON 是否合法。
命令行检查:
cmake --list-presets
如果命令行能看到:
linux-debug
linux-release
说明 preset 文件本身没问题。
VSCode CMake Tools 不能运行或调试
CMake Tools 运行或调试需要几件事同时正常:
- CMake Tools 能识别 preset。
- 工程已经 Configure 和 Build。
- CMake Tools 已经选择可执行 target。
- 如果要 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。
模板的核心规则
最后总结一下这个模板最重要的几条规则:
- 顶层
CMakeLists.txt只做总控。 - 公共编译规则放在
cmake/ProjectOptions.cmake。 - 主程序由
src/CMakeLists.txt管理。 - 每个库目录自己管理自己的源码、头文件、安装和第三方依赖。
- 头文件路径带模块名,例如
lib1/eigen3_test.hpp。 - 第三方库放在使用者自己的
Third-party dependencies区块。 - 构建参数放在
CMakePresets.json,不要写死在顶层 CMake。 - 生成目录
build/、install/、log/不进入 Git。 - VSCode 里运行和调试交给 CMake Tools,命令行构建仍然交给 CMakePresets。
