首页 > 编程语言 > 详细

[C\C++] CMake构建系统

时间:2020-05-07 22:41:35      阅读:72      评论:0      收藏:0      [点我收藏+]

前言

  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.cpp,zip.cpp和lzma.cpp编译而来),一个叫做zipapp的可执行目标(由zipapp.cpp编译而来,并链接了archive静态库)。

可执行二进制目标

  使用add_executable()指令可以定义一个可执行二进制目标:

add_executable(mytool mytool.cpp)

  add_custom_command()等生成构建规则的指令可以使用生成的可执行目标调用命令行。CMake的构建系统可以保证在命令行执行之前构建它所使用的可执行目标。

二进制库类型

普通库

  默认情况下,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()指令默认生成静态库的行为为生成共享库。

  如果不关心使用静态库还是动态库,可以使用MODULE库类型,这一类型不能作为target_link_libraries()指令的参数。通常对于在运行时,由程序自己负责加载的库使用这一类型。

add_library(archive MODULE 7z.cpp)

苹果框架

  可以对共享库目标使用FRAMEWORK目标属性来生成macOS或iOS框架包。通过MACOSX_FRAMEWORK_IDENTIFIER可以设置框架包的标识符。

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

Object库

  Object库类型用来定义编译源代码生成的Object文件集合。它可以被作为其它目标的输入来源:

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)

  在链接阶段,使用Object库的其它目标会直接链接这些Object文件。

  Object库也可以被链接到其它目标中去:

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文件,也就是说当一个使用Object库的目标编译时,Object库的构建需求满足后,之后其它使用Object库的目标有关Object库的构建需求在编译前就已经满足。

  Object库不能被作为add_custom_command(TARGET)指令的TARGET参数。但Object文件列表可以被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
)

  需要注意构建需求的设计不仅仅是为了传递编译选项给下游。设置的参数必须是构建需求。

目标属性

INCLUDE_DIRECTORIES,COMPILE_DEFINITIONS和COMPILE_OPTIONS目标属性在编译二进制目标的源文件时被使用。

INCLUDE_DIRECTORIES属性中的条目会被加上-I或-isystem前缀,按照条目在属性中出现的顺序加入编译使用的命令行中。

COMPILE_DEFINITIONS属性中的条目会被加上-D或/D前缀,按照不确定的顺序加入编译使用的命令行中。为了方便SHARED和MODULE库目标的使用,DEFINE_SYMBOL目标属性也被加入编译命令行中。

COMPILE_OPTIONS属性中的条目被转义处理后,按照它们在属性中出现的顺序加入编译命令行。部分编译选项可能会进行单独的特殊处理,比如POSITION_INDEPENDENT_CODE。

INTERFACE_INCLUDE_DIRECTORIES,INTERFACE_COMPILE_DEFINITIONS和INTERFACE_COMPILE_OPTIONS目标属性的内容也是构建需求,它们指定的内容必须被编译链接的目标所满足。对于任意二进制目标,target_link_libraries()指令所指定链接的目标的INTERFACE目标属性需要被满足。

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依赖,archive的构建需求被传递给consumer。serialization是archiveExtras的PRIVATE依赖,serialization的构建需求不会被传递给consumer。

  一般而言,如果一个目标仅仅使用了一个库的实现,并且目标本身没有使用库的头文件,可以对这一依赖使用PRIVATE关键字。如果目标本身使用了库的实现和头文件(比如用来做类的继承),应该对这一依赖使用PUBLIC关键字。如果目标本身只使用了库的头文件,而没有使用它的实现,应该对这一依赖使用INTERFACE关键字。target_link_libraries()指令可以多次使用依赖关键字:

target_link_libraries(archiveExtras
  PUBLIC archive
  PRIVATE serialization
)

  构建需求传递是通过将依赖对象的INTERFACE类属性添加到目标的非INTERFACE类属性完成的。比如,INTERFACE_INCLUDE_DIRECTORIES的属性值会被添加到依赖它的目标的INCLUDE_DIRECTORIES属性中。对于对顺序有要求的情况,可以通过指令设置属性来更新顺序。

  比如,一个目标所链接的库的顺序必须是:lib1,lib2,lib3,但包含目录的顺序必须是:lib3,lib1,lib2,可以这样设置:

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

  被install(EXPORT)指令导出的安装目标指定构建需求时需要格外小心。

