美文网首页
CMake使用教程(四)

CMake使用教程(四)

作者: 张坤的笔记 | 来源:发表于2019-11-19 15:56 被阅读0次

    CMake 是一种跨平台的免费开源软件工具,用于使用与编译器无关的方法来管理软件的构建过程。在 Android Studio 上进行 NDK 开发默认就是使用 CMake 管理 C/C++ 代码,因此在学习 NDK 之前最好对 CMake 有一定的了解。

    本文主要以翻译 CMake官方教程文档为主,加上自己的一些理解,该教程涵盖了 CMake 的常见使用场景。由于能力有限,翻译部分采用机翻+人工校对,翻译有问题的地方,说声抱歉。

    开发环境:

    • macOS 10.14.6
    • CMake 3.15.1
    • CLion 2018.2.4

    混合静态和共享

    示例程序地址

    在本节中,我们将展示如何使用 BUILD_SHARED_LIBS 变量来控制 add_library 的默认行为,并允许控制构建没有显式类型 (STATIC/SHARED/MODULE/OBJECT) 的库。

    为此,我们需要将 BUILD_SHARED_LIBS 添加到顶级 CMakeLists.txt。我们使用 option 命令,因为它允许用户有选择地选择该值是 On 还是 Off

    接下来,我们将重构 MathFunctions 使其成为使用 mysqrtsqrt 封装的真实库,而不是要求调用代码执行此逻辑。这也意味着 USE_MYMATH 将不会控制构建 MathFuctions,而是将控制此库的行为。

    第一步是将顶级 CMakeLists.txt 的开始部分更新为:

    # 设置运行此配置文件所需的CMake最低版本
    cmake_minimum_required(VERSION 3.15)
    
    # set the project name and version
    # 设置项目名称和版本
    project(Tutorial VERSION 1.0)
    
    # specify the C++ standard
    # 指定C ++标准
    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
    # 控制静态和共享库的构建位置,以便在Windows上我们无需修改运行可执行文件的路径
    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
    # 添加MathFunctions库
    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
    # 将二进制目录添加到包含文件的搜索路径中,以便我们找到TutorialConfig.h
    target_include_directories(Tutorial PUBLIC
            "${PROJECT_BINARY_DIR}"
            )
    

    现在我们将始终使用 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.
    # 说明与我们链接的任何人都需要包括当前源目录才能找到MathFunctions.h,而我们不需要。
    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
        # 只包含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
        # 声明我们依靠二进制目录找到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
    # 定义标记在Windows上构建时使用declspec(dllexport)
    target_compile_definitions(MathFunctions PRIVATE "EXPORTING_MYMATH")
    
    # install rules
    # 安装规则
    install(TARGETS MathFunctions DESTINATION lib)
    install(FILES MathFunctions.h DESTINATION include)
    

    接下来在 MathFunctions 文件目录下, 新建一个 mysqrt.h 文件,内容如下:

    namespace mathfunctions {
        namespace detail {
            double mysqrt(double x);
        }
    }
    

    接下来在 MathFunctions 文件目录下, 新建一个 MathFunctions.cxx 文件,内容如下:

    #include "MathFunctions.h"
    
    #ifdef USE_MYMATH
    #  include "mysqrt.h"
    #else
    #  include <cmath>
    #endif
    
    namespace mathfunctions {
        double sqrt(double x) {
    #ifdef USE_MYMATH
            return detail::mysqrt(x);
    #else
            return std::sqrt(x);
    #endif
        }
    }
    

    接下来,更新 MathFunctions/mysqrt.cxx 以使用 mathfunctionsdetail 命名空间:

    #include <iostream>
    
    #include "mysqrt.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;
                }
    
                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

    移除 TutorialConfig.h.in 中关于 USE_MYMATH 的定义,最后,更新 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);
    }
    

    此时,如果您构建了所有内容,则会注意到链接会失败,因为我们将没有位置的静态库代码库与具有位置的代码库相结合。解决方案是无论构建类型如何,都将 SqrtLibraryPOSITION_INDEPENDENT_CODE 目标属性显式设置为 True

    # state that SqrtLibrary need PIC when the default is shared libraries
    # 声明默认为共享库时,SqrtLibrary需要PIC
    set_target_properties(SqrtLibrary PROPERTIES
            POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS}
            )
            
    target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
    

    使用 cmake-gui 构建项目,勾选 BUILD_SHARED_LIBS

    在项目根目录运行命令生成可执行文件:

    cmake --build cmake-build-debug
    

    在项目根目录运行生成的可执行文件:

    ./cmake-build-debug/Tutorial 2
    

    终端输出:

    Use the table to help find an initial value 
    Computing sqrt of 2 to be 1.41421
    Computing sqrt of 2 to be 1.41421
    Computing sqrt of 2 to be 1.41421
    Computing sqrt of 2 to be 1.41421
    Computing sqrt of 2 to be 1.41421
    Computing sqrt of 2 to be 1.41421
    Computing sqrt of 2 to be 1.41421
    Computing sqrt of 2 to be 1.41421
    Computing sqrt of 2 to be 1.41421
    Computing sqrt of 2 to be 1.41421
    The square root of 2 is 1.41421
    

    使用 cmake-gui 重新构建项目,取消勾选 BUILD_SHARED_LIBS

    在项目根目录运行命令生成可执行文件:

    cmake --build cmake-build-debug
    

    在项目根目录运行生成的可执行文件:

    ./cmake-build-debug/Tutorial 2
    

    终端输出:

    Use the table to help find an initial value 
    Computing sqrt of 2 to be 1.41421
    Computing sqrt of 2 to be 1.41421
    Computing sqrt of 2 to be 1.41421
    Computing sqrt of 2 to be 1.41421
    Computing sqrt of 2 to be 1.41421
    Computing sqrt of 2 to be 1.41421
    Computing sqrt of 2 to be 1.41421
    Computing sqrt of 2 to be 1.41421
    Computing sqrt of 2 to be 1.41421
    Computing sqrt of 2 to be 1.41421
    The square root of 2 is 1.41421
    

    添加生成器表达式

    示例程序地址

    在构建系统生成期间会评估生成器表达式,以生成特定于每个构建配置的信息。

    在许多目标属性的上下文中允许使用生成器表达式,例如 LINK_LIBRARIESINCLUDE_DIRECTORIESCOMPILE_DEFINITIONS 等。当使用命令填充这些属性时,也可以使用它们,例如 target_link_libraries()target_include_directories()target_compile_definitions()等。

    生成器表达式可用于启用条件链接、编译时使用的条件定义、条件包含目录等。这些条件可以基于构建配置、目标属性、平台信息或任何其他可查询信息。

    生成器表达式有不同类型,包括逻辑,信息和输出表达式。

    逻辑表达式用于创建条件输出,基本的表达式是0和1表达式,即布尔表达式。$<0:…> 代表冒号前的条件为假,表达式的结果为空字符串。 $<1:…> 代表冒号前的条件为真,表达式的结果为“…”的内容

    生成器表达式的一个常见用法是有条件地添加编译器标志,例如语言级别或警告标志。一个好的模式是将此信息与允许传播此信息的 INTERFACE 目标相关联。让我们开始构建 INTERFACE 目标,并指定所需的 C++ 标准级别11,而不是使用 CMACHYCXXY 标准。

    所以下面的代码:

    # specify the C++ standard
    # 指定C ++标准
    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 的情况下应应用的标志,如下所示:

    # add compiler warning flags just when building this project via
    # the BUILD_INTERFACE genex
    # 仅当通过BUILD_INTERFACE生成此项目时添加编译器警告标志
    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_flagstarget_link_libraries

    添加导出配置

    示例程序地址

    在本教程的 “安装” 一节,我们增加了 CMake 安装库和项目头的能力。在 "生成安装程序“ 一节,我们添加了打包此信息的功能,以便将其分发给其他人。

    下一步是添加必要的信息,以便其他 CMake 项目可以使用我们的项目,无论是构建目录、本地安装还是打包。

    第一步是更新我们的 install(TARGETS) 命令,不仅要指定 DESTINATION,还要指定 EXPORTEXPORT 关键字将生成并安装一个CMake文件,该文件包含用于从安装树中导入 install 命令中列出的所有目标的代码。通过更新 MathFunctions/CMakeLists.txt 中的 install 命令,显式导出 MathFunctions库,如下所示:

    # install rules
    # 安装规则
    install(TARGETS MathFunctions tutorial_compiler_flags
            DESTINATION lib
            EXPORT MathFunctionsTargets)
    install(FILES MathFunctions.h DESTINATION include)
    

    现在我们已经导出了 MathFunctions,我们还需要显式安装生成的 MathFunctionsTargets.cmake 文件。这是通过将以下内容添加到顶级 CMakeLists.txt 的底部来完成的:

    # install the configuration targets
    # 安装配置目标
    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,让 CMake 理解在从生成目录和安装/打包中使用时需要不同的接口位置。这意味着将 MathFunctions 调用的 target_include_directories 转换为如下所示:

    # state that anybody linking to us needs to include the current source dir
    # to find MathFunctions.h, while we don't.
    # 说明与我们链接的任何人都需要包括当前源目录才能找到MathFunctions.h,而我们不需要。
    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 的底部添加以下内容:

    # install the configuration targets
    # 安装配置目标
    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 的底部:

    # generate the export targets for the build tree
    # needs to be after the install(TARGETS ) command
    # 在install(TARGETS)命令之后生成生成树的导出目标
    export(EXPORT MathFunctionsTargets
            FILE "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsTargets.cmake"
            )
    

    通过此导出调用我们将生成一个 Targets.cmake,允许在构建目录中配置的 MathFunctionsConfig.cmake 由其他项目使用,而无需安装它。

    CMake使用教程系列文章

    相关文章

      网友评论

          本文标题:CMake使用教程(四)

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