> 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/2.0-chinese/2.3-chinese.md).

# 2.3 处理与编译器相关的源代码

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

这个方法与前面的方法类似，我们将使用CMake来编译依赖于环境的条件源代码：本例将依赖于编译器。为了可移植性，我们尽量避免去编写新代码，但遇到有依赖的情况我们也要去解决，特别是当使用历史代码或处理编译器依赖工具，如[sanitizers](https://github.com/google/sanitizers)。从这一章和前一章的示例中，我们已经掌握了实现这一目标的所有方法。尽管如此，讨论与编译器相关的源代码的处理问题还是很有用的，这样我们将有机会从另一方面了解CMake。

## 准备工作

本示例中，我们将从`C++`中的一个示例开始，稍后我们将演示一个`Fortran`示例，并尝试重构和简化CMake代码。

看一下`hello-world.cpp`源代码:

```cpp
#include <cstdlib>
#include <iostream>
#include <string>

std::string say_hello() {
#ifdef IS_INTEL_CXX_COMPILER
  // only compiled when Intel compiler is selected
  // such compiler will not compile the other branches
  return std::string("Hello Intel compiler!");
#elif IS_GNU_CXX_COMPILER
  // only compiled when GNU compiler is selected
  // such compiler will not compile the other branches
  return std::string("Hello GNU compiler!");
#elif IS_PGI_CXX_COMPILER
  // etc.
  return std::string("Hello PGI compiler!");
#elif IS_XL_CXX_COMPILER
  return std::string("Hello XL compiler!");
#else
  return std::string("Hello unknown compiler - have we met before?");
#endif
}

int main() {
  std::cout << say_hello() << std::endl;
  std::cout << "compiler name is " COMPILER_NAME << std::endl;
  return EXIT_SUCCESS;
}
```

`Fortran`示例(`hello-world.F90`):

```
program hello

  implicit none
#ifdef IS_Intel_FORTRAN_COMPILER
  print *, 'Hello Intel compiler!'
#elif IS_GNU_FORTRAN_COMPILER
  print *, 'Hello GNU compiler!'
#elif IS_PGI_FORTRAN_COMPILER
  print *, 'Hello PGI compiler!'
#elif IS_XL_FORTRAN_COMPILER
  print *, 'Hello XL compiler!'
#else
  print *, 'Hello unknown compiler - have we met before?'
#endif

end program
```

## 具体实施

我们将从`C++`的例子开始，然后再看`Fortran`的例子:

1. `CMakeLists.txt`文件中，定义了CMake最低版本、项目名称和支持的语言:

   ```
   cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
   project(recipe-03 LANGUAGES CXX)
   ```
2. 然后，定义可执行目标及其对应的源文件:

   ```
   add_executable(hello-world hello-world.cpp)
   ```
3. 通过定义以下目标编译定义，让预处理器了解编译器的名称和供应商:

   ```
   target_compile_definitions(hello-world PUBLIC "COMPILER_NAME=\"${CMAKE_CXX_COMPILER_ID}\"")

   if(CMAKE_CXX_COMPILER_ID MATCHES Intel)
     target_compile_definitions(hello-world PUBLIC "IS_INTEL_CXX_COMPILER")
   endif()
   if(CMAKE_CXX_COMPILER_ID MATCHES GNU)
     target_compile_definitions(hello-world PUBLIC "IS_GNU_CXX_COMPILER")
   endif()
   if(CMAKE_CXX_COMPILER_ID MATCHES PGI)
     target_compile_definitions(hello-world PUBLIC "IS_PGI_CXX_COMPILER")
   endif()
   if(CMAKE_CXX_COMPILER_ID MATCHES XL)
     target_compile_definitions(hello-world PUBLIC "IS_XL_CXX_COMPILER")
   endif()
   ```

现在我们已经可以预测结果了:

```
$ mkdir -p build
$ cd build
$ cmake ..
$ cmake --build .
$ ./hello-world

Hello GNU compiler!
```

使用不同的编译器，此示例代码将打印不同的问候语。

前一个示例的`CMakeLists.txt`文件中的`if`语句似乎是重复的，我们不喜欢重复的语句。能更简洁地表达吗？当然可以！为此，让我们再来看看`Fortran`示例。

`Fortran`例子的`CMakeLists.txt`文件中，我们需要做以下工作:

1. 需要使`Fortran`语言:

   ```
   project(recipe-03 LANGUAGES Fortran)
   ```
2. 然后，定义可执行文件及其对应的源文件。在本例中，使用大写`.F90`后缀:

   ```
   add_executable(hello-world hello-world.F90)
   ```
3. 我们通过定义下面的目标编译定义，让预处理器非常清楚地了解编译器:

   ```
   target_compile_definitions(hello-world
     PUBLIC "IS_${CMAKE_Fortran_COMPILER_ID}_FORTRAN_COMPILER"
     )
   ```

其余行为与`C++`示例相同。

## 工作原理

`CMakeLists.txt`会在配置时，进行预处理定义，并传递给预处理器。`Fortran`示例包含非常紧凑的表达式，我们使用`CMAKE_Fortran_COMPILER_ID`变量，通过`target_compile_definition`使用构造预处理器进行预处理定义。为了适应这种情况，我们必须将"Intel"从`IS_INTEL_CXX_COMPILER`更改为`IS_Intel_FORTRAN_COMPILER`。通过使用相应的`CMAKE_C_COMPILER_ID`和`CMAKE_CXX_COMPILER_ID`变量，我们可以在`C`或`C++`中实现相同的效果。但是，请注意，`CMAKE_<LANG>_COMPILER_ID`不能保证为所有编译器或语言都定义。

**NOTE**:*对于应该预处理的`Fortran`代码使用`.F90`后缀，对于不需要预处理的代码使用`.f90`后缀。*
