CMake构建系统下的C++开发模板

Rodent-Nick | 2026年09月14日

前言

本啮齿静物在Windows和Ubuntu两个平台下开发。在Windows下使用的是W64DevKit( 说到底就是MinGW-w64 GCC/G++编译器);在Ubuntu下使用的是build-essential。 两个平台都安装了CMake。

制作这个模板的起因是:

  1. 我使用的IDE(VS Code或者NeoViM)都不支持“项目模板”这个功能[1]
  2. 不想每一次开一个新项目的时候都要从头打一遍CMakeLists.txt
  3. 在一些小程序里我会用ld程序把一些资源直接编译进.data段里,这个CMake指令写 起来有点复杂,每次写都要查上一次是怎么写的。

故此,写了一个小的CMake模板,并上传到服务器中Git远程仓库,并配套类似degit 的小工具,能够在下载模板的同时去掉Git提交历史。

整体项目结构

.
├── CMakeLists.txt
├── include
│ └── res.hxx
├── res
│ └── remove-me
└── src
└── main.cxx

CMakeLists.txt

# After cloning, run `rm -rf .git` to remove any association with the original
# template repository.
# Then, run `git init` within the project. Don't forget to rename the project!

# EDIT WITH `%s` to customize.
# PROJECT_NAME = The name of this project
# PROJECT_EXEC = The name of the target program

# Run `cmake -B build` to generate build files.
# Run `cmake --build build` to actual build the target.

cmake_minimum_required(VERSION 3.16)
project(PROJECT_NAME)

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

set(TARGET_CONSOLE_APP ON CACHE BOOL "Whether to compile a GUI application on Windows. Disregarded on other platform.")
if(WIN32 AND NOT TARGET_CONSOLE_APP)
add_executable(PROJECT_EXEC WIN32)
else()
add_executable(PROJECT_EXEC)
endif()

file(GLOB SRCCXX src/*)
target_sources(PROJECT_EXEC PRIVATE ${SRCCXX})

set(SHOULD_PACK_RESOURCES ON CACHE BOOL "Whether to use `ld` to pack resources into the final executable.")
file(GLOB RESC RELATIVE ${CMAKE_SOURCE_DIR} res/*)
if(SHOULD_PACK_RESOURCES AND RESC)
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/res.o
COMMAND
${CMAKE_LINKER} -r -b binary
-o ${CMAKE_BINARY_DIR}/res.o ${RESC}
DEPENDS ${RESC}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Generating res.o from binary resources..."
)
target_sources(PROJECT_EXEC PRIVATE ${CMAKE_BINARY_DIR}/res.o)
if(NOT WIN32)
target_link_options(PROJECT_EXEC PRIVATE "-Wl,-z,noexecstack")
endif()
message(STATUS "Makefile will now generate res.o and link it against the program.")
endif()

file(GLOB_RECURSE RAWR raw/*)
if(RAWR)
file(COPY raw/ DESTINATION ${CMAKE_BINARY_DIR}/bin)
set_directory_properties(
PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES ${CMAKE_BINARY_DIR}/bin
)
message(STATUS "Will copy `raw` files.")
endif()

# FIND LIBRARY HERE, such as
# DO NOT FORGET TO ACTUALLY LINK IT.
# find_package(wxWidgets REQUIRED COMPONENTS core base)
# include(${wxWidgets_USE_FILE})

target_include_directories(PROJECT_EXEC PRIVATE include)
target_link_libraries(PROJECT_EXEC PRIVATE)

# End of CMakeLists.txt

2026-09-23 更新:

        if(NOT WIN32)
target_link_options(PROJECT_EXEC PRIVATE "-Wl,-z,noexecstack")
endif()

因为ld程序在Windows上不识别-z参数。

关于二进制文件

在上面的CMakeLists.txt中,第29 - 43行是用于确定:

  1. 项目res/文件夹中是否有文件;
  2. 如果有,则使用ld链接程序将其转化为一个Object文件;
  3. 然后再最后的链接过程中将其与其他对象文件合并进入可执行文件中。

我们在include文件夹中加一个资源头文件res.hxx:

#pragma once

/// This header file contains all predefined pointers and variables that help locate packed resources.
/// Use the `MAKE_EXT_RES` to make resources.

#define MAKE_EXT_RES(name) extern const char _binary_res_##name##_start[];\
extern const char _binary_res_##name##_end[];\
const unsigned int _binary_res_##name##_len =\
_binary_res_##name##_end - _binary_res_##name##_start;

#define EXT_RES_BEG(name) _binary_res_##name##_start
#define EXT_RES_END(name) _binary_res_##name##_end
#define EXT_RES_LEN(name) _binary_res_##name##_len

其中,name必须是合法的变量名。变量名根据资源所在的地址决定,例如要找到res/pics/apple.png这 张图片,name应该是pics_apple_png。

在使用时,首先使用MAKE_EXT_RES宏来声明这三个指针,然后可以使用EXT_RES_XXX宏 来使用。

其中:

如果你发现某一个资源指针在链接过程中为未定义,那么可以直接在构建的文件夹中以16进制 的形式打开res.o。其中你应该能看到正确的资源指针的命名是什么样子的。

内存使用

到此你会发现,如果你使用这种方法将资源附于程序内部,那么在加载程序时,所有的资源 都会被加载进入内存。因此,不应该使用这种方法来加载很大的二进制资源——可以用raw文 件夹,然后让程序读取磁盘文件即可。

degit的替代品

当前,degit只支持在Github和GitLab等代码托管平台上使用。而我将我的项目模板放在 了自己的服务器上的git仓库,不受degit的支持。

以Ubuntu Bash为例,我们可以在~/.bashrc里定义这样一个函数,帮助我们从远程仓库中 下载代码模板,并将模板的git历史删去:

# Strip template of any git trace.
# gtpl <repo-url> <target-dir>
gtpl() {
if [ $# -ne 2 ]; then
echo "Usage: gtpl <repo-url> <target-dir>"; return 1
fi
git clone --depth 1 "$1" "$2" && rm -rf "$2/.git" \
&& echo "gtpl done."
}

别忘了重新source一下这个.bashrc,就可以使用了。

至此,完。

封面图片是CMake软件的logo,来自CMake官网


  1. 也有可能是我没有找到合适的VS Code插件…… ↩︎