CMake 笔记 | [40] 超级构建模式

注意
本文最后更新于 2024-02-01,文中内容可能已过时。

一、导言

导言

每个项目都需要处理依赖关系,使用**CMake很容易查询这些依赖关系,是否存在于配置项目中。前面的笔记中,展示了如何找到安装在系统上的依赖项,到目前为止我们一直使用这种模式。但是,当不满足依赖关系,我们只能使配置失败,并向用户警告失败的原因。然而,使用CMake可以组织我们的项目,如果在系统上找不到依赖项,就可以自动获取和构建依赖项。后续的几篇笔记将介绍和分析ExternalProject.cmakeFetchContent.cmake标准模块,及在超级构建模式中的使用。前者允许在构建时检索项目的依赖项,后者允许我们在配置时检索依赖项(CMake3.11版本后添加)。使用超级构建模式,我们可以利用CMake作为包管理器:相同的项目中,将以相同的方式处理依赖项,无论依赖项在系统上是已经可用,还是需要重新构建。**

首先通过一个简单示例,介绍超级构建模式。我们将展示如何使用**ExternalProject_Add命令来构建一个的hello_world程序。**

二、项目结构

1
2
3
4
5
.
├── CMakeLists.txt
└── src
      ├── CMakeLists.txt
      └── hello-world.cpp

项目结构:

https://gitee.com/jiangli01/tutorials/tree/master/cmake-tutorial/chapter8/01

三、相关源码

src/CMakeLists.txt

1
2
3
4
5
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)

project(example_core LANGUAGES CXX)

add_executable(hello-world hello_world.cpp)

src/hello_world.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <cstdlib>
#include <iostream>
#include <string>
std::string say_hello() {
  return std::string("Hello, CMake superbuild world!");
}

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

CMakeLists.txt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)

project(example LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set_property(DIRECTORY PROPERTY EP_BASE ${CMAKE_BINARY_DIR}/subprojects)

include(ExternalProject)

ExternalProject_Add(${PROJECT_NAME}_core
  SOURCE_DIR
    ${CMAKE_CURRENT_LIST_DIR}/src
  CMAKE_ARGS
    -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
    -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}
    -DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS}
    -DCMAKE_CXX_STANDARD_REQUIRED=${CMAKE_CXX_STANDARD_REQUIRED}
  CMAKE_CACHE_ARGS
    -DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS}
  BUILD_ALWAYS
    1
  INSTALL_COMMAND
    ""
  )
引用
1
set_property(DIRECTORY PROPERTY EP_BASE ${CMAKE_BINARY_DIR}/subprojects)

为当前目录和底层目录设置EP_BASE目录属性。

引用
1
include(ExternalProject)

包括ExternalProject.cmake标准模块。该模块提供了ExternalProject_Add函数。

引用
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
ExternalProject_Add(${PROJECT_NAME}_core
  SOURCE_DIR
    ${CMAKE_CURRENT_LIST_DIR}/src
  CMAKE_ARGS
    -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
    -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}
    -DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS}
    -DCMAKE_CXX_STANDARD_REQUIRED=${CMAKE_CXX_STANDARD_REQUIRED}
  CMAKE_CACHE_ARGS
    -DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS}
  BUILD_ALWAYS
    1
  INSTALL_COMMAND
    ""
  )

Hello, World源代码通过调用ExternalProject_Add函数作为外部项目添加的。外部项目的名称为example_core

ExternalProject_Add命令可用于添加第三方源。本篇通过将自己的项目,分为不同CMake项目的集合管理。本例中,主CMakeLists.txt和子CMakeLists.txt都声明了一个CMake项目,它们都使用了project命令。