兼容接口属性

  一些目标属性被用来在目标和依赖的接口之间进行兼容。比如,POSITION_INDEPENDENT_CODE目标属性可以用来指定一个目标是否被作为位置无关代码进行编译。一个目标也可以指定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都被作为位置无关代码进行编译。由于位置无关是SHARED库的默认设置,所以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库要求它的使用者被编译为位置无关代码,然而目标exe1被指定编译为非位置无关代码,由此引发CMake的错误诊断。

  lib1和lib2的构建需求也是不兼容的。它们中的一个要求使用者被编译为位置无关代码,另一个要求使用者被编译为非位置无关代码。目标exe2同时链接了这两个库,引发构建需求冲突。

  为了构建需求的兼容,对于POSITION_INDEPENDENT_CODE属性的设置应该和依赖的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

  非布尔属性也可以参与兼容接口计算。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提供了一个用来调试属性来源的功能。可以用它来调试的属性列表可以在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生成的一些构建系统有一些预定义的构建配置集合,这些配置集合被保存在了CMAKE_BUILD_TYPE变量中。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_CMP0041NEW进行编译,这是因为shared_lib目标创建时,策略CMP0041是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)

  上面代码中的Downstream目标会根据ClimbingStats包的来源选择使用-DClimbingStats_FROM_BUILD_LOCATION或-DClimbingStats_FROM_INSTALL_LOCATION进行编译。

包含目录和构建需求

  包含目录在被作为构建需求或被生成器表达式使用时有一些地方需要格外注意。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)指令:

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

  上面的代码等价于在install(EXPORT)生成安装目标时添加${CMAKE_INSTALL_PREFIX}/include到INTERFACE_INCLUDE_DIRECTORIES目标属性中。

  当一个IMPORTED目标的INTERFACE_INCLUDE_DIRECTORIES被使用,属性中的条目会被作为SYSTEM包含目录,就像它们被列在依赖的INTERFACE_SYSTEM_INCLUDE_DIRECTORIES中一样。这可能导致头文件的查找冲突问题,可以通过IMPORTED目标使用者的NO_SYSTEM_FROM_IMPORTED属性控制这一行为。

  如果一个二进制目标被传递链接到Mac OX框架,框架的头文件目录也会被作为一个构建需求。这和将框架目录作为包含目录的效果是一样的。

链接库和生成器表达式

  链接库可以通过生成器表达式来指定。构建需求来自使用者和它所链接的依赖,整个依赖关系应该是一个有向无环图。如果目标的一个链接依赖于一个目标属性,那么这个目标属性不能依赖于这个链接:

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,而lib3依赖于目标的POSITION_INDEPENDENT_CODE属性,造成循环依赖。

输出构件

  构建系统使用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目标属性可以被用来控制运行时构件的输出位置和名称。

库输出构件

  构建系统的库输出构件可以是:

  • 使用add_library()指令以MODULE选项创建的模块库目标生成的一个可载入的模块文件(比如.dll或.so)。
  • 非DLL平台上:使用add_library()指令以SHARED选项创建的SHARED模式的库文件(比如.so或.dylib)。

  LIBRARY_OUTPUT_DIRECTORY和LIBRARY_OUTPUT_NAME目标属性可以用来控制库输出构件的位置和名字。

归档输出构件

  构建系统的归档输出构件可以是:

  • 使用add_library()指令以STATIC选项创建的静态库目标生成的静态库文件。
  • 使用DLL的平台上:使用add_library()指令以SHARED选项创建的SHARED模式的库目标的导入文件(比如.lib或.a)。这一文件只有在库至少导出了一个非托管符号时才存在。
  • 使用DLL的平台上:使用add_executable()指令设置ENABLE_EXPORTS目标属性创建的可执行目标生成的导出库文件(比如.lib)。

  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目标的定义域位于定义它的目录。它包含了子目录,但不包含父目录和兄弟目录。这里的定义域类似于cmake变量的作用域。

  可以定义在整个构建系统全局都可以访问的GLOBAL IMPORTED目标.

  可以参考cmake-packages(7)指南获取更多关于创建IMPORTED目标的信息。

别名目标

  一个ALIAS目标可以用来引用一个二进制目标,并以只读的形式使用这个二进制目标。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目标不可更改,不能被安装和导出。ALIAS对于构建系统的描述是完全局部的。可以通过读取ALIASED_TARGET属性确定一个名字是否是一个ALIAS别名:

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

接口库

  一个INTERFACE目标除了没有LOCATION,并且是可变的,其它和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_*类属性。
  • COMPATIBLE_INTERFACE_*类内建属性。
  • EXPORT_NAME
  • NAME
  • IMPORTED_LIBNAME_*类属性。
  • 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
)

备注

  引用自:https://zhuanlan.zhihu.com/p/56167140

[C\C++] CMake构建系统

原文:https://www.cnblogs.com/wxxujian/p/12845500.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!