Skip to content

Wiki

Template Extension and Common Issues

About 18 min read

Existing English translations were reused. 34 current source blocks remain in Simplified Chinese, without triggering paid translation.

This section summarizes the most common extension methods and troubleshooting when using this CMake template.

1.Add a new source file

Current database directory in use:

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

Therefore, as long as the new .cpp is placed under the src/ directory of the corresponding module, CMake will automatically collect it.

For example:

src/lib1/src/math_utils.cpp

Then rebuild:

cmake --build --preset linux-debug

If you find that the new file is not included in the compilation, you can manually re-configure:

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

2.Add new header file

Recommended path:

src/lib1/inc/lib1/math_utils.hpp

The code includes:

#include "lib1/math_utils.hpp"

Do not directly put it as:

src/lib1/inc/math_utils.hpp

Because multiple libraries may have header files with the same name. Using the inc/lib1/ structure can avoid conflicts.

3.Add a new library module

Suppose we want to add lib3.

Directory structure:

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
  ${${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}
)

example.hpp 使用普通的 C++ 函数声明:

#pragma once

namespace lib3 {
void example();
}

对应的 example.cpp 包含这个头文件,并提供 lib3::example() 的定义。

Then in src/CMakeLists.txt add:

add_subdirectory(lib3)

and link to the main program:

target_link_libraries(${PROJECT_NAME}
  PRIVATE
    lib1_src_lib
    lib2_src_lib
    lib3_src_lib
)

4.Add new executable file

If a project has multiple programs, for example:

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

Can be added in 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}
)

After installation:

install/linux-debug/bin/calibrate_camera

5.Change Project Name

Top level:

project(cmake_template VERSION 1.0.0 LANGUAGES C CXX)

Change to:

project(robot_app VERSION 1.0.0 LANGUAGES C CXX)

Because the main program uses:

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

So the executable file will be from:

cmake_template

becomes:

robot_app

The run commands in the README also need to be updated accordingly:

./install/linux-debug/bin/robot_app

6.Modify C++ standard

当前模板默认使用 C++20:

set(CMAKE_CXX_STANDARD 20)
target_compile_features(project_options INTERFACE cxx_std_20)

If you want to change C++23:

set(CMAKE_CXX_STANDARD 23)
target_compile_features(project_options INTERFACE cxx_std_23)

如果项目使用 C++17:

set(CMAKE_CXX_STANDARD 17)
target_compile_features(project_options INTERFACE cxx_std_17)

修改 cmake/ProjectOptions.cmake 中已有的这两行,并确认编译器支持对应标准模式;具体语法和标准库功能还取决于编译器及其标准库版本。本模板的 CMake 最低版本仍为 3.22。

7.Change the dynamic library to a static library

如果某个模块显式指定动态库:

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

Change to a static library:

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

Comparison of dynamic libraries and static libraries:

类型 优点 缺点
SHARED 可执行文件较小,库可独立更新 Linux 运行时要能找到相应 .so
STATIC 部署简单,很多代码打进可执行文件 可执行文件更大,更新库要重新链接

本模板采用下一节的配置方式:Linux 预设使用动态库,Windows 预设使用静态库。两种方式都保留同样的头文件声明和调用代码。

8.Use BUILD_SHARED_LIBS to control the library type

当前模板不写死库类型:

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

Then control it in the preset:

"BUILD_SHARED_LIBS": "ON"

Or:

"BUILD_SHARED_LIBS": "OFF"

ON 选择动态库,OFF 选择静态库。当前 Linux 预设为 ON,Windows 预设为 OFF,Release 继承对应设置。不要在模块中再写死 SHARED,否则会绕开这个开关。

9.Add macro definition

Add a macro to a certain target:

target_compile_definitions(${PREFIX}_src_lib
  PRIVATE
    LIB1_ENABLE_LOG
)

Translated to en: C++ used in:

#ifdef LIB1_ENABLE_LOG
// log code
#endif

Valued macro:

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

Visibility selection:

Keyword Scenario
PRIVATE Affects only this library's source code
PUBLIC Both this library's source code and users need this macro
INTERFACE This target itself does not use it; it is only passed to users

10.Add compile option

Add options for a specific library individually:

target_compile_options(${PREFIX}_src_lib
  PRIVATE
    -Wshadow
)

Not recommended to use global:

add_compile_options(-Wshadow)

Because global options affect all targets, which is not conducive to troubleshooting.

11.Add include path

Recommended:

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

Not recommended:

include_directories(some_private_include)

Reason:

  1. include_directories is a directory-level impact, with a larger scope.
  2. target_include_directories can clearly specify which target needs this path.

12.Add test directory

If tests are to be added later, the following can be introduced:

