CMake buildsystem

2023-11-02

官方文档:https://cmake.org/cmake/help/latest/manual/cmake-buildsystem.7.html

介绍

基于CMake的构建系统(buildsystem),其组织形式是一组高级逻辑目标(high-level logical targets)。每个目标(target)对应于一个可执行文件或库,或者一个包含自定义命令的自定义目标。构建系统说明了目标之间的依赖关系,从而确定构建顺序和响应更改时的重生成规则。

二进制目标(Binary Targets)

可执行文件(Executables)和库(libraries)是使用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静态库被连接到zipapp。

二进制可执行文件(Binary Executables)

add_executable()命令定义可执行目标(executable target),如:

add_executable(mytool mytool.cpp)

一些用于生成构建时运行(run at build time)的规则的命令,如add_custom_command(),可以在其COMMAND选项中使用EXECUTABLE目标。

二进制库类型(Binary Library Types)

普通库(Normal Libraries)

默认情况下,不指定库类型,add_library()命令定义STATIC库。
在命令中指定类型:

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()的行为以默认生成共享库。

在整个构建系统定义中,特定库是SHARED还是STATIC在很大程度上无关紧要——无论何种库类型,命令、依赖项和其他API的工作方式都是类似的。MODULE_LIBRARY库类型不同,因为它通常不被链接——它不用于target_link_libraries()命令的右侧。它是一种使用运行时技术加载的插件的类型。如果库未导出任何非托管符号(例如Windows资源DLL、C++/CLI DLL),则库不可以是SHARED类型,因为CMake希望SHARED库导出至少一个符号。

Apple 框架

SHARED库可以用FRAMEWORK 目标属性进行标记,以创建macOS或iOS框架包。具有FRAMEWORK 目标属性的库还应设置FRAMEWORK_VERSION目标属性。根据macOS惯例,此属性通常设置为“A”。MACOSX_FRAMEWORK_IDENTIFIER设置CFBundleIdentifier键,并对包进行唯一识别。

add_library(MyFramework SHARED MyFramework.cpp)
set_target_properties(MyFramework PROPERTIES
FRAMEWORK TRUE
FRAMEWORK_VERSION A # Version “A” is macOS convention
MACOSX_FRAMEWORK_IDENTIFIER org.cmake.MyFramework
)

对象库(Object Libraries)

OBJECT库类型定义了编译给定源文件后产生的对象文件的非归档集合(non-archival collection)。通过使用语法$<TARGET_OBJECTS:name>,对象文件集合可以用作其他目标的源输入。这是一个生成器表达式,可用于将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)

这些其他目标的链接(或归档)步骤除了使用自身来源的对象文件之外,也将使用该指定的对象文件集合。

或者,可以将对象库链接到其他目标(使用target_link_libraries()):

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库的使用要求。此外,这些使用要求将传递到这些其他目标的依赖项。

在使用add_custom_command(TARGET)命令时,对象库不能用作目标。但是,对象列表可以通过$<TARGET_OBJECTS:objlib>表达式在add_custom_command(OUTPUT)file(GENERATE)命令中使用。

构建规范和使用要求(Build Specification and Usage Requirements)

target_include_directories()target_compile_definitions()target_compile_options()命令指定二进制目标的构建规范和使用要求。这些命令分别向目标属性INCLUDE_DIRECTORIESCOMPILE_DEFINITIONSCOMPILE_OPTIONS和/或INTERFACE_INCLUDE_DIRECTORIESINTERFACE_COMPILE_DEFINITIONSINTERFACE_COMPILE_OPTIONS添加数据。

每个命令都有PRIVATEPUBLICINTERFACE模式。PRIVATE模式仅填充目标属性的非INTERFACE_开头的变量,INTERFACE模式仅填充INTERFACE_开头的变量。PUBLIC模式则填充两者。每个命令中可以使用多种关键字:

target_compile_definitions(archive
PRIVATE BUILDING_WITH_LZMA
INTERFACE USING_ARCHIVE_LIB
)

请注意,使用要求的设计目的并不是为了方便起见,例如让下游使用特定的COMPILE_OPTIONSCOMPILE_DEFINITIONS等。属性的内容必须是要求(requirements),而不仅仅是建议(recommendations)或方便(convenience)。

