内容
CMake教程提供了一个分步指南,涵盖了CMake帮助解决的常见构建系统问题。在一个示例项目中查看各个主题如何协同工作非常有帮助。示例的教程文档和源代码可以在CMake源代码树的Help/guide/tutorial目录中找到。每个步骤都有自己的子目录,其中包含可以用作起点的代码。教程示例是渐进式的,因此每个步骤都为上一步提供了完整的解决方案。
#include
#include
#include
int main(int argc,char *argv[])
{
if(argc < 2)
{
fprintf(stdout,"Usage:%s number\n",argv[0]);
return 1;
}
double inputValue = atof(argv[1]);
double outputValue = sqrt(inputValue);
fprintf(stdout,"The square root of %g is %g\n",
inputValue,outputValue);
return 0;
}
最基本的项目是从源代码文件构建的可执行文件。对于简单的项目,只需要一个三行的CMakeLists.txt文件。这将是我们教程的起点。在Step1目录中创建一个CMakeLists.txt文件,如下所示:
cmake_minimum_required(VERSION 3.10)
#设置项目名称
project(Tutorial)
# 添加可执行项目名称
add_executable(Tutorial tutorial.cxx)
请注意,此示例在CMakeLists.txt文件中使用小写命令。CMake支持大写、小写和混合大小写命令。tutorial.cxx的源代码在Step1目录中提供,可用于计算数字的平方根。
我们将添加的第一个功能是为可执行文件和项目提供版本号。虽然我们可以在源代码中做到这一点,但是使用CMAKELIST.TXT提供了更多的灵活性。首先,修改CMakeLists.txt文件以设置版本号。
cmake_minimum_required(VERSION 3.10)
# 设置项目名称和版本号
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,其中包含以下内容:
// 教程的配置选项和设置
#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;
}
接下来,我们将通过在storal.cxx中用std::stod替换acof:将一些C++ 11特性添加到我们的项目中。同时,删除include
const double inputValue = std::stod(argv[1]);
我们需要在CMake代码中明确声明它应该使用正确的标志。在CMake中支持特定C++标准的最简单方法是使用CMAKE_CXX_STANDARD。对于本教程,请将CMakeLists.txt文件中的CMAKE_CXX_STANDARD设置为11,并将CMAKE_CXX_STANDARD设置为True:
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配置项目,然后使用所选生成工具生成项目。例如,从命令行中,我们可以导航到cmake源代码树的“帮助/指南/教程”目录,然后运行以下命令:
mkdir Step1_build
cd Step1_build
cmake ../Step1
cmake --build .
导航到生成教程的目录(可能是make目录或Debug或Release build配置子目录),然后运行以下命令:
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文件的最后几行现在应该如下所示:
#添加 MathFunctions 库
add_subdirectory(MathFunctions)
# 添加执行文件
add_executable(Tutorial tutorial.cxx)
target_link_libraries(Tutorial PUBLIC MathFunctions)
# 在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})
#为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
由于源代码现在需要使用MYMATH,我们可以使用以下行将其添加到TutorialConfig.h.in:
#cmakedefine USE_MYMATH
练习:为什么在使用MYMATH选项之后配置TutorialConfig.h.in很重要?如果我们把两个倒过来会怎么样?运行cmake或cmake gui来配置项目,然后使用所选的生成工具生成它。然后运行构建的教程可执行文件。使用ccmake或CMake GUI更新Use_MYMATH的值。重新生成并再次运行教程。sqrt和mysqrt哪个函数能给出更好的结果?
使用需求允许对库或可执行文件的链接和include行进行更好的控制,同时也允许对CMake中目标的可传递属性进行更多的控制。利用使用要求的主要命令有:
target_compile_definitions
target_compile_options
target_include_directories
target_link_libraries
让我们从添加库(步骤2)到使用现代CMake方法来重构代码。我们首先声明任何链接到MathFunctions的人都需要包含当前的源目录,而MathFunctions本身不需要,所以这可能成为接口使用需求。
记住,接口意味着消费者需要但生产者不需要的东西。请在MathFunctions/CMakeLists.txt的末尾添加以下行:
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
既然我们已经为MathFunctions指定了使用要求,那么我们可以安全地从顶级CMakeLists.txt中删除对EXTRA-INCLUDES变量的使用,如下所示:
if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
endif()
And here:
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
)
完成后,运行cmake或cmake gui来配置项目,然后使用您选择的构建工具或使用cmake--build构建项目 . 从生成目录。
现在我们可以开始向项目添加安装规则和测试支持。
安装规则相当简单:对于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来配置项目,然后使用所选的生成工具生成它。键入cmake--install运行安装步骤。(在3.15中引入,较旧版本的CMake必须使用make install)或从IDE构建安装目标。这将安装适当的头文件、库和可执行文件。
CMake变量CMake_INSTALL_PREFIX用于确定文件安装位置的根目录。如果使用cmake--install,则可以通过--prefix参数提供自定义安装目录。对于多配置工具,使用--config参数指定配置。
验证已安装的教程是否运行。
接下来让我们测试我们的应用程序。在顶级CMakeLists.txt文件的末尾,我们可以启用测试,然后添加一些基本测试来验证应用程序是否正常工作。
enable_testing()
#应用程序是否运行
add_test(NAME Runs COMMAND Tutorial 25)
# 使用消息是否有效?
add_test(NAME Usage COMMAND Tutorial)
set_tests_properties(Usage
PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number"
)
#定义函数以简化添加测试
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_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")
第一个测试只是验证应用程序是否运行、是否segfault或以其他方式崩溃,以及是否具有零返回值。这是CTest测试的基本形式。
下一个测试使用PASS_REGULAR_EXPRESSION test属性来验证测试的输出是否包含某些字符串。在这种情况下,请验证当提供的参数数目不正确时是否打印了用法消息。
最后,我们有一个名为do_test的函数,它运行应用程序并验证计算的平方根对于给定的输入是否正确。对于do_test的每次调用,都会向项目中添加另一个测试,其中包含名称、输入和基于传递的参数的预期结果。
重新构建应用程序,然后将cd转换到二进制目录并运行ctest-N和ctest-VV。对于多配置生成器(如Visual Studio),必须指定配置类型。例如,要在调试模式下运行测试,请使用构建目录(而不是调试子目录!)中的ctest-C Debug-VV。或者,从IDE生成运行测试目标。
让我们考虑在项目中添加一些代码,这些代码取决于目标平台可能没有的特性。对于本例,我们将添加一些代码,这取决于目标平台是否具有log和exp函数。当然,几乎每个平台都有这些功能,但本教程假设它们并不常见。
如果平台有log和exp,那么我们将使用它们来计算mysqrt函数的平方根。我们首先使用顶级CMAKLISTS.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)
现在让我们添加如下指令到 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的替代实现(在返回结果之前不要忘记#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来配置项目,然后使用您选择的构建工具构建项目并运行教程可执行文件。您会注意到我们没有使用log和exp,即使我们认为它们应该是可用的。我们应该很快意识到,我们忘记在mysqrt.cxx中包含TutorialConfig.h。我们还需要更新MathFunctions/CMakeLists.txt,以便mysqrt.cxx知道此文件的位置:
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE ${CMAKE_BINARY_DIR}
)
完成此更新后,继续并再次生成项目,然后运行生成的教程可执行文件。如果仍不使用log和exp,请从构建目录打开生成的TutorialConfig.h文件。可能在当前系统上不可用?
哪个函数现在能给出更好的结果,sqrt还是mysqrt?
是否有比TutorialConfig.h中更好的地方来保存HAVE_日志和HAVE_EXP值?让我们尝试使用target_compile_定义。
首先,从TutorialConfig.h.in中删除定义。我们不再需要包含mysqrt.cxx中的TutorialConfig.h或MathFunctions/CMakeLists.txt中的额外include。
接下来,我们可以将HAVE-LOG和HAVE-EXP的检查移动到MathFunctions/CMakeLists.txt,然后将这些值指定为私有编译定义。
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()
完成这些更新后,请继续并重新生成项目。运行构建的教程可执行文件并验证结果是否与本步骤前面的相同。
假设,在本教程中,我们决定不使用平台日志和exp函数,而是希望生成一个预计算值表,以便在mysqrt函数中使用。在本节中,我们将创建表作为构建过程的一部分,然后将该表编译到应用程序中。
首先,让我们取消对MathFunctions/CMakeLists.txt中的log和exp函数的检查。然后从mysqrt.cxx中删除HAVE_LOG和HAVE_EXP的检查。同时,我们可以删除include
下一步是将适当的命令添加到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;
}
// 使用表帮助找到一个初始值
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(x)];
}
// 做十次循环
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库。
运行教程可执行文件并验证它是否正在使用表。
接下来假设我们想将我们的项目分发给其他人,以便他们可以使用它。我们希望在各种平台上提供二进制和源代码分发。这与我们之前在安装和测试(步骤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
或者右击 Package
目标 并且编译项目从
IDE.
运行文件目录中的二进制安装程序.然后运行安装程序来检查运行情况.
Adding support for submitting our test results to a dashboard is very easy. We already defined a number of tests for our project in Testing Support. Now we just have to run those tests and submit them to a dashboard. To include support for dashboards we include the CTest module in our top-level CMakeLists.txt
.
Replace:
#测试使能
enable_testing()
With:
# enable dashboard scripting
include(CTest)
The CTest module will automatically call enable_testing()
, so we can remove it from our CMake files.
We will also need to create a CTestConfig.cmake
file in the top-level directory where we can specify the name of the project and where to submit the 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 will read in this file when it runs. To create a simple dashboard you can run cmake or cmake-gui to configure the project, but do not build it yet. Instead, change directory to the binary tree, and then run:
ctest [-VV] -D Experimental
Remember, for multi-config generators (e.g. Visual Studio), the configuration type must be specified:
ctest [-VV] -C Debug -D Experimental
Or, from an IDE, build the Experimental
target.
ctest
will build and test the project and submit the results to the Kitware public dashboard. The results of your dashboard will be uploaded to Kitware’s public dashboard here: https://my.cdash.org/index.php?project=CMakeTutorial.
In this section we will show how by using the BUILD_SHARED_LIBS
variable we can control the default behavior of add_library
, and allow control over how libraries without an explicit type (STATIC
, SHARED
, MODULE
or OBJECT
) are built.
To accomplish this we need to add BUILD_SHARED_LIBS
to the top-level CMakeLists.txt
. We use the option
command as it allows users to optionally select if the value should be On or Off.
Next we are going to refactor MathFunctions to become a real library that encapsulates using mysqrt
or sqrt
, instead of requiring the calling code to do this logic. This will also mean that USE_MYMATH
will not control building MathFuctions, but instead will control the behavior of this library.
The first step is to update the starting section of the top-level CMakeLists.txt
to look like:
cmake_minimum_required(VERSION 3.10)
# 设置项目名和版本
project(Tutorial VERSION 1.0)
#指定C++标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
#控制生成静态库和共享库的路径,以便在widnows上我们也不需要考虑执行文件的路径
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_file(TutorialConfig.h.in TutorialConfig.h)
# 添加MathFunctions 库
add_subdirectory(MathFunctions)
# 添加可执行文件
add_executable(Tutorial tutorial.cxx)
target_link_libraries(Tutorial PUBLIC MathFunctions)
Now that we have made MathFunctions always be used, we will need to update the logic of that library. So, in MathFunctions/CMakeLists.txt
we need to create a SqrtLibrary that will conditionally be built when USE_MYMATH
is enabled. Now, since this is a tutorial, we are going to explicitly require that SqrtLibrary is built statically.
The end result is that MathFunctions/CMakeLists.txt
should look like:
//add the library that runs
add_library(MathFunctions MathFunctions.cxx)
//声明任何链接到我们的人都需要包含当前源目录去寻找数学函数。
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
//我们应该用自己的数学函数吗
option(USE_MYMATH "Use tutorial provided math implementation" ON)
if(USE_MYMATH)
target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
//首先,我们添加生成表的可执行文件
add_executable(MakeTable MakeTable.cxx)
//添加生成源文件的命令
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
DEPENDS MakeTable
)
//求平方根的库
add_library(SqrtLibrary STATIC
mysqrt.cxx
${CMAKE_CURRENT_BINARY_DIR}/Table.h
)
//声明我们依赖二进制dir来查找Table.h
target_include_directories(SqrtLibrary PRIVATE
${CMAKE_CURRENT_BINARY_DIR}
)
target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
endif()
//定义一个符号,说明我们在windows下编译的时候可以使用declspecdllexport)
target_compile_definitions(MathFunctions PRIVATE "EXPORTING_MYMATH")
//安装规则
install(TARGETS MathFunctions DESTINATION lib)
install(FILES MathFunctions.h DESTINATION include)
Next, update MathFunctions/mysqrt.cxx
to use the mathfunctions
and detail
namespaces:
#include
#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(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;
}
}
}
我们 还需要在torial.cxx
中做出一些改变,这样就可以 不用USE_MYMATH
了:
MathFunctions.h
mathfunctions::sqrt
最后, 更新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);
}
At this point, if you build everything, you will notice that linking fails as we are combining a static library without position independent code with a library that has position independent code. The solution to this is to explicitly set the POSITION_INDEPENDENT_CODE
target property of SqrtLibrary to be True no matter the build type.
# 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)
Exercise: We modified MathFunctions.h
to use dll export defines. Using CMake documentation can you find a helper module to simplify this?
生成器表达式在生成生成系统期间被评估,以生成特定于每个生成配置的信息。在许多目标属性(如链接库、包含目录、编译定义和其他)的上下文中允许使用生成器表达式。当使用命令填充这些属性时,也可以使用它们,例如target_link_libraries()、target_include_directories()、target_compile_definitions()等等。
生成器表达式可用于启用条件链接、编译时使用的条件定义、条件包含目录等。这些条件可以基于构建配置、目标属性、平台信息或任何其他可查询信息。有不同类型的生成器表达式,包括逻辑表达式、信息表达式和输出表达式。
逻辑表达式用于创建条件输出。基本表达式是0和1表达式。一个<0:…>将导致空字符串,而<1:…>将导致“…”的内容。它们也可以嵌套。
生成器表达式的一个常见用法是有条件地添加编译器标志,例如用于语言级别或警告的标志。一个好的模式是将此信息与允许传播此信息的接口目标相关联。让我们开始构建接口目标,并指定所需的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)
Next we add the desired compiler warning flags that we want for our project. As warning flags vary based on the compiler we use the COMPILE_LANG_AND_ID
generator expression to control which flags to apply given a language and a set of compiler ids as seen below:
set(gcc_like_cxx "$")
set(msvc_cxx "$")
target_compile_options(tutorial_compiler_flags INTERFACE
"$<${gcc_like_cxx}:$>"
"$<${msvc_cxx}:$>"
)
Looking at this we see that the warning flags are encapsulated inside a BUILD_INTERFACE
condition. This is done so that consumers of our installed project will not inherit our warning flags.
Exercise: Modify MathFunctions/CMakeLists.txt
so that all targets have a target_link_libraries()
call to tutorial_compiler_flags
.
During Installing and Testing (Step 4) of the tutorial we added the ability for CMake to install the library and headers of the project. During Building an Installer (Step 7) we added the ability to package up this information so it could be distributed to other people.
The next step is to add the necessary information so that other CMake projects can use our project, be it from a build directory, a local install or when packaged.
The first step is to update our install(TARGETS)
commands to not only specify a DESTINATION
but also an EXPORT
. The EXPORT
keyword generates and installs a CMake file containing code to import all targets listed in the install command from the installation tree. So let’s go ahead and explicitly EXPORT
the MathFunctions library by updating the install
command in MathFunctions/CMakeLists.txt
to look like:
install(TARGETS MathFunctions tutorial_compiler_flags
DESTINATION lib
EXPORT MathFunctionsTargets)
install(FILES MathFunctions.h DESTINATION include)
Now that we have MathFunctions being exported, we also need to explicitly install the generated MathFunctionsTargets.cmake
file. This is done by adding the following to the bottom of the top-level CMakeLists.txt
:
install(EXPORT MathFunctionsTargets
FILE MathFunctionsTargets.cmake
DESTINATION lib/cmake/MathFunctions
)
At this point you should try and run CMake. If everything is setup properly you will see that CMake will generate an error that looks like:
Target "MathFunctions" INTERFACE_INCLUDE_DIRECTORIES property contains
path:
"/Users/robert/Documents/CMakeClass/Tutorial/Step11/MathFunctions"
which is prefixed in the source directory.
What CMake is trying to say is that during generating the export information it will export a path that is intrinsically tied to the current machine and will not be valid on other machines. The solution to this is to update the MathFunctions target_include_directories
to understand that it needs different INTERFACE
locations when being used from within the build directory and from an install / package. This means converting the target_include_directories
call for MathFunctions to look like:
target_include_directories(MathFunctions
INTERFACE
$
$
)
Once this has been updated, we can re-run CMake and verify that it doesn’t warn anymore.
At this point, we have CMake properly packaging the target information that is required but we will still need to generate a MathFunctionsConfig.cmake
so that the CMake find_package
command can find our project. So let’s go ahead and add a new file to the top-level of the project called Config.cmake.in
with the following contents:
@PACKAGE_INIT@
include ( "${CMAKE_CURRENT_LIST_DIR}/MathFunctionsTargets.cmake" )
Then, to properly configure and install that file, add the following to the bottom of the top-level 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下面创建一个调试和发布目录。最后,你应该有一个布局,看起来像:
─ multi_config
├── debug
└── release
现在,我们需要设置调试和发布版本,这大致需要以下内容:
cd debug
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