在 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})
后续待更新