请参阅cmake-packages(7) 手册的Creating Relocatable Packages部分,了解在指定创建用于重新分发的软件包的使用要求时必须注意的其他事项。

目标属性(Target Properties)

编译二进制目标的源文件时,适当使用INCLUDE_DIRECTORIESCOMPILE_DEFINITIONSCOMPILE_OPTIONS目标属性的内容。

INCLUDE_DIRECTORIES中的条目通过-I-isystem前缀按照属性中出现的顺序添加到编译行。

COMPILE_DEFINITIONS中的条目以-D/D作为前缀,并按未指定的顺序添加到编译行。DEFINE_SYMBOL目标属性也被添加为编译定义,作为SHAREDMODULE库目标的特殊方便情况。

COMPILE_OPTIONS中的条目将为shell转义,并按出现在属性值中的顺序添加。一些编译选项具有特殊的单独处理,例如POSITION_INDEPENDENT_CODE

INTERFACE_INCLUDE_DIRECTORIESINTERFACE_COMPILE_DEFINITIONSINTERFACE_COMPILE_OPTIONS目标属性的内容是使用要求(Usage Requirements)——它们指定用户必须使用的内容,以正确编译和链接目标。对于任何二进制目标,将使用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()命令使用多个不同目录中的目标。

可传递的使用要求(Transitive Usage Requirements)

一个目标的使用需求可以传递到依赖项。target_link_libraries()命令通过PRIVATEINTERFACEPUBLIC关键字来控制传递过程。

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)

因为archivearchiveExtrasPUBLIC依赖,所以它的使用要求也会传递给consumer。因为serializationarchiveExtrasPRIVATE依赖,所以它的使用要求不会传递给consumer

通常,一个依赖项如果仅由库的实现使用,而不在头文件中使用,则应在target_link_libraries()命令中使用PRIVATE关键字标识。如果依赖项也在库的头文件中使用(例如用于类继承),则应将其指定为PUBLIC。依赖项如果仅在库的头文件中使用,而不在实现中使用,则应指定为INTERFACEtarget_link_libraries()命令可以组合使用这些关键字:

target_link_libraries(archiveExtras
PUBLIC archive
PRIVATE serialization
)

通过从依赖项中读取目标属性的INTERFACE_开头的变量,并将值附加到操作对象的非接口变量,可以对使用要求进行传递。例如,读取依赖项的INTERFACE_INCLUDE_DIRECTORIES并将其附加到操作对象的INCLUDE_DIRECTORIES。如果顺序是相关的并且需要保持,并且target_link_libraries()调用结果的顺序不允许正确编译,则使用适当的命令直接设置属性可能会更新顺序。

例如,一个目标的linked libraries必须按lib1 lib2 lib3的顺序指定,而include directories必须按lib3 lib1 lib2的顺序指定:

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

注意,对于将使用install(EXPORT)命令进行导出来安装的目标,对其指定使用要求时必须小心。更多信息见Creating Packages

兼容的接口属性(Compatible Interface Properties)

