> For the complete documentation index, see [llms.txt](https://chenxiaowei.gitbook.io/cmake-cookbook/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://chenxiaowei.gitbook.io/cmake-cookbook/6.0-chinese/6.7-chinese.md).

# 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值:

```cpp
#include "version.hpp"

#include <iostream>

int main() {
    std::cout << "This code has been built from version " << GIT_HASH << std::endl;
}
```

## 具体实施

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

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

   ```
   # in case Git is not available, we default to "unknown"
   set(GIT_HASH "unknown")

   # find Git and if available set GIT_HASH variable
   find_package(Git QUIET)
   if(GIT_FOUND)
     execute_process(
       COMMAND ${GIT_EXECUTABLE} log -1 --pretty=format:%h
       OUTPUT_VARIABLE GIT_HASH
       OUTPUT_STRIP_TRAILING_WHITESPACE
       ERROR_QUIET
       )
   endif()

   message(STATUS "Git hash is ${GIT_HASH}")

   # generate file version.hpp based on version.hpp.in
   configure_file(
     ${CMAKE_CURRENT_LIST_DIR}/version.hpp.in
     ${TARGET_DIR}/generated/version.hpp
     @ONLY
     )
   ```
2. `CMakeLists.txt`熟悉的部分:

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

   ```
   add_custom_command(
    OUTPUT
        ${CMAKE_CURRENT_BINARY_DIR}/generated/version.hpp
    ALL
    COMMAND
        ${CMAKE_COMMAND} -D TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/git-hash.cmake
    WORKING_DIRECTORY
        ${CMAKE_CURRENT_SOURCE_DIR}
    )

   # rebuild version.hpp every time
   add_custom_target(
    get_git_hash
    ALL
    DEPENDS
        ${CMAKE_CURRENT_BINARY_DIR}/generated/version.hpp
    )

   # version.hpp has to be generated
   # before we start building example
   add_dependencies(example get_git_hash)
   ```

## 工作原理

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

```
add_custom_command(
  OUTPUT
      ${CMAKE_CURRENT_BINARY_DIR}/generated/version.hpp
  ALL
  COMMAND
      ${CMAKE_COMMAND} -D TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/git-hash.cmake
  WORKING_DIRECTORY
      ${CMAKE_CURRENT_SOURCE_DIR}
  )
```

我们还定义了一个目标:

```
add_custom_target(
  get_git_hash
  ALL
  DEPENDS
      ${CMAKE_CURRENT_BINARY_DIR}/generated/version.hpp
  )
```

自定义命令调用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的状态，完整输出记录到头文件中，我们将这些功能作为课后习题留给读者自己完成。
