版权声明:本文翻译自《CMake tutorial v3.16》。未经做者容许,严禁用于商业出版,不然追究法律责任。网络转载请注明出处!!!php
CMake教程提供了逐步指南,涵盖了CMake能够帮助解决的常见构建系统问题。 了解示例项目中各个主题如何协同工做将很是有帮助。 示例的教程文档和源代码可在CMake源代码树的Help/guide/tutorial
目录中找到。 每一个步骤都有其本身的子目录,其中包含能够用做起点的代码。 教程示例是渐进式的,所以每一个步骤都为上一步提供了完整的解决方案。html
最基本的项目是从源代码文件构建一个可执行文件。 对于简单的项目,只需三行CMakeLists.txt
文件。 这是本教程的起点。 在Step1目录中建立一个CMakeLists.txt
文件,以下所示:ios
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
的源代码,可用于计算数字的平方根。c++
咱们将添加的第一个功能是为咱们的可执行文件和项目提供版本号。 虽然咱们能够仅在源代码中执行此操做,可是使用CMakeLists.txt
能够提供更大的灵活性。shell
首先,修改CMakeLists.txt
文件来设置版本号。windows
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}")
使用您喜欢的编辑器在源码目录中建立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@
的值将被替换。app
接下来,修改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;
}
完整的CMakeLists.txt
以下:
cmake_minimum_required(VERSION 3.10)
#set project name and version
project(Tutorial VERSION 1.0)
configure_file(TutorialConfig.h.in TutorialConfig.h)
#add the executable
add_executable(Tutorial tutorial.cxx)
target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}" )
接下来,经过在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)
# 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以配置项目,而后使用所选的构建工具进行构建。
例如,从命令行咱们能够导航到CMake源代码树的Help /guide/tutorial
目录并运行如下命令:
mkdir Step1_build cd Step1_build cmake ../Step1 cmake --build .
导航到构建教程的目录(多是make目录或Debug或Release构建配置子目录),而后运行如下命令:
Tutorial 4294967296 Tutorial 10 Tutorial
如今,咱们将添加一个库到咱们的项目中。 该库是咱们本身的实现的用于计算数字的平方根的库。 可执行文件可使用此库,而不是使用编译器提供的标准平方根函数。
在本教程中,咱们将库放入名为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
文件的最后几行如今应以下所示:
# add the MathFunctions library
add_subdirectory(MathFunctions)
# add the executable
add_executable(Tutorial tutorial.cxx)
#必须位于add_excutable以后
target_link_libraries(Tutorial PUBLIC MathFunctions)
###早期版本的写法
### target_link_libraries(Tutorial 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)
#必须位于add_executable以后
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})
###早期版本的写法
### 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 tutorial.cxx)
### target_link_libraries(Tutorial ${EXTRA_LIBS})
请注意,使用变量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
练习:为何在USE_MYMATH
选项以后配置TutorialConfig.h.in如此重要? 若是咱们将二者倒置会怎样?
运行cmake或cmake-gui以配置项目,而后使用所选的构建工具进行构建。 而后运行构建的Tutorial可执行文件。
使用ccmake或CMake GUI更新USE_MYMATH
的值。 从新生成并再次运行本教程。 sqrt或mysqrt哪一个函数可提供更好的结果?
完整的CMakeLists.txt
文件以下:
cmake_minimum_required(VERSION 3.5)
# 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)
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)
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)
#必须位于add_executable以后
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})
使用要求能够更好地控制库或可执行文件的连接和include行,同时还能够更好地控制CMake内部目标的传递属性。 利用使用要求的主要命令是:
让咱们从第2步中重构代码,以利用现代的CMake方法编写使用要求。 咱们首先声明,连接到MathFunctions的任何东西都须要包括当前源码目录,而MathFunctions自己不须要。 所以,这能够成为INTERFACE
使用要求。
请记住,INTERFACE
是指消费者须要的,而生产者不须要东西。 将如下行添加到MathFunctions/CMakeLists.txt
的末尾:
target_include_directories(MathFunctions INTERFACE ${CMAKE_CURRENT_SOURCE_DIR)
如今,咱们已经指定了MathFunction的使用要求,咱们能够安全地从顶级CMakeLists.txt
中删除对EXTRA_INCLUDES
变量的使用:
if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
endif()
... ...
... ...
target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}")
如今,咱们能够开始向项目添加安装规则和测试支持。
安装规则很是简单:对于MathFunctions,咱们要安装库和头文件,对于应用程序,咱们要安装可执行文件和配置的头文件。
所以,在MathFunctions/CMakeLists.txt
的末尾添加:
install(TARGETS MathFunctions DESTINATION lib)
install(FILES MathFunctions.h DESTINATION include)
而后在顶级cmakelt .txt的末尾添加
install(TARGETS Tutorial DESTINATION bin)
install(FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h" DESTINATION include)
这就是建立本教程的基本本地安装所需的所有工做。
运行cmake或cmake-gui以配置项目,而后使用所选的构建工具进行构建。 从命令行键入cmake --install
进行安装(自3.15中引入,较早版本的CMake必须使用make install),或从IDE构建INSTALL
目标。 这将安装适当的头文件,库和可执行文件。
CMake变量CMAKE_INSTALL_PREFIX
用于肯定文件的安装根目录。 若是使用cmake --install
,则能够经过--prefix
参数指定自定义安装目录。 对于多配置工具,请使用--config
参数指定配置。
验证已安装的Tutorial能够运行。
接下来,测试咱们的应用程序。 在顶级CMakeLists.txt
文件的末尾,咱们能够启用测试,而后添加一些基本测试以验证应用程序是否正常运行。
enable_testing()
# does the application run
add_test(NAME Runs COMMAND Tutorial 25)
# does it sqrt of 25
add_test (NAME Comp25 COMMAND Tutorial 25)
set_tests_properties (Comp25 PROPERTIES PASS_REGULAR_EXPRESSION "25 is 5")
# 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")
###早期版本的写法
### include(CTest)
### add_test (TutorialRuns Tutorial 25)
###
### add_test (TutorialComp25 Tutorial 25)
### set_tests_properties (TutorialComp25 PROPERTIES PASS_REGULAR_EXPRESSION "25 is 5")
###
### add_test (TutorialUsage Tutorial)
### set_tests_properties (TutorialUsage PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number")
###
### #define a macro to simplify adding tests, then use it
### macro (do_test arg result)
### add_test (TutorialComp${arg} Tutorial ${arg})
### set_tests_properties (TutorialComp${arg} PROPERTIES PASS_REGULAR_EXPRESSION ${result})
### endmacro (do_test)
###
### do_test(4 "4 is 2")
### do_test(9 "9 is 3")
### do_test(5 "5 is 2.236")
### do_test(7 "7 is 2.645")
### do_test(25 "25 is 5")
### do_test(-25 "-25 is [-nan|nan|0]")
### do_test(0.0001 "0.0001 is 0.01")
第一个测试只是验证应用程序你可否运行,没有段错误或其余崩溃,而且返回值为零。 这是CTest测试的基本形式。
下一个测试使用PASS_REGULAR_EXPRESSION
测试属性来验证测试的输出是否包含某些字符串。 在这种状况下,验证在提供了错误数量的参数时是否打印了用法消息。
最后,咱们有一个名为do_test
的函数,该函数运行应用程序并验证所计算的平方根对于给定输入是否正确。 对于do_test
的每次调用,都会基于传递的参数将另外一个测试添加到项目中,该测试具备名称,输入和预期结果。
从新构建应用程序,而后cd到二进制目录并运行ctest -N
和ctest -VV
。 对于多配置生成器(例如Visual Studio),必须指定配置类型。 例如,要在“调试”模式下运行测试,请从构建目录(而不是“调试”子目录!)中使用ctest -C Debug -VV
。 或者,从IDE构建RUN_TESTS
目标。
早期版本的另外一种写法是:
让咱们考虑向咱们的项目中添加一些代码,这些代码取决于目标平台可能不具有的功能。 对于此示例,咱们将添加一些代码,具体取决于目标平台是否具备log
和exp
函数。 固然,几乎每一个平台都具备这些函数,但对于本教程而言,假设它们并不常见。
若是平台具备log
和exp
,那么咱们将使用它们来计算mysqrt
函数中的平方根。 咱们首先使用顶级CMakeLists.txt
中的CheckSymbolExists
模块测试这些函数的可用性。 咱们将在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)
###早期版本的写法
### include (CheckFunctionExists)
### check_function_exists (log HAVE_LOG)
### check_function_exists (exp HAVE_EXP)
如今,将这些定义添加到TutorialConfig.h.in
中,以便咱们能够从mysqrt.cxx中
使用它们:
// does the platform provide exp and log functions?
#cmakedefine HAVE_LOG
#cmakedefine HAVE_EXP
修改mysqrt.cxx
以包括cmath。 接下来,在mysqrt
函数的同一文件中,咱们可使用如下代码提供基于log
和exp
(若是在系统上可用)的替代实现(在return result;
前不要忘记#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;
运行cmake或cmake-gui来配置项目,而后使用所选的构建工具进行构建并运行Tutorial可执行文件。
您会注意到,咱们也没有使用log
和exp
,即便咱们认为它们应该是可用。 咱们应该很快意识到,咱们忘记在mysqrt.cxx
中包含TutorialConfig.h
。
咱们还须要更新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
?
除了在TutorialConfig.h
中保存HAVE_LOG
和HAVE_EXP
值,对咱们来讲还有更好的地方吗? 让咱们尝试使用target_compile_definitions
。
首先,从TutorialConfig.h.in
中删除定义。 咱们再也不须要包含mysqrt.cxx
中的TutorialConfig.h
或MathFunctions/CMakeLists.txt
中的其余包含内容。
接下来,咱们能够将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可执行文件,并验证结果与本步骤前面的内容相同。
出于本教程的目的,假设咱们决定再也不使用平台log
和exp
函数,而是但愿生成一个可在mysqrt
函数中使用的预计算值表。 在本节中,咱们将在构建过程当中建立表,而后将该表编译到咱们的应用程序中。
首先,让咱们删除MathFunctions/CMakeLists.txt
中对log
和exp
函数的检查。 而后从mysqrt.cxx中删除对HAVE_LOG
和HAVE_EXP
的检查。 同时,咱们能够删除#include <cmath>
。
在MathFunctions
子目录中,提供了一个名为MakeTable.cxx
的新的源文件以生成表。
查看完文件后,咱们能够看到该表是做为有效的C++代码生成的,而且输出文件名做为参数传入。
下一步是将适当的命令添加到MathFunctions/CMakeLists.txt
文件中,以构建MakeTable可执行文件,而后在构建过程当中运行它。 须要一些命令来完成此操做。
首先,在MathFunctions/CMakeLists.txt
的顶部,添加MakeTable
的可执行文件,就像添加任何其余可执行文件同样。
add_executable(MakeTable MakeTable.cxx)
而后,咱们添加一个自定义命令,该命令指定如何经过运行MakeTable生成Table.h
。
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 )
咱们还必须将当前的二进制目录添加到include目录列表中,以便mysqrt.cxx
能够找到并包含Table.h
。
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
。 最后,它将编译包括了Table.h
的mysqrt.cxx
,以生成MathFunctions库。
运行Tutorial可执行文件,并验证它是否正在使用该表。
:wq# 7 构建一个安装程序(第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模块,该模块将使用这些变量和当前系统的其余一些属性来设置安装程序。
下一步是以常规方式构建项目,而后在其上运行CPack。 要构建二进制发行版,请从二进制目录运行:
cpack
要指定生成器,请使用-G选项。 对于多配置构建,请使用-C指定配置。 例如:
cpack -G ZIP -C Debug
要建立源码分发,您能够输入:
cpack --config CPackSourceConfig.cmake
或者,运行make package
或在IDE中右键单击Package
目标和Build Project
。
运行在二进制目录中找到的安装程序。 而后运行已安装的可执行文件,并验证其是否有效。
添加支持以将测试结果提交到Dashboard很是容易。 咱们已经在“测试支持”中为咱们的项目定义了许多测试。 如今,咱们只须要运行这些测试并将其提交到Dashboard便可。 为了包含对Dashboard的支持,咱们在顶层CMakeLists.txt
中包含了CTest模块。
用
# enable dashboard scripting
include(CTest)
替换
# enable testing
enable_testing()
CTest模块将自动调用enable_testing()
,所以咱们能够将其从CMake文件中删除。
咱们还须要在顶级目录中建立一个CTestConfig.cmake
文件,在该目录中咱们能够指定项目的名称以及提交Dashboard的位置。
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将在运行时读入该文件。 要建立一个简单的Dashboard,您能够运行cmake或cmake-gui来配置项目,但不构建它。 而是,将目录更改成二进制树,而后运行:
ctest [-VV] -D Experimental
请记住,对于多配置生成器(例如Visual Studio),必须指定配置类型:
ctest [-VV] -C Debug -D Experimental
或者,从IDE中构建Experimental
目标。
ctest
将构建和测试项目,并将结果提交给Kitware公共仪表板Dashboard。 Dashboard的结果将被上传到Kitware的公共Dashboard:https://my.cdash.org/index.php?project=CMakeTutorial。
在本节中,咱们将展现如何使用BUILD_SHARED_LIBS
变量来控制add_library
的默认行为,并容许控制如何构建没有显式类型(STATIC,SHARED,MODULE或OBJECT
)的库。
为此,咱们须要将BUILD_SHARED_LIBS
添加到顶级CMakeLists.txt
。 咱们使用option
命令,由于它容许用户能够选择该值是On仍是Off。
接下来,咱们将重构MathFunctions使其成为使用mysqrt或sqrt封装的真实库,而不是要求调用代码执行此逻辑。 这也意味着USE_MYMATH
将不会控制构建MathFuctions,而是将控制此库的行为。
第一步是将顶级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);
}
此时,若是您构建了全部内容,则会注意到连接失败,由于咱们将没有位置独立代码的静态库与具备位置独立代码的库组合在一块儿。 解决方案是不管构建类型如何,都将SqrtLibrary的POSITION_INDEPENDENT_CODE
目标属性显式设置为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文档,您能够找到一个帮助器模块来简化此过程吗?
在构建系统生成期间会评估生成器表达式,以生成特定于每一个构建配置的信息。
在许多目标属性(例如LINK_LIBRARIES,INCLUDE_DIRECTORIES,COMPLIE_DEFINITIONS
等)的上下文中容许生成器表达式。 在使用命令填充这些属性(例如target_link_libraries(),target_include_directories() ,target_compile_definitions()
等)时,也可使用它们。
生成器表达式可用于启用条件连接,编译时使用的条件定义,条件包含目录等。 条件能够基于构建配置,目标属性,平台信息或任何其余可查询信息。
生成器表达式有不一样类型,包括逻辑,信息和输出表达式。
逻辑表达式用于建立条件输出。 基本表达式是0和1表达式。$<0:...>
致使空字符串,而<1:...>
致使内容“…”。 它们也能够嵌套。
生成器表达式的常见用法是有条件地添加编译器标志,例如用于语言级别或警告的标志。 一个不错的模式是将该信息与一个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
,以便全部目标都具备对tutorial_compiler_flags
target_link_libraries()
调用。
在本教程的“安装和测试”(第4步)中,咱们添加了CMake的功能,以安装项目的库和头文件。 在"构建安装程序"(第7步)期间,咱们添加了打包此资料的功能,以即可以将其分发给其余人。
下一步是添加必要的信息,以便其余CMake项目可使用咱们的项目,不管是从构建目录,本地安装仍是打包的文件。
第一步是更新咱们的install(TARGETS)
命令,不只要指定DESTINATION
,还要指定EXPORT
。 EXPORT
关键字生成并安装一个CMake文件,该文件包含用于从安装树中导入install命令中列出的全部目标的代码。 所以,让咱们继续,经过更新MathFunctions/CMakeLists.txt
中的install
命令显式EXPORT
MathFunctions库,以下所示:
install(TARGETS MathFunctions tutorial_compiler_flags
DESTINATION lib
EXPORT MathFunctionsTargets)
install(FILES MathFunctions.h DESTINATION include)
如今咱们已经导出了MathFunctions,咱们还须要显式安装生成的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试图说的是,在生成导出信息的过程当中,它将导出与当前机器固有联系的路径,而且在其余机器上无效。 解决方案是更新MathFunctions target_include_directories
,以了解从构建目录和install/包中使用它时须要不一样的INTERFACE
位置。 这意味着将MathFunctions的target_include_directories
调用转换为:
target_include_directories(MathFunctions
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>
)
更新后,咱们能够从新运行CMake并确认它再也不发出警告。
至此,咱们已经正确地打包了CMake所需的目标信息,但仍然须要生成MathFunctionsConfig.cmake
,以便CMake find_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
由其余项目使用,而无需安装它。
本示例说明项目如何查找生成Config.cmake
文件的其余CMake软件包。
它还显示了在生成Config.cmake
时如何声明项目的外部依赖关系。
默认状况下,CMake的模型是一个构建目录仅包含一个配置,能够是Debug,Release,MinSizeRel或RelWithDebInfo。
可是能够将CPack设置为同时捆绑多个构建目录,以构建一个包含同一项目的多个配置的软件包。
首先,咱们须要构建一个名为multi_config
的目录,该目录将包含咱们要打包在一块儿的全部构建。
其次,在multi_config
下建立一个debug
和release
目录。 最后,您应该具备以下布局:
─ multi_config ├── debug └── release
如今,咱们须要设置调试和发布版本,这大体须要如下内容:
cmake -DCMAKE_BUILD_TYPE=Debug ../../../MultiPackage/ cmake --build . cd ../release cmake -DCMAKE_BUILD_TYPE=Release ../../../MultiPackage/ cmake --build . cd ..
既然调试和发行版本均已完成,咱们就可使用自定义的MultiCPackConfig.cmake
文件将两个版本打包到一个发行版中。
cpack --config ../../MultiPackage/MultiCPackConfig.cmake
版权声明:本文翻译自《CMake tutorial v3.16》。未经做者容许,严禁用于商业出版,不然追究法律责任。网络转载请注明出处!!!