某些目标属性需要在目标和每个依赖项的接口之间兼容。例如,POSITION_INDEPENDENT_CODE 目标属性可以设置布尔值,来指定是否应将目标编译为针对位置独立代码(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都将编译为位置独立代码。lib1也将编译为位置独立代码,因为这是SHARED库的默认设置。如果依赖项具有冲突、不兼容的需求,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链接到这两者,并且它们相互冲突,因此会发出一条CMake错误消息:

CMake Error: The INTERFACE_POSITION_INDEPENDENT_CODE property of “lib2” does
not agree with the value of POSITION_INDEPENDENT_CODE already determined
for “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可用于从依赖关系计算属性的数值最小值。

可以在生成时使用生成器表达式在使用者中读取每个计算得到的“兼容”属性值。

注意,对于每个被依赖者,每个兼容接口属性中指定的属性集不得与任何其他属性中指定的集相交。

属性源调试(Property Origin Debugging)

由于构建规范可以由依赖项确定,因此创建目标的代码和负责设置构建规范的代码的缺乏本地性的特点可能会使代码更难以推理。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_BOOLCOMPATIBLE_INTERFACE_STRING中列出的属性,调试输出显示哪个目标负责设置属性,以及哪些其他依赖项也定义了该属性。对于COMPATIBLE_INTERFACE_NUMBER_MAXCOMPATIBLE_INTERFACE_NUMBER_MIN,调试输出显示每个依赖项给点的该属性的值,以及该值是否确定新的极值。

使用生成器表达式生成规范(Build Specification with Generator Expressions)

生成规范可以使用生成器表达式,其中包含的内容可能是有条件的或在生成时(generate-time)才能确定。例如,可以使用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进行编译。

一元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_INTERFACEBUILD_INTERFACE,*_INTERFACE标记。一个使用ClimbingStats包的独立项目可能包含:

find_package(ClimbingStats REQUIRED)

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

根据是从构建位置还是安装位置使用ClimbingStats包,Downstream目标将使用-DClimbingStats_FROM_BUILD_LOCATION-DClimbingStats_FROM_INSTALL_LOCATION编译。有关包和导出的更多信息,请参阅cmake-packages(7)

include目录和使用要求(Include Directories and Usage Requirements)

当include目录被指定为使用要求以及与生成器表达式一起使用时,需要特别考虑。target_include_directories()命令接受相对和绝对的include目录:

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

相对路径是相对于命令出现的源目录进行解释的。IMPORTED目标的INTERFACE_INCLUDE_DIRECTORIES变量中不允许存在相对路径。

一种复杂的生成器表达式情况是,在INSTALL_INTERFACE表达式的参数内使用INSTALL_PREFIX表达式。它(INSTALL_PREFIX)是一个替换标记,在由使用项目导入时扩展为安装前缀。

生成树(build-tree)和安装树(install-tree)对include目录的使用要求通常有所不同。BUILD_INTERFACEINSTALL_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>
)

CMake提供了两个与include目录使用要求相关的方便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}>
)

针对安装目标的方便API是install(TARGETS)命令中的INCLUDES DESTINATION组件:

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追加到每个已安装的IMPORTED目标的INTERFACE_INCLUDE_DIRECTORIES变量中。

当使用IMPORTED目标的INTERFACE_INCLUDE_DIRECTORIES变量时,其中的条目将被视为SYSTEM include目录,就像它们列在依赖项的INTERFACE_SYSTEM_INCLUDE_DIRECTORIES中一样。这可能会导致在这些目录中找到的头的编译器警告被忽略。可以通过在IMPORTED目标的使用者上设置NO_SYSTEM_FROM_IMPORTED 目标属性,或在IMPORTED目标本身上设置IMPORTED_NO_SYSTEM目标属性来控制IMPORTED目标的此行为。

如果二进制目标被传递链接到macOS框架,则框架的Headers目录也被视为使用要求。这与将框架目录作为include目录传递具有相同的效果。

链接库和生成器表达式(Link Libraries and Generator Expressions)

与生成规范一样,可以使用生成器表达式指定link libraries。然而,由于使用需求的消耗是基于从链接依赖项收集的,因此还有一个额外的限制,即链接依赖项必须形成“有向无环图”。也就是说,如果链接到某个目标依赖于某个目标属性的值,则该目标属性不能依赖于被链接的依赖项:

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属性确定,因此上面的依赖图包含一个循环。cmake将发出错误消息。

输出工件(Output Artifacts)

add_library()add_executable()命令创建的构建系统目标创建规则来生成二进制输出。二进制文件的确切输出位置只能在生成时(generate-time)确定,因为它可能取决于构建配置(build-configuration)和链接依赖项的链接语言(link-language)等。TARGET_FILETARGET_LINKER_FILE和相关表达式可用于访问生成的二进制文件的名称和位置。但是,这些表达式不适用于OBJECT库,因为此类库没有生成与表达式相关的单个文件。

有三种输出可以由目标构建,详见以下章节。它们的分类在DLL平台和非DLL平台之间有所不同。包括Cygwin在内的所有基于Windows的系统都是DLL平台。

运行时输出工件(Runtime Output Artifacts)

