• its time to do cmake right


     

     

    its time to do cmake right

     

    Enough preambles. Does this look familiar to you?

     

    find_package(Boost 1.55 COMPONENTS asio)

    list(APPEND INCLUDE_DIRS ${BOOST_INCLUDE_DIRS})

    list(APPEND LIBRARIES ${BOOST_LIBRARIES})

    include_directories(${INCLUDE_DIRS})

    link_libraries(${LIBRARIES})

     

    includelink是路径级别的,这个路径以下的所有目标都要遵循这个规则,这个太大了,会污染其他目标的规则

     

    Don’t. Just don’t. This is wrong in so many dimensions. You are just blindly throwing stuff into a pot of include directories and compiler ags. There is no structure. There is no transparency. Not to mention that functions like include_directories work at the directory level and apply to all entities dened in scope.

     

    And this isn’t even the real problem, what do you do with transitive dependencies? What about the order of linking? Yes, you need to take care about that yourself. The moment you need to deal with the dependencies of your dependencies is the moment your life needs to be reevaluated.

     

    现代的cmake基于targets,为每个targets设定属性

    Targets and Properties

    Modern CMake is all about targets and properties.

    Targets model the components of you application. An executable is a target, a library is a target. Your application is built as a collection of targets that depend on and use each other.

    Targets have properties. Properties of a target are the source les it’s built from, the compiler options it requires, the libraries it links against. In modern CMake you create a list of targets and dene the necessary properties on them.

     

    注意指定是private还是interface,这个选项是给谁用的

    Build Requirements vs Usage Requirements

    INTERFACE and PRIVATE,一个是内部编译选项,一个是外部使用选项

    Target properties are dened in one of two scopes: INTERFACE and PRIVATE. Private properties are used internally to build the target, while interface properties are used externally by users of the target. In other words, interface properties model usage requirements, whereas private properties model build requirements of targets.

     

    Interface properties have the prex, wait for it, INTERFACE_ prepended to them.

     

    典型的文件夹结构应该是这样

    libjsonutils

    ├── CMakeLists.txt

    ├── include

    └── jsonutils

            └── json_utils.h--------外部用的头文件

    ├── src

        ├── file_utils.h----内部用的头文件

        └── json_utils.cpp

    └── test

        ├── CMakeLists.txt

        └── src

            └── test_main.cpp

     

    cmake_minimum_required(VERSION 3.5)

    project(libjsonutils VERSION 1.0.0 LANGUAGES CXX)

    Nothing surprising here. The rst step is to create our library target:

    add_library(JSONUtils src/json_utils.cpp)

    Now let’s dene some properties on our target. Why not start with the include directories?

    target_include_directories(JSONUtils

    PUBLIC

    $<INSTALL_INTERFACE:include>------库安装好后直接在include

    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>----在构建过程中是在这个include

    PRIVATE

    ${CMAKE_CURRENT_SOURCE_DIR}/src

    )

     

    Our headers are located in two different places: inside src/ , which contains a utility header called file_utils.h , and in include/ , where our public header json_utils.h lives. For building our library

    we need all headers in both locations ( json_utils.cpp includes both), so INCLUDE_DIRS must contain src/ , as well as include/ .

     

    On the other hand, users of jsonutils only need to know about the location of the public header

    json_utils.h , so INTERFACE_INCLUDE_DIRS only needs to contain include/ , but not src/ .

     

    There is still a problem, though. While building jsonutils, include/ is at /home/pablo/libjsonutils/include/ , but after installing our library, it will be under ${CMAKE_INSTALL_PREFIX}/include/ . Therefore, the location of this directory needs to be different generator

    expressions (https://cmake.org/cmake/help/v3.5/manual/cmake-generator-expressions.7.html)

    depending on the situation.

    depending on whether we are building or installing the library. To solve this problem, we use

     

    不要用CMAKE_CXX_FLAGS

    Leave CMAKE_CXX_FLAGS Alone

    不要手动指定标准参数,指定一些属性就OK!

    target_compile_options(JSONUtils PRIVATE -Werror)

    target_compile_features(JSONUtils PRIVATE cxx_std_11)

    Note that there is no reason to manually append -std=c++11 to CMAKE_CXX_FLAGS, let CMake do that for you! Stay away from variable land, model your requirements via properties.

     

    Model dependencies with target_link_libraries

    In CMake, target_link_libraries is used to model dependencies between targets.

    find_package(Boost 1.55 REQUIRED COMPONENTS regex)

    find_package(RapidJSON 1.0 REQUIRED MODULE)

    target_link_libraries(JSONUtils

    PUBLIC Boost::boost RapidJSON::RapidJSON

    PRIVATE Boost::regex

    )

     

    Dependencies (a.k.a link libraries) of a target are just another property and are dened in an INTERFACE or PRIVATE scope. In our case, both rapidjson and boost optional (dened in the target Boost::boost ) have to be interface dependencies and be propagated to users, as they are used in a public header that’s imported by clients.

     

    This means that users of JSONUtils don’t just require JSONUtil ’s interface properties, but also the interface properties of its interface dependencies (which dene the public headers of boost and rapidjson in this case), and those of the dependencies of the dependencies, etc.

     

    But how does CMake solve this problem? Easy, it adds all interface properties of Boost::boost and RapidJSON::RapidJSON to the corresponding JSONUtil ’s own interface properties. This means that users of JSONUtils will transitively receive the interface properties of targets all up the dependency chain.

     

    On the other hand, Boost::regex is only used internally and can be a private dependency. Here, Boost::regexes interface properties will be appended to the corresponding JSONUtil ’s private

    properties, and won’t be propagated to users.

     

    Isn’t this beautiful? Usage requirements are propagated and build requirements encapsulated. Welcome to modern CMake.

     

    关于痛苦的第三方依赖

    自己来处理,70%的findmodule.cmake都没能够正确处理,所以你得亲自来做

    CMake太灵活了,需要一个最佳实践!

     

    Yes, welcome to hell. This is where the real pain begins: 3rdparty dependencies. In the case of rapidjson, a single variable is set to point to its include directories. This is exactly what we don’t want, we don’t want variables, we want targets!

    In my case, 70% of my dependencies didn’t dene any targets in their nd modules or congs. The reality is that CMake usage is an anarchy. There are few rules and too much exibility. We need standard practices, we need guidelines. We have design patterns for C++, why not for CMake?

     

    If you want it done right do it yourself

    你需要考虑一下,有没有可以替换的组件支持modern cmake usage?

    自己写!FindRapidJSON.cmake ourselves:

     

    # FindRapidJSON.cmake

    #

    # Finds the rapidjson library #

    # This will define the following variables #

    #

    #

    RapidJSON_FOUND

    RapidJSON_INCLUDE_DIRS

    #

    # and the following imported targets

    #

    # RapidJSON::RapidJSON

    #

    # Author: Pablo Arias - pabloariasal@gmail.com

    find_package(PkgConfig)

    pkg_check_modules(PC_RapidJSON QUIET RapidJSON)

    find_path(RapidJSON_INCLUDE_DIR NAMES rapidjson.h

    PATHS ${PC_RapidJSON_INCLUDE_DIRS} PATH_SUFFIXES rapidjson

    )

    set(RapidJSON_VERSION ${PC_RapidJSON_VERSION})

    mark_as_advanced(RapidJSON_FOUND RapidJSON_INCLUDE_DIR RapidJSON_VERSION)

    include(FindPackageHandleStandardArgs)

    find_package_handle_standard_args(RapidJSON REQUIRED_VARS RapidJSON_INCLUDE_DIR VERSION_VAR RapidJSON_VERSION

    )

    if(RapidJSON_FOUND)

    set(RapidJSON_INCLUDE_DIRS ${RapidJSON_INCLUDE_DIR})

    endif()

    if(RapidJSON_FOUND AND NOT TARGET RapidJSON::RapidJSON) add_library(RapidJSON::RapidJSON INTERFACE IMPORTED) set_target_properties(RapidJSON::RapidJSON PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${RapidJSON_INCLUDE_DIR}"

    )

    endif()

     

    This is a very simple nd module that looks for rapidjson’s headers in the system and creates the imported target RapidJSON::RapidJSON that we require. I use INTERFACE to indicate that this “library” isn’t really a library, as there is no correponding .a or .so, but just denes usage requirements.

    并不产生a或者so文件,而是仅仅定义使用需求

     

    外部如何使用我们的库呢?jsonutils

     

    find_package(JSONUtils 1.0 REQUIRED)

    target_link_libraries(example JSONUtils::JSONUtils)

     

    为了用户能够正确使用我们的库,我们需要把库安装好

    include(GNUInstallDirs)

    install(TARGETS JSONUtils

    EXPORT jsonutils-export

    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}

    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}

    )

    In CMake, installed targets are registered to exports using the EXPORT argument. Exports are therefore just a set of targets that can be exported and installed. Here we just told CMake to install our library and to register the target in the export jsonutils-export.

     

    Then we can go ahead and install the export that we dened above:

    install(EXPORT jsonutils-targets

    FILE JSONUtilsTargets.cmake

    NAMESPACE JSONUtils::

    DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/JSONUtils

    )

    This will install the import script JSONUtilsTargets.cmake that, when included in other scripts, will load the targets dened in the export jsonutils-export. By using the NAMESPACE argument, we tell CMake to prepend the prex JSONUtils:: to all targets imported.

     

    自己写的config.cmake

    Import your targets inside your Config.cmake

     

    Remember, when clients call find_package(JSONUtils) , CMake will look for and execute a

    JSONUtilsConfig.cmake .

    So that our target JSONUtils::JSONUtils is imported and can be used by clients, we need to load JSONUtilsTargets.cmake in our config file

     

    get_filename_component(JSONUtils_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) include(CMakeFindDependencyMacro)

    find_dependency(Boost 1.55 REQUIRED COMPONENTS regex) find_dependency(RapidJSON 1.0 REQUIRED MODULE)

    if(NOT TARGET JSONUtils::JSONUtils)

    include("${JSONUtils_CMAKE_DIR}/JSONUtilsTargets.cmake")

    endif()

     

    Be aware that JSONUtilsTargets.cmake contains code like:

     

    add_library(JSONUtils::JSONUtils STATIC IMPORTED)

    set_target_properties(JSONUtils::JSONUtils PROPERTIES

    INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"

    INTERFACE_LINK_LIBRARIES "Boost::boost;RapidJSON::RapidJSON;$<LINK_ONLY:Boost::regex>"

    )

     

    github (https://github.com/pabloariasal/modern-cmake-sample)


  • 相关阅读:
    使用npm安装一些包失败了的看过来(npm国内镜像介绍)(解决生成空的abp模板项目一直卡在还原cpm包中)
    .NET CORE 发布到IIS问题 HTTP ERROR 500.30
    .NET Core默认不支持GB2312,使用Encoding.GetEncoding(“GB2312”)的时候会抛出异常。
    .net c# 文件分片/断点续传之下载--客户端
    aspnetcore 实现断点续传
    C# 反射获取属性值、名称、类型以及集合的属性值、类型名称
    C# 3Des两种加密方式 (对应java中的desede/CBC/PKCS5Padding加密)
    Asp.NetCore3.1中多次读取Request.Body
    ASP.NET Core 2.0系列学习笔记-DI依赖注入
    C# Newtonsoft.Json JObject合并对象整理
  • 原文地址:https://www.cnblogs.com/lizhensheng/p/11117269.html
Copyright © 2020-2023  润新知