美文网首页
CMake官方教程

CMake官方教程

作者: YottaYuan | 来源:发表于2020-03-17 02:54 被阅读0次

    CMake官方教程

    CMake教程提供了逐步指南,涵盖了CMake可以解决的常见构建系统问题。了解示例项目中各个主题如何协同工作将非常有帮助。教程文档和示例的源代码可以在CMake源代码树的Help/guide/tutorial目录中找到 。每个步骤都有其自己的子目录,其中包含可以用作起点的代码。教程示例是渐进式的,因此每个步骤都为上一步提供了完整的解决方案。

    基本起点(步骤1)

    最基本的项目是从源代码文件构建的可执行文件。对于简单项目,只需要三行CMakeLists.txt文件。这将是本教程的起点。CMakeLists.txtStep1目录中创建一个 文件,如下所示:

    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::stodin 替换atof为我们的项目添加一些C ++ 11功能tutorial.cxx。同时,删除 #include <cstdlib>

      const double inputValue = std::stod(argv[1]);
    

    我们将需要在CMake代码中明确声明应使用正确的标志。在CMake中启用对特定C ++标准的支持的最简单方法是使用CMAKE_CXX_STANDARD变量。对于本教程,请设置CMAKE_CXX_STANDARDCMakeLists.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/tutorialCMake源代码树的 目录并运行以下命令:

    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-guiccmake 用户可以更改的默认值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)

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

    如果平台具有logexp则我们将使用它们来计算函数中的平方根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
    

    如果logexp在系统上可用,那么我们将使用它们来计算函数中的平方根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可执行文件。

    您会注意到我们没有使用logexp,即使我们认为它们应该可用。我们应该很快认识到,我们都忘记了,包括TutorialConfig.hmysqrt.cxx

    我们还需要更新,MathFunctions/CMakeLists.txt以便mysqrt.cxx 知道此文件的位置:

    target_include_directories(MathFunctions
              INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
              PRIVATE ${CMAKE_BINARY_DIR}
              )
    

    进行此更新之后,继续并再次构建项目并运行已构建的Tutorial可执行文件。如果logexp仍未使用,请TutorialConfig.h从构建目录打开生成的文件。也许它们在当前系统上不可用?

    sqrt或mysqrt哪个函数现在可以提供更好的结果?

    指定编译定义

    除了in之外,还有更好的地方供我们保存HAVE_LOGHAVE_EXPTutorialConfig.h吗?让我们尝试使用 target_compile_definitions()

    首先,从中删除定义TutorialConfig.h.in。我们不再需要包含TutorialConfig.hfrom mysqrt.cxx或多余的include in MathFunctions/CMakeLists.txt

    接下来,我们可以将check HAVE_LOGHAVE_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)

    假设,出于本教程的目的,我们决定我们不想使用平台logexp函数,而是想生成一个在mysqrt函数中使用的预计算值表。在本节中,我们将在构建过程中创建表,然后将该表编译到我们的应用程序中。

    首先,让我们删除中对logexp功能 的检查MathFunctions/CMakeLists.txt。然后取出支票HAVE_LOGHAVE_EXPmysqrt.cxx。同时,我们可以删除 。#include <cmath>

    MathFunctions子目录中,提供了一个名为的新源文件 MakeTable.cxx来生成表。

    查看完文件后,我们可以看到该表是作为有效的C ++代码生成的,并且输出文件名作为参数传入。

    下一步是将适当的命令添加到 MathFunctions/CMakeLists.txt文件中以构建MakeTable可执行文件,然后在构建过程中运行它。需要一些命令来完成此操作。

    首先,在的顶部MathFunctions/CMakeLists.txtMakeTable添加的可执行文件, 就像添加任何其他可执行文件一样。

    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.cxxTable.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(),并允许在如何没有显式类型库(控制STATICSHAREDMODULEOBJECT)构建的。

    为此,我们需要添加 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为使用mathfunctionsdetail名称空间:

    #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

    1. 一律包括 MathFunctions.h

    2. 一律使用 mathfunctions::sqrt

    3. 不包括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_LIBRARIESINCLUDE_DIRECTORIESCOMPILE_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,设置VERSIONSOVERSION 特性:

    set_property(TARGET MathFunctions PROPERTY VERSION "1.0.0")
    set_property(TARGET MathFunctions PROPERTY SOVERSION "1")
    

    Step12目录中,创建debugrelease 子目录。布局将如下所示:

    - 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
    

    相关文章

      网友评论

          本文标题:CMake官方教程

          本文链接:https://www.haomeiwen.com/subject/mrxoehtx.html