CMake教程(二)

    技术2024-07-20  72

    安装和测试(步骤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命令,然后选择你的构建工具进行构建。使用以下install选项运行安装步骤

    make install

    测试支持 接下来让我们测试我们的应用程序。在顶级 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可执行文件:

    ctest -Nctest -VVctest -C Debug -VVRUN_TESTS

    添加系统自省(步骤5)

    让我们考虑将一些代码添加到我们的项目中,这取决于目标平台可能不具备的功能。在此示例中,我们将添加一些代码,具体取决于目标平台是否具有log和exp 功能。当然,几乎每个平台都具有这些功能,但对于本教程而言,它们并不常见。

    我们将在TutorialConfig.h.in中使用新的定义,因此请确保在配置该文件之前进行设置。

    include(CheckSymbolExists) check_symbol_exists(log "math.h" HAVE_LOG) check_symbol_exists(exp "math.h" HAVE_EXP) if(NOT (HAVE_LOG AND HAVE_EXP)) unset(HAVE_LOG CACHE) unset(HAVE_EXP CACHE) 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_link_libraries(MathFunctions PRIVATE m) endif() endif()

    现在,将这些定义添加到TutorialConfig.h.in中以便我们可以从mysqrt.cxx中使用它们:

    // does the platform provide exp and log functions? #cmakedefine HAVE_LOG #cmakedefine HAVE_EXP

    如果log和exp在系统上可用,那么我们将使用它们来计算函数中的平方根mysqrt。将以下代码添加到MathFunctions/mysqrt.cxx中的mysqrt函数中

    #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命令,然后选择你的构建工具进行构建,并运行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文件。也许它们在当前系统上不可用?

    指定编译定义 除了in之外,还有更好的地方供我们保存HAVE_LOG和HAVE_EXP值TutorialConfig.h吗?让我们尝试使用 target_compile_definitions()。

    首先,从TutorialConfig.h.in中删除定义。

    接下来,我们可以将check HAVE_LOG和HAVE_EXP移至 MathFunctions/CMakeLists.txt,然后将这些值指定为PRIVATE编译定义。

    include(CheckSymbolExists) check_symbol_exists(log "math.h" HAVE_LOG) check_symbol_exists(exp "math.h" HAVE_EXP) if(NOT (HAVE_LOG AND HAVE_EXP)) unset(HAVE_LOG CACHE) unset(HAVE_EXP CACHE) 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_link_libraries(MathFunctions PRIVATE m) endif() endif() # add compile definitions if(HAVE_LOG AND HAVE_EXP) target_compile_definitions(MathFunctions PRIVATE "HAVE_LOG" "HAVE_EXP") endif()

    完成这些更新后,继续并重新构建项目。运行内置的Tutorial可执行文件,并验证结果与本步骤前面的内容相同。

    添加自定义命令和生成的文件(步骤6)

    假设,出于本教程的目的,我们决定永远不想使用平台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} )

    现在让我们使用生成的表。首先为Table.h修改mysqrt.cxx。接下来,我们可以重写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重新构建项目。构建此项目时,将首先构建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可执行文件。要构建二进制发行版,请从二进制目录运行:

    cpack

    要指定生成器,请使用-G选项。对于多配置版本,用于 -C指定配置。例如:

    cpack -G ZIP -C Debug

    要创建源分发,您可以输入:

    cpack --config CPackSourceConfig.cmake

    运行在二进制目录中找到的安装程序。然后运行已安装的可执行文件,并验证其是否有效。

    添加对仪表盘的支持(步骤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 可执行文件,但尚未构建。进入二进制代码目录,然后运行:

    ctest [-VV] -D Experimental

    请记住,对于多配置生成器(例如Visual Studio),必须指定配置类型:

    ctest [-VV] -C Debug -D Experimental

    ctest可执行文件将构建并测试项目,并将结果提交到Kitware的公共仪表板:https://my.cdash.org/index.php?project=CMakeTutorial.

    Processed: 0.021, SQL: 12