一个构建系统目标的运行时输出工件可能是:

  • add_executable()命令创建的可执行目标的可执行文件(例如.exe)。
  • 在DLL平台上:由add_library()命令和SHARED选项创建的共享库目标的可执行文件(例如.dll)。

RUNTIME_OUTPUT_DIRECTORYRUNTIME_OUTPUT_NAME目标属性可用于控制生成树中的运行时输出工件的位置和名称。

库输出工件(Library Output Artifact)

一个构建系统目标的运行时输出工件可能是:

  • add_library()命令和MODULE选项创建的模块库目标的可加载模块文件(例如.dll或.so)。
  • 在非DLL平台:由add_library()命令和SHARED选项创建的共享库目标的共享库文件(例如.so或.dylib)。
    LIBRARY_OUTPUT_DIRECTORYLIBRARY_OUTPUT_NAME目标属性可用于控制构建树中的库输出工件的位置和名称。

归档输出工件(Archive Output Artifacts)

一个构建系统目标的归档输出工件可能是:

  • add_library()命令和STATIC选项创建的静态库目标的静态库文件(例如.lib或.a)。
  • 在DLL平台:由add_library()命令和SHARED选项创建的共享库目标的导入库文件(例如.lib)。仅当库导出至少一个非托管符号时,才保证此文件存在。
  • 在DLL平台:由add_executable()命令创建的设置了ENABLE_EXPORTS 目标属性的可执行目标的导入库文件(例如.lib)。
  • 在AIX:由add_executable()命令创建的设置了ENABLE_EXPORTS 目标属性的可执行目标的链接器导入文件(例如.imp)。

ARCHIVE_OUTPUT_DIRECTORYARCHIVE_OUTPUT_NAME目标属性可用于控制生成树中的归档输出工件位置和名称。

目录范围命令(Directory-Scoped Commands)

target_include_directories()target_compile_definitions()target_compile_options()命令一次只影响一个目标。add_compile_definitions()add_compile_options()include_directories()命令具有类似的功能,但它们在目录范围而不是目标范围内工作。

生成配置(Build Configurations)

配置决定了特定类型构建的规范,例如ReleaseDebug。指定的方式取决于所使用的生成器(generators)类型。对于Makefile生成器和Ninja等单配置生成器,其配置在配置时(configure time)由CMAKE_BUILD_TYPE变量指定。对于Visual Studio、Xcode和Ninja Multi-Config等多配置生成器,配置由用户在构建时(build time)选择, CMAKE_BUILD_TYPE则被忽略。在多配置情况下,可用配置集在配置时由CMAKE_CONFIGURATION_TYPES变量指定,但直到构建阶段才能知道使用的实际配置。这种差异经常被误解,导致出现以下问题代码:

# WARNING: This is wrong for multi-config generators because they don’t use
#                        and typically don’t even set CMAKE_BUILD_TYPE
string(TOLOWER ${CMAKE_BUILD_TYPE} build_type)
if (build_type STREQUAL debug)
    target_compile_definitions(exe1 PRIVATE DEBUG_BUILD)
endif()

应该使用生成器表达式来正确处理特定于配置的逻辑,而不管使用的是哪种生成器。例如:

# Works correctly for both single and multi-config generators
target_compile_definitions(exe1 PRIVATE
    $<$<CONFIG:Debug>:DEBUG_BUILD>
)

在存在IMPORTED目标的情况下,MAP_IMPORTED_CONFIG_DEBUG的内容也由上述$<CONFIG:Debug>表达式解释。

区分大小写(Case Sensitivity)

CMAKE_BUILD_TYPECMAKE_CONFIGURATION_TYPES与其他变量一样,对其值进行的任何字符串比较都是区分大小写的。$<CONFIG>生成器表达式还保留了由用户或CMake默认值设置的配置大小写。例如:

# NOTE: Don’t use these patterns, they are for illustration purposes only.

set(CMAKE_BUILD_TYPE Debug)
if(CMAKE_BUILD_TYPE STREQUAL DEBUG)
    # … will never get here, “Debug” != “DEBUG”
