一、核心需求与版本规则复盘
在动手前先明确核心目标,避免版本管理混乱:
版本格式:主版本.MINOR.补丁版本(语义化规范,如 2.3.15)
规则 1:主版本 / 小版本(MINOR)手动更新时,补丁版本重置为当前 Git 提交数
规则 2:无主 / 小版本变更时,补丁版本自动跟随 Git 提交数递增
规则 3:版本号需嵌入代码(如 version.h)、构建产物(如二进制文件名)、CI/CD 流程
优势:无需手动维护补丁版本,Git 提交记录即版本追溯依据,避免重复或遗漏。
二、CMake 实现方案(零外部依赖)
核心思路:用 CMake 内置命令调用 Git 获取提交数,结合手动配置的主 / 小版本,自动生成完整版本号,并同步到代码和构建流程。
1. 完整 CMake 脚本(version.cmake)
创建独立的 version.cmake 文件(便于复用),放入工程根目录:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| # ============================================================================== # 基于 Git 提交数的版本管理脚本(CMake 3.12+ 兼容) # 用法:在根 CMakeLists.txt 中 include(version.cmake) # ==============================================================================
# -------------------------- 1. 手动配置:主版本和小版本 -------------------------- set(MAJOR_VERSION 2) # 重大变更时递增(如 1→2) set(MINOR_VERSION 1) # 兼容性功能新增时递增(如 1→2)
# -------------------------- 2. 自动获取:Git 提交数(补丁版本) -------------------------- # 初始化补丁版本为 0(无 Git 仓库时 fallback) set(PATCH_VERSION 0)
# 检查是否为 Git 仓库,且存在 git 命令 find_package(Git QUIET) if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git") # 统计当前分支的有效提交数(排除合并提交、空提交) execute_process( COMMAND ${GIT_EXECUTABLE} rev-list --count --no-merges HEAD WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE GIT_COMMIT_COUNT OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET # 忽略 Git 命令执行失败(如无提交记录) )
# 转换为整数(确保非空) if(GIT_COMMIT_COUNT MATCHES "^[0-9]+$") set(PATCH_VERSION ${GIT_COMMIT_COUNT}) endif() endif()
# -------------------------- 3. 生成完整版本号 -------------------------- # 完整版本字符串(如 "2.1.15") set(FULL_VERSION "${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION}") # 版本号整数(用于代码宏定义,如 20115 = 2*10000 + 1*100 +15) math(EXPR VERSION_INT "${MAJOR_VERSION}*10000 + ${MINOR_VERSION}*100 + ${PATCH_VERSION}")
# -------------------------- 4. 暴露版本信息到工程 -------------------------- # 1) 给 CMake 其他模块使用(如根 CMakeLists.txt) set(PROJECT_VERSION ${FULL_VERSION} CACHE INTERNAL "Project full version") set(PROJECT_VERSION_MAJOR ${MAJOR_VERSION} CACHE INTERNAL "Project major version") set(PROJECT_VERSION_MINOR ${MINOR_VERSION} CACHE INTERNAL "Project minor version") set(PROJECT_VERSION_PATCH ${PATCH_VERSION} CACHE INTERNAL "Project patch version")
# 2) 打印版本信息(构建时控制台输出) message(STATUS "========================================") message(STATUS "Project Version: ${FULL_VERSION} (INT: ${VERSION_INT})") message(STATUS " - Major: ${MAJOR_VERSION}") message(STATUS " - Minor: ${MINOR_VERSION}") message(STATUS " - Patch: ${PATCH_VERSION} (Git commit count)") message(STATUS "========================================")
# -------------------------- 5. 生成版本头文件(供代码调用) -------------------------- # 定义头文件模板(会替换 {PLACEHOLDER} 为实际版本值) set(VERSION_HEADER_CONTENT " #ifndef PROJECT_VERSION_H #define PROJECT_VERSION_H
// 版本号宏定义(语义化拆分) #define PROJECT_VERSION_MAJOR ${MAJOR_VERSION} #define PROJECT_VERSION_MINOR ${MINOR_VERSION} #define PROJECT_VERSION_PATCH ${PATCH_VERSION}
// 完整版本字符串(如 \"2.1.15\") #define PROJECT_FULL_VERSION \"${FULL_VERSION}\"
// 版本号整数(用于版本比较,如 20115 > 20000) #define PROJECT_VERSION_INT ${VERSION_INT}
#endif // PROJECT_VERSION_H ")
# 生成头文件到构建目录(避免污染源码) file(WRITE "${CMAKE_BINARY_DIR}/generated/version.h" "${VERSION_HEADER_CONTENT}")
# 让工程能 include 该头文件(无需手动复制) include_directories("${CMAKE_BINARY_DIR}/generated")
|
2. 根目录 CMakeLists.txt 集成
在工程根 CMakeLists.txt 中引入上述脚本,核心步骤:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| # 最低 CMake 版本要求(需支持 execute_process 稳定调用 Git) cmake_minimum_required(VERSION 3.12)
# 项目名称(版本号会被脚本覆盖,此处可留空) project(MyProject LANGUAGES CXX)
# -------------------------- 引入版本管理脚本 -------------------------- include(version.cmake)
# -------------------------- 后续工程配置(示例) -------------------------- # 1. 配置可执行文件,嵌入版本号到文件名 add_executable(MyApp src/main.cpp) set_target_properties(MyApp PROPERTIES OUTPUT_NAME "MyApp-v${FULL_VERSION}" # 输出文件名:MyApp-v2.1.15 CXX_STANDARD 17 CXX_STANDARD_REQUIRED ON )
# 2. 传递版本号到编译选项(可选,如给编译器定义) target_compile_definitions(MyApp PRIVATE PROJECT_FULL_VERSION="${FULL_VERSION}" )
# 3. 安装时带上版本信息(可选) install(TARGETS MyApp RUNTIME DESTINATION bin CONFIGURATIONS Release ) install(FILES "${CMAKE_BINARY_DIR}/generated/version.h" DESTINATION include/MyProject )
|
三、代码中使用版本号
生成的 version.h 会自动放入构建目录的 generated 文件夹,代码中直接引用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| // src/main.cpp #include <iostream> #include "version.h" // 无需手动复制,CMake 已配置 include 路径
int main() { std::cout << "========================================" << std::endl; std::cout << "MyApp Version: " << PROJECT_FULL_VERSION << std::endl; std::cout << "Version Int: " << PROJECT_VERSION_INT << std::endl; std::cout << "========================================" << std::endl;
// 版本比较示例(如仅支持 2.0.0 以上版本的功能) if (PROJECT_VERSION_INT >= 20000) { std::cout << "支持高级功能(版本 >= 2.0.0)" << std::endl; } else { std::cout << "基础功能模式" << std::endl; } return 0; }
|
四、关键操作指南
1. 如何更新主版本 / 小版本?
直接修改 version.cmake 中的 MAJOR_VERSION 或 MINOR_VERSION:
主版本更新(如 2.1.x → 3.0.x):set(MAJOR_VERSION 3) + set(MINOR_VERSION 0)
小版本更新(如 2.1.x → 2.2.x):set(MINOR_VERSION 2)
效果:补丁版本自动重置为当前 Git 提交数(无需手动改 PATCH_VERSION)
2. 构建产物示例
1 2 3 4 5 6
| ======================================== Project Version: 2.1.15 (INT: 20115) - Major: 2 - Minor: 1 - Patch: 15 (Git commit count) ========================================
|
3. 无 Git 仓库场景适配
总结
本方案通过 CMake 原生命令实现了 “主 / 小版本手动控制、补丁版本自动跟随 Git 提交数” 的核心需求,无需依赖 Python/Shell 脚本,工程集成成本低,且支持代码嵌入、产物命名、CI/CD 等全流程适配。
只需修改 version.cmake 中的主 / 小版本,补丁版本会自动同步 Git 提交记录,既符合语义化版本规范,又减少了手动维护版本号的工作量,适合中小型 C/C++ 工程直接使用。