cmake的基础使用

一、cmake的使用依赖以及安装

一、cmake的安装

$ cmake_version="3.5.2"
$ target_path=$HOME/Deps/cmake/${cmake_version}
$ cmake_url="https://cmake.org/files/v${cmake_version%.*}/cmake-${cmake_version}-Linux-x86_64.tar.gz"
$ mkdir -p "${target_path}"
$ curl -Ls "${cmake_url}" | tar -xz -C "${target_path}" --strip-components=1
$ export PATH=$HOME/Deps/cmake/${cmake_version}/bin${PATH:+:$PATH}
$ cmake --version

二、ninja安装

在安装ninja之前,需要安装其依赖re2c

tar -xvzf re2c-1.0.3.tar.gz
cd re2c-1.0.3/
autoreconf -i -W all(没有configure可以先执行Autotools)
./configure
make
make install
git clone git://github.com/ninja-build/ninja.git && cd ninja
./configure.py --bootstrap
cp ninja /usr/bin/
ninja --version

三、clang的安装

$ sudo apt-get install g++ gcc gfortran
$ sudo apt-get install clang clang++

四、安装conda

$ curl -Ls https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh > miniconda.sh
$ bash miniconda.sh -b -p "$HOME"/Deps/conda &> /dev/null
$ touch "$HOME"/Deps/conda/conda-meta/pinned
$ export PATH=$HOME/Deps/conda/bin${PATH:+:$PATH}
$ conda config --set show_channel_urls True
$ conda config --set changeps1 no
$ conda update --all
$ conda clean -tipy

五、Conda的构建和部署

$ conda install --yes --quiet conda-build anaconda-client jinja2 setuptools
$ conda clean -tipsy
$ conda info -a

 

六、其他的依赖软件

1.BLAS和LAPACK

$ sudo apt-get install libatlas-dev liblapack-dev liblapacke-dev

2.消息传递接口(MPI)

$ sudo apt-get install openmpi-bin libopenmpi-dev

3.线性代数模板库

 

4.Boost库

sudo apt-get install libboost-filesystem-dev libboost-python-dev libboost-test-dev

5.交叉编译器

sudo apt-get install gcc-mingw-w64 g++-mingw-w64 gfortran-mingw-w64

6.ZeroMQ, pkg-config, UUID和Doxygen

sudo apt-get install pkg-config libzmq3-dev doxygen graphviz-dev uuid-dev

 

二、初步了解cmake的使用

  1. 用编辑器打开一个文本文件,将这个文件命名为CMakeLists.txt

  2. 第一行,设置CMake所需的最低版本。如果使用的CMake版本低于该版本,则会发出致命错误:

    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
  3. 第二行,声明了项目的名称(recipe-01)和支持的编程语言(CXX代表C++):

    project(recipe-01 LANGUAGES CXX)
  4. 指示CMake创建一个新目标:可执行文件hello-world。这个可执行文件是通过编译和链接源文件hello-world.cpp生成的。CMake将为编译器使用默认设置,并自动选择生成工具:

    add_executable(hello-world hello-world.cpp)
  5. 将该文件与源文件hello-world.cpp放在相同的目录中。记住,它只能被命名为CMakeLists.txt

  6. 现在,可以通过创建build目录,在build目录下来配置项目:

    $ mkdir -p build
    $ cd build
    $ cmake ..
  7. 如果一切顺利,项目的配置已经在build目录中生成。我们现在可以编译可执行文件:

    $ cmake --build .

一、工作原理

示例中,我们使用了一个简单的CMakeLists.txt来构建“Hello world”可执行文件:

cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(recipe-01 LANGUAGES CXX)
add_executable(hello-world hello-world.cpp)

NOTE:CMake语言不区分大小写,但是参数区分大小写。

TIPS:CMake中,C++是默认的编程语言。不过,我们还是建议使用LANGUAGES选项在project命令中显式地声明项目的语言。

NOTE:在与CMakeLists.txt相同的目录中执行cmake .,原则上足以配置一个项目。然而,CMake会将所有生成的文件写到项目的根目录中。这将是一个源代码内构建,通常是不推荐的,因为这会混合源代码和项目的目录树。我们首选的是源外构建。

GNU/Linux上,CMake默认生成Unix Makefile来构建项目:

  • Makefile: make将运行指令来构建项目。

  • CMakefile:包含临时文件的目录,CMake用于检测操作系统、编译器等。此外,根据所选的生成器,它还包含特定的文件。

  • cmake_install.cmake:处理安装规则的CMake脚本,在项目安装时使用。

  • CMakeCache.txt:如文件名所示,CMake缓存。CMake在重新运行配置时使用这个文件。

要构建示例项目,我们运行以下命令:

$ cmake --build .

最后,CMake不强制指定构建目录执行名称或位置,我们完全可以把它放在项目路径之外。这样做同样有效:

$ mkdir -p /tmp/someplace$ cd /tmp/someplace$ cmake /path/to/source$ cmake --build .

二、切换生成器

CMake是一个构建系统生成器,可以使用单个CMakeLists.txt为不同平台上的不同工具集配置项目。您可以在CMakeLists.txt中描述构建系统必须运行的操作,以配置并编译代码。基于这些指令,CMake将为所选的构建系统(Unix Makefile、Ninja、Visual Studio等等)生成相应的指令。

后续需要的几个demo文件

hello-world.cpp

#include "Message.hpp"
#include <cstdlib>
#include <iostream>

int main() {
  Message say_hello("Hello, CMake World!");
  std::cout << say_hello << std::endl;
  Message say_goodbye("Goodbye, CMake World");
  std::cout << say_goodbye << std::endl;
  return EXIT_SUCCESS;
}

Message.hpp

#pragma once

#include <iosfwd>
#include <string>

class Message {
public:
  Message(const std::string &m) : message_(m) {}

  friend std::ostream &operator<<(std::ostream &os, Message &obj) {
    return obj.printObject(os);
  }

private:
  std::string message_;
  std::ostream &printObject(std::ostream &os);
};

Message.cpp

#include "Message.hpp"
#include <iostream>
#include <string>

std::ostream &Message::printObject(std::ostream &os) {
  os << "This is my very nice message: " << std::endl;
  os << message_;

  return os;
}

CmakeLists.txt

# set minimum cmake version
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)

# project name and language
project(recipe-01 LANGUAGES CXX)

add_executable(hello-world hello-world.cpp)

利用上面的代码。切换生成器和前面第一节惟一的区别在使用CMake时,因为现在必须显式地使用命令行方式,用-G切换生成器。

$ mkdir -p build
$ cd build
$ cmake -G Ninja ..
$ cmake --build .

三、如何工作

与前一个配置相比,每一步的输出没什么变化。每个生成器都有自己的文件集,所以编译步骤的输出和构建目录的内容是不同的:

  • build.ninjarules.ninja:包含Ninja的所有的构建语句和构建规则。

  • CMakeCache.txt:CMake会在这个文件中进行缓存,与生成器无关。

  • CMakeFiles:包含由CMake在配置期间生成的临时文件。

  • cmake_install.cmake:CMake脚本处理安装规则,并在安装时使用。