ExternalProject_Add有许多选项,可用于外部项目的配置和编译等所有方面。这些选择可以分为以下几类:

  • Directory:用于调优源码的结构,并为外部项目构建目录。本篇,我们使用SOURCE_DIR选项让CMake知道源文件在${CMAKE_CURRENT_LIST_DIR}/src文件夹中。用于构建项目和存储临时文件的目录,也可以在此类选项或目录属性中指定。通过设置EP_BASE目录属性,CMake将按照以下布局为各个子项目设置所有目录:

    1
    2
    3
    4
    5
    6
    
    TMP_DIR = <EP_BASE>/tmp/<name>
    STAMP_DIR = <EP_BASE>/Stamp/<name>
    DOWNLOAD_DIR = <EP_BASE>/Download/<name>
    SOURCE_DIR = <EP_BASE>/Source/<name>
    BINARY_DIR = <EP_BASE>/Build/<name>
    INSTALL_DIR = <EP_BASE>/Install/<name>
  • Download:外部项目的代码可能需要从在线存储库或资源处下载。

  • Update和Patch:可用于定义如何更新外部项目的源代码或如何应用补丁。

  • Configure:默认情况下,CMake会假定外部项目是使用CMake配置的。如下所示,我们并不局限于这种情况。如果外部项目是CMake项目,ExternalProject_Add将调用CMake可执行文件,并传递选项。对于本篇示例,我们通过CMAKE_ARGSCMAKE_CACHE_ARGS选项传递配置参数。前者作为命令行参数直接传递,而后者通过CMake脚本文件传递。实际,脚本文件位于build/subprojects/tmp/example_core/example_core- cache-.cmake。然后,配置如以下所示:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    loading initial cache file /home/jiangli/repo/tutorials/cmake-tutorial/chapter8/01/build/subprojects/tmp/example_core/example_core-cache-.cmake
    -- The CXX compiler identification is GNU 9.4.0
    -- Check for working CXX compiler: /usr/bin/c++
    -- Check for working CXX compiler: /usr/bin/c++ -- works
    -- Detecting CXX compiler ABI info
    -- Detecting CXX compiler ABI info - done
    -- Detecting CXX compile features
    -- Detecting CXX compile features - done
    -- Configuring done
    -- Generating done
    -- Build files have been written to: /home/jiangli/repo/tutorials/cmake-tutorial/chapter8/01/build/subprojects/Build/example_core
  • Build:可用于调整外部项目的实际编译。我们使用BUILD_ALWAYS选项确保外部项目总会重新构建。

  • Install:这些选项用于配置应该如何安装外部项目。我们将INSTALL_COMMAND保留为空。

  • Test:为基于源代码构建的软件运行测试。ExternalProject_Add的这类选项可以用于此目的。我们的没有使用这些选项,因为Hello, World示例没有任何测试。

四、结果展示

1
2
3
$ mkdir -p build
$ cmake ..
$ cmake --build .

构建目录的结构稍微复杂一些,subprojects文件夹的内容如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
build/subprojects/
├── Build
│    └── example_core
│        ├── CMakeCache.txt
│        ├── CMakeFiles
│        ├── cmake_install.cmake
│        ├── hello-world
│        └── Makefile
├── Download
│    └── example_core
├── Install
│    └── example_core
├── Stamp
│    └── exampleq_core
│        ├── example_core-configure
│        ├── example_core-done
│        ├── example_core-download
│        ├── example_core-install
│        ├── example_core-mkdir
│        ├── example_core-patch
│        └── example_core-update
└── tmp
    └── example_core
        ├── example_core-cache-.cmake
        ├── example_core-cfgcmd.txt
        └── example_core-cfgcmd.txt.in

补充内容

ExternalProject.cmake定义了ExternalProject_Get_Property命令,该命令对于检索外部项目的属性非常有用。外部项目的属性是在首次调用ExternalProject_Add命令时设置的。例如,在配置example_core时,检索要传递给CMake的参数可以通过以下方法实现:

1
2
ExternalProject_Get_Property(${PROJECT_NAME}_core CMAKE_ARGS)
message(STATUS "CMAKE_ARGS of ${PROJECT_NAME}_core ${CMAKE_ARGS}")

ExternalProject.cmake模块定义了以下附加命令:

  • ExternalProject_Add_Step: 当添加了外部项目,此命令允许将附加的命令作为自定义步骤锁定在其上。
  • ExternalProject_Add_StepTargets:允许将外部项目中的步骤(例如:构建和测试步骤)定义为单独的目标。这意味着可以从完整的外部项目中单独触发这些步骤,并允许对项目中的复杂依赖项,进行细粒度控制。
  • ExternalProject_Add_StepDependencies:外部项目的步骤有时可能依赖于外部目标,而这个命令的设计目的就是处理这些情况。
Buy me a coffee~
支付宝
微信
0%