endif()
add_custom_target(print_config ALL
    # Prints “Config is Debug” in this single-config case
    COMMAND ${CMAKE_COMMAND} -E echo “Config is $<CONFIG>”
    VERBATIM
)

set(CMAKE_CONFIGURATION_TYPES Debug Release)
if(DEBUG IN_LIST CMAKE_CONFIGURATION_TYPES)
    # … will never get here, “Debug” != “DEBUG”
endif()

相反,当在内部基于配置修改行为的地方使用配置类型时,CMake对其大小写不敏感。例如,当配置为DebugDEBUGdebug甚至DeBuG时,$<CONFIG:Debug>生成器表达式都将计算为1。因此,您可以在CMAKE_BUILD_TYPECMAKE_CONFIGURATION_TYPES中使用任何大小写混合来指定配置类型,尽管有很强的惯例(请参阅下一节)。如果必须在字符串比较中测试值,请始终首先将值转换为大写或小写,并相应地调整测试。

默认和自定义配置(Default And Custom Configurations)

默认情况下,CMake定义了许多标准配置:

  • Debug
  • Release
  • RelWithDebInfo
  • MinSizeRel

在多配置生成器中,默认情况下CMAKE_CONFIGURATION_TYPES变量将由上述列表(可能是其中的一个子集)填充,除非该变量被项目或用户覆盖。使用的实际配置由用户在构建时选择。

对于单配置生成器,配置在配置时(configure time)使用CMAKE_BUILD_TYPE变量指定,不能在构建时(build time)更改。其默认值通常不是上述标准配置,而是一个空字符串。一个常见的误解是,这与Debug相同,但事实并非如此。用户应始终明确指定构建类型,以避免这一常见问题。

上述标准配置类型在大多数平台上提供了合理的行为,但它们可以扩展以提供其他类型。每个配置都为所使用的语言定义了一组编译器和链接器标志变量。这些变量名遵循约定CMAKE_<LANG>_FLAGS_<CONFIG>,其中<CONFIG>始终是大写配置名称。定义自定义配置类型时,请确保适当设置了这些变量,通常应作为缓存变量。

伪目标(Pseudo Targets)

一些目标类型不表示构建系统的输出,而仅表示输入,例如外部依赖项、别名或其他非构建工件(non-build artifacts)。生成的构建系统中不表示伪目标。

导入目标(Imported Targets)

IMPORTED目标表示预先存在的依赖项。通常,此类目标由上游包定义,应视为不可变的。在声明IMPORTED目标后,可以像使用任何其他常规目标一样,使用诸如target_compile_definitions()target_include_directories()target_compile_options()target_link_libraries()等常用命令来调整其目标属性。

IMPORTED目标可能具有与二进制目标相同的使用要求属性,例如INTERFACE_INCLUDE_DIRECTORIESINTERFACE_COMPILE_DEFINITIONSINTERFACE_COMPILE_OPTIONSINTERFACE_LINK_LIBRARIESINTERFACE_POSITION_INDEPENDENT_CODE

也可以从IMPORTED目标读取LOCATION,尽管很少有理由这样做。诸如add_custom_command()之类的命令可以清晰地将IMPORTED EXECUTABLE目标用作COMMAND选项的可执行对象。

IMPORTED目标的定义范围是定义它的目录。它可以被子目录访问和使用,但不能被父目录或同级目录访问和使用。该范围类似于cmake变量的范围。

还可以定义GLOBAL IMPORTED目标,该目标可以在构建系统全局中访问。

有关使用IMPORTED目标创建包的更多信息,请参阅cmake-packages手册。

别名目标(Alias Targets)

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)

别名目标不可更改、安装或导出。它们对于构建系统描述来说是完全局部的。通过从名称中读取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 Libraries)

INTERFACE库目标不编译源代码,也不在磁盘上生成库工件,因此它没有LOCATION

可以指定使用要求,例如INTERFACE_INCLUDE_DIRECTORIESINTERFACE_COMPILE_DEFINITIONSINTERFACE_COMPILE_OPTIONSINTERFACE_LINK_LIBRARIESINTERFACE_SOURCESINTERFACE_POSITION_INDEPENDENT_CODE。只有target_include_directories()target_compile_definitions()target_compile_options()target_sources()target_link_libraries()命令在指定INTERFACE模式时才可以使用INTERFACE库。