cmake --build .ninja命令封装在一个跨平台的接口中。

三、构建和链接静态库和动态库

项目中会有单个源文件构建的多个可执行文件的可能。项目中有多个源文件,通常分布在不同子目录中。这种实践有助于项目的源代码结构,而且支持模块化、代码重用和关注点分离。同时,这种分离可以简化并加速项目的重新编译。

修改前面的hello-world.cpp程序,Message和前面的一样

#include "Message.hpp"
#include <cstdlib>
#include <iostream>
int main() {
  Message say_hello("Hello, CMake World!");
  std::cout << say_hello << std::endl;
  Message say_goodbye("Goodbye, CMake World");
  std::cout << say_goodbye << std::endl;
  return EXIT_SUCCESS;
}

一、具体实施

这里有两个文件需要编译,所以CMakeLists.txt必须进行修改。本例中,先把它们编译成一个库,而不是直接编译成可执行文件:

  1. 创建目标——静态库。库的名称和源码文件名相同,具体代码如下:

    add_library(message  STATIC    Message.hpp    Message.cpp  )
  2. 创建hello-world可执行文件的目标部分不需要修改:

    add_executable(hello-world hello-world.cpp)
  3. 最后,将目标库链接到可执行目标:

    target_link_libraries(hello-world message)
  4. 对项目进行配置和构建。库编译完成后,将连接到hello-world可执行文件中:

    $ mkdir -p build
    $ cd build
    $ cmake ..
    $ cmake --build .

    运行结果

    $ ./hello-world
    This is my very nice message:
    Hello, CMake World!
    This is my very nice message:
    Goodbye, CMake World

二、工作原理

在此引入了两个新命令:

  • add_library(message STATIC Message.hpp Message.cpp):生成必要的构建指令,将指定的源码编译到库中。add_library的第一个参数是目标名。整个CMakeLists.txt中,可使用相同的名称来引用库。生成的库的实际名称将由CMake通过在前面添加前缀lib和适当的扩展名作为后缀来形成。生成库是根据第二个参数(STATICSHARED)和操作系统确定的。

  • target_link_libraries(hello-world message): 将库链接到可执行文件。此命令还确保hello-world可执行文件可以正确地依赖于消息库。因此,在消息库链接到hello-world可执行文件之前,需要完成消息库的构建。

编译成功后,构建目录包含libmessage.a一个静态库(在GNU/Linux上)和hello-world可执行文件。

CMake接受其他值作为add_library的第二个参数的有效值,后续会用到的值:

  • STATIC:用于创建静态库,即编译文件的打包存档,以便在链接其他目标时使用,例如:可执行文件。

  • SHARED:用于创建动态库,即可以动态链接,并在运行时加载的库。可以在CMakeLists.txt中使用add_library(message SHARED Message.hpp Message.cpp)从静态库切换到动态共享对象(DSO)。

  • OBJECT:可将给定add_library的列表中的源码编译到目标文件,不将它们归档到静态库中,也不能将它们链接到共享对象中。如果需要一次性创建静态库和动态库,那么使用对象库尤其有用。我们将在本示例中演示。

  • MODULE:又为DSO组。与SHARED库不同,它们不链接到项目中的任何目标,不过可以进行动态加载。该参数可以用于构建运行时插件。

CMake还能够生成特殊类型的库,这不会在构建系统中产生输出,但是对于组织目标之间的依赖关系,和构建需求非常有用:

  • IMPORTED:此类库目标表示位于项目外部的库。此类库的主要用途是,对现有依赖项进行构建。因此,IMPORTED库将被视为不可变的。

  • INTERFACE:与IMPORTED库类似。不过,该类型库可变,没有位置信息。它主要用于项目之外的目标构建使用。我们将在本章第5节中演示INTERFACE库的示例。

  • ALIAS:顾名思义,这种库为项目中已存在的库目标定义别名。不过,不能为IMPORTED库选择别名。

三、OBJECT库的使用

修改CMakeLists.txt,如下:

cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(recipe-03 LANGUAGES CXX)
add_library(message-objs
    OBJECT
        Message.hpp
        Message.cpp
    )
# this is only needed for older compilers
# but doesn't hurt either to have it
set_target_properties(message-objs
    PROPERTIES
        POSITION_INDEPENDENT_CODE 1
    )
add_library(message-shared
    SHARED
        $<TARGET_OBJECTS:message-objs>
    )
add_library(message-static
    STATIC
        $<TARGET_OBJECTS:message-objs>
    )
add_executable(hello-world hello-world.cpp)
target_link_libraries(hello-world message-static)

首先,add_library改为add_library(Message-objs OBJECT Message.hpp Message.cpp)。此外,需要保证编译的目标文件与生成位置无关。可以通过使用set_target_properties命令,设置message-objs目标的相应属性来实现。

NOTE: 可能在某些平台和/或使用较老的编译器上,需要显式地为目标设置POSITION_INDEPENDENT_CODE属性。

现在,可以使用这个对象库来获取静态库(message-static)和动态库(message-shared)。要注意引用对象库的生成器表达式语法:$<TARGET_OBJECTS:message-objs>。生成器表达式是CMake在生成时(即配置之后)构造,用于生成特定于配置的构建输出。

是否可以让CMake生成同名的两个库?换句话说,它们都可以被称为message,而不是message-staticmessage-shared吗?我们需要修改这两个目标的属性:

add_library(message-shared
  SHARED
    $<TARGET_OBJECTS:message-objs>
    )
set_target_properties(message-shared
    PROPERTIES
        OUTPUT_NAME "message"
    )
add_library(message-static
    STATIC
        $<TARGET_OBJECTS:message-objs>
    )
set_target_properties(message-static
    PROPERTIES
        OUTPUT_NAME "message"
    )

四、条件控制语句

一、用条件句控制编译

目前为止,看到的示例比较简单,CMake执行流是线性的:从一组源文件到单个可执行文件,也可以生成静态库或动态库。为了确保完全控制构建项目、配置、编译和链接所涉及的所有步骤的执行流,CMake提供了自己的语言。本节中,我们将探索条件结构if-else- else-endif的使用。

NOTE: CMake语言相当庞杂,由基本的控制结构、特定于CMake的命令和使用新函数模块化扩展语言的基础设施组成

如果我们希望在下面两种状态之间进行切换

  1. Message.hppMessage.cpp构建成一个库(静态或动态),然后将生成库链接到hello-world可执行文件中。

  2. Message.hppMessage.cpphello-world.cpp构建成一个可执行文件,但不生成任何一个库。

