CMake教程
CMake是一个构建系统,可以用于构建跨平台的C和C++项目。其它的构建系统有makefile、MSBuild以及qmake。
使用CMake作为C或C++的构建系统已经变得越来越流行了,例如:
- 在Jetbrains的C++ 编程 - 2020 开发人员生态系统信息图调查中,CMake是最流行的构建系统。
- CMake是CLion的默认构建系统。
- CMake是Qt6的默认构建系统,之前是qmake。
- Visual Studio IDE将对CMake的支持添加到2020年规划的路线图中。
- 许多C和C++的第三方库使用CMake作为构建系统。
在使用一个第三方库时,例如Libevent。无论是在Linux还是Windows中,我们只需要进入它的源代码目录,然后在命令行中执行:
mkdir build
cd build
cmake ..
cmake --build .
cmake --install .
之后在我们自己的项目中,只需要在CMakeLists.txt中添加:
find_package(Libevent REQUIRED core)
target_link_libraries(你的可执行目标 libevent::core)
即可使用该库。
对于一部分人来说,了解上面这些就足够了。但如果想进一步地学习CMake构建系统,可以查看CMake官方文档或CMake Wiki。
下面是CMake官方文档中的一篇教程,你可以跟着这些步骤一步一步地操作,并学习CMake构建系统。同时,我们还能感受到C或C++为了跨平台而付出的努力。
文章目录
引入
本教程是循序渐进的,它包括CMake能解决的常见的构建系统问题。在一个示例项目中查看不同的主题之间如何协同工作,对于理解CMake非常有帮助。该教程中所有案例的源代码可以在CMake源码下的Help/guide/tutorial
目录中找到(GitHub链接)。每一步都有独立的子目录,其中包含了可能会用到的代码。教程示例是渐进式的,因此每个步骤都为上一步提供了完整的解决方案。
第1步 开始
最基础的项目是将源代码构建成可执行文件。对于一个简单的项目,只需要一个3行的CMakeLists.txt
文件即可。在Step1
目录下创建一个CMakeLists.txt
文件:
cmake_minimum_required(VERSION 3.10)
# 设置项目名
project(Tutorial)
# 添加可执行目标
add_executable(Tutorial tutorial.cxx)
注意:CMake支持大写、小写或大小写混合命令。但在这些例子中,每个CMakeLists.txt
文件都使用小写命令。tutorial.cxx
中的源代码放在Step1
目录下,它用于计算一个数的平方根。
添加版本号并且配置头文件
我们将添加的第一个特性是:为我们的可执行文件和项目提供一个版本号。尽管我们可以在源代码中做到这一点,但是使用CMakeLists.txt
会更加灵活。
首先,修改CMakeLists.txt
文件,使用project()
命令设置项目名和版本号:
cmake_minimum_required(VERSION 3.10)
# 设置项目名和版本号
project(Tutorial VERSION 1.0)
然后,配置一个头文件并将版本号传递给源代码:
configure_file(TutorialConfig.h.in TutorialConfig.h)
因为配置的文件将被写到binary tree目录下(CMakeCache.txt
所在的目录,即build目录),所以我们必须将那个目录添加到include搜索路径列表中。将下面这行添加到CMakeLists.txt
文件的末尾:
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
)
使用你最喜欢的编辑器,在源代码目录(Step1
目录)中创建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 << "Usage: " << argv[0] << " number" << std::endl;
return 1;
}
指定C++标准
接下来,给我们的项目添加一些C++11特性,在tutorial.cxx
中用std::stod
替换atof
。同时,删除#include<cstdlib>
。
const double inputValue = std::stod(argv[1]);
在CMake中指定C++标准最简单的方法是使用CMAKE_CXX_STANDARD
变量。将CMakeLists.txt
文件中的CMAKE_CXX_STANDARD
变量设置为11,并将CMAKE_CXX_STANDARD_REQUIRED
设置为True。确保将CMAKE_CXX_STANDARD
声明添加到add_executable
的上面:
cmake_minimum_required(VERSION 3.10)
# 设置项目名和版本号
project(Tutorial VERSION 1.0)
# 指定c++标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
构建和测试
运行 cmake
或 cmake-gui
来配置(configure)项目,然后使用你所选的构建工具构建(build)它。
例如,在命令行中,我们应该导航到CMake源码中的Help/guide/tutorial
目录中,然后创建构建目录:
mkdir Step1_build
接下来,导航到构建目录并运行CMake,让CMake配置项目并生成一个本地构建系统:
cd Step1_build
cmake ../Step1
然后调用那个构建系统来编译、链接该项目:
cmake --build .
最后,尝试使用新生成可执行文件Tutorial
,你可以运行以下命令:
Tutorial 4294967296
Tutorial 10
Tutorial
第2步 添加一个库
现在,我们将添加一个库到项目中。这个库包含了我们自己实现的计算平方根的算法。之后的可执行文件可以使用这个库而不是编译器提供的标准平方根函数。
我们把这个库放进一个名为MathFunctions
的子目录中,这个目录已经包含了一个头文件MathFunctions.h
和一个源文件mysqrt.cxx
。这个源文件有一个名为mysqrt
的函数,它提供了和标准库中的sqrt
相似的功能。
在MathFunctions
目录中创建CMakeLists.txt
文件,并将下面这行添加到MathFunctions
目录中的CMakeLists.txt
文件中:
add_library(MathFunctions mysqrt.cxx)
要利用这个库,我们需要在顶级目录(Step2
目录)下的CMakeLists.txt
文件中添加一个add_subdirectory()
调用,以便该库可以被构建。我们在可执行目标中添加这个新的库,然后添加MathFunctions
目录作为include目录以便头文件mqsqrt.h
能够被找到。根目录(Step2
目录)下CMakeLists.txt
文件的最后几行现在应该如下所示:
# 添加MathFunctions库
add_subdirectory(MathFunctions)
# 添加可执行目标
add_executable(Tutorial tutorial.cxx)
target_link_libraries(Tutorial PUBLIC MathFunctions)
# 添加binary tree目录到include文件的搜索路径
# 以便我们能找到TutorialConfig.h
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
"${PROJECT_SOURCE_DIR}/MathFunctions"
)
现在,让我们将MathFunctions库设置为可选。虽然对于该教程来说确实不需要这样做,但是对于更大的项目来说,这是常见的事情。第一步是向顶级目录下的CMakeLists.txt
文件添加一个选项。
option(USE_MYMATH "Use tutorial provided math implementation" 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(Tutorial tutorial.cxx)
target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS})
# 添加binary tree目录到include文件的搜索路径
# 以便我们能找到TutorialConfig.h
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
${EXTRA_INCLUDES}
)
注意:使用变量EXTRA_LIBS
收集所有可选库,以便之后链接到可执行文件中。类似地,变量EXTRA_INCLUDES
表示可选的头文件。这是处理多个可选组件的一种传统方式,下一步我们将介绍现代方式。
对源代码的更改非常简单。首先,在tutorial.cxx
中,包含头文件MathFunctions.h
,如果我们需要的话:
#ifdef USE_MYMATH
# include "MathFunctions.h"
#endif
然后,在同一个文件中,通过USE_MYMATH
控制要使用哪个平方根函数:
#ifdef USE_MYMATH
const double outputValue = mysqrt(inputValue);
#else
const double outputValue = sqrt(inputValue);
#endif
因为源代码现在需要USE_MYMATH
,所以我们将下面这行添加到