# 7.5 重新定义函数和宏

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

我们已经提到模块包含不应该用作函数调用，因为模块可能被包含多次。本示例中，我们将编写我们自己的“包含保护”机制，如果多次包含一个模块，将触发警告。内置的`include_guard`命令从3.10版开始可以使用，对于C/C++头文件，它的行为就像`#pragma`一样。对于当前版本的CMake，我们将演示如何重新定义函数和宏，并且展示如何检查CMake版本，对于低于3.10的版本，我们将使用定制的“包含保护”机制。

## 准备工作

这个例子中，我们将使用三个文件:

```
.
├── cmake
│     ├── custom.cmake
│     └── include_guard.cmake
└── CMakeLists.txt
```

`custom.cmake`模块包含以下代码:

```
include_guard(GLOBAL)
message(STATUS "custom.cmake is included and processed")
```

我们稍后会对`cmake/include_guard.cmake`进行讨论。

## 具体实施

我们对三个CMake文件的逐步分解:

1. 示例中，我们不会编译任何代码，因此我们的语言要求是`NONE`:

   ```
   cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
   project(recipe-05 LANGUAGES NONE)
   ```
2. 定义一个`include_guard`宏，将其放在一个单独的模块中:

   ```
   # (re)defines include_guard
   include(cmake/include_guard.cmake)
   ```
3. `cmake/include_guard.cmake`文件包含以下内容(稍后将详细讨论):

   ```
   macro(include_guard)
     if (CMAKE_VERSION VERSION_LESS "3.10")
       # for CMake below 3.10 we define our
       # own include_guard(GLOBAL)
       message(STATUS "calling our custom include_guard")

       # if this macro is called the first time
       # we start with an empty list
       if(NOT DEFINED included_modules)
         set(included_modules)
       endif()

       if ("${CMAKE_CURRENT_LIST_FILE}" IN_LIST included_modules)
         message(WARNING "module ${CMAKE_CURRENT_LIST_FILE} processed more than once")
       endif()

       list(APPEND included_modules ${CMAKE_CURRENT_LIST_FILE})
       else()
       # for CMake 3.10 or higher we augment
       # the built-in include_guard
       message(STATUS "calling the built-in include_guard")

       _include_guard(${ARGV})
     endif()
   endmacro()
   ```
4. 主CMakeLists.txt中，我们模拟了两次包含自定义模块的情况:

   ```
   include(cmake/custom.cmake)
   include(cmake/custom.cmake)
   ```
5. 最后，使用以下命令进行配置:

   ```
   $ mkdir -p build
   $ cd build
   $ cmake ..
   ```
6. 使用CMake 3.10及更高版本的结果如下:

   ```
   -- calling the built-in include_guard
   -- custom.cmake is included and processed
   -- calling the built-in include_guard
   ```
7. 使用CMake得到3.10以下的结果如下:

   ```
   - calling our custom include_guard
   -- custom.cmake is included and processed
   -- calling our custom include_guard
   CMake Warning at cmake/include_guard.cmake:7 (message):
   module
   /home/user/example/cmake/custom.cmake
   processed more than once
   Call Stack (most recent call first):
   cmake/custom.cmake:1 (include_guard)
   CMakeLists.txt:12 (include)
   ```

## 工作原理

`include_guard`宏包含两个分支，一个用于CMake低于3.10，另一个用于CMake高于3.10:

```
macro(include_guard)
  if (CMAKE_VERSION VERSION_LESS "3.10")
      # ...
  else()
      # ...
  endif()
endmacro()
```

如果CMake版本低于3.10，进入第一个分支，并且内置的`include_guard`不可用，所以我们自定义了一个:

```
message(STATUS "calling our custom include_guard")

# if this macro is called the first time
# we start with an empty list
if(NOT DEFINED included_modules)
    set(included_modules)
endif()

if ("${CMAKE_CURRENT_LIST_FILE}" IN_LIST included_modules)
    message(WARNING "module ${CMAKE_CURRENT_LIST_FILE} processed more than once")
endif()

list(APPEND included_modules ${CMAKE_CURRENT_LIST_FILE})
```

如果第一次调用宏，则`included_modules`变量没有定义，因此我们将其设置为空列表。然后检查`${CMAKE_CURRENT_LIST_FILE}`是否是`included_modules`列表中的元素。如果是，则会发出警告；如果没有，我们将`${CMAKE_CURRENT_LIST_FILE}`追加到这个列表。CMake输出中，我们可以验证自定义模块的第二个包含确实会导致警告。

CMake 3.10及更高版本的情况有所不同；在这种情况下，存在一个内置的`include_guard`，我们用自己的宏接收到参数并调用它:

```
macro(include_guard)
  if (CMAKE_VERSION VERSION_LESS "3.10")
      # ...
  else()
      message(STATUS "calling the built-in include_guard")

      _include_guard(${ARGV})
  endif()
endmacro()
```

这里，`_include_guard(${ARGV})`指向内置的`include_guard`。本例中，使用自定义消息(“调用内置的`include_guard`”)进行了扩展。这种模式为我们提供了一种机制，来重新定义自己的或内置的函数和宏，这对于调试或记录日志来说非常有用。

**NOTE**:*这种模式可能很有用，但是应该谨慎使用，因为CMake不会对重新定义的宏或函数进行警告。*


---

# 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/7.0-chinese/7.5-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.
