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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://chenxiaowei.gitbook.io/cmake-cookbook/6.0-chinese/6.7-chinese.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