test/
└── CMakeLists.txt

Add at the top level:

include(CTest)

if(BUILD_TESTING)
  add_subdirectory(test)
endif()

In preset, the following can be controlled:

"BUILD_TESTING": "ON"

However, the current template requires not adding new examples; whether to add a test directory depends on project requirements.

13.模板已有的运行测试

原始模板已经通过 include(CTest) 启用测试。构建后可以执行 ctest --preset linux-debug 或 ctest --preset windows-debug。

eigen_example 检查原始 Eigen 输出。把 main.cpp 换成 Hello World 或其他练习以后,这个测试的输出断言也要调整;测试失败不等于新程序无法编译。添加真实业务后,应为新行为编写对应测试。

14.Clean the build and installation directories

Since the template already has .gitignore Ignore:

build/
install/
log/

So these directories are all build artifacts.

Clean up Debug:

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

Cleanup Release:

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

Rebuild:

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

Windows 的 PowerShell 中,先确认对应目录存在,再清理:

Remove-Item -Recurse -Force build/windows-debug
Remove-Item -Recurse -Force install/windows-debug
cmake --preset windows-debug

清理前检查当前工作目录和要删除的路径。不同编译器、不同平台使用各自的 preset 目录,不共用同一份 CMake 缓存。

15.Why might the installation directory contain lib64

This template uses:

include(GNUInstallDirs)

Used during library installation:

LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}

On certain Linux distributions:

CMAKE_INSTALL_LIBDIR = lib64

So the installation result is:

install/linux-debug/lib64/

This is not an error, but rather a system convention.

Do not write by hand:

LIBRARY DESTINATION lib

Otherwise, it will bypass CMake's determination of system directories.

16.Linux 运行时报找不到 .so

The error is similar to:

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

Common causes:

  1. 没有执行 cmake --install ...。
  2. 可执行文件不是从安装目录运行的。
  3. 主程序或其依赖库的 INSTALL_RPATH 没设置对。
  4. 手动移动了 bin/ 或 lib64/ 的相对位置。

本模板在 src/CMakeLists.txt 中为主程序设置:

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

还在 src/lib1/CMakeLists.txt 中为 lib1 设置:

set_target_properties(${PREFIX}_src_lib PROPERTIES
  INSTALL_RPATH "$ORIGIN"
)

前者从主程序的 bin/ 目录找到库目录,后者让 lib1 找到同目录的 lib2。如果报错提到 liblib2_src_lib.so,即使主程序已经设置了搜索路径,也应检查 lib1 的配置。修改后重新 Configure、Build 和 Install,再运行安装后的程序。

以库目录为 lib64 的系统为例,保持这种结构即可:

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

17.Windows 程序启动失败

先确认运行的是 build/windows-debug/bin/cmake_template.exe,并且所用 GCC 的 bin/ 在 PATH 中。默认 MSYS2 安装位置对应 C:/msys64/ucrt64/bin。

验证安装结果时,重新执行:

cmake --install build/windows-debug
./install/windows-debug/bin/cmake_template.exe

安装后保持整个目录结构再移动、运行。新增第三方依赖时,按相应库的安装要求检查运行环境。

18.CMake cannot find Eigen or OpenCV

First confirm the installation of the development package.

Eigen:

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

OpenCV:

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

Then reconfigure:

cmake --preset linux-debug

If the library is installed in a non-standard path, add:

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

Or write to preset:

"CMAKE_PREFIX_PATH": "/opt/my_library"

19.ccache causes configure to fail

In some environments, gcc and g++ may point to a ccache wrapper. If the ccache directory is not writable, the CMake compiler check may fail.

Temporary workaround:

rm -rf build/linux-debug
CC=/usr/bin/gcc CXX=/usr/bin/g++ cmake --preset linux-debug

This is only handled in verification or special environments, and it is not recommended to hard-code it into the template.

If you really want to specify the compiler in the preset, you can add:

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

However, this reduces template generality, so it is not written by default.

20.clangd cannot find project header files or third-party libraries

If the project compiles normally, but VSCode still reports that it cannot find the project header files, Eigen, or OpenCV, it is usually not that target_include_directories was written incorrectly, but rather that clangd has not read the compilation database generated by CMake.

First, confirm that the Debug compilation database exists:

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

Then create in the project root directory .clangd:

CompileFlags:
  CompilationDatabase: build/linux-debug

Run in the VSCode Command Palette:

clangd: Restart language server

Then go to 查看 -> 输出 -> clangd to check the logs. Normal logs should contain:

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

If the log shows:

Failed to find compilation database
command clangd fallback

This indicates that clangd is still using fallback parameters, and the project's include paths, third-party library paths, and C++ standards may all be recognized incorrectly.

