Bootstrap

“深入浅出”系列之C++:(6)CMake构建项目

在 C++ 开发里,每一个大型项目都像是一座复杂的迷宫,代码文件、库文件相互交织。曾经,构建这些项目就像是在黑暗中摸索前行,不同的平台、各异的编译器,让开发者们头痛不已。但后来,一盏明灯出现了CMake。今天,就让我们一起走进 CMake 的奇妙世界,去解密这个现代 C++ 项目构建工具的神奇之处,看看它是如何引领我们走出构建迷宫的。

一、CMake介绍

你或许听过好几种 Make 工具,例如 GNU Make ,QT 的 qmake ,微软的 MS nmake,BSD Make(pmake),Makepp,等等。这些 Make 工具遵循着不同的规范和标准,所执行的 Makefile 格式也千差万别。这样就带来了一个严峻的问题:如果软件想跨平台,必须要保证能够在不同平台编译。而如果使用上面的 Make 工具,就得为每一种标准写一次 Makefile ,这将是一件让人抓狂的工作。

CMake就是针对上面问题所设计的工具:它首先允许开发者编写一种平台无关的 CMakeList.txt 文件来定制整个编译流程,然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。从而做到“Write once, run everywhere”。显然,CMake 是一个比上述几种 make 更高级的编译配置工具。一些使用 CMake 作为项目架构系统的知名开源项目有 VTK、ITK、KDE、OpenCV、OSG 等。

1.CMake教程

在Linux平台下使用 CMake生成Makefile 并编译的流程如下:

  • 编写 CMake 配置文件 CMakeLists.txt 。

  • 执行命令 cmake PATH 或者 ccmake PATH 生成 Makefile 1 1ccmake 和 cmake 的区别在于前者提供了一个交互式的界面。。其中, PATH 是 CMakeLists.txt 所在的目录。

  • 使用 make 命令进行编译。

CMake教程提供了逐步指南,涵盖了CMake可以帮助解决的常见构建系统问题。了解示例项目中各个主题如何协同工作将非常有帮助。示例的教程文档和源代码可在CMake源代码树的Help/guide/tutorial目录中找到。每个步骤都有其自己的子目录,其中包含可以用作起点的代码。教程示例是渐进式的,因此每个步骤都为上一步提供了完整的解决方案。

(第1步)基本起点

最基本的项目是从源代码文件构建一个可执行文件。对于简单的项目,只需三行CMakeLists.txt文件。这是本教程的起点。在Step1目录中创建一个CMakeLists.txt文件,如下所示:

cmake_minimum_required(版本3.10)
# 设置项目名称project(教程)
# 添加可执行文件add_executable(教程tutorial.cxx)

请注意,此示例在CMakeLists.txt文件中使用小写的命令。CMake支持大写,小写和大小写混合的命令。Step1目录中提供了tutorial.cxx的源代码,可用于计算数字的平方根。

添加版本号和配置头文件

我们将添加的第一个功能是为我们的可执行文件和项目提供版本号。虽然我们可以仅在源代码中执行此操作,但是使用CMakeLists.txt可以提供更大的灵活性。

首先,修改CMakeLists.txt文件来设置版本号。

cmake_minimum_required(VERSION 3.10)

# set the project name and version
project(Tutorial VERSION 1.0)

###早期版本的写法
###		project(Tutorial)
###		set (Tutorial_VERSION_MAJOR 1)
###		set (Tutorial_VERSION_MINOR 0)

然后,配置一个头文件,将版本号传递给源代码:

configure_file(TutorialConfig.h.in TutorialConfig.h)

###早期版本的写法
###configure_file ("${PROJECT_SOURCE_DIR}/TutorialConfig.h.in" "${PROJECT_BINARY_DIR}/TutorialConfig.h")

由于配置的文件将被写入二进制树中,所以我们必须将该目录添加到搜索include文件的路径列表中。在CMakeLists.txt文件的末尾添加以下行:

#必须在add_excutable之后
target_include_directories(Tutorial PUBLIC  "${PROJECT_BINARY_DIR}")
###早期版本的写法:
###可以位于任意位置,一般放在add_excutable之前
###include_directories("${PROJECT_BINARY_DIR}")

使用您喜欢的编辑器在源码目录中创建http://TutorialConfig.h.in,内容如下:

// 教程的配置选项和设置
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@ 
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@

当CMake配置这个头文件时,@Tutorial_VERSION_MAJOR@和@Tutorial_VERSION_MINOR@的值将被替换。接下来,修改tutorial.cxx以包括配置的头文件TutorialConfig.h。最后,通过更新tutorial.cxx来打印出版本号,如下所示:

if (argc < 2) { 
    // 报告版本
    std::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "." 
              << Tutorial_VERSION_MINOR << std::endl; 
    std::cout << "用法:" << argv[0] << "数字" << std::endl;
    返回1;
  }

完整的CMakeLists.txt如下:

cmake_minimum_required(VERSION 3.10) 

#设置项目名称和版本
project(Tutorial VERSION 1.0) 

configure_file(TutorialConfig.h.in TutorialConfig.h) 

#添加可执行文件
add_executable(Tutorialtutorial.cxx) 

target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}" )

指定c++标准

接下来,通过在tutorial.cxx中用std::stod替换atof,将一些C ++ 11功能添加到我们的项目中。同时,删除#include <cstdlib>。

const double inputValue = std::stod(argv[1]);

我们需要在CMake代码中明确声明应使用正确的标志。在CMake中启用对特定C ++标准的支持的最简单方法是使用CMAKE_CXX_STANDARD变量。对于本教程,请将CMakeLists.txt文件中的CMAKE_CXX_STANDARD变量设置为11,并将CMAKE_CXX_STANDARD_REQUIRED设置为True:

