CMake 秘籍(四)(2)

简介: CMake 秘籍(四)

CMake 秘籍(四)(1)https://developer.aliyun.com/article/1525209

它是如何工作的

我们使用了以下结构从名为VERSION的文件中读取版本字符串:

if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/VERSION")
  file(READ "${CMAKE_CURRENT_SOURCE_DIR}/VERSION" PROGRAM_VERSION)
  string(STRIP "${PROGRAM_VERSION}" PROGRAM_VERSION)
else()
  message(FATAL_ERROR "File ${CMAKE_CURRENT_SOURCE_DIR}/VERSION not found")
endif()

在这里,我们首先检查该文件是否存在,如果不存在则发出错误消息。如果存在,我们将文件内容读入名为PROGRAM_VERSION的变量中,并去除任何尾随空格。一旦设置了变量PROGRAM_VERSION,就可以用来配置version.hpp.in以生成generated/version.hpp,如下所示:

configure_file(
  version.hpp.in
  generated/version.hpp
  @ONLY
  )

在配置时记录 Git 哈希

本食谱的代码可在github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-06/recipe-06找到,包括一个 C++示例。该食谱适用于 CMake 版本 3.5(及以上),并在 GNU/Linux、macOS 和 Windows 上进行了测试。

大多数现代源代码仓库都使用 Git 作为版本控制系统进行跟踪,这一事实可以归因于仓库托管平台 GitHub 的巨大流行。因此,在本食谱中,我们将使用 Git;然而,动机和实现将适用于其他版本控制系统。如果我们以 Git 为例,一个提交的 Git 哈希值唯一地确定了源代码的状态。因此,为了唯一地标记可执行文件,我们将尝试通过在头文件中记录哈希字符串来将 Git 哈希值烧录到可执行文件中,该头文件可以在代码中的正确位置包含和使用。

准备工作

我们需要两个源文件,都与之前的食谱非常相似。一个将使用记录的哈希值进行配置(version.hpp.in),如下所示:

#pragma once
#include <string>
const std::string GIT_HASH = "@GIT_HASH@";

我们还需要一个示例源文件(example.cpp),它将打印哈希值到屏幕上:

#include "version.hpp"
#include <iostream>
int main() {
  std::cout << "This code has been configured from version " << GIT_HASH
            << std::endl;
}

这个食谱还假设我们处于至少有一个提交的 Git 仓库中。因此,使用 git init 初始化这个示例,并通过 git add git commit 创建提交,以获得有意义的示例。

如何操作

