💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 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`源代码: ```c++ #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`): ```fortran 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 cmake_minimum_required(VERSION 3.5 FATAL_ERROR) project(recipe-03 LANGUAGES CXX) ``` 2. 然后,定义可执行目标及其对应的源文件: ```cmake add_executable(hello-world hello-world.cpp) ``` 3. 通过定义以下目标编译定义,让预处理器了解编译器的名称和供应商: ```cmake 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() ``` 现在我们已经可以预测结果了: ```shell $ mkdir -p build $ cd build $ cmake .. $ cmake --build . $ ./hello-world Hello GNU compiler! ``` 使用不同的编译器,此示例代码将打印不同的问候语。 前一个示例的`CMakeLists.txt`文件中的`if`语句似乎是重复的,我们不喜欢重复的语句。能更简洁地表达吗?当然可以!为此,让我们再来看看`Fortran`示例。 `Fortran`例子的`CMakeLists.txt`文件中,我们需要做以下工作: 1. 需要使`Fortran`语言: ```cmake project(recipe-03 LANGUAGES Fortran) ``` 2. 然后,定义可执行文件及其对应的源文件。在本例中,使用大写`.F90`后缀: ```cmake add_executable(hello-world hello-world.F90) ``` 3. 我们通过定义下面的目标编译定义,让预处理器非常清楚地了解编译器: ```cmake 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`后缀。*