zoukankan      html  css  js  c++  java
  • cmake-buildsystem(7)

    https://cmake.org/cmake/help/v3.18/manual/cmake-buildsystem.7.html

    介绍

    一个cmake基础的编译系统是有一系列高层次的逻辑标签组成的。每一个目标都指定一个可执行文件或是类库,或者是包含定制命令的定制目标。目标之间的依赖在编译系统中表现来决定编译的顺序和规则,在修改时重建。

    编译目标

    可执行文件和类库使用add_executable()和add_library()定义。最终结果的二进制文件包含前缀,后缀和对于平台的扩展。二进制目标之间的依赖使用target_link_libraries()命令:

    add_library(archive archive.cpp zip.cpp lzma.cpp)
    add_executable(zipapp zipapp.cpp)
    target_link_libraries(zipapp archive)
    

    archive定义为一个静态库-一个archive包含从archive.cpp, zip.cpp, and lzma.cpp编译的结构体。zipapp是一个从zipapp.cpp链接编译的可执行文件。当链接zipapp时,会把archive静态库链接进去。

    二进制可执行文件

    add_executable()命令定义了可执行目标:

    add_executable(mytool mytool.cpp)
    

    命令行,比如add_custom_command(),构建规则就是在编译的时候可以明确的使用可执行目标作为一个命令。编译系统的规则可以保证可执行文件在使用前编译好。

    二进制类库类型

    常规的类库

    默认情况下add_library()命令行定义的是静态库,除非指定一个类型。可以指定如下类型:

    add_library(archive SHARED archive.cpp zip.cpp lzma.cpp)
    
    add_library(archive STATIC archive.cpp zip.cpp lzma.cpp)
    

    BUILD_SHARED_LIBS变量可以修改add_library()的默认行为,改成默认是共享库。

    在编译系统定义的全局上下文中,不管详细的类库是共享的还是静态的,这都没有关系-不管类库是什么类型,命令行,依赖定义和其他的api工作方式都是类似的。组件类库不一样,它并不会链接进去-它不会在target_link_libraries()的右边使用。这个类型是在运行的时候加载插件的模式。如果类库没有输出任何非托管符号(比如,windows的DLL,C++/CLI DLL)它就不能是共享库,因为cmake会把共享库作为一个符号输出。

    add_library(archive MODULE 7z.cpp)
    

    苹果架构

    一个共享库需要标明FRAMEWORK目标属性,用来创建macOS或者iOS的框架bundle。MACOSX_FRAMEWORK_IDENTIFIER设置了CFBundleIdentifier并且定义了唯一的bundle。

    add_library(MyFramework SHARED MyFramework.cpp)
    set_target_properties(MyFramework PROPERTIES
      FRAMEWORK TRUE
      FRAMEWORK_VERSION A
      MACOSX_FRAMEWORK_IDENTIFIER org.cmake.MyFramework
    )
    

    对象库

    这个对象库类型定义了一个非归档的合集,会把所有的对象文件,从编译的 给定的源文件中采集出来。这个对象文件的采集可以用作源码安装或是其他目标:

    add_library(archive OBJECT archive.cpp zip.cpp lzma.cpp)
    
    add_library(archiveExtras STATIC $<TARGET_OBJECTS:archive> extras.cpp)
    
    add_executable(test_exe $<TARGET_OBJECTS:archive> test.cpp)
    

    这个链接或是打包的步骤,其他对象将会使用这个目标搜集的文件,避免从源码中获得。

    否则,对象库将会链接到其他目标:

    add_library(archive OBJECT archive.cpp zip.cpp lzma.cpp)
    
    add_library(archiveExtras STATIC extras.cpp)
    target_link_libraries(archiveExtras PUBLIC archive)
    
    add_executable(test_exe test.cpp)
    target_link_libraries(test_exe archive)
    

    这个链接或是打包步骤,其他目标将会使用对象文件,从OBJECT类库中获得。此外,在编译其他目标源码时,这个OBJECT是需要提供的。还有就是这个使用的时候的依赖需要,是传递的,对于其他的目标编译。

    对象类库,对于TARGET,在使用add_custom_command(TARGET)的时候,可能不会使用。但是列出的对象可能会被add_custom_command(OUTPUT)或者file(GENERATE)在用$<TARGET_OBJECTS:objlib>的时候使用。

    编译规格说明和使用需求

    target_include_directories(),target_compile_definitions()和target_compile_options()命令定义了编译规格说明和二进制目标的使用需求。这些命令把INCLUDE_DIRECTORIES, COMPILE_DEFINITIONS和COMPILE_OPTIONS目标的属性分别致到库中,或者还有INTERFACE_INCLUDE_DIRECTORIES, INTERFACE_COMPILE_DEFINITIONS 和 INTERFACE_COMPILE_OPTIONS。

    每个命令都有PRIVATE, PUBLIC 和 INTERFACE 模式。PRIVATE 模式仅仅把non-INTERFACE_ 目标属性的变形体致到库中,INTERFACE 模式仅仅把INTERFACE_ 内容致到库中。PUBLIC 模式把两个属性都致到库中。每一个命令都会被各种关键字引用:

    target_compile_definitions(archive
      PRIVATE BUILDING_WITH_LZMA
      INTERFACE USING_ARCHIVE_LIB
    )
    

    需要注意的是,使用需求并没有设计成使下游使用COMPILE_OPTIONS 或者 COMPILE_DEFINITIONS 等等更方便。这个属性的内容是必须的,并不是建议或是便捷的。

    参考创建重新打包的模块cmake-packages(7)的手册,讨论额外的注意,当定义创建重更新发布的包的时候的使用需求。

    目标属性

    INCLUDE_DIRECTORIES, COMPILE_DEFINITIONS 和 COMPILE_OPTIONS的目标属性需要在编译二进制目标源码的时候适当的设置。

    INCLUDE_DIRECTORIES条目会使用编译行-I 或者 -isystem 前缀,用来添加到属性值中。

    COMPILE_DEFINITIONS条目会添加前缀使用-D 或者 /D 添加到未定义顺序的编译行中。DEFINE_SYMBOL目标属性同样是添加到编译定义中,作为特殊的便捷的情况用作SHARED 和 MODULE 类目标。

    COMPILE_OPTIONS对于shell是脱离的,并且会顺序的添加到属性值中。一些编译选项有着特殊的分离的句柄,比如POSITION_INDEPENDENT_CODE.

    INTERFACE_INCLUDE_DIRECTORIES, INTERFACE_COMPILE_DEFINITIONS 和 INTERFACE_COMPILE_OPTIONS 的目标属性是必须的 - 他们定义了内容,使用者必须正确的编译并且链接,当他们出现的时候。对于任何二进制目标,每个INTERFACE_属性在每个目标定义于target_link_libraries()命令是充满迷惑的:

    set(srcs archive.cpp zip.cpp)
    if (LZMA_FOUND)
      list(APPEND srcs lzma.cpp)
    endif()
    add_library(archive SHARED ${srcs})
    if (LZMA_FOUND)
      # The archive library sources are compiled with -DBUILDING_WITH_LZMA
      target_compile_definitions(archive PRIVATE BUILDING_WITH_LZMA)
    endif()
    target_compile_definitions(archive INTERFACE USING_ARCHIVE_LIB)
    
    add_executable(consumer)
    # Link consumer to archive and consume its usage requirements. The consumer
    # executable sources are compiled with -DUSING_ARCHIVE_LIB.
    target_link_libraries(consumer archive)
    

    因为设置源代码文件夹和相关的编译文件夹会添加到INCLUDE_DIRECTORIES, CMAKE_INCLUDE_CURRENT_DIR参数中去,这样很常见,这样做可以非常方便的田间相关的文件到所有目标的INCLUDE_DIRECTORIES。参数CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE可以增加相关的目录到所有目标的INTERFACE_INCLUDE_DIRECTORIES。这样会导致使用多个不同的目录,当为了更便捷,使用target_link_libraries()命令的时候。

    可传递的使用需求

    使用需求的目标参数可以传递给依赖的项。target_link_libraries()有PRIVATE, INTERFACE 和 PUBLIC关键字用来控制传播。

    add_library(archive archive.cpp)
    target_compile_definitions(archive INTERFACE USING_ARCHIVE_LIB)
    
    add_library(serialization serialization.cpp)
    target_compile_definitions(serialization INTERFACE USING_SERIALIZATION_LIB)
    
    add_library(archiveExtras extras.cpp)
    target_link_libraries(archiveExtras PUBLIC archive)
    target_link_libraries(archiveExtras PRIVATE serialization)
    # archiveExtras is compiled with -DUSING_ARCHIVE_LIB
    # and -DUSING_SERIALIZATION_LIB
    
    add_executable(consumer consumer.cpp)
    # consumer is compiled with -DUSING_ARCHIVE_LIB
    target_link_libraries(consumer archiveExtras)
    

    因为archive对于archiveExtras的依赖是PUBLIC。所以它的使用需求也会传递给消费者。因为serialization对于archiveExtras的依赖是PRIVATE,所以使用需求不会传递给消费者。

    一般情况下,如果依赖仅仅在实现类库的时候使用,那么应该通过target_link_libraries()定义为PRIVATE关键字。而不是在头文件中。如果依赖已经在头文件中(比如类的接口)定义了,那么它应该定义为PUBLIC依赖。一个依赖,如果没有被使用在实现类库中,但是仅仅被自己的头文件使用,那么应该定义为INTERFACE。target_link_libraries()可以被多种使用引用,关键字是:

    target_link_libraries(archiveExtras
      PUBLIC archive
      PRIVATE serialization
    )
    

    使用需求通过从依赖读取目标属性的INTERFACE_变体进行传播,并且附加对应的值为non-INTERFACE_变体。比如INTERFACE_INCLUDE_DIRECTORIES的依赖已经读取了,并且附加到INCLUDE_DIRECTORIES注意顺序是否相关并且保持了,并且从target_link_libraries()调用的顺序并不能保证正确的编译,使用适当的命令来直接设置属性,可以更新到正确的顺序。

    比如,链接类库的顺序必须是lib1 lib2 lib3,但是引入的目录必须是lib3 lib1 lib2:

    target_link_libraries(myExe lib1 lib2 lib3)
    target_include_directories(myExe
      PRIVATE $<TARGET_PROPERTY:lib3,INTERFACE_INCLUDE_DIRECTORIES>)
    

    注意,当定义使用需求是,必须通过安装的EXPORT报告安装进度。参考创建包获取更多的信息。

    兼容接口属性

    有些目标属性需要兼容目标和接口的依赖。比如,POSITION_INDEPENDENT_CODE目标属性将会定义一个boolean值用来判断是否编译一个位置无关的代码,拥有平台定义的结果。一个目标也可以定义使用需求INTERFACE_POSITION_INDEPENDENT_CODE用来告诉消费者必须编译位置无关的代码。

    add_executable(exe1 exe1.cpp)
    set_property(TARGET exe1 PROPERTY POSITION_INDEPENDENT_CODE ON)
    
    add_library(lib1 SHARED lib1.cpp)
    set_property(TARGET lib1 PROPERTY INTERFACE_POSITION_INDEPENDENT_CODE ON)
    
    add_executable(exe2 exe2.cpp)
    target_link_libraries(exe2 lib1)
    

    这里,exe1 和 exe2都被便以为位置无关的代码。lib1是因为默认是共享类库,所以被编译为位置无关的代码。如果依赖出现了冲突,不兼容的需求导致cmake的检测错误:

    add_library(lib1 SHARED lib1.cpp)
    set_property(TARGET lib1 PROPERTY INTERFACE_POSITION_INDEPENDENT_CODE ON)
    
    add_library(lib2 SHARED lib2.cpp)
    set_property(TARGET lib2 PROPERTY INTERFACE_POSITION_INDEPENDENT_CODE OFF)
    
    add_executable(exe1 exe1.cpp)
    target_link_libraries(exe1 lib1)
    set_property(TARGET exe1 PROPERTY POSITION_INDEPENDENT_CODE OFF)
    
    add_executable(exe2 exe2.cpp)
    target_link_libraries(exe2 lib1 lib2)
    

    lib1设置的INTERFACE_POSITION_INDEPENDENT_CODE与exe1的属性POSITION_INDEPENDENT_CODE不兼容。这个类库需要使用者使用位置无关的代码编译,但是可执行文件定义的是不以位置无关的代码编译,所以有检测错误。

    lib1 和 lib2的配置也不兼容。一个需要使用者便以为位置无关的代码,另一个需要消费者不以位置无关的代码编译。因为exe2链接了这两个,有冲突,所以有检测错误。

    为了兼容,POSITION_INDEPENDENT_CODE属性,如果设置了,就必须作为boolean类型,必须与从所有的依赖继承的INTERFACE_POSITION_INDEPENDENT_CODE属性一致。

    这个关于依赖接口配置的属性可以扩展为其他的通过在COMPATIBLE_INTERFACE_BOOL定义的属性内容。每一个定义的属性都必须在使用者和依赖着中使用INTERFACE_前缀来保证兼容:

    add_library(lib1Version2 SHARED lib1_v2.cpp)
    set_property(TARGET lib1Version2 PROPERTY INTERFACE_CUSTOM_PROP ON)
    set_property(TARGET lib1Version2 APPEND PROPERTY
      COMPATIBLE_INTERFACE_BOOL CUSTOM_PROP
    )
    
    add_library(lib1Version3 SHARED lib1_v3.cpp)
    set_property(TARGET lib1Version3 PROPERTY INTERFACE_CUSTOM_PROP OFF)
    
    add_executable(exe1 exe1.cpp)
    target_link_libraries(exe1 lib1Version2) # CUSTOM_PROP will be ON
    
    add_executable(exe2 exe2.cpp)
    target_link_libraries(exe2 lib1Version2 lib1Version3) # Diagnostic
    

    Non-boolean的属性同样可以参与到兼容接口的计算。属性在COMPATIBLE_INTERFACE_STRING定义的,必须没有在所有的传递定义依赖中声明或是是同样的内容。这样可以保证多个不兼容的版本类库不会在传递参数中编译到一起:

    add_library(lib1Version2 SHARED lib1_v2.cpp)
    set_property(TARGET lib1Version2 PROPERTY INTERFACE_LIB_VERSION 2)
    set_property(TARGET lib1Version2 APPEND PROPERTY
      COMPATIBLE_INTERFACE_STRING LIB_VERSION
    )
    
    add_library(lib1Version3 SHARED lib1_v3.cpp)
    set_property(TARGET lib1Version3 PROPERTY INTERFACE_LIB_VERSION 3)
    
    add_executable(exe1 exe1.cpp)
    target_link_libraries(exe1 lib1Version2) # LIB_VERSION will be "2"
    
    add_executable(exe2 exe2.cpp)
    target_link_libraries(exe2 lib1Version2 lib1Version3) # Diagnostic
    

    COMPATIBLE_INTERFACE_NUMBER_MAX属性定义了内容,作为一个数值,所有定义的最大数值将会被计算:

    add_library(lib1Version2 SHARED lib1_v2.cpp)
    set_property(TARGET lib1Version2 PROPERTY INTERFACE_CONTAINER_SIZE_REQUIRED 200)
    set_property(TARGET lib1Version2 APPEND PROPERTY
      COMPATIBLE_INTERFACE_NUMBER_MAX CONTAINER_SIZE_REQUIRED
    )
    
    add_library(lib1Version3 SHARED lib1_v3.cpp)
    set_property(TARGET lib1Version3 PROPERTY INTERFACE_CONTAINER_SIZE_REQUIRED 1000)
    
    add_executable(exe1 exe1.cpp)
    # CONTAINER_SIZE_REQUIRED will be "200"
    target_link_libraries(exe1 lib1Version2)
    
    add_executable(exe2 exe2.cpp)
    # CONTAINER_SIZE_REQUIRED will be "1000"
    target_link_libraries(exe2 lib1Version2 lib1Version3)
    

    同样,COMPATIBLE_INTERFACE_NUMBER_MIN被使用于计算所有依赖的最好数值。

    每一个计算兼容属性的值都会在使用者使用生成表达式生成的时候读取。

    注意每一个被依赖者,对于每一个兼容接口的定义属性集合都不能交叉的定义。

    属性原始值调试

    由于编译的规格可以通过依赖定义,所以在创建目标文件和代码时,为了设置编译规则,可能会导致代码位置缺失,这样很难找到原因。cmake(1)提供了调试的方法,用于打印原始的属性内容,有可能是依赖决定的。这些属性可以通过在CMAKE_DEBUG_TARGET_PROPERTIES参数文档中列出来进行调试:

    set(CMAKE_DEBUG_TARGET_PROPERTIES
      INCLUDE_DIRECTORIES
      COMPILE_DEFINITIONS
      POSITION_INDEPENDENT_CODE
      CONTAINER_SIZE_REQUIRED
      LIB_VERSION
    )
    add_executable(exe1 exe1.cpp)
    

    注意列出在COMPATIBLE_INTERFACE_BOOL 或者 COMPATIBLE_INTERFACE_STRING的属性,调试输出会显示谁设置决定了这个属性值,并且还有哪些依赖定义了这个值。比如COMPATIBLE_INTERFACE_NUMBER_MAX和COMPATIBLE_INTERFACE_NUMBER_MIN,调试会输出从每一个依赖属性的数值,是否这个值决定了最终值。

    通过生成表达式编译规则

    编译规则可能会使用生成表达式,包含的内容是有条件的或者在生成的时候获得的。比如,计算兼容性,将会从TARGET_PROPERTY读取表达式:

    add_library(lib1Version2 SHARED lib1_v2.cpp)
    set_property(TARGET lib1Version2 PROPERTY
      INTERFACE_CONTAINER_SIZE_REQUIRED 200)
    set_property(TARGET lib1Version2 APPEND PROPERTY
      COMPATIBLE_INTERFACE_NUMBER_MAX CONTAINER_SIZE_REQUIRED
    )
    
    add_executable(exe1 exe1.cpp)
    target_link_libraries(exe1 lib1Version2)
    target_compile_definitions(exe1 PRIVATE
        CONTAINER_SIZE=$<TARGET_PROPERTY:CONTAINER_SIZE_REQUIRED>
    )
    

    在这个例子中,exe1的源码文件将会使用-DCONTAINER_SIZE=200编译。

    举鼎编译规则的配置可以通过CONFIG生成表达式方便的设置。

    target_compile_definitions(exe1 PRIVATE
        $<$<CONFIG:Debug>:DEBUG_BUILD>
    )
    

    CONFIG参数与编译时的配置会进行比较,不区分大小写。有IMPORTED时,MAP_IMPORTED_CONFIG_DEBUG的内容同样会作为这个表达式。

    有些编译系统在通过cmake(1)生成时,会有一些设置在CMAKE_BUILD_TYPE里面的编译预选项。为IDE比如Visual Studio 和 Xcode 的编译系统会根据编译配置独立的生成,并且真实的编译配置在编译的时候才会知道。因此,代码比如:

    string(TOLOWER ${CMAKE_BUILD_TYPE} _type)
    if (_type STREQUAL debug)
      target_compile_definitions(exe1 PRIVATE DEBUG_BUILD)
    endif()
    

    会出现在Makefile生成器或是Ninja生成器中,但是并不是跨平台IDE的生成器。除此之外,IMPORTED配置的映射并不被这样认为,所以是禁止的。

    一元生成表达式TARGET_PROPERTY和TARGET_POLICY表达式都被认为是使用者的上下文。也就是使用的参数定义根据使用者而不一样:

    add_library(lib1 lib1.cpp)
    target_compile_definitions(lib1 INTERFACE
      $<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,EXECUTABLE>:LIB1_WITH_EXE>
      $<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,SHARED_LIBRARY>:LIB1_WITH_SHARED_LIB>
      $<$<TARGET_POLICY:CMP0041>:CONSUMER_CMP0041_NEW>
    )
    
    add_executable(exe1 exe1.cpp)
    target_link_libraries(exe1 lib1)
    
    cmake_policy(SET CMP0041 NEW)
    
    add_library(shared_lib shared_lib.cpp)
    target_link_libraries(shared_lib lib1)
    

    exe1将会根据-DLIB1_WITH_EXE编译,而shared_lib将会根据-DLIB1_WITH_SHARED_LIB 和 -DCONSUMER_CMP0041_NEW编译,因为CMP0041在shared_lib创建的时候是NEW。

    BUILD_INTERFACE表达式封装参数仅仅用作使用者来自于同一个编译系统,或者使用者来自于目标通过export()输出到编译目录。INSTALL_INTERFACE表达式封装需要使用者已经安装或是输出install(EXPORT):

    add_library(ClimbingStats climbingstats.cpp)
    target_compile_definitions(ClimbingStats INTERFACE
      $<BUILD_INTERFACE:ClimbingStats_FROM_BUILD_LOCATION>
      $<INSTALL_INTERFACE:ClimbingStats_FROM_INSTALLED_LOCATION>
    )
    install(TARGETS ClimbingStats EXPORT libExport ${InstallArgs})
    install(EXPORT libExport NAMESPACE Upstream::
            DESTINATION lib/cmake/ClimbingStats)
    export(EXPORT libExport NAMESPACE Upstream::)
    
    add_executable(exe1 exe1.cpp)
    target_link_libraries(exe1 ClimbingStats)
    

    在这个例子中,exe1将会以-DClimbingStats_FROM_BUILD_LOCATION参数编译。输出命令生成IMPORTED会以INSTALL_INTERFACE 或者 BUILD_INTERFACE,*_INTERFACE 标记剥离。一个分离工程使用ClimbingStats将会包括:

    find_package(ClimbingStats REQUIRED)
    
    add_executable(Downstream main.cpp)
    target_link_libraries(Downstream Upstream::ClimbingStats)
    

    依赖于ClimbingStats是否使用于编译位置还是安装位置,文档将会使用-DClimbingStats_FROM_BUILD_LOCATION 或者 -DClimbingStats_FROM_INSTALL_LOCATION编译。对于更多的信息了解打包和输出,查看cmake-packages(7)手册。

    引入目录和配置参数

    引入目录的时候需要一些特殊的考虑,当定义配置参数和使用生成表达式的时候。target_include_directories()接收相对路径和绝对路径的引入:

    add_library(lib1 lib1.cpp)
    target_include_directories(lib1 PRIVATE
      /absolute/path
      relative/path
    )
    

    相对路径理解为针对于源码路径。相对路径不允许出现在IMPORTED的INTERFACE_INCLUDE_DIRECTORIES中。

    在使用非复杂生成器表达式的情况下,INSTALL_PREFIX表达式可能会使用在INSTALL_INTERFACE中。它是一个替换标记,在使用者导入的时候,扩展到安装的前缀中。

    在编译树和安装树中,引用目录参数常常是不一样的。BUILD_INTERFACE 和 INSTALL_INTERFACE 生成器表达式可以用作区分不同用法的不同参数。相对路径在INSTALL_INTERFACE中是允许的,理解为针对安装前缀的目录,比如:

    add_library(ClimbingStats climbingstats.cpp)
    target_include_directories(ClimbingStats INTERFACE
      $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/generated>
      $<INSTALL_INTERFACE:/absolute/path>
      $<INSTALL_INTERFACE:relative/path>
      $<INSTALL_INTERFACE:$<INSTALL_PREFIX>/$<CONFIG>/generated>
    )
    

    两个便捷的api都提供了相对于安装目录的参数。CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE需要打开,等价于:

    set_property(TARGET tgt APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES
      $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR};${CMAKE_CURRENT_BINARY_DIR}>
    )
    

    对于每一个有效果的目标,便捷的安装配置是INCLUDES DESTINATION,由安装命令组成:

    install(TARGETS foo bar bat EXPORT tgts ${dest_args}
      INCLUDES DESTINATION include
    )
    install(EXPORT tgts ${other_args})
    install(FILES ${headers} DESTINATION include)
    

    这个与把${CMAKE_INSTALL_PREFIX}/include到INTERFACE_INCLUDE_DIRECTORIES上,对于每一个安装的IMPORTED,在通过安装(EXPORT)安装的时候是一样的。

    当INTERFACE_INCLUDE_DIRECTORIES中重要的配置出现疑惑,这个属性中的条目就会被认为是系统的引用目录,就像列在INTERFACE_SYSTEM_INCLUDE_DIRECTORIES中的依赖。这可能会导致编译器忽略在这些目录发现的头文件的警告。对于重要的目标,这个行为可以通过设置在使用者重要标志的NO_SYSTEM_FROM_IMPORTED上。

    如果二进制文件因为传递性的链接到了macOS框架,框架的头文件目录同样会被添加到配置选项中。这个与把框架目录设置为引用目录的效果是一样的。

    链接类库并且生成表达式

    就像编译规则一样,链接类库也会定义生成表达式条件。但是,使用者的设置选项也会给予链接依赖的收集,有一个额外的限制就是链接依赖必须是有向非循环图。也就是,如果链接到一个目标,这个目标依赖于一个目标属性值,那么这个目标属性值不能依赖其他的依赖:

    add_library(lib1 lib1.cpp)
    add_library(lib2 lib2.cpp)
    target_link_libraries(lib1 PUBLIC
      $<$<TARGET_PROPERTY:POSITION_INDEPENDENT_CODE>:lib2>
    )
    add_library(lib3 lib3.cpp)
    set_property(TARGET lib3 PROPERTY INTERFACE_POSITION_INDEPENDENT_CODE ON)
    
    add_executable(exe1 exe1.cpp)
    target_link_libraries(exe1 lib1 lib3)
    

    exe1的POSITION_INDEPENDENT_CODE属性依赖于链接类库lib3,链接exe1同样直接链接POSITION_INDEPENDENT_CODE属性 property,依赖图出现了环。cmake(1)会报告一个错误。

    输出

    编译系统,通过add_library() 和 add_executable() 命令创建生成二进制输出的规则。准确的输出目录可以在生成二进制的时候确定,因为它有可能会依赖一些编译配置或者链接语言的依赖信息等等。TARGET_FILE, TARGET_LINKER_FILE 和依赖的表达式可以被用作访问生成二进制的名字和目录。但是这个表达式对于OBJECT类库没有作用,因为这种类库没有一个单独生成的文件,比如类库等。

    可以通过配置编译三种输出模式,就如接下来详细介绍的一样。这些等级有DLL平台和非DLL平台区分。所有windows包括cygwin都是dll平台。

    运行时的输出

    一个编译系统运行时输出的产物可能是:

    • 通过add_executable()创建的可执行文件,比如.exe

    • 在DLL平台:通过add_library()的SHARED设置生成的可执行文件共享库,比如.dll

    RUNTIME_OUTPUT_DIRECTORY 和 RUNTIME_OUTPUT_NAME可以用作控制生成输出内容的目录和名字。

    类库输出

    一个编译系统类库的输出内容可能是:

    • 可加载的模块文件,比如.dll .so,是由add_library()附带MODULE设置生成的

    • 在非DLL平台:共享库文件,比如.so .dylib,是由add_library()附带SHARED生成的。

    LIBRARY_OUTPUT_DIRECTORY 和 LIBRARY_OUTPUT_NAME用作控制输出类库的名字和目录。

    打包输出

    一个编译系统打包输出的内容可能是:

    • 通过add_library()命令附带STATIC参数创建的静态库文件,比如.lib .a

    • 在DLL平台:通过add_library()命令附带SHARED参数创建共享库文件,比如.lib等。这个文件仅仅能保证在到处至少一个非托管的描述符的时候存在

    • 在DLL平台:导入的类库文件,比如.lib,通过add_executable()命令附带ENABLE_EXPORTS参数设置。

    • 在AIX平台:链接器导出文件,比如.imp,是一个可执行文件,通过add_executalbe()创建,设置ENABLE_EXPORTS参数

    ARCHIVE_OUTPUT_DIRECTORY 和 ARCHIVE_OUTPUT_NAME可以控制打包输出的文件的目录和名字。

    目录审查命令

    target_include_directories(), target_compile_definitions() 和 target_compile_options()命令一次只对一个命令有效。add_compile_definitions(), add_compile_options() 和 include_directories()有着同样的功能,但是仅仅在目录代替设置的时候有效。

    伪目标

    有些目标,并不表示编译系统的输出,而是输入,比如扩展依赖、别名或者非编译输出。伪目标不回出现在生成编译系统。

    导入目标

    IMPORTED出现在一个已经存在的依赖。通常有上游包定义并且被认为不可修改的。当定义了IMPORTED目标后,可以通过常见的命令,比如target_compile_definitions(), target_include_directories(), target_compile_options() 或 target_link_libraries()调整属性,就像使用其他规则的目标一样。

    IMPORTED目标有着同样的作用对于二进制目标,比如INTERFACE_INCLUDE_DIRECTORIES, INTERFACE_COMPILE_DEFINITIONS, INTERFACE_COMPILE_OPTIONS, INTERFACE_LINK_LIBRARIES, 和 INTERFACE_POSITION_INDEPENDENT_CODE.

    LOCATION也可以从IMPORTED读取,但是很少会这样做。add_custom_command()等命令可以明确的使用IMPORTED EXECUTABLE作为可执行的COMMAND。

    IMPORTED的作用域就是它定义的地方。它可能会被子目录访问或是使用,但是不回被父目录或者兄弟目录使用。这个作用域与cmake变量的作用域类似。

    也可以定义一个GLOBAL IMPORTED,这样就可以在整个编译系统使用。

    查看cmake-packages(7)获取更多关于IMPORTED的信息。

    别名目标

    一个ALIAS是一个名字,可以在二进制目标的时候以只读的形式交换的使用。常用的情况就是示例或单元测试,对于可执行的文件附带类库,可能是一个编译系统的部分或者给予用户配置的分开编译。

    add_library(lib1 lib1.cpp)
    install(TARGETS lib1 EXPORT lib1Export ${dest_args})
    install(EXPORT lib1Export NAMESPACE Upstream:: ${other_args})
    
    add_library(Upstream::lib1 ALIAS lib1)
    

    在另一个目录,我们可以无条件的链接Upstream::lib1,可能是一个IMPORTED目标的包,或者如果是同一个编译系统,就是一个ALIAS目标。

    if (NOT TARGET Upstream::lib1)
      find_package(lib1 REQUIRED)
    endif()
    add_executable(exe1 exe1.cpp)
    target_link_libraries(exe1 Upstream::lib1)
    

    ALIAS目标并不是可变的,可安装的或者可导出的。它万全是本地编译系统的解释。可以通过访问ALIASED_TARGET来判断是不是别名:

    get_target_property(_aliased Upstream::lib1 ALIASED_TARGET)
    if(_aliased)
      message(STATUS "The name Upstream::lib1 is an ALIAS for ${_aliased}.")
    endif()
    

    接口类库

    一个INTERFACE目标是非本地的,并且可移植的,但是在其他方面类似于IMPORTED。

    可能定义一些参数,比如INTERFACE_INCLUDE_DIRECTORIES, INTERFACE_COMPILE_DEFINITIONS, INTERFACE_COMPILE_OPTIONS, INTERFACE_LINK_LIBRARIES, INTERFACE_SOURCES, 和 INTERFACE_POSITION_INDEPENDENT_CODE。仅仅INTERFACE模式的 target_include_directories(), target_compile_definitions(), target_compile_options(), target_sources(), 和 target_link_libraries()命令可以用作INTERFACE类库。

    接口类库主要的用法是只有头文件的类。

    add_library(Eigen INTERFACE)
    target_include_directories(Eigen INTERFACE
      $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
      $<INSTALL_INTERFACE:include/Eigen>
    )
    
    add_executable(exe1 exe1.cpp)
    target_link_libraries(exe1 Eigen)
    

    在这里,Eigen目标是消耗者,并且在编译的时候使用,但是没有链接。

    另一种用法就是完全基于目标的设计:

    add_library(pic_on INTERFACE)
    set_property(TARGET pic_on PROPERTY INTERFACE_POSITION_INDEPENDENT_CODE ON)
    add_library(pic_off INTERFACE)
    set_property(TARGET pic_off PROPERTY INTERFACE_POSITION_INDEPENDENT_CODE OFF)
    
    add_library(enable_rtti INTERFACE)
    target_compile_options(enable_rtti INTERFACE
      $<$<OR:$<COMPILER_ID:GNU>,$<COMPILER_ID:Clang>>:-rtti>
    )
    
    add_executable(exe1 exe1.cpp)
    target_link_libraries(exe1 pic_on enable_rtti)
    

    在这里,编译参数exe1被认为是完全的链接目标,复杂的编译定义标志被封装在INTERFACE类库中。

    允许从INTERFACE类库设置或者读取的属性有:

        Properties matching INTERFACE_*
    
        Built-in properties matching COMPATIBLE_INTERFACE_*
    
        EXPORT_NAME
    
        EXPORT_PROPERTIES
    
        IMPORTED
    
        MANUALLY_ADDED_DEPENDENCIES
    
        NAME
    
        Properties matching IMPORTED_LIBNAME_*
    
        Properties matching MAP_IMPORTED_CONFIG_*
    

    INTERFACE的类库可以被安装或是导出。任何指定的内容都必须被分别安装:

    add_library(Eigen INTERFACE)
    target_include_directories(Eigen INTERFACE
      $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
      $<INSTALL_INTERFACE:include/Eigen>
    )
    
    install(TARGETS Eigen EXPORT eigenExport)
    install(EXPORT eigenExport NAMESPACE Upstream::
      DESTINATION lib/cmake/Eigen
    )
    install(FILES
        ${CMAKE_CURRENT_SOURCE_DIR}/src/eigen.h
        ${CMAKE_CURRENT_SOURCE_DIR}/src/vector.h
        ${CMAKE_CURRENT_SOURCE_DIR}/src/matrix.h
      DESTINATION include/Eigen
    )
    
  • 相关阅读:
    vue——图片懒加载v-lazy
    vue——利用intersectionOberver实现全局appear/disappear事件
    WXS-----学会使用WXS
    使用内联样式
    样式引入
    小程序开发框架----WXSS
    引入内部外部模板
    Selenium元素定位的几种方式
    Response Assertion(响应断言)
    参数化CSV Data Set config元件
  • 原文地址:https://www.cnblogs.com/studywithallofyou/p/13735975.html
Copyright © 2011-2022 走看看