让我们来看看如何使用CMakeLists.txt来实现:

  1. 首先,定义最低CMake版本、项目名称和支持的语言:

    cmake_minimum_required(VERSION 3.5 FATAL_ERROR) 
    project(hello_world LANGUAGES CXX)
  2. 我们引入了一个新变量USE_LIBRARY,这是一个逻辑变量,值为OFF。我们还打印了它的值:

    set(USE_LIBRARY OFF)
    message(STATUS "Compile sources into a library? ${USE_LIBRARY}")
  3. CMake中定义BUILD_SHARED_LIBS全局变量,并设置为OFF。调用add_library并省略第二个参数,将构建一个静态库:

    set(BUILD_SHARED_LIBS OFF)
  4. 然后,引入一个变量_sources,包括Message.hppMessage.cpp

    list(APPEND _sources Message.hpp Message.cpp)
  5. 然后,引入一个基于USE_LIBRARY值的if-else语句。如果逻辑为真,则Message.hppMessage.cpp将打包成一个库:

    if(USE_LIBRARY)    
    	# add_library will create a static library    
    	# since BUILD_SHARED_LIBS is OFF    
    	add_library(message ${_sources})    
    	add_executable(hello-world hello-world.cpp)    
    	target_link_libraries(hello-world message)
    else()    
    	add_executable(hello-world hello-world.cpp ${_sources})
    endif()
  6. 我们可以再次使用相同的命令集进行构建。由于USE_LIBRARYOFF, hello-world可执行文件将使用所有源文件来编译。

    CmakeLists.txt 全文

    # set minimum cmake version
    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    
    # project name and language
    project(recipe-04 LANGUAGES CXX)
    
    # introduce a toggle for using a library
    set(USE_LIBRARY OFF)
    message(STATUS "Compile sources into a library? ${USE_LIBRARY}")
    
    # BUILD_SHARED_LIBS is a global flag offered by CMake
    # to toggle the behavior of add_library
    set(BUILD_SHARED_LIBS OFF)
    
    # list sources
    list(APPEND _sources Message.hpp Message.cpp)
    
    if(USE_LIBRARY)
      # add_library will create a static library
      # since BUILD_SHARED_LIBS is OFF
      add_library(message ${_sources})
      add_executable(hello-world hello-world.cpp)
      target_link_libraries(hello-world message)
    else()
      add_executable(hello-world hello-world.cpp ${_sources})
    endif()
    

     

二、工作原理

我们介绍了两个变量:USE_LIBRARYBUILD_SHARED_LIBS。这两个变量都设置为OFF。如CMake语言文档中描述,逻辑真或假可以用多种方式表示:

  • 如果将逻辑变量设置为以下任意一种:1ONYEStrueY或非零数,则逻辑变量为true

  • 如果将逻辑变量设置为以下任意一种:0OFFNOfalseNIGNORE、NOTFOUND、空字符串,或者以-NOTFOUND为后缀,则逻辑变量为false

USE_LIBRARY变量将在第一个和第二个行为之间切换。BUILD_SHARED_LIBS是CMake的一个全局标志。因为CMake内部要查询BUILD_SHARED_LIBS全局变量,所以add_library命令可以在不传递STATIC/SHARED/OBJECT参数的情况下调用;如果为false或未定义,将生成一个静态库。

这个例子说明,可以引入条件来控制CMake中的执行流。但是,当前的设置不允许从外部切换,不需要手动修改CMakeLists.txt。原则上,我们希望能够向用户开放所有设置,这样就可以在不修改构建代码的情况下调整配置,稍后将展示如何做到这一点。

NOTE:else()endif()中的(),可能会让刚开始学习CMake代码的同学感到惊讶。其历史原因是,因为其能够指出指令的作用范围。例如,可以使用if(USE_LIBRARY)…else(USE_LIBRARY)…endif(USE_LIBIRAY)。这个格式并不唯一,可以根据个人喜好来决定使用哪种格式。

TIPS:_sources变量是一个局部变量,不应该在当前范围之外使用,可以在名称前加下划线。

五、向用户显示选项

前面的配置中,我们引入了条件句:通过硬编码的方式给定逻辑变量值。不过,这会影响用户修改这些变量。CMake代码没有向读者传达,该值可以从外部进行修改。推荐在CMakeLists.txt中使用option()命令,以选项的形式显示逻辑开关,用于外部设置,从而切换构建系统的生成行为。本节的示例将向您展示,如何使用这个命令。

一、具体实施

看一下前面示例中的静态/动态库示例。与其硬编码USE_LIBRARYONOFF,现在为其设置一个默认值,同时也可以从外部进行更改:

  1. 用一个选项替换上一个示例的set(USE_LIBRARY OFF)命令。该选项将修改USE_LIBRARY的值,并设置其默认值为OFF

    option(USE_LIBRARY "Compile sources into a library" OFF)
  2. 现在,可以通过CMake的-DCLI选项,将信息传递给CMake来切换库的行为:

    $ mkdir -p build
    $ cd build
    $ cmake -D USE_LIBRARY=ON ..
    $ cmake --build .

    -D开关用于为CMake设置任何类型的变量:逻辑变量、路径等等。

    CmakeLists.txt 全文

    # set minimum cmake version
    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    
    # project name and language
    project(recipe-05 LANGUAGES CXX)
    
    # expose options to the user
    option(USE_LIBRARY "Compile sources into a library" OFF)
    message(STATUS "Compile sources into a library? ${USE_LIBRARY}")
    include(CMakeDependentOption)
    
    # second option depends on the value of the first
    cmake_dependent_option(
      MAKE_STATIC_LIBRARY "Compile sources into a static library" OFF
      "USE_LIBRARY" ON
      )
    
    # third option depends on the value of the first
    cmake_dependent_option(
      MAKE_SHARED_LIBRARY "Compile sources into a shared library" ON
      "USE_LIBRARY" ON
      )
    
    set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
    
    # list sources
    list(APPEND _sources Message.hpp Message.cpp)
    
    if(USE_LIBRARY)
      message(STATUS "Compile sources into a STATIC library? ${MAKE_STATIC_LIBRARY}")
      message(STATUS "Compile sources into a SHARED library? ${MAKE_SHARED_LIBRARY}")
    
      if(MAKE_SHARED_LIBRARY)
        add_library(message SHARED ${_sources})
    
        add_executable(hello-world hello-world.cpp)
    
        target_link_libraries(hello-world message)
      endif()
    
      if(MAKE_STATIC_LIBRARY)
        add_library(message STATIC ${_sources})
    
        add_executable(hello-world hello-world.cpp)
    
        target_link_libraries(hello-world message)
      endif()
    else()
      add_executable(hello-world hello-world.cpp ${_sources})
    endif()
    

     

二、工作原理

option可接受三个参数:

option(<option_variable> "help string" [initial value])
  • <option_variable>表示该选项的变量的名称。

  • "help string"记录选项的字符串,在CMake的终端或图形用户界面中可见。

  • [initial value]选项的默认值,可以是ONOFF

三、更多选项