cmake_minimum_required(VERSION 3.10) 
# 设置项目名称和版本
project(Tutorial VERSION 1.0) 
# 指定C++标准
集(CMAKE_CXX_STANDARD 11) 
set(CMAKE_CXX_STANDARD_REQUIRED True)

构建和测试

运行cmake或cmake-gui以配置项目,然后使用所选的构建工具进行构建。例如,从命令行我们可以导航到CMake源代码树的Help /guide/tutorial目录并运行以下命令:

mkdir Step1_build 
cd Step1_build 
cmake ../Step1 
cmake --build .

导航到构建教程的目录(可能是make目录或Debug或Release构建配置子目录),然后运行以下命令:

教程 4294967296
教程 10
教程

(第2步)添加库

现在,我们将添加一个库到我们的项目中。该库是我们自己的实现的用于计算数字的平方根的库。可执行文件可以使用此库,而不是使用编译器提供的标准平方根函数。

在本教程中,我们将库放入名为MathFunctions的子目录中。该目录已包含头文件MathFunctions.h和源文件mysqrt.cxx。源文件具有一个称为mysqrt的函数,该函数提供与编译器的sqrt函数类似的功能。

将以下一行CMakeLists.txt文件添加到MathFunctions目录中:

add_library(MathFunctions mysqrt.cxx)

为了使用新的库,我们将在顶层CMakeLists.txt文件中添加add_subdirectory调用,以便构建该库。我们将新的库添加到可执行文件,并将MathFunctions添加为include目录,以便可以找到mqsqrt.h头文件。顶级CMakeLists.txt文件的最后几行现在应如下所示:

# 添加 MathFunctions 库
add_subdirectory(MathFunctions) 

# 添加可执行文件
add_executable(Tutorialtutorial.cxx) 

#必须位于add_excutable之后
target_link_libraries(Tutorial PUBLIC MathFunctions) 

###早期版本的写法
###target_link_libraries(Tutorial MathFunctions) 

#add the binary树到包含文件的搜索路径,以便我们找到TutorialConfig.h 
target_include_directories(教程 PUBLIC "${PROJECT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/MathFunctions")

现在让我们将MathFunctions库设为可选。虽然对于本教程而言确实不需要这样做,但是对于大型项目来说,这是很常见的。第一步是向顶层CMakeLists.txt文件添加一个选项。

option(USE_MYMATH "使用教程提供的数学实现" ON) 
# 配置头文件以将一些 CMake 设置传递到源代码
configure_file(TutorialConfig.h.in TutorialConfig.h)

此选项将显示在CMake GUI和ccmake中,默认值ON,可由用户更改。此设置将存储在缓存中,因此用户不必每次在构建目录上运行CMake时设置该值。

下一个更改是使构建和链接MathFunctions库成为布尔选项。为此,我们将顶层CMakeLists.txt文件的结尾更改为如下所示:

if(USE_MYMATH) 
  add_subdirectory(MathFunctions) 
  list(APPEND EXTRA_LIBS MathFunctions) 
  list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions") 
endif() 

# 添加可执行文件
add_executable(Tutorialtutorial.cxx) 

#之后必须位于
add_executabletarget_link_libraries(Tutorial PUBLIC ) ${EXTRA_LIBS}) 

# 将二叉树添加到包含文件的搜索路径中,以便我们找到TutorialConfig.h 
target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}" ${EXTRA_INCLUDES}) 

###早期版本的写法
##if (USE_MYMATH) 
###include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions") 
###add_subdirectory (MathFunctions) 
###set(EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
###endif(USE_MYMATH) 
###include_directories("${PROJECT_BINARY_DIR}") 
###add_executable(教程tutorial.cxx) 
###target_link_libraries(教程${EXTRA_LIBS})

请注意,使用变量EXTRA_LIBS来收集任意可选库,以供以后链接到可执行文件中。变量EXTRA_INCLUDES类似地用于可选的头文件。当处理许多可选组件时,这是一种经典方法,我们将在下一步中介绍现代方法。

对源代码的相应更改非常简单。首先,如果需要,在tutorial.cxx中包含MathFunctions.h头文件:

#ifdef USE_MYMATH 
# 包含“MathFunctions.h” 
#endif

然后,在同一文件中,使USE_MYMATH控制使用哪个平方根函数:

#ifdef USE_MYMATH 
  const 双输出值 = mysqrt(inputValue); 
#else 
  const double outputValue = sqrt(inputValue); 
#endif

由于源代码现在需要USE_MYMATH,因此可以使用以下行将其添加到http://TutorialConfig.h.in中:

#cmake定义 USE_MYMATH

完整的CMakeLists.txt文件如下:

cmake_minimum_required(VERSION 3.5)                                                                                   
# 设置项目名称和版本
project(Tutorial VERSION 1.0) 
 
# 指定 C++ 标准
集(CMAKE_CXX_STANDARD 11) 
set(CMAKE_CXX_STANDARD_REQUIRED True) 
 
option(USE_MYMATH "Use coachprovided mathimplementation" ON) 
 
# 配置头文件将一些 CMake 设置传递给源代码
configure_file(TutorialConfig.h.in TutorialConfig.h) 
 
if(USE_MYMATH) 
    add_subdirectory(MathFunctions) 
    list(APPEND EXTRA_LIBS MathFunctions) 
    list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions") 
endif() 
 
# 添加可执行文件
add_executable(Tutorial教程.cxx) 

#必须位于add_executable之后
target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS})   

# 将二叉树添加到包含文件的搜索路径中,以便我们找到TutorialConfig.h 
target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR} “ ${EXTRA_INCLUDES})

后续待更新

;