关于 compile_commands.json、.clangd、Debug/Release 数据库切换和 命令行验证的完整说明,见 CMakePresets与构建安装。

Windows 下先确认 .clangd 的 CompilationDatabase 指向 build/windows-debug,并使用正确的 clangd.exe。如果能编译却找不到标准库头文件,可按 Windows 环境搭建 配置 --query-driver。

21.VSCode does not recognize the preset

Check:

  1. Whether the CMake Tools extension is installed.
  2. Whether the opened directory is the project root directory.
  3. CMakePresets.json Whether it is in the project root directory.
  4. Whether the JSON is valid.

Command-line check:

cmake --list-presets

If the command line shows:

linux-debug
linux-release

This indicates that the preset file itself is fine.

Windows 的配置列表对应 windows-debug、windows-release。如果 CMake Tools 仍拿到旧工具路径,重启 VSCode,再用 where.exe g++、where.exe cmake 检查;更换编译器后清理对应构建缓存。

22.VSCode CMake Tools cannot run or debug

Several things need to work simultaneously for CMake Tools to run or debug:

  1. CMake Tools can recognize presets.
  2. The project has already been Configured and Built.
  3. CMake Tools has already selected an executable target.
  4. To Debug, GDB must be able to start normally.

22.1.Check preset

先确认 VSCode 打开的是项目根目录,并且能识别 CMakePresets.json。

The command line can be checked like this:

cmake --list-presets

If it can be seen:

linux-debug
linux-release

This indicates that the preset file itself is fine.

In VSCode, you need to select Configure Preset, for example:

Linux Debug

Then execute Configure.

22.2.Check target

CMake Tools needs to know which executable target you want to run or debug.

The executable target in the template is:

cmake_template

如果运行或调试按钮不可用,先确认 CMake Tools 已经选择了 cmake_template。

22.3.Check whether it has already been built

CMake Tools typically runs or debugs the executable files in the build directory, for example:

build/linux-debug/src/cmake_template

If there is no Build yet, this file does not exist, and running or debugging cannot start.

First execute:

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

Or execute Configure and Build in the VSCode CMake Tools.

22.4.Check gdb

If you are only running an ordinary program, GDB is not necessarily required. If you need to debug, you need to confirm that GDB is installed.

Command-line check:

gdb --version

If not installed, run:

sudo apt install gdb

Or:

sudo dnf install gdb

22.5.Running and debugging do not require installing every time

During daily development, CMake Tools usually runs the build directory artifacts:

./build/linux-debug/src/cmake_template

Not an installation directory artifact:

./install/linux-debug/bin/cmake_template

install Mainly used to verify whether the installation layout, header file installation, dynamic library RPATH, etc. are correct.

23..gitignore How to Handle VSCode Configuration

This template does not rely on an additional VSCode debugging configuration file, because CMake Tools can directly run or debug the current CMake target.

If you do not want to commit your personal VSCode configuration to the repository, you can simply ignore .vscode/:

.vscode/

It is also possible to ignore only common local configurations:

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

If the team really wants to share certain VSCode configurations, such as recommended extensions or formatting settings, you can separately discuss which files should go into Git. Running and debugging themselves can be left to CMake Tools.

24.Core rules of the template

Finally, let's summarize the most important rules of this template:

  1. Top level CMakeLists.txt only performs overall control.
  2. Common compilation rules are placed in cmake/ProjectOptions.cmake.
  3. The main program is managed by src/CMakeLists.txt.
  4. Each library directory manages its own source code, header files, installation, and third-party dependencies.
  5. Header file paths include the module name, for example lib1/eigen3_test.hpp.
  6. Third-party libraries are placed in the user's own Third-party dependencies block.
  7. Build parameters are placed in CMakePresets.json; do not hard-code them in the top-level CMake.
  8. Generated directories build/、install/、log/ are not included in Git.
  9. In VSCode, running and debugging are handled by CMake Tools, while command-line builds are still handled by CMakePresets.

25.Windows 的运行和调试路径

前面图形界面操作中选择 cmake_template target 的方法不变。Windows 使用 windows-debug,运行的程序是 build/windows-debug/bin/cmake_template.exe。

GDB 来自同一套工具链,默认 MSYS2 路径为 C:/msys64/ucrt64/bin/gdb.exe。先用 CMake Tools 的 Debug 按钮检查;想使用 F5 时,把 CMake: Debug 绑定到 F5 即可。

如果旧版本教程中看到 cmake --fresh,注意该选项从 CMake 3.24 才加入。本模板兼容 CMake 3.22,因此这里统一用清理构建目录后重新 Configure 的方式处理缓存。参见 CMake 3.24 发布说明。