有时选项之间会有依赖的情况。示例中,我们提供生成静态库或动态库的选项。但是,如果没有 将 USE_LIBRARY 逻辑设置为 ON ,则此选项没有任何意义。CMake提 供 cmake_dependent_option() 命令用来定义依赖于其他选项的选项:

include(CMakeDependentOption)

second option depends on the value of the first

cmake_dependent_option(

MAKE_STATIC_LIBRARY "Compile sources into a static library" OFF

	"USE_LIBRARY" ON

)

third option depends on the value of the first

cmake_dependent_option(

	MAKE_SHARED_LIBRARY "Compile sources into a shared library" ON

	"USE_LIBRARY" ON

)

如果 USE_LIBRARY 为 ON , MAKE_STATIC_LIBRARY 默认值为 OFF ,否 则 MAKE_SHARED_LIBRARY 默认值为 ON 。可以这样运行:

$ cmake -D USE_LIBRARY=OFF -D MAKE_SHARED_LIBRARY=ON .. 这仍然不会构建库,因为 USE_LIBRARY 仍然为 OFF 。 工作原理 更多信息 CMake有适当的机制,通过包含模块来扩展其语法和功能,这些模块要么是CMake自带的,要么是定制 的。本例中,包含了一个名为 CMakeDependentOption 的模块。如果没有 include 这个模 块, cmake_dependent_option() 命令将不可用。

六、指定编译器

目前为止,我们还没有过多考虑如何选择编译器。CMake可以根据平台和生成器选择编译器,还能将编 译器标志设置为默认值。然而,我们通常控制编译器的选择。在后面的示例中,我们还将考虑构建类型 的选择,并展示如何控制编译器标志。

一、具体实施

如何选择一个特定的编译器?例如,如果想使用Intel或Portland Group编译器怎么办?CMake将语 言的编译器存储在 CMAKE<LANG>COMPILER 变量中,其中 <LANG> 是受支持的任何一种语言,对 于我们的目的是 CXX 、 C 或 Fortran 。用户可以通过以下两种方式之一设置此变量:

  1. 使用CLI中的 -D 选项,例如:

    $ cmake -D CMAKE_CXX_COMPILER=clang++ ..
  2. 通过导出环境变量 CXX (C++编译器)、 CC (C编译器)和 FC (Fortran编译器)。例如, 使用这个命令使用 clang++ 作为 C++ 编译器:

  $ env CXX=clang++ cmake ..

到目前为止讨论的示例,都可以通过传递适当的选项,配置合适的编译器。 NOTE:CMake了解运行环境,可以通过其CLI的 -D 开关或环境变量设置许多选项。前一种机制覆盖 后一种机制,但是我们建议使用 -D 显式设置选项。显式优于隐式,因为环境变量可能被设置为不适 合(当前项目)的值。 我们在这里假设,其他编译器在标准路径中可用,CMake在标准路径中执行查找编译器。如果不是这 样,用户将需要将完整的编译器可执行文件或包装器路径传递给CMake。 TIPS:我们建议使用 -D CMAKE<LANG>COMPILER CLI选项设置编译器,而不是导出 CXX 、 CC 和 FC 。这是确保跨平台并与非POSIX兼容的唯一方法。为了避免变量污染环境,这些变量可能会影响与项目一起构建的外部库环境。

二、工作原理

1.指定编译器

配置时,CMake会进行一系列平台测试,以确定哪些编译器可用,以及它们是否适合当前的项目。一个 合适的编译器不仅取决于我们所使用的平台,还取决于我们想要使用的生成器。CMake执行的第一个测 试基于项目语言的编译器的名称。例如, cc 是一个工作的 C 编译器,那么它将用作 C 项目的默 认编译器。GNU/Linux上,使用Unix Makefile或Ninja时, GCC家族中的编译器很可能 是 C++ 、 C 和 Fortran 的默认选择。Microsoft Windows上,将选择Visual Studio中 的 C++ 和 C 编译器(前提是Visual Studio是生成器)。如果选择MinGW或MSYS Makefile作为 生成器,则默认使用MinGW编译器。

三、更多信息

我们的平台上的CMake,在哪里可以找到可用的编译器和编译器标志?CMake提供 --system- information 标志,它将把关于系统的所有信息转储到屏幕或文件中。要查看这个信息,请尝试以下 操作:

$ cmake --system-information information.txt

文件中(本例中是 information.txt )可以看到 CMAKE_CXX_COMPILER 、 CMAKE_C_COMPILER 和 CMAKE_Fortran_COMPILER 的默认值,以及默认标志。我们将在下一个示例中看到相关的标志。 CMake提供了额外的变量来与编译器交互:

  • CMAKE<LANG>COMPILER_LOADED :如果为项目启用了语言 <LANG> ,则将设置为 TRUE 。

  • CMAKE<LANG>COMPILER_ID :编译器标识字符串,编译器供应商所特有。例如, GCC 用于GNU编译器集合, AppleClang 用于macOS上的Clang, MSVC 用于Microsoft Visual Studio编译器。注意,不能保证为所有编译器或语言定义此变量。

  • CMAKE_COMPILER_IS_GNU<LANG> :如果语言 <LANG> 是GNU编译器集合的一部分,则将此逻辑变量设置为 TRUE 。注意变量名的 <LANG> 部分遵循GNU约定:C语言为 CC , C++语言为 CXX , Fortran语言为 G77

  • CMAKE<LANG>COMPILER_VERSION :此变量包含一个字符串,该字符串给定语言的编译器版本。版本信息在 major[.minor[.patch[.tweak]]] 中给出。但是,对于 CMAKE<LANG>COMPILER_ID ,不能保证所有编译器或语言都定义了此变量。

我们可以尝试使用不同的编译器,配置下面的示例 CMakeLists.txt 。这个例子中,我们将使用CMake变量来探索已使用的编译器(及版本):

cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(recipe-06 LANGUAGES C CXX)
message(STATUS "Is the C++ compiler loaded? ${CMAKE_CXX_COMPILER_LOADED}")
if(CMAKE_CXX_COMPILER_LOADED)
	message(STATUS "The C++ compiler ID is: ${CMAKE_CXX_COMPILER_ID}")
    message(STATUS "Is the C++ from GNU? ${CMAKE_COMPILER_IS_GNUCXX}")
	message(STATUS "The C++ compiler version is:${CMAKE_CXX_COMPILER_VERSION}")
endif()
message(STATUS "Is the C compiler loaded? ${CMAKE_C_COMPILER_LOADED}")
if(CMAKE_C_COMPILER_LOADED)
	message(STATUS "The C compiler ID is: ${CMAKE_C_COMPILER_ID}")
	message(STATUS "Is the C from GNU? ${CMAKE_COMPILER_IS_GNUCC}")
	message(STATUS "The C compiler version is: ${CMAKE_C_COMPILER_VERSION}")
endif()

七、切换构建类型