以下步骤说明了如何从 Git 记录版本信息:

  1. CMakeLists.txt 中,我们首先定义项目和语言支持:
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(recipe-06 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
  1. 然后,我们使用以下代码片段来定义一个变量,GIT_HASH
# in case Git is not available, we default to "unknown"
set(GIT_HASH "unknown")
# find Git and if available set GIT_HASH variable
find_package(Git QUIET)
if(GIT_FOUND)
  execute_process(
    COMMAND ${GIT_EXECUTABLE} log -1 --pretty=format:%h
    OUTPUT_VARIABLE GIT_HASH
    OUTPUT_STRIP_TRAILING_WHITESPACE
    ERROR_QUIET
    WORKING_DIRECTORY
      ${CMAKE_CURRENT_SOURCE_DIR}
    )
endif()
message(STATUS "Git hash is ${GIT_HASH}")
  1. 其余的 CMakeLists.txt 与之前的食谱中的相似:
# generate file version.hpp based on version.hpp.in
configure_file(
  version.hpp.in
  generated/version.hpp
  @ONLY
  )
# example code
add_executable(example example.cpp)
# needs to find the generated header file
target_include_directories(example
  PRIVATE
    ${CMAKE_CURRENT_BINARY_DIR}/generated
  )
  1. 我们可以通过以下方式验证输出(哈希值会有所不同):
$ mkdir -p build
$ cd build
$ cmake ..
$ cmake --build .
$ ./example
This code has been configured from version d58c64f

工作原理

我们使用 find_package(Git QUIET) 来检测系统上是否安装了 Git。如果安装了(如果 GIT_FOUND 为真),我们运行一个 Git 命令:${GIT_EXECUTABLE} log -1 --pretty=format:%h。这个命令给我们提供了当前提交哈希的简短版本。当然,我们完全有灵活性来运行另一个 Git 命令,而不是这个。我们要求 execute_process 命令将命令的结果放入一个名为 GIT_HASH 的变量中,然后我们去除任何尾随的空白。使用 ERROR_QUIET,我们要求命令在 Git 命令由于某种原因失败时不停止配置。

由于 Git 命令可能会失败(源代码可能已经在 Git 仓库之外分发)或者系统上甚至可能没有安装 Git,我们希望为变量设置一个默认值,如下所示:

set(GIT_HASH "unknown")

这个食谱的一个问题是 Git 哈希值是在配置时记录的,而不是在构建时。在下一个食谱中,我们将演示如何实现后一种方法。

在构建时记录 Git 哈希值

这个食谱的代码可以在 github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-06/recipe-07 找到,包括一个 C++ 示例。这个食谱适用于 CMake 版本 3.5(及更高版本),并且已经在 GNU/Linux、macOS 和 Windows 上进行了测试。

在之前的配方中,我们在配置时记录了代码仓库的状态(Git 哈希),并且在可执行文件中记录仓库状态非常有用。然而,之前方法的一个不满意之处是,如果我们更改分支或提交更改后配置代码,源代码中包含的版本记录可能会指向错误的 Git 哈希。在本配方中,我们希望更进一步,并演示如何在构建时记录 Git 哈希(或一般而言,执行其他操作),以确保每次我们构建代码时都会运行这些操作,因为我们可能只配置一次,但构建多次。

准备工作

我们将使用与之前配方相同的version.hpp.in,并且只会对example.cpp文件进行最小限度的修改,以确保它打印出构建时的 Git 哈希值:

#include "version.hpp"
#include <iostream>
int main() {
  std::cout << "This code has been built from version " << GIT_HASH << std::endl;
}

如何操作

在构建时将 Git 信息保存到version.hpp头文件将需要以下操作:

  1. 我们将把之前配方中CMakeLists.txt的大部分代码移动到一个单独的文件中,并将其命名为git-hash.cmake
# in case Git is not available, we default to "unknown"
set(GIT_HASH "unknown")
# find Git and if available set GIT_HASH variable
find_package(Git QUIET)
if(GIT_FOUND)
  execute_process(
    COMMAND ${GIT_EXECUTABLE} log -1 --pretty=format:%h
    OUTPUT_VARIABLE GIT_HASH
    OUTPUT_STRIP_TRAILING_WHITESPACE
    ERROR_QUIET
    )
endif()
message(STATUS "Git hash is ${GIT_HASH}")
# generate file version.hpp based on version.hpp.in
configure_file(
  ${CMAKE_CURRENT_LIST_DIR}/version.hpp.in
  ${TARGET_DIR}/generated/version.hpp
  @ONLY
  )
  1. CMakeLists.txt现在剩下我们非常熟悉的部分:
# set minimum cmake version
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
# project name and language
project(recipe-07 LANGUAGES CXX)
# require C++11
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# example code
add_executable(example example.cpp)
# needs to find the generated header file
target_include_directories(example
  PRIVATE
    ${CMAKE_CURRENT_BINARY_DIR}/generated
  )
  1. CMakeLists.txt的剩余部分记录了每次我们构建代码时 Git 哈希值,如下所示:
add_custom_command(
  OUTPUT
    ${CMAKE_CURRENT_BINARY_DIR}/generated/version.hpp
    ALL
  COMMAND
    ${CMAKE_COMMAND} -D TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/git-hash.cmake
  WORKING_DIRECTORY
    ${CMAKE_CURRENT_SOURCE_DIR}
  )
# rebuild version.hpp every time
add_custom_target(
  get_git_hash
  ALL
  DEPENDS
    ${CMAKE_CURRENT_BINARY_DIR}/generated/version.hpp
  )
# version.hpp has to be generated
# before we start building example
add_dependencies(example get_git_hash)

它是如何工作的

在本配方中,我们实现了在构建时执行 CMake 代码。为此,我们定义了一个自定义命令:

add_custom_command(
  OUTPUT
    ${CMAKE_CURRENT_BINARY_DIR}/generated/version.hpp
    ALL
  COMMAND
    ${CMAKE_COMMAND} -D TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/git-hash.cmake
  WORKING_DIRECTORY
    ${CMAKE_CURRENT_SOURCE_DIR}
  )

我们还定义了一个自定义目标,如下所示:

add_custom_target(
  get_git_hash
  ALL
  DEPENDS
    ${CMAKE_CURRENT_BINARY_DIR}/generated/version.hpp
  )

自定义命令调用 CMake 执行git-hash.cmakeCMake 脚本。这是通过使用-PCLI 开关来实现的,以传递脚本的位置。请注意,我们可以使用-DCLI 开关传递选项,就像我们通常所做的那样。git-hash.cmake脚本生成${TARGET_DIR}/generated/version.hpp。自定义目标添加到ALL目标,并依赖于自定义命令的输出。换句话说,当我们构建默认目标时,我们确保自定义命令被执行。此外,请注意自定义命令将ALL目标作为输出。这样,我们确保每次都会生成version.hpp

还有更多

我们可以增强配方,以便在记录的 Git 哈希之外包含额外信息。检测构建环境是否“脏”(即是否包含未提交的更改和未跟踪的文件)或“干净”并不罕见。可以使用git describe --abbrev=7 --long --always --dirty --tags检测此信息。根据可重复性的雄心,甚至可以将git status的完整输出记录到头文件中,但我们将其作为练习留给读者。

第八章:项目结构

在本章中,我们将涵盖以下配方:

  • 使用函数和宏实现代码复用
  • 将 CMake 源代码拆分为模块
  • 编写一个函数来测试和设置编译器标志
  • 使用命名参数定义函数或宏
  • 重新定义函数和宏
  • 弃用函数、宏和变量
  • 使用add_subdirectory限制作用域
  • 使用target_sources避免全局变量
  • 组织 Fortran 项目

引言

在前几章中,我们已经探索了使用 CMake 配置和构建项目所需的多个构建块。在本章中,我们将讨论如何组合这些构建块,并引入抽象概念以避免庞大的CMakeLists.txt文件,并最小化代码重复、全局变量、全局状态和显式排序。我们的目标是展示模块化 CMake 代码结构的范式,并限制变量的作用域。我们将讨论的策略也将帮助我们控制中等至大型代码项目的 CMake 代码复杂性。

使用函数和宏实现代码复用

本配方的代码可在github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-07/recipe-01获取,并包含一个 C++示例。该配方适用于 CMake 版本 3.5(及以上),并在 GNU/Linux、macOS 和 Windows 上进行了测试。

在任何编程语言中,函数允许我们抽象(隐藏)细节并避免代码重复,CMake 也不例外。在本配方中,我们将讨论宏和函数作为示例,并引入一个宏,使我们定义测试和设置测试顺序更加方便。我们不是通过调用add_testset_tests_properties来定义每个集合并设置每个测试的预期COST(第四章,创建和运行测试,配方 8,并行运行测试),我们的目标是定义一个宏,能够一次性处理这两项任务。

准备工作

我们将从第四章,创建和运行测试,配方 2,使用 Catch2 库定义单元测试中介绍的示例开始。main.cppsum_integers.cppsum_integers.hpp文件保持不变,可以用来计算作为命令行参数提供的整数之和。单元测试的源代码(test.cpp)也保持不变。我们还需要 Catch2 头文件catch.hpp。与第四章,创建和运行测试,配方 2,使用 Catch2 库定义单元测试不同,我们将把源文件结构化为子目录,并形成以下文件树(稍后我们将讨论 CMake 代码):

.
├── CMakeLists.txt
├── src
│   ├── CMakeLists.txt
│   ├── main.cpp
│   ├── sum_integers.cpp
│   └── sum_integers.hpp
└── tests
    ├── catch.hpp
    ├── CMakeLists.txt
    └── test.cpp

如何操作

让我们按照所需的步骤进行:

  1. 顶层的CMakeLists.txt文件定义了最低 CMake 版本、项目名称和支持的语言,并要求使用 C++11 标准:
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(recipe-01 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
  1. 我们进一步根据 GNU 标准定义了二进制和库路径:
include(GNUInstallDirs)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY
  ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY
  ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY
  ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
  1. 最后,我们使用add_subdirectory调用来将我们的 CMake 代码结构化为src/CMakeLists.txttests/CMakeLists.txt部分。我们还启用了测试:
add_subdirectory(src)
enable_testing()
add_subdirectory(tests)
  1. src/CMakeLists.txt文件中定义了源代码目标:
set(CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE ON)
add_library(sum_integers sum_integers.cpp)
add_executable(sum_up main.cpp)
target_link_libraries(sum_up sum_integers)
  1. tests/CMakeLists.txt中,我们首先构建并链接cpp_test可执行文件:
add_executable(cpp_test test.cpp)
target_link_libraries(cpp_test sum_integers)
  1. 然后我们定义了一个新的宏,add_catch_test,我们将在后面讨论它:
macro(add_catch_test _name _cost)
  math(EXPR num_macro_calls "${num_macro_calls} + 1")
  message(STATUS "add_catch_test called with ${ARGC} arguments: ${ARGV}")
  set(_argn "${ARGN}")
  if(_argn)
    message(STATUS "oops - macro received argument(s) we did not expect: ${ARGN}")
  endif()
  add_test(
    NAME
      ${_name}
    COMMAND
      $<TARGET_FILE:cpp_test>
      [${_name}] --success --out
      ${PROJECT_BINARY_DIR}/tests/${_name}.log --durations yes
    WORKING_DIRECTORY
      ${CMAKE_CURRENT_BINARY_DIR}
    )
  set_tests_properties(
    ${_name}
    PROPERTIES
      COST ${_cost}
    )
endmacro()
  1. 最后,我们使用add_catch_test定义了两个测试,此外,我们还设置并打印了一个变量的值:
set(num_macro_calls 0)
add_catch_test(short 1.5)
add_catch_test(long 2.5 extra_argument)
message(STATUS "in total there were ${num_macro_calls} calls to add_catch_test")
  1. 现在,我们准备测试一下。首先,我们配置项目(显示的有趣输出行):
$ mkdir -p build
$ cd build
$ cmake ..
-- ...
-- add_catch_test called with 2 arguments: short;1.5
-- add_catch_test called with 3 arguments: long;2.5;extra_argument
-- oops - macro received argument(s) we did not expect: extra_argument
-- in total there were 2 calls to add_catch_test
-- ...
  1. 最后,我们构建并运行测试:
$ cmake --build .
$ ctest
  1. 注意,首先启动的是长测试:
Start 2: long
1/2 Test #2: long ............................. Passed 0.00 sec
    Start 1: short
2/2 Test #1: short ............................ Passed 0.00 sec
100% tests passed, 0 tests failed out of 2

它是如何工作的

本食谱中的新特性是add_catch_test宏。宏期望两个参数,_name_cost,我们可以在宏内部使用这些参数来调用add_testset_tests_properties。前面的下划线是我们的选择,但通过这种方式,我们向读者表明这些参数具有局部作用域,并且只能在宏内部访问。还要注意,宏会自动填充${ARGC}(参数数量)和${ARGV}(参数列表),我们在输出中验证了这一点:

-- add_catch_test called with 2 arguments: short;1.5
-- add_catch_test called with 3 arguments: long;2.5;extra_argument

宏还定义了${ARGN},它保存了最后一个预期参数之后的参数列表。此外,我们还可以使用${ARGV0}${ARGV1}等来引用参数。观察我们是如何在这个调用中捕获意外参数(extra_argument)的:

add_catch_test(long 2.5 extra_argument)

我们使用以下方式完成了这一步骤:

set(_argn "${ARGN}")
if(_argn)
  message(STATUS "oops - macro received argument(s) we did not expect: ${ARGN}")
endif()

在这个条件检查中,我们不得不引入一个新的变量,并且不能直接查询ARGN,因为它在通常的 CMake 意义上不是一个变量。通过这个宏,我们不仅能够通过名称和命令定义测试,还能够指示预期的成本,这导致由于COST属性,“长”测试在“短”测试之前启动。

我们可以使用具有相同语法的函数而不是宏来实现这一点:

function(add_catch_test _name _cost)
  ...
endfunction()

宏和函数之间的区别在于它们的变量作用域。宏在调用者的作用域内执行,而函数有自己的变量作用域。换句话说,如果我们需要设置或修改应该对调用者可用的变量,我们通常使用宏。如果没有设置或修改输出变量,我们更倾向于使用函数。我们注意到,在函数中也可以修改父作用域的变量,但这必须使用PARENT_SCOPE明确指示:

set(variable_visible_outside "some value" PARENT_SCOPE)

为了展示范围,我们在宏定义之后编写了以下调用:

set(num_macro_calls 0)
add_catch_test(short 1.5)
add_catch_test(long 2.5 extra_argument)
message(STATUS "in total there were ${num_macro_calls} calls to add_catch_test")

在宏内部,我们将num_macro_calls增加 1:

math(EXPR num_macro_calls "${num_macro_calls} + 1")

这是产生的输出:

-- in total there were 2 calls to add_catch_test

如果我们把宏改为函数,测试仍然有效,但num_macro_calls在整个父作用域的调用过程中将保持为 0。想象一下,CMake 宏就像函数一样,它们直接被替换到调用它们的位置(在 C 语言中称为内联)。想象一下,CMake 函数就像黑盒子,除非你明确将其定义为PARENT_SCOPE,否则什么都不会返回。CMake 函数没有返回值。

还有更多

在宏中嵌套函数调用和在函数中嵌套宏调用是可能的,但我们需要仔细考虑变量的作用域。如果可以使用函数实现某个功能,那么这可能比宏更可取,因为它提供了对父作用域状态的更多默认控制。

我们还应该提到在src/CMakeLists.txt中使用CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE

set(CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE ON)

此命令将当前目录添加到此CMakeLists.txt文件中定义的所有目标的INTERFACE_INCLUDE_DIRECTORIES属性中。换句话说,我们不需要使用target_include_directories来指示cpp_test的头文件位置。

将 CMake 源代码拆分为模块

本例的代码可在github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-07/recipe-02找到。本例适用于 CMake 版本 3.5(及更高版本),并在 GNU/Linux、macOS 和 Windows 上进行了测试。

项目通常从一个CMakeLists.txt文件开始,但随着时间的推移,这个文件会增长,在本例中,我们将演示一种将CMakeLists.txt拆分为较小单元的方法。将CMakeLists.txt拆分为可以在主CMakeLists.txt或其他模块中包含的模块有几个动机:

  • 主要的CMakeLists.txt更容易阅读。
  • CMake 模块可以在其他项目中重用。
  • 结合函数,模块可以帮助我们限制变量的作用域。

在本例中,我们将演示如何定义和包含一个宏,该宏允许我们获取彩色的 CMake 输出(用于重要状态消息或警告)。

准备工作

在本例中,我们将使用两个文件,主CMakeLists.txtcmake/colors.cmake

.
├── cmake
│   └── colors.cmake
└── CMakeLists.txt

cmake/colors.cmake文件包含彩色输出的定义:

# colorize CMake output
# code adapted from stackoverflow: http://stackoverflow.com/a/19578320
# from post authored by https://stackoverflow.com/users/2556117/fraser
macro(define_colors)
  if(WIN32)
    # has no effect on WIN32
    set(ColourReset "")
    set(ColourBold "")
    set(Red "")
    set(Green "")
    set(Yellow "")
    set(Blue "")
    set(Magenta "")
    set(Cyan "")
    set(White "")
    set(BoldRed "")
    set(BoldGreen "")
    set(BoldYellow "")
    set(BoldBlue "")
    set(BoldMagenta "")
    set(BoldCyan "")
    set(BoldWhite "")
  else()
    string(ASCII 27 Esc)
    set(ColourReset "${Esc}m")
    set(ColourBold "${Esc}[1m")
set(Red "${Esc}[31m")
    set(Green "${Esc}[32m")
    set(Yellow "${Esc}[33m")
    set(Blue "${Esc}[34m")
    set(Magenta "${Esc}[35m")
    set(Cyan "${Esc}[36m")
    set(White "${Esc}[37m")
    set(BoldRed "${Esc}[1;31m")
    set(BoldGreen "${Esc}[1;32m")
    set(BoldYellow "${Esc}[1;33m")
    set(BoldBlue "${Esc}[1;34m")
    set(BoldMagenta "${Esc}[1;35m")
    set(BoldCyan "${Esc}[1;36m")
    set(BoldWhite "${Esc}[1;37m")
  endif()
endmacro()

如何操作

这就是我们如何使用颜色定义来生成彩色状态消息的方法:

  1. 我们从一个熟悉的开头开始:
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(recipe-02 LANGUAGES NONE)
  1. 然后,我们将cmake子目录添加到 CMake 将搜索的模块路径列表中:
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
  1. 然后,我们包含colors.cmake模块并调用其中定义的宏:
include(colors)
define_colors()
  1. 最后,我们打印几条不同颜色的消息:
message(STATUS "This is a normal message")
message(STATUS "${Red}This is a red${ColourReset}")
message(STATUS "${BoldRed}This is a bold red${ColourReset}")
message(STATUS "${Green}This is a green${ColourReset}")
message(STATUS "${BoldMagenta}This is bold${ColourReset}")
  1. 让我们测试一下(如果使用 macOS 或 Linux,此输出应该以彩色显示在屏幕上):

![

工作原理

这是一个示例,其中不编译任何代码,也不需要语言支持,我们通过LANGUAGES NONE表明了这一点:

project(recipe-02 LANGUAGES NONE)

我们定义了define_colors宏,并将其放置在cmake/colors.cmake中。我们选择使用宏而不是函数,因为我们还希望在调用范围内使用宏内部定义的变量来改变消息的颜色。我们包含了宏,并使用以下行调用了define_colors

include(colors)
define_colors()

然而,我们还需要告诉 CMake 在哪里查找宏:

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")

include(colors)命令指示 CMake 在${CMAKE_MODULE_PATH}中搜索名为colors.cmake的模块。

而不是这样写:

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
include(colors)

我们可以使用以下明确的包含:

include(cmake/colors.cmake)

还有更多

推荐的做法是在模块中定义宏或函数,然后调用宏或函数。使用模块包含作为函数调用是不好的做法。包含模块不应该做更多的事情,除了定义函数和宏以及发现程序、库和路径。实际的包含命令不应该定义或修改变量,这样做的原因是,重复的包含,可能是意外的,不应该引入任何不希望的副作用。在食谱 5,“重新定义函数和宏”中,我们将创建一个防止意外包含的保护措施。

编写一个测试和设置编译器标志的函数

本食谱的代码可在github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-07/recipe-03找到,并包含一个 C/C++示例。该食谱适用于 CMake 版本 3.5(及以上),并在 GNU/Linux、macOS 和 Windows 上进行了测试。

在前两个食谱中,我们使用了宏;在本食谱中,我们将使用一个函数来抽象细节并避免代码重复。在示例中,我们将实现一个接受编译器标志列表的函数。该函数将尝试使用这些标志逐一编译测试代码,并返回编译器理解的第一标志。通过这样做,我们将学习一些新特性:函数、列表操作、字符串操作以及检查编译器是否支持编译器标志。

CMake 秘籍(四)(3)https://developer.aliyun.com/article/1525213

相关文章
|
6月前
|
Linux API iOS开发
CMake 秘籍(六)(1)
CMake 秘籍(六)
48 1
|
6月前
|
消息中间件 Unix C语言
CMake 秘籍(二)(5)
CMake 秘籍(二)
141 1
|
6月前
|
Shell Linux C++
CMake 秘籍(六)(4)
CMake 秘籍(六)
50 1
|
6月前
|
编译器 Linux C++
CMake 秘籍(六)(5)
CMake 秘籍(六)
36 1
|
6月前
|
Linux C++ iOS开发
CMake 秘籍(四)(3)
CMake 秘籍(四)
24 0
|
6月前
|
并行计算 Unix 编译器
CMake 秘籍(七)(5)
CMake 秘籍(七)
89 0
|
6月前
|
Linux C++ iOS开发
CMake 秘籍(四)(1)
CMake 秘籍(四)
28 0
|
6月前
|
编译器 Linux C++
CMake 秘籍(二)(1)
CMake 秘籍(二)
39 0
|
6月前
|
XML 监控 Linux
CMake 秘籍(七)(4)
CMake 秘籍(七)
74 0
|
6月前
|
并行计算 关系型数据库 编译器
CMake 秘籍(七)(3)
CMake 秘籍(七)
88 0
下一篇
无影云桌面