CMake 系列(十):处理与平台相关的源代码

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

提示:本示例包含一个 C++ 示例,在 CMake 3.5 或更高版本中有效,并已在 GNU/Linux、macOS 和 Windows 上测试通过。

准备工作

修改 hello-world.cpp 示例代码,以系列第 1 篇中的例子为基础:

#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 实例,使我们能够基于目标操作系统有条件地编译源代码。

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

cmake_minimum_required(VERSION 3.5 FATAL_ERROR)

project(recipe-02 LANGUAGES CXX)

然后,定义可执行文件及其对应的源文件:

add_executable(hello-world hello-world.cpp)

通过定义以下目标编译定义,让预处理器知道系统名称:

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()

继续之前,先检查前面的表达式,并考虑在不同系统上分别有哪些行为。

现在,准备测试它,配置、构建并运行:

$ 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_definitions() 在预处理阶段使用。可以不重复 if-endif 语句,以更紧凑的表达式实现,我们将在下一篇示例中演示这种重构方式。也可以把 if-endif 语句合并到一个 if-elseif-elseif-endif 语句中。这个阶段,可以使用 add_definitions(-DIS_LINUX) 来设置定义(当然,可以根据平台调整定义),而不是使用 target_compile_definitions()。使用 add_definitions() 的缺点是,会修改编译整个项目的定义,而 target_compile_definitions() 给我们机会,将定义限制于一个特定的目标,以及通过 PRIVATE|PUBLIC|INTERFACE 限定符限制这些定义的可见性。设置编译器选项的示例中对这些限定符有详细的说明:

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

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

系列导航


   转载规则


《CMake 系列(十):处理与平台相关的源代码》 由 吴杭沉 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
CMake 系列(十一):处理与编译器相关的源代码 CMake 系列(十一):处理与编译器相关的源代码
这个方法与前面的方法类似:我们将使用 CMake 来编译依赖于环境的条件源代码,本例中该依赖是编译器。为了可移植性,我们应尽量避免编写新代码,但遇到有依赖的情况也要去解决,特别是在使用历史代码或处理编译器相关工具(如 sanitizers)
2022-05-24
下一篇 
CMake 系列(九):检测操作系统 CMake 系列(九):检测操作系统
CMake 是一组跨平台工具。不过,了解操作系统(OS)上执行的配置或构建步骤也很重要,与操作系统相关的 CMake 代码会根据操作系统启用条件编译,或者在可用或必要时使用特定于编译器的扩展。本示例中,我们将通过一个不需要编译任何源代码的示
2022-05-10
  目录