CMake可以配置构建类型,例如:Debug、Release等。配置时,可以为Debug或Release构建设置相关的选项或属性,例如:编译器和链接器标志。控制生成构建系统使用的配置变量是CMAKE_BUILD_TYPE。该变量默认为空,CMake识别的值为:

  1. Debug:用于在没有优化的情况下,使用带有调试符号构建库或可执行文件。

  2. Release:用于构建的优化的库或可执行文件,不包含调试符号。

  3. RelWithDebInfo:用于构建较少的优化库或可执行文件,包含调试符号。

  4. MinSizeRel:用于不增加目标代码大小的优化方式,来构建库或可执行文件。

一、具体实施

示例中,我们将展示如何为项目设置构建类型:

  1. 首先,定义最低CMake版本、项目名称和支持的语言:

    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    project(recipe-07 LANGUAGES C CXX)
  2. 然后,设置一个默认的构建类型(本例中是Release),并打印一条消息。要注意的是,该变量被设置为缓存变量,可以通过缓存进行编辑:

    if(NOT CMAKE_BUILD_TYPE)    
    	set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
    endif()
    message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
  3. 最后,打印出CMake设置的相应编译标志:

    message(STATUS "C flags, Debug configuration: ${CMAKE_C_FLAGS_DEBUG}")
    message(STATUS "C flags, Release configuration: ${CMAKE_C_FLAGS_RELEASE}")
    message(STATUS "C flags, Release configuration with Debug info: ${CMAKE_C_FLAGS_RELWITHDEBINFO}")
    message(STATUS "C flags, minimal Release configuration: ${CMAKE_C_FLAGS_MINSIZEREL}")
    message(STATUS "C++ flags, Debug configuration: ${CMAKE_CXX_FLAGS_DEBUG}")
    message(STATUS "C++ flags, Release configuration: ${CMAKE_CXX_FLAGS_RELEASE}")
    message(STATUS "C++ flags, Release configuration with Debug info: ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
    message(STATUS "C++ flags, minimal Release configuration: ${CMAKE_CXX_FLAGS_MINSIZEREL}")
  4. 验证配置的输出:

    $ mkdir -p build$ cd build$ cmake .....-- Build type: Release-- C flags, Debug configuration: -g-- C flags, Release configuration: -O3 -DNDEBUG-- C flags, Release configuration with Debug info: -O2 -g -DNDEBUG-- C flags, minimal Release configuration: -Os -DNDEBUG-- C++ flags, Debug configuration: -g-- C++ flags, Release configuration: -O3 -DNDEBUG-- C++ flags, Release configuration with Debug info: -O2 -g -DNDEBUG-- C++ flags, minimal Release configuration: -Os -DNDEBUG
  5. 切换构建类型:

    $ cmake -D CMAKE_BUILD_TYPE=Debug ..
    
    -- Build type: Debug-- C flags, Debug configuration: -g
    -- C flags, Release configuration: -O3 -DNDEBUG
    -- C flags, Release configuration with Debug info: -O2 -g -DNDEBUG
    -- C flags, minimal Release configuration: -Os -DNDEBUG
    -- C++ flags, Debug configuration: -g
    -- C++ flags, Release configuration: -O3 -DNDEBUG
    -- C++ flags, Release configuration with Debug info: -O2 -g -DNDEBUG
    -- C++ flags, minimal Release configuration: -Os -DNDEBUG

二、工作原理

我们演示了如何设置默认构建类型,以及如何(从命令行)覆盖它。这样,就可以控制项目,是使用优化,还是关闭优化启用调试。我们还看到了不同配置使用了哪些标志,这主要取决于选择的编译器。需要在运行CMake时显式地打印标志,也可以仔细阅读运行CMake --system-information的输出,以了解当前平台、默认编译器和语言的默认组合是什么。下一个示例中,我们将讨论如何为不同的编译器和不同的构建类型,扩展或调整编译器标志。

三、更多信息

我们展示了变量CMAKE_BUILD_TYPE,如何切换生成构建系统的配置(这个链接中有说明: https://cmake.org/cmake/help/v3.5/variable/CMAKE_BUILD_TYPE.html )。Release和Debug配置中构建项目通常很有用,例如:评估编译器优化级别的效果。对于单配置生成器,如Unix Makefile、MSYS Makefile或Ninja,因为要对项目重新配置,这里需要运行CMake两次。不过,CMake也支持复合配置生成器。这些通常是集成开发环境提供的项目文件,最显著的是Visual Studio和Xcode,它们可以同时处理多个配置。可以使用CMAKE_CONFIGURATION_TYPES变量可以对这些生成器的可用配置类型进行调整,该变量将接受一个值列表(可从这个链接获得文档:https://cmake.org/cmake/help/v3.5/variable/CMAKE_CONFIGURATION_TYPES.html)。

下面是对Visual Studio的CMake调用:

$ mkdir -p build$ cd build
$ cmake .. -G"Visual Studio 12 2017 Win64" -D CMAKE_CONFIGURATION_TYPES="Release;Debug"

将为Release和Debug配置生成一个构建树。然后,您可以使--config标志来决定构建这两个中的哪一个:

$ cmake --build . --config Release

NOTE:当使用单配置生成器开发代码时,为Release版和Debug创建单独的构建目录,两者使用相同的源代码。这样,就可以在两者之间切换,而不用重新配置和编译。

八、设置编译器选项

前面的示例展示了如何探测CMake,从而获得关于编译器的信息,以及如何切换项目中的编译器。后一个任务是控制项目的编译器标志。CMake为调整或扩展编译器标志提供了很大的灵活性,您可以选择下面两种方法:

  • CMake将编译选项视为目标属性。因此,可以根据每个目标设置编译选项,而不需要覆盖CMake默认值。

  • 可以使用-DCLI标志直接修改CMAKE_<LANG>_FLAGS_<CONFIG>变量。这将影响项目中的所有目标,并覆盖或扩展CMake默认值。

本示例中,我们将展示这两种方法。

一、准备工作

编写一个示例程序,计算不同几何形状的面积,computer_area.cpp

#include "geometry_circle.hpp"
#include "geometry_polygon.hpp"
#include "geometry_rhombus.hpp"
#include "geometry_square.hpp"
#include <cstdlib>
#include <iostream>
int main() {  
	using namespace geometry;  
	double radius = 2.5293;  
	double A_circle = area::circle(radius);  
	std::cout << "A circle of radius " << radius << " has an area of " << A_circle            << std::endl;  
	int nSides = 19;  
	double side = 1.29312;  
	double A_polygon = area::polygon(nSides, side);  
	std::cout << "A regular polygon of " << nSides << " sides of length " << side            << " has an area of " << A_polygon << std::endl;  
	double d1 = 5.0;  
	double d2 = 7.8912;  
	double A_rhombus = area::rhombus(d1, d2);  
	std::cout << "A rhombus of major diagonal " << d1 << " and minor diagonal " << d2            << " has an area of " << A_rhombus << std::endl;  
	double l = 10.0;  
	double A_square = area::square(l);  
	std::cout << "A square of side " << l << " has an area of " << A_square  << std::endl;  
	return EXIT_SUCCESS;
}

函数的各种实现分布在不同的文件中,每个几何形状都有一个头文件和源文件。总共有4个头文件和5个源文件要编译:

.
├─ CMakeLists.txt
├─ compute-areas.cpp
├─ geometry_circle.cpp
├─ geometry_circle.hpp
├─ geometry_polygon.cpp
├─ geometry_polygon.hpp
├─ geometry_rhombus.cpp
├─ geometry_rhombus.hpp
├─ geometry_square.cpp
└─ geometry_square.hpp

我们不会为所有文件提供清单,读者可以参考 https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-01/recipe-08

二、具体实施

现在已经有了源代码,我们的目标是配置项目,并使用编译器标示进行实验:

  1. 设置CMake的最低版本:

    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
  2. 声明项目名称和语言:

    project(recipe-08 LANGUAGES CXX)
  3. 然后,打印当前编译器标志。CMake将对所有C++目标使用这些:

    message("C++ compiler flags: ${CMAKE_CXX_FLAGS}")
  4. 为目标准备了标志列表,其中一些将无法在Windows上使用:

    list(APPEND flags "-fPIC" "-Wall")
    if(NOT WIN32)  
    	list(APPEND flags "-Wextra" "-Wpedantic")
    endif()
  5. 添加了一个新的目标——geometry库,并列出它的源依赖关系:

    add_library(geometry  STATIC    geometry_circle.cpp    geometry_circle.hpp    geometry_polygon.cpp    geometry_polygon.hpp    geometry_rhombus.cpp    geometry_rhombus.hpp    geometry_square.cpp    geometry_square.hpp  )
  6. 为这个库目标设置了编译选项:

    target_compile_options(geometry  PRIVATE    ${flags}  )
  7. 然后,将生成compute-areas可执行文件作为一个目标:

    add_executable(compute-areas compute-areas.cpp)
  8. 还为可执行目标设置了编译选项:

    target_compile_options(compute-areas  PRIVATE    "-fPIC"  )
  9. 最后,将可执行文件链接到geometry库:

    target_link_libraries(compute-areas geometry)

三、如何工作

本例中,警告标志有-Wall-Wextra-Wpedantic,将这些标示添加到geometry目标的编译选项中; compute-areasgeometry目标都将使用-fPIC标志。编译选项可以添加三个级别的可见性:INTERFACEPUBLICPRIVATE

可见性的含义如下:

  • PRIVATE,编译选项会应用于给定的目标,不会传递给与目标相关的目标。我们的示例中, 即使compute-areas将链接到geometry库,compute-areas也不会继承geometry目标上设置的编译器选项。

  • INTERFACE,给定的编译选项将只应用于指定目标,并传递给与目标相关的目标。

  • PUBLIC,编译选项将应用于指定目标和使用它的目标。

目标属性的可见性CMake的核心,我们将在本书中经常讨论这个话题。以这种方式添加编译选项,不会影响全局CMake变量CMAKE_<LANG>_FLAGS_<CONFIG>,并能更细粒度控制在哪些目标上使用哪些选项。

我们如何验证,这些标志是否按照我们的意图正确使用呢?或者换句话说,如何确定项目在CMake构建时,实际使用了哪些编译标志?一种方法是,使用CMake将额外的参数传递给本地构建工具。本例中会设置环境变量VERBOSE=1

$ mkdir -p build$ cd build$ cmake ..$ cmake --build . -- VERBOSE=1... lots of output ...[ 14%] Building CXX object CMakeFiles/geometry.dir/geometry_circle.cpp.o/usr/bin/c++ -fPIC -Wall -Wextra -Wpedantic -o CMakeFiles/geometry.dir/geometry_circle.cpp.o -c /home/bast/tmp/cmake-cookbook/chapter-01/recipe-08/cxx-example/geometry_circle.cpp[ 28%] Building CXX object CMakeFiles/geometry.dir/geometry_polygon.cpp.o/usr/bin/c++ -fPIC -Wall -Wextra -Wpedantic -o CMakeFiles/geometry.dir/geometry_polygon.cpp.o -c /home/bast/tmp/cmake-cookbook/chapter-01/recipe-08/cxx-example/geometry_polygon.cpp[ 42%] Building CXX object CMakeFiles/geometry.dir/geometry_rhombus.cpp.o/usr/bin/c++ -fPIC -Wall -Wextra -Wpedantic -o CMakeFiles/geometry.dir/geometry_rhombus.cpp.o -c /home/bast/tmp/cmake-cookbook/chapter-01/recipe-08/cxx-example/geometry_rhombus.cpp[ 57%] Building CXX object CMakeFiles/geometry.dir/geometry_square.cpp.o/usr/bin/c++ -fPIC -Wall -Wextra -Wpedantic -o CMakeFiles/geometry.dir/geometry_square.cpp.o -c /home/bast/tmp/cmake-cookbook/chapter-01/recipe-08/cxx-example/geometry_square.cpp... more output ...[ 85%] Building CXX object CMakeFiles/compute-areas.dir/compute-areas.cpp.o/usr/bin/c++ -fPIC -o CMakeFiles/compute-areas.dir/compute-areas.cpp.o -c /home/bast/tmp/cmake-cookbook/chapter-01/recipe-08/cxx-example/compute-areas.cpp... more output ...

输出确认编译标志,确认指令设置正确。

控制编译器标志的第二种方法,不用对CMakeLists.txt进行修改。如果想在这个项目中修改geometrycompute-areas目标的编译器选项,可以使用CMake参数进行配置:

$ cmake -D CMAKE_CXX_FLAGS="-fno-exceptions -fno-rtti" ..

这个命令将编译项目,禁用异常和运行时类型标识(RTTI)。

也可以使用全局标志,可以使用CMakeLists.txt运行以下命令:

$ cmake -D CMAKE_CXX_FLAGS="-fno-exceptions -fno-rtti" ..

这将使用-fno-rtti - fpic - wall - Wextra - wpedantic配置geometry目标,同时使用-fno exception -fno-rtti - fpic配置compute-areas

NOTE:本书中,我们推荐为每个目标设置编译器标志。使用target_compile_options()不仅允许对编译选项进行细粒度控制,而且还可以更好地与CMake的更高级特性进行集成。

四、更多信息

大多数时候,编译器有特性标示。当前的例子只适用于GCCClang;其他供应商的编译器不确定是否会理解(如果不是全部)这些标志。如果项目是真正跨平台,那么这个问题就必须得到解决,有三种方法可以解决这个问题。

最典型的方法是将所需编译器标志列表附加到每个配置类型CMake变量CMAKE_<LANG>_FLAGS_<CONFIG>。标志确定设置为给定编译器有效的标志,因此将包含在if-endif子句中,用于检查CMAKE_<LANG>_COMPILER_ID变量,例如:

if(CMAKE_CXX_COMPILER_ID MATCHES GNU)
  list(APPEND CMAKE_CXX_FLAGS "-fno-rtti" "-fno-exceptions")
  list(APPEND CMAKE_CXX_FLAGS_DEBUG "-Wsuggest-final-types" "-Wsuggest-final-methods" "-Wsuggest-override")
  list(APPEND CMAKE_CXX_FLAGS_RELEASE "-O3" "-Wno-unused")
endif()
if(CMAKE_CXX_COMPILER_ID MATCHES Clang)
  list(APPEND CMAKE_CXX_FLAGS "-fno-rtti" "-fno-exceptions" "-Qunused-arguments" "-fcolor-diagnostics")
  list(APPEND CMAKE_CXX_FLAGS_DEBUG "-Wdocumentation")
  list(APPEND CMAKE_CXX_FLAGS_RELEASE "-O3" "-Wno-unused")
endif()

更细粒度的方法是,不修改CMAKE_<LANG>_FLAGS_<CONFIG>变量,而是定义特定的标志列表:

set(COMPILER_FLAGS)
set(COMPILER_FLAGS_DEBUG)
set(COMPILER_FLAGS_RELEASE)
if(CMAKE_CXX_COMPILER_ID MATCHES GNU)
  list(APPEND CXX_FLAGS "-fno-rtti""-fno-exceptions")
  list(APPEND CXX_FLAGS_DEBUG "-Wsuggest-final-types""-Wsuggest-final-methods""-Wsuggest-override")
  list(APPEND CXX_FLAGS_RELEASE "-O3""-Wno-unused")
endif()
if(CMAKE_CXX_COMPILER_ID MATCHES Clang)
  list(APPEND CXX_FLAGS "-fno-rtti""-fno-exceptions""-Qunused-arguments""-fcolor-diagnostics")
  list(APPEND CXX_FLAGS_DEBUG "-Wdocumentation")
  list(APPEND CXX_FLAGS_RELEASE "-O3""-Wno-unused")
endif()

稍后,使用生成器表达式来设置编译器标志的基础上,为每个配置和每个目标生成构建系统:

target_compile_option(compute-areas
  PRIVATE
    ${CXX_FLAGS}
"$<$<CONFIG:Debug>:${CXX_FLAGS_DEBUG}>"
"$<$<CONFIG:Release>:${CXX_FLAGS_RELEASE}>"
)

当前示例中展示了这两种方法,我们推荐后者(特定于项目的变量和target_compile_options)。

两种方法都有效,并在许多项目中得到广泛应用。不过,每种方式都有缺点。CMAKE_<LANG>_COMPILER_ID不能保证为所有编译器都定义。此外,一些标志可能会被弃用,或者在编译器的较晚版本中引入。与CMAKE_<LANG>_COMPILER_ID类似,CMAKE_<LANG>_COMPILER_VERSION变量不能保证为所有语言和供应商都提供定义。尽管检查这些变量的方式非常流行,但我们认为更健壮的替代方法是检查所需的标志集是否与给定的编译器一起工作,这样项目中实际上只使用有效的标志。结合特定于项目的变量、target_compile_options和生成器表达式,会让解决方案变得非常强大。我们将在第7章的第3节中展示,如何使用check-and-set模式。

九、为语言设定标准

编程语言有不同的标准,即提供改进的语言版本。启用新标准是通过设置适当的编译器标志来实现的。前面的示例中,我们已经展示了如何为每个目标或全局进行配置。3.1版本中,CMake引入了一个独立于平台和编译器的机制,用于为C++C设置语言标准:为目标设置<LANG>_STANDARD属性。

一、准备工作

对于下面的示例,需要一个符合C++14标准或更高版本的C++编译器。此示例代码定义了动物的多态,我们使用std::unique_ptr作为结构中的基类:

std::unique_ptr<Animal> cat = Cat("Simon");
std::unique_ptr<Animal> dog = Dog("Marlowe);

没有为各种子类型显式地使用构造函数,而是使用工厂方法的实现。工厂方法使用C++11的可变参数模板实现。它包含继承层次结构中每个对象的创建函数映射:

typedef std::function<std::unique_ptr<Animal>(conststd::string &)> CreateAnimal;

基于预先分配的标签来分派它们,创建对象:

std::unique_ptr<Animal> simon = farm.create("CAT", "Simon");
std::unique_ptr<Animal> marlowe = farm.create("DOG", "Marlowe");

标签和创建功能在工厂使用前已注册:

Factory<CreateAnimal> farm;
farm.subscribe("CAT", [](const std::string & n) { return std::make_unique<Cat>(n); });
farm.subscribe("DOG", [](const std::string & n) { return std::make_unique<Dog>(n); });

使用C++11 Lambda函数定义创建函数,使用std::make_unique来避免引入裸指针的操作。这个工厂函数是在C++14中引入。

NOTE:CMake的这一功能是在3.1版中添加的,并且还在更新。CMake的后续版本为C++标准的后续版本和不同的编译器,提供了越来越好的支持。我们建议您在文档页面上检查您选择的编译器是否受支持: https://cmake.org/cmake/help/latest/manual/cmake-compile-features.7.html#supported-compiler

二、具体实施

将逐步构建CMakeLists.txt,并展示如何设置语言标准(本例中是C++14):

  1. 声明最低要求的CMake版本,项目名称和语言:

    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    project(recipe-09 LANGUAGES CXX)
  2. 要求在Windows上导出所有库符号:

    set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
  3. 需要为库添加一个目标,这将编译源代码为一个动态库:

    add_library(animals  SHARED    Animal.cpp    Animal.hpp    Cat.cpp    Cat.hpp    Dog.cpp    Dog.hpp    Factory.hpp  )
  4. 现在,为目标设置了CXX_STANDARDCXX_EXTENSIONSCXX_STANDARD_REQUIRED属性。还设置了position_independent ent_code属性,以避免在使用一些编译器构建DSO时出现问题:

    set_target_properties(animals  PROPERTIES    CXX_STANDARD 14    CXX_EXTENSIONS OFF    CXX_STANDARD_REQUIRED ON    POSITION_INDEPENDENT_CODE 1)
  5. 然后,为”动物农场”的可执行文件添加一个新目标,并设置它的属性:

    add_executable(animal-farm animal-farm.cpp)
    set_target_properties(animal-farm  PROPERTIES    CXX_STANDARD 14    CXX_EXTENSIONS OFF    CXX_STANDARD_REQUIRED ON  )
  6. 最后,将可执行文件链接到库:

    target_link_libraries(animal-farm animals)
  7. 现在,来看看猫和狗都说了什么:

    $ mkdir -p build$ cd build
    
    
    $ cmake ..$ cmake --build .$ ./animal-farmI'm Simon the cat!I'm Marlowe the dog!

工作原理

步骤4和步骤5中,我们为动物和动物农场目标设置了一些属性:

  • CXX_STANDARD会设置我们想要的标准。

  • CXX_EXTENSIONS告诉CMake,只启用ISO C++标准的编译器标志,而不使用特定编译器的扩展。

  • CXX_STANDARD_REQUIRED指定所选标准的版本。如果这个版本不可用,CMake将停止配置并出现错误。当这个属性被设置为OFF时,CMake将寻找下一个标准的最新版本,直到一个合适的标志。这意味着,首先查找C++14,然后是C++11,然后是C++98。(译者注:目前会从C++20C++17开始查找)

NOTE:本书发布时,还没有Fortran_STANDARD可用,但是可以使用target_compile_options设置标准,可以参见: https://github.com/devcafe/cmake-cookbook/tree/v1.0/chapter-01/recipe-09

TIPS:如果语言标准是所有目标共享的全局属性,那么可以将CMAKE_<LANG>_STANDARDCMAKE_<LANG>_EXTENSIONSCMAKE_<LANG>_STANDARD_REQUIRED变量设置为相应的值。所有目标上的对应属性都将使用这些设置。

更多信息

通过引入编译特性,CMake对语言标准提供了更精细的控制。这些是语言标准引入的特性,比如C++11中的可变参数模板和Lambda表达式,以及C++14中的自动返回类型推断。可以使用target_compile_features()命令要求为特定的目标提供特定的特性,CMake将自动为标准设置正确的编译器标志。也可以让CMake为可选编译器特性,生成兼容头文件。

TIPS:我们建议阅读CMake在线文档,全面了解cmake-compile-features和如何处理编译特性和语言标准: https://cmake.org/cmake/help/latest/manual/cmake-compile-features.7.html

 

十、使用控制流

本章前面的示例中,已经使用过if-else-endif。CMake还提供了创建循环的语言工具:foreach endforeachwhile-endwhile。两者都可以与break结合使用,以便尽早从循环中跳出。本示例将展示如何使用foreach,来循环源文件列表。我们将应用这样的循环,在引入新目标的前提下,来为一组源文件进行优化降级。

一、准备工作

将重用第8节中的几何示例,目标是通过将一些源代码汇集到一个列表中,从而微调编译器的优化。

具体实施

下面是CMakeLists.txt中要的详细步骤:

  1. 与示例8中一样,指定了CMake的最低版本、项目名称和语言,并声明了几何库目标:

    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    project(recipe-10 LANGUAGES CXX)
    add_library(geometry  STATIC    geometry_circle.cpp    geometry_circle.hpp    geometry_polygon.cpp    geometry_polygon.hpp    geometry_rhombus.cpp    geometry_rhombus.hpp    geometry_square.cpp    geometry_square.hpp  )
  2. 使用-O3编译器优化级别编译库,对目标设置一个私有编译器选项:

    target_compile_options(geometry  PRIVATE      -O3  )
  3. 然后,生成一个源文件列表,以较低的优化选项进行编译:

    list(  APPEND sources_with_lower_optimization    geometry_circle.cpp    geometry_rhombus.cpp  )
  4. 循环这些源文件,将它们的优化级别调到-O2。使用它们的源文件属性完成:

    message(STATUS "Setting source properties using IN LISTS syntax:")
    foreach(_source IN LISTS sources_with_lower_optimization)				 
    	set_source_files_properties(${_source} PROPERTIES COMPILE_FLAGS -O2)
    	message(STATUS "Appending -O2 flag for ${_source}")
    endforeach()
  5. 为了确保设置属性,再次循环并在打印每个源文件的COMPILE_FLAGS属性:

    message(STATUS "Querying sources properties using plain syntax:")
    foreach(_source ${sources_with_lower_optimization})  
    	get_source_file_property(_flags ${_source} COMPILE_FLAGS)  
    	message(STATUS "Source ${_source} has the following extra COMPILE_FLAGS: ${_flags}")
    endforeach()
  6. 最后,添加compute-areas可执行目标,并将geometry库连接上去:

    add_executable(compute-areas compute-areas.cpp)
    target_link_libraries(compute-areas geometry)
  7. 验证在配置步骤中正确设置了标志:

    $ mkdir -p build
    $ cd build
    $ cmake ..
    ...
    -- Setting source properties using IN LISTS syntax:
    -- Appending -O2 flag for geometry_circle.cpp
    -- Appending -O2 flag for geometry_rhombus.cpp
    -- Querying sources properties using plain syntax:
    -- Source geometry_circle.cpp has the following extra COMPILE_FLAGS: -O2
    -- Source geometry_rhombus.cpp has the following extra COMPILE_FLAGS: -O2
  8. 最后,还使用VERBOSE=1检查构建步骤。将看到-O2标志添加在-O3标志之后,但是最后一个优化级别标志(在本例中是-O2)不同:

    $ cmake --build . -- VERBOSE=1

工作原理

foreach-endforeach语法可用于在变量列表上,表示重复特定任务。本示例中,使用它来操作、设置和获取项目中特定文件的编译器标志。CMake代码片段中引入了另外两个新命令:

  • set_source_files_properties(file PROPERTIES property value),它将属性设置为给定文件的传递值。与目标非常相似,文件在CMake中也有属性,允许对构建系统进行非常细粒度的控制。源文件的可用属性列表可以在这里找到: https://cmake.org/cmake/help/v3.5/manual/cmake-properties.7.html#source-file-properties

  • get_source_file_property(VAR file property),检索给定文件所需属性的值,并将其存储在CMakeVAR变量中。

NOTE:CMake中,列表是用分号分隔的字符串组。列表可以由listset命令创建。例如,set(var a b c d e)list(APPEND a b c d e)都创建了列表a;b;c;d;e

TIPS:为了对一组文件降低优化,将它们收集到一个单独的目标(库)中,并为这个目标显式地设置优化级别,而不是附加一个标志,这样可能会更简洁,不过在本示例中,我们的重点是foreach-endforeach

更多信息

foreach()的四种使用方式:

  • foreach(loop_var arg1 arg2 ...): 其中提供循环变量和显式项列表。当为sources_with_lower_optimization中的项打印编译器标志集时,使用此表单。注意,如果项目列表位于变量中,则必须显式展开它;也就是说,${sources_with_lower_optimization}必须作为参数传递。

  • 通过指定一个范围,可以对整数进行循环,例如:foreach(loop_var range total)foreach(loop_var range start stop [step])

  • 对列表值变量的循环,例如:foreach(loop_var IN LISTS [list1[...]]) 。参数解释为列表,其内容就会自动展开。

  • 对变量的循环,例如:foreach(loop_var IN ITEMS [item1 [...]])。参数的内容没有展开。

上一篇:Centos 7--pdf2htmlEX安装和配置


下一篇:安装和使用dlib库的一系列问题与解决办法