> 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.2-chinese.md).

# 2.2 处理与平台相关的源代码

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

理想情况下，应该避免依赖于平台的源代码，但是有时我们没有选择，特别是当要求配置和编译不是自己编写的代码时。本示例中，将演示如何使用CMake根据操作系统编译源代码。

## 准备工作

修改`hello-world.cpp`示例代码，将第1章第1节的例子进行修改:

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

std::string say_hello() {
#ifdef IS_WINDOWS
  return std::string("Hello from Windows!");
#elif IS_LINUX
  return std::string("Hello from Linux!");
#elif IS_MACOS
  return std::string("Hello from macOS!");
#else
  return std::string("Hello from an unknown system!");
#endif
}

int main() {
  std::cout << say_hello() << std::endl;
  return EXIT_SUCCESS;
}
```

## 具体实施

完成一个`CMakeLists.txt`实例，使我们能够基于目标操作系统有条件地编译源代码：

1. 首先，设置了CMake最低版本、项目名称和支持的语言:

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

   ```
   add_executable(hello-world hello-world.cpp)
   ```
3. 通过定义以下目标编译定义，让预处理器知道系统名称:

   ```
   if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
     target_compile_definitions(hello-world PUBLIC "IS_LINUX")
   endif()
   if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
     target_compile_definitions(hello-world PUBLIC "IS_MACOS")
   endif()
   if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
     target_compile_definitions(hello-world PUBLIC "IS_WINDOWS")
   endif()
   ```

   继续之前，先检查前面的表达式，并考虑在不同系统上有哪些行为。
4. 现在，准备测试它，并配置项目:

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

   Hello from Linux!
   ```

Windows系统上，将看到来自Windows的Hello。其他操作系统将产生不同的输出。

## 工作原理

`hello-world.cpp`示例中，有趣的部分是基于预处理器定义`IS_WINDOWS`、`IS_LINUX`或`IS_MACOS`的条件编译:

```
std::string say_hello() {
#ifdef IS_WINDOWS
  return std::string("Hello from Windows!");
#elif IS_LINUX
  return std::string("Hello from Linux!");
#elif IS_MACOS
  return std::string("Hello from macOS!");
#else
  return std::string("Hello from an unknown system!");
#endif
}
```

这些定义在CMakeLists.txt中配置时定义，通过使用`target_compile_definition`在预处理阶段使用。可以不重复`if-endif`语句，以更紧凑的表达式实现，我们将在下一个示例中演示这种重构方式。也可以把`if-endif`语句加入到一个`if-else-else-endif`语句中。这个阶段，可以使用`add_definitions(-DIS_LINUX)`来设置定义(当然，可以根据平台调整定义)，而不是使用`target_compile_definition`。使用`add_definitions`的缺点是，会修改编译整个项目的定义，而`target_compile_definitions`给我们机会，将定义限制于一个特定的目标，以及通过`PRIVATE|PUBLIC|INTERFACE`限定符，限制这些定义可见性。第1章的第8节，对这些限定符有详细的说明:

* **PRIVATE**，编译定义将只应用于给定的目标，而不应用于相关的其他目标。
* **INTERFACE**，对给定目标的编译定义将只应用于使用它的目标。
* **PUBLIC**，编译定义将应用于给定的目标和使用它的所有其他目标。

**NOTE**:*将项目中的源代码与平台相关性最小化，可使移植更加容易。*
