如前一章所示,在CMake中定义一个简单的可执行文件是相对简单的。前面给出的简单示例需要为可执行文件定义一个目标名称,并列出要编译的源文件:
add_executable(myApp main.cpp)
这假设开发者想要构建一个基本的控制台可执行文件,但是CMake也允许开发者定义其他类型的可执行文件,比如苹果平台上的应用程序包和Windows GUI应用程序。接下来讨论add_executable()可以指定的附加选项来指定这些细节。
除了可执行文件外,开发人员还经常需要构建和链接库。CMake支持几种不同的库,包括静态库、共享库、模块库和框架库。CMake还提供了非常强大的特性来管理目标之间的依赖关系以及库的链接方式。这整个库领域以及如何在CMake中使用它们构成了本章的主要内容。还介绍了变量和属性的一些非常基本的用法,以说明这些CMake特性如何与库和目标相关。
更完整的add_executable()命令形式如下:
add_executable(targetName [WIN32] [MACOSX_BUNDLE]
[EXCLUDE_FROM_ALL]
source1 [source2 ...]
)
与前面显示的表单的唯一区别是新的可选关键字。
WIN32
当在Windows平台上构建可执行文件时,该选项指示CMake将可执行文件构建为Windows GUI应用程序。在实践中,这意味着它将使用WinMain()入口点创建,而不仅仅是main(),并且它将与/ /SUBSYSTEM:WINDOWS选项链接。在所有其他平台上,WIN32选项被忽略。
MACOSX_BUNDLE
当这个选项出现时,它会指示CMake在苹果平台上构建一个应用包。与选项名称所暗示的相反,它不仅适用于macOS,也适用于其他苹果平台,如iOS。这个选项的确切效果在不同平台之间有所不同。例如,在macOS上,app bundle布局有一个非常特定的目录结构,而在iOS上,目录结构是扁平化的。CMake也会生成一个基本的信息。捆绑包的Plist文件。在非苹果平台上,MACOSX_BUNDLE关键字会被忽略。
EXCLUDE_FROM_ALL
有时,一个项目定义了许多目标,但默认情况下,只应该构建其中的一些。如果在构建时没有指定目标,则会构建默认的ALL目标(取决于使用的CMake生成器,名称可能会略有不同,比如Xcode的ALL_BUILD)。如果一个可执行文件是用EXCLUDE_FROM_ALL选项定义的,它将不会被包含在默认的ALL目标中。只有当构建命令显式地请求可执行文件时,或者当它是默认ALL构建的另一个目标的依赖项时,才会构建可执行文件。从ALL中排除目标很有用的一种常见情况是,可执行文件是一个开发人员工具,只是偶尔需要。
除了上面的,还有其他形式的add_executable()命令,它们产生一种对现有可执行文件或目标的引用,而不是定义一个要构建的新文件。
创建简单的可执行文件是任何构建系统的基本需求。对于许多较大的项目,创建和使用库的能力对于保持项目的可管理性也是必不可少的。CMake支持构建各种不同类型的库,照顾到许多平台差异,但仍然支持每种库的本地特性。库目标是使用add_library()命令定义的,该命令有许多形式。其中最基本的是:
add_library(targetName [STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
source1 [source2 ...]
)
这种形式类似于add_executable()用于定义一个简单的可执行文件。在CMakeLists.txt文件中使用targetName来引用库,默认情况下,文件系统上构建的库的名称派生自此名称。EXCLUDE_FROM_ALL关键字与add_executable()具有完全相同的效果,即防止库被包含在默认的ALL目标中。要构建的库的类型由剩下的三个关键字之一STATIC、SHARED或MODULE指定。
STATIC
指定静态库或存档。在Windows上,默认的库名是targetName.lib,而Unix-like平台上,它通常是libtargetName.a。
SHARED
指定共享或动态链接库。在Windows上,默认的库名是targetName.dll,在苹果平台上是libtargetName.dylib,在其他类unix平台上,它通常是libtargetName.so。在苹果平台上,共享库也可以被标记为框架。
MODULE
指定某种程度上类似于共享库的库,但旨在在运行时动态加载,而不是直接链接到库或可执行文件。这是个典型的插件或可选组件,用户可以选择是否加载。在Windows平台上,没有为DLL创建导入库。
可以省略定义要构建的库类型的关键字。除非项目特别需要特定类型的库,否则首选的做法是不指定它,让开发人员在构建项目时自行选择。在这种情况下,库要么是静态的,要么是共享的,这取决于一个名为BUILD_SHARED_LIBS的CMake变量的值。如果BUILD_SHARED_LIBS设置为true,则库目标将是一个共享库,否则将是静态库。变量的使用将在第5章变量中详细介绍,但是现在,设置这个变量的一种方法是在cmake命令行中包含一个-D选项,如下所示:
cmake -DBUILD_SHARED_LIBS=YES /path/to/source
它可以在CMakeLists.txt文件中设置,而不是在任何add_library()命令之前,但这将要求开发人员修改它,如果他们想要更改它(即,它将不那么灵活):
set(BUILD_SHARED_LIBS YES)
就像可执行文件一样,库目标也可以定义为引用一些现有的二进制文件或目标,而不是由项目构建。还支持另一种类型的伪库,它可以在不创建静态库的情况下将对象文件收集在一起.
当考虑组成一个项目的目标时,开发人员通常习惯于从库a需要库B的角度来考虑,因此a与B是链接的。这是看待库处理的传统方式,其中一个库需要另一个库的想法是非常简单的。然而,在现实中,库之间有几种不同类型的依赖关系:
私有依赖关系指定了库A在它自己的内部实现中使用库B。任何链接到库A的东西都不需要知道B,因为它是A的内部实现细节。
公共依赖关系指定了库A不仅在内部使用库B,它还在它的接口中使用库B。这意味着不能使用没有B,所以任何使用也会有直接依赖B的一个例子是一个函数库中定义一个具有至少一个参数的类型定义和实现库B,所以代码不能从没有提供一个调用该函数参数的类型来自B。
INTERFACE
接口依赖关系指定,为了使用库A,库B的部分也必须使用。这与公共依赖不同,因为库a在内部不需要B,它只在其接口中使用B。使用add_library()的INTERFACE形式定义的库目标是很有用的一个例子,例如使用一个目标来表示一个只包含头文件的库的依赖。
CMake用它的target_link_libraries()命令捕获了更丰富的依赖关系集,而不仅仅是简单的需要链接的想法。该命令的一般形式是:
target_link_libraries(targetName
item1 [item2 ...]
[ item3 [item4 ...]]
...
)
这允许项目精确定义一个库如何依赖其他库。然后CMake负责管理以这种方式链接的整个库链中的依赖项。例如,考虑以下情况:
add_library(collector src1.cpp)
add_library(algo src2.cpp)
add_library(engine src3.cpp)
add_library(ui src4.cpp)
add_executable(myApp main.cpp)
target_link_libraries(collector
PUBLIC ui
PRIVATE algo engine
)
target_link_libraries(myApp PRIVATE collector)
在这个例子中,ui库作为PUBLIC链接到收集器库,所以即使myApp只直接链接到收集器,myApp也会因为PUBLIC关系而链接到ui。另一方面,算法库和引擎库以PRIVATE的形式链接到收集器,因此myApp不会直接链接到它们。
后面将介绍一些其他的target_…()命令,这些命令进一步增强了在目标之间携带的依赖信息。当被target_link_libraries()连接时,它们允许编译器/链接器标志和头文件搜索路径也从一个目标传递到另一个目标。从CMake 2.8.11到3.2,这些特性是逐步添加的,并导致CMakeLists.txt文件变得更加简单和健壮。_
后面还将讨论更复杂的源目录层次结构的使用。在这种情况下,与target_link_libraries()一起使用的targetName必须由add_executable()或add_library()命令在调用target_link_libraries()的同一目录下定义。
在前一节中,所有被链接的项都是现有的CMake目标,但是target_link_libraries()命令比它更灵活。除了CMake目标,下面的东西也可以在target_link_libraries()命令中指定为项:
CMake会将库文件添加到链接器命令中。如果库文件发生了变化,CMake会检测到这个变化并重新链接目标。请注意,在CMake 3.3版本中,链接器命令总是使用指定的完整路径,但在3.3版本之前,有一些情况下,CMake可能会要求链接器搜索库(例如替换/usr/lib/libfoo. so 为- lfoo)。3.3版本之前的行为的推理和细节是非常重要的,而且很大程度上是历史的,但是对于感兴趣的读者来说,在CMP0060策略下的CMake文档中可以找到完整的信息。
普通库名称
如果只给出了库名而没有给出路径,则链接器命令将搜索该库(例如foo变成-lfoo或foo.Lib,取决于平台)。这对于系统提供的库来说是很常见的。
链接标记
作为特殊情况,以连字符开头的项目,而不是-l或-framework将被视为要添加到链接器命令的标志。CMake文档警告说,这些应该只用于私有项目,因为如果定义为PUBLIC或INTERFACE,它们将被携带到其他目标,这可能并不总是安全的。
除了上述,由于历史原因,任何项目之前都可以有一个关键字调试,优化或一般。这些关键字的作用是根据构建是否配置为调试构建来进一步细化后面的项目。如果项目前面有debug关键字,那么只有在构建是调试构建时才会添加它。如果一个项目前面有一个 optimized的关键字,它只会在构建不是调试构建时被添加。通用关键字指定应该为所有构建配置添加该项,如果不使用关键字,这是默认行为。对于新项目,应该避免使用debug, optimized和general关键字,因为现在的CMake特性有更清晰、更灵活和更健壮的方法来实现同样的功能。
target_link_libraries()命令还有一些其他的形式,其中一些在2.8.11版本之前就已经是CMake的一部分了。这里讨论这些表单是为了更好地理解旧的CMake项目,但是新项目通常不鼓励使用它们。应该首选前面显示的PRIVATE、PUBLIC和INTERFACE部分的完整形式,因为它更准确地表达了依赖关系的性质。
target_link_libraries(targetName item [item...])
上述形式通常相当于定义为PUBLIC的项,但在某些情况下,它们可能被视为PRIVATE。特别是,如果一个项目定义了一个库依赖链,它混合使用了旧形式和新形式的命令,那么旧形式的命令通常会被视为PRIVATE。
另一种支持但不赞成的形式是:
target_link_libraries(targetName
LINK_INTERFACE_LIBRARIES item [item...]
)
这是前面提到的新表单的INTERFACE关键字的前光标,但是CMake文档不鼓励使用它。它的行为可以影响不同的目标属性,策略设置可以控制该行为。对于开发人员来说,这是一个潜在的困惑来源,可以通过使用较新的INTERFACE形式来避免。
target_link_libraries(targetName
lib [lib...]
[ lib [lib...]]
)
与前面的旧式表单类似,这个表单是指向新表单PRIVATE和PUBLIC关键字版本的前游标。同样,旧式表单也会混淆它会影响哪些目标属性,而PRIVATE/PUBLIC关键字表单应该更适合于新项目。
更多请关注微信公众号【Hope Hut】: