6.7 构建时记录Git Hash值

NOTE:此示例代码可以在 https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-6/recipe-07 中找到,其中包含一个C++例子。该示例在CMake 3.5版(或更高版本)中是有效的,并且已经在GNU/Linux、macOS和Windows上进行过测试。

前面的示例中,在配置时记录了代码存储库(Git Hash)的状态。然而,前一种方法有一个令人不满意的地方,如果在配置代码之后更改分支或提交更改,则源代码中包含的版本记录可能指向错误的Git Hash值。在这个示例中,我们将演示如何在构建时记录Git Hash(或者,执行其他操作),以确保每次构建代码时都运行这些操作,因为我们可能只配置一次,但是会构建多次。

准备工作

我们将使用与之前示例相同的version.hpp.in,只会对example.cpp文件进行修改,以确保它打印构建时Git提交Hash值:

  1. #include "version.hpp"
  2. #include <iostream>
  3. int main() {
  4. std::cout << "This code has been built from version " << GIT_HASH << std::endl;
  5. }

具体实施

将Git信息保存到version.hpp头文件在构建时需要进行以下操作:

  1. 把前一个示例的CMakeLists.txt中的大部分代码移到一个单独的文件中,并将该文件命名为git-hash.cmake:

    1. # in case Git is not available, we default to "unknown"
    2. set(GIT_HASH "unknown")
    3. # find Git and if available set GIT_HASH variable
    4. find_package(Git QUIET)
    5. if(GIT_FOUND)
    6. execute_process(
    7. COMMAND ${GIT_EXECUTABLE} log -1 --pretty=format:%h
    8. OUTPUT_VARIABLE GIT_HASH
    9. OUTPUT_STRIP_TRAILING_WHITESPACE
    10. ERROR_QUIET
    11. )
    12. endif()
    13. message(STATUS "Git hash is ${GIT_HASH}")
    14. # generate file version.hpp based on version.hpp.in
    15. configure_file(
    16. ${CMAKE_CURRENT_LIST_DIR}/version.hpp.in
    17. ${TARGET_DIR}/generated/version.hpp
    18. @ONLY
    19. )
  2. CMakeLists.txt熟悉的部分:

    1. # set minimum cmake version
    2. cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    3. # project name and language
    4. project(recipe-07 LANGUAGES CXX)
    5. # require C++11
    6. set(CMAKE_CXX_STANDARD 11)
    7. set(CMAKE_CXX_EXTENSIONS OFF)
    8. set(CMAKE_CXX_STANDARD_REQUIRED ON)
    9. # example code
    10. add_executable(example example.cpp)
    11. # needs to find the generated header file
    12. target_include_directories(example
    13. PRIVATE
    14. ${CMAKE_CURRENT_BINARY_DIR}/generated
    15. )
  3. CMakeLists.txt的剩余部分,记录了每次编译代码时的Git Hash:

    1. add_custom_command(
    2. OUTPUT
    3. ${CMAKE_CURRENT_BINARY_DIR}/generated/version.hpp
    4. ALL
    5. COMMAND
    6. ${CMAKE_COMMAND} -D TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/git-hash.cmake
    7. WORKING_DIRECTORY
    8. ${CMAKE_CURRENT_SOURCE_DIR}
    9. )
    10. # rebuild version.hpp every time
    11. add_custom_target(
    12. get_git_hash
    13. ALL
    14. DEPENDS
    15. ${CMAKE_CURRENT_BINARY_DIR}/generated/version.hpp
    16. )
    17. # version.hpp has to be generated
    18. # before we start building example
    19. add_dependencies(example get_git_hash)

工作原理

示例中,在构建时执行CMake代码。为此,定义了一个自定义命令:

  1. add_custom_command(
  2. OUTPUT
  3. ${CMAKE_CURRENT_BINARY_DIR}/generated/version.hpp
  4. ALL
  5. COMMAND
  6. ${CMAKE_COMMAND} -D TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/git-hash.cmake
  7. WORKING_DIRECTORY
  8. ${CMAKE_CURRENT_SOURCE_DIR}
  9. )

我们还定义了一个目标:

  1. add_custom_target(
  2. get_git_hash
  3. ALL
  4. DEPENDS
  5. ${CMAKE_CURRENT_BINARY_DIR}/generated/version.hpp
  6. )

自定义命令调用CMake来执行git-hash.cmake脚本。这里使用CLI的-P开关,通过传入脚本的位置实现的。请注意,可以像往常一样使用CLI开关-D传递选项。git-hash.cmake脚本生成 ${TARGET_DIR}/generated/version.hpp。自定义目标被添加到ALL目标中,并且依赖于自定义命令的输出。换句话说,当构建默认目标时,我们确保自定义命令已经运行。此外,自定义命令将ALL目标作为输出。这样,我们就能确保每次都会生成version.hpp了。

更多信息

我们可以改进配置,以便在记录的Git Hash外,包含其他的信息。检测构建环境是否“污染”(即是否包含未提交的更改和未跟踪的文件),或者“干净”。可以使用git describe --abbrev=7 --long --always --dirty --tags检测这些信息。根据可重现性,甚至可以将Git的状态,完整输出记录到头文件中,我们将这些功能作为课后习题留给读者自己完成。