CMake官方教程
CMake教程提供了逐步指南,涵盖了CMake可以解决的常见构建系统问题。了解示例项目中各个主题如何协同工作将非常有帮助。教程文档和示例的源代码可以在CMake源代码树的Help/guide/tutorial
目录中找到 。每个步骤都有其自己的子目录,其中包含可以用作起点的代码。教程示例是渐进式的,因此每个步骤都为上一步提供了完整的解决方案。
基本起点(步骤1)
最基本的项目是从源代码文件构建的可执行文件。对于简单项目,只需要三行CMakeLists.txt
文件。这将是本教程的起点。CMakeLists.txt
在Step1
目录中创建一个 文件,如下所示:
cmake_minimum_required(VERSION 3.10)
# set the project name
project(Tutorial)
# add the executable
add_executable(Tutorial tutorial.cxx)
请注意,此示例在CMakeLists.txt
文件中使用小写命令。CMake支持大写,小写和大小写混合命令。Step1
目录中提供的源代码tutorial.cxx
可用于计算数字的平方根。
添加一个版本号和配置的头文件
我们将添加的第一个功能是为我们的可执行文件和项目提供版本号。虽然我们可以在源代码中专门执行此操作,但是使用 CMakeLists.txt
可以提供更大的灵活性。
首先,修改CMakeLists.txt
文件以使用project()
命令设置项目名称和版本号。
cmake_minimum_required(VERSION 3.10)
# set the project name and version
project(Tutorial VERSION 1.0)
然后,配置头文件以将版本号传递给源代码:
configure_file(TutorialConfig.h.in TutorialConfig.h)
由于配置的文件将被写入二进制树,因此我们必须将该目录添加到路径列表中以搜索包含文件。将以下行添加到CMakeLists.txt
文件的末尾:
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
)
使用您喜欢的编辑器,在源目录TutorialConfig.h.in
中创建以下内容:
// the configured options and settings for Tutorial
#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) {
// report version
std::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."
<< Tutorial_VERSION_MINOR << std::endl;
std::cout << "Usage: " << argv[0] << " number" << std::endl;
return 1;
}
指定C ++标准
接下来,我们用 std::stod
in 替换atof
为我们的项目添加一些C ++ 11功能tutorial.cxx
。同时,删除 #include <cstdlib>
const double inputValue = std::stod(argv[1]);
我们将需要在CMake代码中明确声明应使用正确的标志。在CMake中启用对特定C ++标准的支持的最简单方法是使用CMAKE_CXX_STANDARD
变量。对于本教程,请设置CMAKE_CXX_STANDARD
将CMakeLists.txt
文件中的变量设置 为11并CMAKE_CXX_STANDARD_REQUIRED
改为True:
cmake_minimum_required(VERSION 3.10)
# set the project name and version
project(Tutorial VERSION 1.0)
# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
构建和测试
运行 cmake
可执行文件或 cmake-gui
配置项目,然后使用您选择的构建工具进行构建。
例如,从命令行我们可以导航到Help/guide/tutorial
CMake源代码树的 目录并运行以下命令:
mkdir Step1_build
cd Step1_build
cmake ../Step1
cmake --build .
导航到构建Tutorial的目录(可能是make目录或Debug或Release构建配置子目录),然后运行以下命令:
Tutorial 4294967296
Tutorial 10
Tutorial
添加一个库(第二步)
现在,我们将库添加到我们的项目中。该库将包含我们自己的实现,用于计算数字的平方根。然后可执行文件可以使用此库而不是编译器提供的标准平方根函数。
在本教程中,我们将库放入名为的子目录中MathFunctions
。该目录已经包含一个头文件 MathFunctions.h
和一个源文件mysqrt.cxx
。源文件具有一个称为的mysqrt
功能,该功能提供与编译器sqrt
功能相似的功能。
将以下一个行CMakeLists.txt
文件添加到MathFunctions
目录:
add_library(MathFunctions mysqrt.cxx)
为了利用新库,我们将添加一个 add_subdirectory()
调用顶级CMakeLists.txt
文件,以便构建库。我们将新库添加到可执行文件,并添加MathFunctions
为包含目录,以便mqsqrt.h
可以找到头文件。现在,顶级CMakeLists.txt
文件的最后几行应如下所示:
# add the MathFunctions library
add_subdirectory(MathFunctions)
# add the executable
add_executable(Tutorial tutorial.cxx)
target_link_libraries(Tutorial PUBLIC MathFunctions)
# add the binary tree to the search path for include files
# so that we will find 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)
# configure a header file to pass some of the CMake settings
# to the source code
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 the executable
add_executable(Tutorial tutorial.cxx)
target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS})
# add the binary tree to the search path for include files
# so that we will find 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
我们可以TutorialConfig.h.in
使用以下行将其添加到 其中:
#cmakedefine USE_MYMATH
练习:为什么TutorialConfig.h.in
在选项之后进行配置很重要USE_MYMATH
?如果我们将两者倒置会怎样?
跑过 cmake
可执行文件或 cmake-gui
配置项目,然后使用您选择的构建工具进行构建。然后运行构建的Tutorial可执行文件。
使用 ccmake
可执行文件或 cmake-gui
更新的值USE_MYMATH
。重新生成并再次运行本教程。sqrt或mysqrt哪个函数可提供更好的结果?
添加库的使用要求(步骤3)
使用要求可以更好地控制库或可执行文件的链接并包含行,同时还可以更好地控制CMake内部目标的传递属性。利用使用需求的主要命令是:
让我们从添加库(第2步)中重构代码,以使用使用率需求的现代CMake方法。我们首先声明,链接到MathFunctions的任何人都需要包括当前源目录,而MathFunctions本身不需要。因此这可能成为INTERFACE
使用要求。
记住INTERFACE
是指消费者需要的东西,而生产者则不需要。将以下行添加到的末尾 MathFunctions/CMakeLists.txt
:
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
既然我们已经指定了MathFunction的使用要求,我们就可以安全地EXTRA_INCLUDES
从顶级CMakeLists.txt
(这里)删除对变量 的使用:
if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
endif()
和这里:
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
)
完成后,运行 cmake
可执行文件或 cmake-gui
配置项目,然后使用您选择的构建工具或通过构建目录进行构建。cmake --build .
安装和测试(步骤4)
现在,我们可以开始为项目添加安装规则和测试支持。
安装规则
安装规则非常简单:对于MathFunctions,我们要安装库和头文件,对于应用程序,我们要安装可执行文件和已配置的头文件。
因此,MathFunctions/CMakeLists.txt
我们最后添加:
install(TARGETS MathFunctions DESTINATION lib)
install(FILES MathFunctions.h DESTINATION include)
并在顶层末尾CMakeLists.txt
添加:
install(TARGETS Tutorial DESTINATION bin)
install(FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
DESTINATION include
)
这是创建本教程的基本本地安装所需的全部。
跑过 cmake
可执行文件或 cmake-gui
配置项目,然后使用您选择的构建工具进行构建。使用以下install
选项运行安装步骤cmake
命令(在3.15中引入,较早版本的CMake必须使用),或者从IDE 构建目标。这将安装适当的头文件,库和可执行文件。make install``INSTALL
CMake变量 CMAKE_INSTALL_PREFIX
用于确定文件的安装根目录。如果使用定制安装目录,则可以通过参数指定。对于多配置工具,请使用参数指定配置。cmake --install``--prefix``--config
验证已安装的教程正在运行。
测试支持
接下来让我们测试我们的应用程序。在顶级CMakeLists.txt
文件的末尾,我们可以启用测试,然后添加一些基本测试以验证应用程序是否正常运行。
enable_testing()
# does the application run
add_test(NAME Runs COMMAND Tutorial 25)
# does the usage message work?
add_test(NAME Usage COMMAND Tutorial)
set_tests_properties(Usage
PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number"
)
# define a function to simplify adding tests
function(do_test target arg result)
add_test(NAME Comp${arg} COMMAND ${target} ${arg})
set_tests_properties(Comp${arg}
PROPERTIES PASS_REGULAR_EXPRESSION ${result}
)
endfunction(do_test)
# do a bunch of result based tests
do_test(Tutorial 4 "4 is 2")
do_test(Tutorial 9 "9 is 3")
do_test(Tutorial 5 "5 is 2.236")
do_test(Tutorial 7 "7 is 2.645")
do_test(Tutorial 25 "25 is 5")
do_test(Tutorial -25 "-25 is [-nan|nan|0]")
do_test(Tutorial 0.0001 "0.0001 is 0.01")
第一个测试只是验证应用程序正在运行,没有段错误或其他崩溃,并且返回值为零。这是CTest测试的基本形式。
下一个测试利用 PASS_REGULAR_EXPRESSION
测试属性,以验证测试的输出包含某些字符串。在这种情况下,请验证在提供了错误数量的参数时是否打印了用法消息。
最后,我们有一个名为的函数do_test
,用于运行应用程序,并验证所计算的平方根对于给定输入是否正确。对于的每次调用do_test
,都会根据传递的参数将另一个测试与名称,输入和预期结果一起添加到项目中。
重建应用程序,然后CD到二进制目录并运行 ctest
可执行文件:和。对于多配置生成器(例如Visual Studio),必须指定配置类型。例如,要在“调试”模式下运行测试,请使用 build目录(而非Debug子目录!)。或者,从IDE 构建目标。ctest -N``ctest -VV``ctest -C Debug -VV``RUN_TESTS
添加系统自检(步骤5)
让我们考虑将一些代码添加到我们的项目中,这取决于目标平台可能不具备的功能。对于此示例,我们将添加一些代码,具体取决于目标平台是否具有log
和exp
功能。当然,几乎每个平台都具有这些功能,但对于本教程而言,它们并不常见。
如果平台具有log
,exp
则我们将使用它们来计算函数中的平方根mysqrt
。我们首先使用以下命令测试这些功能的可用性CheckSymbolExists
顶层模块 CMakeLists.txt
。我们将在中使用新的定义 TutorialConfig.h.in
,因此请确保在配置该文件之前进行设置。
include(CheckSymbolExists)
set(CMAKE_REQUIRED_LIBRARIES "m")
check_symbol_exists(log "math.h" HAVE_LOG)
check_symbol_exists(exp "math.h" HAVE_EXP)
现在让我们添加这些定义,TutorialConfig.h.in
以便我们可以从中使用它们mysqrt.cxx
:
// does the platform provide exp and log functions?
#cmakedefine HAVE_LOG
#cmakedefine HAVE_EXP
如果log
和exp
在系统上可用,那么我们将使用它们来计算函数中的平方根mysqrt
。将以下代码添加到中的mysqrt
函数中MathFunctions/mysqrt.cxx
(#endif
返回结果前不要忘了 !):
#if defined(HAVE_LOG) && defined(HAVE_EXP)
double result = exp(log(x) * 0.5);
std::cout << "Computing sqrt of " << x << " to be " << result
<< " using log and exp" << std::endl;
#else
double result = x;
我们还需要修改mysqrt.cxx
为包括cmath
。
#include <cmath>
运行 cmake
可执行文件或 cmake-gui
配置项目,然后使用所选的构建工具进行构建,并运行Tutorial可执行文件。
您会注意到我们没有使用log
和exp
,即使我们认为它们应该可用。我们应该很快认识到,我们都忘记了,包括TutorialConfig.h
在mysqrt.cxx
。
我们还需要更新,MathFunctions/CMakeLists.txt
以便mysqrt.cxx
知道此文件的位置:
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE ${CMAKE_BINARY_DIR}
)
进行此更新之后,继续并再次构建项目并运行已构建的Tutorial可执行文件。如果log
和exp
仍未使用,请TutorialConfig.h
从构建目录打开生成的文件。也许它们在当前系统上不可用?
sqrt或mysqrt哪个函数现在可以提供更好的结果?
指定编译定义
除了in之外,还有更好的地方供我们保存HAVE_LOG
和HAVE_EXP
值TutorialConfig.h
吗?让我们尝试使用 target_compile_definitions()
。
首先,从中删除定义TutorialConfig.h.in
。我们不再需要包含TutorialConfig.h
from mysqrt.cxx
或多余的include in MathFunctions/CMakeLists.txt
。
接下来,我们可以将check HAVE_LOG
和HAVE_EXP
移至 MathFunctions/CMakeLists.txt
,然后将这些值指定为PRIVATE
编译定义。
include(CheckSymbolExists)
set(CMAKE_REQUIRED_LIBRARIES "m")
check_symbol_exists(log "math.h" HAVE_LOG)
check_symbol_exists(exp "math.h" HAVE_EXP)
if(HAVE_LOG AND HAVE_EXP)
target_compile_definitions(MathFunctions
PRIVATE "HAVE_LOG" "HAVE_EXP")
endif()
完成这些更新后,继续并重新构建项目。运行内置的Tutorial可执行文件,并验证结果与本步骤前面的内容相同。
添加自定义命令和生成的文件(步骤6)
假设,出于本教程的目的,我们决定我们不想使用平台log
和exp
函数,而是想生成一个在mysqrt
函数中使用的预计算值表。在本节中,我们将在构建过程中创建表,然后将该表编译到我们的应用程序中。
首先,让我们删除中对log
和exp
功能 的检查MathFunctions/CMakeLists.txt
。然后取出支票HAVE_LOG
和 HAVE_EXP
从mysqrt.cxx
。同时,我们可以删除 。#include <cmath>
在MathFunctions
子目录中,提供了一个名为的新源文件 MakeTable.cxx
来生成表。
查看完文件后,我们可以看到该表是作为有效的C ++代码生成的,并且输出文件名作为参数传入。
下一步是将适当的命令添加到 MathFunctions/CMakeLists.txt
文件中以构建MakeTable可执行文件,然后在构建过程中运行它。需要一些命令来完成此操作。
首先,在的顶部MathFunctions/CMakeLists.txt
,MakeTable
添加的可执行文件, 就像添加任何其他可执行文件一样。
add_executable(MakeTable MakeTable.cxx)
然后,我们添加一个自定义命令,该命令指定如何Table.h
通过运行MakeTable 进行生产。
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
DEPENDS MakeTable
)
接下来,我们必须让CMake知道这mysqrt.cxx
取决于生成的文件Table.h
。这是通过将生成的添加Table.h
到库MathFunctions的源列表中来完成的。
add_library(MathFunctions
mysqrt.cxx
${CMAKE_CURRENT_BINARY_DIR}/Table.h
)
我们还必须将当前的二进制目录添加到包含目录的列表中,以便Table.h
可以找到和包含它mysqrt.cxx
。
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE ${CMAKE_CURRENT_BINARY_DIR}
)
现在让我们使用生成的表。首先,修改mysqrt.cxx
为 Table.h
。接下来,我们可以重写mysqrt函数以使用该表:
double mysqrt(double x)
{
if (x <= 0) {
return 0;
}
// use the table to help find an initial value
double result = x;
if (x >= 1 && x < 10) {
std::cout << "Use the table to help find an initial value " << std::endl;
result = sqrtTable[static_cast<int>(x)];
}
// do ten iterations
for (int i = 0; i < 10; ++i) {
if (result <= 0) {
result = 0.1;
}
double delta = x - (result * result);
result = result + 0.5 * delta / result;
std::cout << "Computing sqrt of " << x << " to be " << result << std::endl;
}
return result;
}
跑过 cmake
可执行文件或 cmake-gui
配置项目,然后使用您选择的构建工具进行构建。
构建此项目时,它将首先构建MakeTable
可执行文件。然后它将运行MakeTable
产生Table.h
。最后,它将进行编译mysqrt.cxx
,包括Table.h
生成MathFunctions库。
运行Tutorial可执行文件,并验证它是否正在使用该表。
构建安装程序(步骤7)
接下来,假设我们要将项目分发给其他人,以便他们可以使用它。我们希望在各种平台上提供二进制和源代码分发。这与我们之前在“ 安装和测试”(第4步)中进行的安装有些不同,在安装和测试中,我们正在安装根据源代码构建的二进制文件。在此示例中,我们将构建支持二进制安装和软件包管理功能的安装软件包。为此,我们将使用CPack创建平台特定的安装程序。具体来说,我们需要在顶级CMakeLists.txt
文件的底部添加几行。
include(InstallRequiredSystemLibraries)
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set(CPACK_PACKAGE_VERSION_MAJOR "${Tutorial_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${Tutorial_VERSION_MINOR}")
include(CPack)
这就是全部。我们首先包括 InstallRequiredSystemLibraries
。该模块将包括项目当前平台所需的任何运行时库。接下来,我们将一些CPack变量设置为存储该项目的许可证和版本信息的位置。版本信息是在本教程的前面设置的,并且license.txt
已包含在此步骤的顶级源目录中。
最后,我们将 CPack module
它将使用这些变量和当前系统的其他一些属性来设置安装程序。
下一步是按照通常的方式构建项目,然后运行 cpack
可执行文件。要构建二进制发行版,请从二进制目录运行:
<pre style="overflow: auto hidden; padding: 5px; background-color: rgb(238, 238, 238); color: rgb(51, 51, 51); line-height: 15.6px; border-top: 1px solid rgb(170, 204, 153); border-bottom: 1px solid rgb(170, 204, 153); border-image: initial; border-left: none; border-right: none;">cpack
</pre>
要指定生成器,请使用-G
选项。对于多配置版本,用于 -C
指定配置。例如:
<pre style="overflow: auto hidden; padding: 5px; background-color: rgb(238, 238, 238); color: rgb(51, 51, 51); line-height: 15.6px; border-top: 1px solid rgb(170, 204, 153); border-bottom: 1px solid rgb(170, 204, 153); border-image: initial; border-left: none; border-right: none;">cpack -G ZIP -C Debug
</pre>
要创建源分发,您可以输入:
<pre style="overflow: auto hidden; padding: 5px; background-color: rgb(238, 238, 238); color: rgb(51, 51, 51); line-height: 15.6px; border-top: 1px solid rgb(170, 204, 153); border-bottom: 1px solid rgb(170, 204, 153); border-image: initial; border-left: none; border-right: none;">cpack --config CPackSourceConfig.cmake
</pre>
或者,从IDE 运行或右键单击目标 。make package``Package``Build Project
运行在二进制目录中找到的安装程序。然后运行已安装的可执行文件,并验证其是否有效。
添加对仪表盘的支持(步骤8)
添加将测试结果提交到仪表板的支持非常简单。我们已经在“ 测试支持”中为我们的项目定义了许多测试。现在,我们只需要运行这些测试并将其提交到仪表板即可。为了包括对仪表板的支持,我们将CTest
顶层模块 CMakeLists.txt
。
更换:
# enable testing
enable_testing()
带有:
# enable dashboard scripting
include(CTest)
的 CTest
模块将自动调用enable_testing()
,因此我们可以将其从CMake文件中删除。
我们还需要CTestConfig.cmake
在顶级目录中创建一个文件,在其中可以指定项目的名称以及提交仪表板的位置。
set(CTEST_PROJECT_NAME "CMakeTutorial")
set(CTEST_NIGHTLY_START_TIME "00:00:00 EST")
set(CTEST_DROP_METHOD "http")
set(CTEST_DROP_SITE "my.cdash.org")
set(CTEST_DROP_LOCATION "/submit.php?project=CMakeTutorial")
set(CTEST_DROP_SITE_CDASH TRUE)
的 ctest
可执行文件将在运行时读入此文件。要创建一个简单的仪表板,您可以运行cmake
可执行文件或 cmake-gui
配置项目,但尚未构建。而是,将目录更改为二进制树,然后运行:
ctest [-VV] -D实验
请记住,对于多配置生成器(例如Visual Studio),必须指定配置类型:
<pre style="overflow: auto hidden; padding: 5px; background-color: rgb(238, 238, 238); color: rgb(51, 51, 51); line-height: 15.6px; border-top: 1px solid rgb(170, 204, 153); border-bottom: 1px solid rgb(170, 204, 153); border-image: initial; border-left: none; border-right: none;">ctest [-VV] -C Debug -D Experimental
</pre>
或者,从IDE中构建Experimental
目标。
的 ctest
可执行文件将构建并测试项目,并将结果提交到Kitware的公共仪表板:https ://my.cdash.org/index.php?project = CMakeTutorial 。
混合静态和共享(步骤9)
在本节中,我们将展示如何 BUILD_SHARED_LIBS
变量可用于控制的默认行为 add_library()
,并允许在如何没有显式类型库(控制STATIC
, SHARED
,MODULE
或OBJECT
)构建的。
为此,我们需要添加 BUILD_SHARED_LIBS
到顶层CMakeLists.txt
。我们使用option()
命令,因为它允许用户选择该值是ON还是OFF。
接下来,我们将重构MathFunctions使其成为使用mysqrt
或封装的真实库sqrt
,而不是要求调用代码执行此逻辑。这也将意味着USE_MYMATH
它将不控制构建MathFunction,而将控制该库的行为。
第一步是将顶层的开始部分更新为 CMakeLists.txt
:
cmake_minimum_required(VERSION 3.10)
# set the project name and version
project(Tutorial VERSION 1.0)
# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# control where the static and shared libraries are built so that on windows
# we don't need to tinker with the path to run the executable
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
# configure a header file to pass the version number only
configure_file(TutorialConfig.h.in TutorialConfig.h)
# add the MathFunctions library
add_subdirectory(MathFunctions)
# add the executable
add_executable(Tutorial tutorial.cxx)
target_link_libraries(Tutorial PUBLIC MathFunctions)
既然我们已经使MathFunctions始终被使用,我们将需要更新该库的逻辑。因此,MathFunctions/CMakeLists.txt
我们需要创建一个SqrtLibrary,USE_MYMATH
启用后将有条件地对其进行构建。现在,由于这是一个教程,我们将明确要求静态地构建SqrtLibrary。
最终结果MathFunctions/CMakeLists.txt
应该是:
# add the library that runs
add_library(MathFunctions MathFunctions.cxx)
# state that anybody linking to us needs to include the current source dir
# to find MathFunctions.h, while we don't.
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
# should we use our own math functions
option(USE_MYMATH "Use tutorial provided math implementation" ON)
if(USE_MYMATH)
target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
# first we add the executable that generates the table
add_executable(MakeTable MakeTable.cxx)
# add the command to generate the source code
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
DEPENDS MakeTable
)
# library that just does sqrt
add_library(SqrtLibrary STATIC
mysqrt.cxx
${CMAKE_CURRENT_BINARY_DIR}/Table.h
)
# state that we depend on our binary dir to find Table.h
target_include_directories(SqrtLibrary PRIVATE
${CMAKE_CURRENT_BINARY_DIR}
)
target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
endif()
# define the symbol stating we are using the declspec(dllexport) when
# building on windows
target_compile_definitions(MathFunctions PRIVATE "EXPORTING_MYMATH")
# install rules
install(TARGETS MathFunctions DESTINATION lib)
install(FILES MathFunctions.h DESTINATION include)
接下来,更新MathFunctions/mysqrt.cxx
为使用mathfunctions
和 detail
名称空间:
#include <iostream>
#include "MathFunctions.h"
// include the generated table
#include "Table.h"
namespace mathfunctions {
namespace detail {
// a hack square root calculation using simple operations
double mysqrt(double x)
{
if (x <= 0) {
return 0;
}
// use the table to help find an initial value
double result = x;
if (x >= 1 && x < 10) {
std::cout << "Use the table to help find an initial value " << std::endl;
result = sqrtTable[static_cast<int>(x)];
}
// do ten iterations
for (int i = 0; i < 10; ++i) {
if (result <= 0) {
result = 0.1;
}
double delta = x - (result * result);
result = result + 0.5 * delta / result;
std::cout << "Computing sqrt of " << x << " to be " << result << std::endl;
}
return result;
}
}
}
我们还需要对中进行一些更改tutorial.cxx
,以使其不再使用USE_MYMATH
:
一律包括
MathFunctions.h
一律使用
mathfunctions::sqrt
不包括cmath
最后,更新MathFunctions/MathFunctions.h
为使用dll导出定义:
#if defined(_WIN32)
# if defined(EXPORTING_MYMATH)
# define DECLSPEC __declspec(dllexport)
# else
# define DECLSPEC __declspec(dllimport)
# endif
#else // non windows
# define DECLSPEC
#endif
namespace mathfunctions {
double DECLSPEC sqrt(double x);
}
此时,如果您构建了所有内容,则会注意到链接失败,因为我们将没有位置独立代码的静态库与具有位置独立代码的库组合在一起。解决方案是明确设置POSITION_INDEPENDENT_CODE
无论构建类型如何,SqrtLibrary的target属性都应为True。
# state that SqrtLibrary need PIC when the default is shared libraries
set_target_properties(SqrtLibrary PROPERTIES
POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS}
)
target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
练习:我们修改MathFunctions.h
为使用dll导出定义。使用CMake文档,您可以找到一个帮助器模块来简化此过程吗?
添加生成器表达式(步骤10)
Generator expressions
在生成系统的过程中进行评估,以生成特定于每个生成配置的信息。
Generator expressions
在许多目标属性的上下文中是允许的,例如 LINK_LIBRARIES
, INCLUDE_DIRECTORIES
, COMPILE_DEFINITIONS
和别的。在使用命令填充这些属性时,也可以使用它们,例如 target_link_libraries()
, target_include_directories()
, target_compile_definitions()
和别的。
Generator expressions
可能用于启用条件链接,编译时使用的条件定义,条件包含目录等。条件可以基于构建配置,目标属性,平台信息或任何其他可查询信息。
有不同类型的 generator expressions
包括逻辑,信息和输出表达式。
逻辑表达式用于创建条件输出。基本表达式是0和1表达式。一个$<0:...>
导致空字符串,而<1:...>
导致的内容“...”。它们也可以嵌套。
的常见用法 generator expressions
是有条件地添加编译器标志,例如用于语言级别或警告的标志。一个不错的模式是将该信息与INTERFACE
目标关联,以允许该信息传播。让我们从构造一个 INTERFACE
目标并指定所需的C ++标准级别开始,11
而不是使用CMAKE_CXX_STANDARD
。
所以下面的代码:
# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
将被替换为:
add_library(tutorial_compiler_flags INTERFACE)
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)
接下来,我们为项目添加所需的编译器警告标志。由于警告标志根据编译器的不同而不同,因此我们使用COMPILE_LANG_AND_ID
生成器表达式来控制在给定一种语言和一组编译器ID的情况下应用哪些标志,如下所示:
set(gcc_like_cxx "$<COMPILE_LANG_AND_ID:CXX,ARMClang,AppleClang,Clang,GNU>")
set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")
target_compile_options(tutorial_compiler_flags INTERFACE
"$<${gcc_like_cxx}:$<BUILD_INTERFACE:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>>"
"$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
)
看着这个,我们看到警告标志被封装在一个 BUILD_INTERFACE
条件中。这样做是为了使我们已安装项目的使用者不会继承我们的警告标志。
练习:修改MathFunctions/CMakeLists.txt
以使所有目标都有一个target_link_libraries()
致电至tutorial_compiler_flags
。
添加导出配置(步骤11)
在本教程的“ 安装和测试”(第4步)中,我们添加了CMake的功能,以安装项目的库和头文件。在 构建安装程序(第7步)期间,我们添加了打包此信息的功能,以便可以将其分发给其他人。
下一步是添加必要的信息,以便其他CMake项目可以使用我们的项目,无论是从构建目录,本地安装还是打包时。
第一步是更新我们的 install(TARGETS)
命令不仅指定a DESTINATION
而且还指定EXPORT
。该EXPORT
关键字生成并安装包含代码导入从安装树安装命令列出的所有目标的CMake的文件。因此,让我们继续进行操作,并EXPORT
通过更新以下install
命令MathFunctions/CMakeLists.txt
来显式地显示MathFunctions库:
install(TARGETS MathFunctions tutorial_compiler_flags
DESTINATION lib
EXPORT MathFunctionsTargets)
install(FILES MathFunctions.h DESTINATION include)
现在我们已经导出了MathFunction,我们还需要显式安装生成的MathFunctionsTargets.cmake
文件。这是通过在顶层底部添加以下内容来完成的CMakeLists.txt
:
install(EXPORT MathFunctionsTargets
FILE MathFunctionsTargets.cmake
DESTINATION lib/cmake/MathFunctions
)
此时,您应该尝试运行CMake。如果一切设置正确,您将看到CMake将生成如下错误:
Target "MathFunctions" INTERFACE_INCLUDE_DIRECTORIES property contains
path:
"/Users/robert/Documents/CMakeClass/Tutorial/Step11/MathFunctions"
which is prefixed in the source directory.
CMake试图说的是,在生成导出信息的过程中,它将导出与当前机器固有联系的路径,并且在其他机器上无效。解决方案是更新MathFunctionstarget_include_directories()
了解INTERFACE
在构建目录和安装/软件包中使用它时需要在不同的位置。这意味着转换 target_include_directories()
要求MathFunctions看起来像:
target_include_directories(MathFunctions
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>
)
更新后,我们可以重新运行CMake并确认它不再发出警告。
在这一点上,我们已经正确地包装了CMake所需的目标信息,但是我们仍然需要生成一个MathFunctionsConfig.cmake
使CMakefind_package()
命令可以找到我们的项目。因此,我们继续将新文件添加到项目的顶层,该文件 Config.cmake.in
具有以下内容:
@PACKAGE_INIT@
include ( "${CMAKE_CURRENT_LIST_DIR}/MathFunctionsTargets.cmake" )
然后,要正确配置和安装该文件,请将以下内容添加到顶层的底部CMakeLists.txt
:
install(EXPORT MathFunctionsTargets
FILE MathFunctionsTargets.cmake
DESTINATION lib/cmake/MathFunctions
)
include(CMakePackageConfigHelpers)
# generate the config file that is includes the exports
configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
INSTALL_DESTINATION "lib/cmake/example"
NO_SET_AND_CHECK_MACRO
NO_CHECK_REQUIRED_COMPONENTS_MACRO
)
# generate the version file for the config file
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
VERSION "${Tutorial_VERSION_MAJOR}.${Tutorial_VERSION_MINOR}"
COMPATIBILITY AnyNewerVersion
)
# install the configuration file
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake
DESTINATION lib/cmake/MathFunctions
)
至此,我们为项目生成了可重定位的CMake配置,可在安装或打包项目后使用它。如果我们也希望从构建目录中使用我们的项目,则只需将以下内容添加到顶层的底部CMakeLists.txt
:
export(EXPORT MathFunctionsTargets
FILE "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsTargets.cmake"
)
通过此导出调用,我们现在生成一个Targets.cmake
,允许MathFunctionsConfig.cmake
构建目录中的配置供其他项目使用,而无需安装它。
打包调试和发布(步骤12)
注意:此示例对单配置生成器有效,而对多配置生成器(例如Visual Studio)无效。
默认情况下,CMake的模型是构建目录仅包含一个配置,可以是Debug,Release,MinSizeRel或RelWithDebInfo。但是,可以将CPack设置为捆绑多个构建目录,并构建一个包含同一项目的多个配置的软件包。
首先,我们要确保调试版本和发行版本对要安装的可执行文件和库使用不同的名称。让我们使用<cite>d</cite>作为调试可执行文件和库的后缀。
组 CMAKE_DEBUG_POSTFIX
在顶级CMakeLists.txt
文件开头附近 :
set(CMAKE_DEBUG_POSTFIX d)
add_library(tutorial_compiler_flags INTERFACE)
和 DEBUG_POSTFIX
教程可执行文件上的属性:
add_executable(Tutorial tutorial.cxx)
set_target_properties(Tutorial PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})
target_link_libraries(Tutorial PUBLIC MathFunctions)
让我们还将版本编号添加到MathFunctions库中。在中 MathFunctions/CMakeLists.txt
,设置VERSION
和 SOVERSION
特性:
set_property(TARGET MathFunctions PROPERTY VERSION "1.0.0")
set_property(TARGET MathFunctions PROPERTY SOVERSION "1")
在Step12
目录中,创建debug
和release
子目录。布局将如下所示:
- Step12
- debug
- release
现在我们需要设置调试和发布版本。我们可以用 CMAKE_BUILD_TYPE
设置配置类型:
cd debug
cmake -DCMAKE_BUILD_TYPE=Debug ..
cmake --build .
cd ../release
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build .
既然调试和发布版本均已完成,我们就可以使用自定义配置文件将两个版本打包到一个版本中。在 Step12
目录中,创建一个名为的文件MultiCPackConfig.cmake
。在此文件中,首先包括由配置文件创建的默认配置文件。 cmake
可执行文件。
接下来,使用CPACK_INSTALL_CMAKE_PROJECTS
变量指定要安装的项目。在这种情况下,我们要同时安装调试和发布。
include("release/CPackConfig.cmake")
set(CPACK_INSTALL_CMAKE_PROJECTS
"debug;Tutorial;ALL;/"
"release;Tutorial;ALL;/"
)
从Step12
目录运行cpack
使用以下config
选项指定我们的自定义配置文件:
cpack --config MultiCPackConfig.cmake