从CMake 3.19开始,INTERFACE库目标可以选择性地包含源文件。包含源文件的INTERFACE库将作为构建目标包含在生成的构建系统中。它不编译源代码,但可能包含用于生成其他源代码的自定义命令。此外,IDE将显示源文件作为目标的一部分来交互式阅读和编辑。

INTERFACE库的一个主要用例是header-only libraries。从CMake 3.23开始,头文件可以通过使用target_sources()命令将其添加到头文件集合来与库相关联:

add_library(Eigen INTERFACE)

target_sources(Eigen INTERFACE
    FILE_SET HEADERS
        BASE_DIRS src
        FILES src/eigen.h src/vector.h src/matrix.h
)

add_executable(exe1 exe1.cpp)
target_link_libraries(exe1 Eigen)

在这里指定FILE_SET时,我们定义的BASE_DIRS自动成为目标Eigen的使用要求中的include directories。编译时会满足和使用目标的使用需求,但对链接没有影响。

另一个用例是针对使用需求采用完全以目标为中心(target-focussed)的设计:

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的构建规范完全表示为链接(linked)目标,特定于编译器的标志的复杂性封装在INTERFACE库目标中。

可以安装和导出INTERFACE库。我们可以将默认头文件集合与目标一起安装:

add_library(Eigen INTERFACE)

target_sources(Eigen INTERFACE
    FILE_SET HEADERS
        BASE_DIRS src
        FILES src/eigen.h src/vector.h src/matrix.h
)

install(TARGETS Eigen EXPORT eigenExport
    FILE_SET HEADERS DESTINATION include/Eigen)
install(EXPORT eigenExport NAMESPACE Upstream::
    DESTINATION lib/cmake/Eigen
)

这里,在头文件集中定义的头被安装到include/Eigen。安装位置自动成为include directory,是使用者的使用要求。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

CMake buildsystem 的相关文章

