# 6.5 从文件中记录项目版本

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

这个示例的目的和前一个相似，但是出发点不同。我们计划是从文件中读取版本信息，而不是将其设置在CMakeLists.txt中。将版本保存在单独文件中的动机，是允许其他构建框架或开发工具使用独立于CMake的信息，而无需将信息复制到多个文件中。与CMake并行使用的构建框架的一个例子是Sphinx文档框架，它生成文档并将其部署到阅读文档服务中，以便在线提供代码文档。

## 准备工作

我们将从一个名为`VERSION`的文件开始，其中包含以下内容:

```
2.0.1-rc-2
```

这一次，选择更安全的数据类型，并将`PROGRAM_VERSION`定义为`version.hpp.in`中的字符串常量:

```cpp
#pragma once
#include <string>
const std::string PROGRAM_VERSION = "@PROGRAM_VERSION@";
```

下面的源码(`example.cpp`)，将包含生成的`version.hpp`:

```cpp
// provides PROGRAM_VERSION
#include "version.hpp"
#include <iostream>

int main() {
  std::cout << "This is output from code v" << PROGRAM_VERSION
  << std::endl;
  std::cout << "Hello CMake world!" << std::endl;
}
```

## 具体实施

逐步来完成我们的任务:

1. CMakeLists.txt定义了最低版本、项目名称、语言和标准:

   ```
   cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
   project(recipe-05 LANGUAGES CXX)
   set(CMAKE_CXX_STANDARD 11)
   set(CMAKE_CXX_EXTENSIONS OFF)
   set(CMAKE_CXX_STANDARD_REQUIRED ON)
   ```
2. 从文件中读取版本信息如下:

   ```
   if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/VERSION")
       file(READ "${CMAKE_CURRENT_SOURCE_DIR}/VERSION" PROGRAM_VERSION)
       string(STRIP "${PROGRAM_VERSION}" PROGRAM_VERSION)
   else()
       message(FATAL_ERROR "File ${CMAKE_CURRENT_SOURCE_DIR}/VERSION not found")
   endif()
   ```
3. 配置头文件:

   ```
   configure_file(
     version.hpp.in
     generated/version.hpp
     @ONLY
     )
   ```
4. 最后，定义了可执行文件及其依赖关系:

   ```
   add_executable(example example.cpp)
   target_include_directories(example
     PRIVATE
         ${CMAKE_CURRENT_BINARY_DIR}/generated
     )
   ```
5. 进行测试:

   ```
   $ mkdir -p build
   $ cd build
   $ cmake ..
   $ cmake --build .
   $ ./example

   This is output from code v2.0.1-rc-2
   Hello CMake world!
   ```

## 工作原理

我们使用以下构造，从一个名为VERSION的文件中读取版本字符串:

```
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/VERSION")
  file(READ "${CMAKE_CURRENT_SOURCE_DIR}/VERSION" PROGRAM_VERSION)
  string(STRIP "${PROGRAM_VERSION}" PROGRAM_VERSION)
else()
    message(FATAL_ERROR "File ${CMAKE_CURRENT_SOURCE_DIR}/VERSION not found")
endif()
```

这里，首先检查该文件是否存在，如果不存在，则发出错误消息。如果存在，将内容读入`PROGRAM_VERSION`变量中，该变量会去掉尾部的空格。当设置了变量`PROGRAM_VERSION`，就可以使用它来配置`version.hpp.in`，生成`generated/version.hpp`:

```
configure_file(
  version.hpp.in
  generated/version.hpp
  @ONLY
  )
```