随机推荐

  • OpenCV+Python简单实践之硬币检测以及条形码检测

    目录标题 一 简单图片格式 1 位图 2 文件压缩比 二 用奇异值分解 SVD 对一张图片进行特征值提取 降维 处理 1 代码 2 效果 三 采用图像的开闭运算 腐蚀 膨胀 检测出2个样本图像中硬币 细胞的个数 1 硬币检测代码 读取图片
  • 使用JSBridge框架来实现Android与H5(JS)交互

    1 首先我们来了解一下什么是JSBridge 在开发中 为了追求开发的效率以及移植的便利性 一些展示性强的页面我们会偏向于使用h5来完成 功能性强的页面我们会偏向于使用native来完成 而一旦使用了h5 为了在h5中尽可能的得到nativ
  • win7(windows 7)系统下安装SQL2005(SQL Server 2005)图文教程

    原文地址 http www cnblogs com icewee articles 2019783 html 由于工作需要 今天要在电脑上安装SQL Server 2005 以往的项目都是使用Oracle MS的数据库还真的没怎么用过 安装
  • 使用flask框架进行数据可视化时,出现404等没有效果的问题

    Failed to load resource the server responded with a status of 404 NOT FOUND 提示文件找不到 但是觉得自己的路径没问题 例如 前端想要引用一个video播放器 明明正
  • 白宫即将出台人工智能新规,保持美国在AI领域的领导地位

    据美国白宫高级技术官员迈克尔 克拉特西奥斯 Michael Kratsios 表示 特朗普政府正在完善对各机构如何监管人工智能的指导方针 以平衡促进和控制 AI 行业有序稳定地发展 克拉特西奥斯表示 这份指导意见遵循了 1 月份起草的十项原
  • 【学习笔记】【计算机网络【总】】物理层;链路层;网络层;传输层;应用层;详解

    目录 框架 一 计算机网络 1 层次结构设计 2 现代互联网拓扑 3 网络性能指标 二 物理层 三 数据链路层 2 最大传输单元MTU 3 以太网协议详解 四 网络层 链接 五 传输层 链接 六 应用层 1 DNS详解 2 DHCP详解 3
  • 从程序员到项目经理(一)

    从程序员到项目经理 这个标题让我想起了很久以前一本书的名字 从Javascript到Java 然而 从Javascript到Java充其量只是工具的更新 而从程序员到项目经理 却是一个脱胎换骨的过程 从Javascript到Java 是一个
  • Vijava 学习笔记之模板

    Vijava 代码 package com vmware template import com vmware util Session import com vmware vim25 CustomizationSpecInfo impor
  • quilleditor 字体大小设置_vue-quill-editor如何设置字体默认大小?

    集众家之言后 我采用以下方式初步实现了设置默认字体大小 1 全局引入以下内容 完成自定义字号的注册 import as Quill from quill let fontSizeStyle Quill import attributors
  • shell中调用mysql的sql命令脚本,如何执行sql语句

    shell中调用mysql的sql命令脚本 第一步 套用下列模板 bin bash database idm 数据库实例 host 10 200 10 255 数据库地址 port 3306 数据库端口 account test 数据库用户
  • 通过CSIG—走进合合信息探讨生成式AI及文档图像处理的前景和价值

    一 前言 最近有幸参加了由中国图象图形学学会 CSIG 主办 合合信息 CSIG文档图像分析与识别专业委员会联合承办的 CSIG企业行 走进合合信息 的分享会 这次活动以 图文智能处理与多场景应用技术展望 为主题 聚焦图像文档处理中的结构建
  • 为你详解Linux安装GCC方法

    下载 http ftp gnu org gnu gcc gcc 4 5 1 gcc 4 5 1 tar bz2 浏览 http ftp gnu org gnu gcc gcc 4 5 1 查看Changes http gcc gnu org
  • JVM知识总结

    JVM知识点总结 什么是jvm jvm就是java虚拟机 本质上是一个程序虚拟机 所以我们首先得搞懂什么是虚拟机 虚拟机是在操作系统上层的一款软件 分为程序虚拟机和系统虚拟机 程序虚拟机就是用于执行程序的 系统虚拟机可以用于模拟一台物理设备
  • 深度学习的基础知识与问题汇总

    20200813 引言 这里记录一下深度学习使用过程中的一些细节的地方 多分类时 预测过程 损失函数为Nan或者Inf 如何在keras中计算精确率和召回率 如何获取中间某一层的输出 如何获取网络结构 从别人的存储的h5模型文件中 关于so
  • VSCode设置git-bash终端,显示分支名(未解决)

    学习时发现我的终端不能像老师的这样显示分支名 解决方法如下 设置git bash终端 file gt preferences gt settings 搜索window 编辑settings json 添加以下内容 这里出现了一个问题 我直接
  • AD22如何添加元器件库

    1 打开libPkg项目 2 编译 3 查看添加好的库 4 查看添加好的库 当然 如果你手上有IntLib文件 已经编译好的库 可以直接点击install 进行安装 比如从这个地方去下载 1 Other Installers User Ma
  • no such file or directory, scandir ‘xxxxxnode_modules/node-sass/vendor‘

    Syntax Error Error ENOENT no such file or directory scandir xxxxx node modules node sass vendor 一 报错信息 Syntax Error Erro
  • Redis+Mysql模式和内存+硬盘模式的异同

    学习任何新知识 都是一个循序渐进的过程 从刚开始的懵懂无知 到简单熟悉 然后突然的彻悟 成果让人欣喜若狂 心情也会快乐很久 redis mysql和内存 硬盘类似的地方 首先看图 首先 我们知道 mysql是持久化存储 存放在磁盘里面 检索
  • Latex:图片、表格占据双栏排版的两栏时 的位置控制

    目录 1 问题 怎么在双栏排版中 让占据两栏的表格出现在页面顶端 2 解决 在latex中加入 usepackage stfloats 即可 1 图片 占据两栏显示在页面顶端 2 表格 占据两栏显示在页面顶端 1 问题 怎么在双栏排版中 让
  • CMake buildsystem

    官方文档 https cmake org cmake help latest manual cmake buildsystem 7 html 介绍 基于CMake的构建系统 buildsystem 其组织形式是一组高级逻辑目标 high l