第4章:构建目标
如前一章所示,在CMake中构建可执行文件不会很难。给出的例子需要为可执行文件定义目标名称,并列出要编译的源文件:
假设开发者想要构建一个控制台可执行文件(当然CMake也允许开发者定义其他类型的可执行文件),比如苹果平台上的应用程序包和Windows GUI应用程序。本章只讨论add_executable()
提供的其他选项。
除了可执行文件之外,开发人员还需要构建和链接库。CMake支持几种不同类型的库,包括静态、动态、模块和框架。CMake提供了强大的特性来管理目标间的依赖关系,以及如何链接库,以及如何在CMake中使用它们。还会介绍了一些基本变量和属性的使用,以说明这些CMake特性是如何与库和目标相关联的。
4.1. 可执行文件
add_executable()
命令的完整形式如下:
与之前的区别是有新的可选关键字:
WIN32
在Windows平台上构建可执行文件时,此选项表示CMake将该可执行文件构建为Windows GUI应用程序。实际上,会使用WinMain()
创建入口,而不是main()
,并且会添加链接选项/SUBSYSTEM:WINDOWS
。其他平台会忽略WIN32
选项。
MACOSX_BUNDLE
它会引导CMake在苹果平台上构建应用程序包。与选项名称不同,不仅适用于macOS,也适用于其他苹果平台,如iOS。此选项的确切效果在不同平台间略有不同。例如,在macOS上,app bundle有一个非常特定的目录结构,而iOS的目录结构是扁平的。CMake还会生成一个 Info.plist文件。这些细节将在22.2节中详细地介绍。非Apple平台上,会忽略MACOSX_BUNDLE
关键字。
EXCLUDE_FROM_ALL
项目定义了许多目标,但默认情况下只需要构建一部分。当在构建没有指定目标时,将构建默认的ALL
目标(根据使用CMake生成器的不同,名称略有不同,比如:Xcode的ALL_BUILD
)。如果可执行文件用EXCLUDE_FROM_ALL
定义,就不会包含在默认的ALL
目标中。然后,只有当构建命令显式地请求该可执行文件,或者作为默认构建对其有依赖时,可执行文件才会构建。
除此之外,还有其他形式的add_executable()
,它们对现有可执行文件或目标是一种引用,而非要构建的新文件。这些别名可执行文件将在第16章中详细介绍。
4.2. 定义库
创建可执行文件是任何构建系统的基本需求。对于许多大型项目,创建和使用库也是保持项目可管理性的关键。CMake支持构建不同类型的库,可以处理平台差异,同时支持库的本地特性。库目标使用add_library()
命令构建,命令中有许多参数。其中基本参数如下所示:
使用add_executable()
可以构建可执行文件。CMakeLists.txt文件中使用targetName
来引用库,默认构建库的名称源于targetName
。EXCLUDE_FROM_ALL
关键字具有与add_executable()
相同的效果,避免库包含在默认ALL
目标中。构建库的类型由其余的三个关键字指定:STATIC
、SHARED
或MODULE
。
STATIC
指定为静态库。在Windows上构建的库为targetName.lib,而在类Unix平台上,通常是libtargetName.a。
SHARED
指定动态库。在Windows上的库名称是targetName.dll,在Apple平台上是libtargetName.dylib,在类Unix平台上,通常是libtargetName.so。Apple平台上,动态库也可以标记为框架,这个将在第22.3节中介绍。
MODULE
这种方式类似于动态库,不同的是在运行时动态加载。通常会作为插件或可选组件,用户可以选择加载或不加载。Windows平台上,不会为DLL创建导入库。
其实可以省略要构建的库类型的关键字,除非项目特别需要特定类型的库,并让开发人员在构建项目时进行选择。这样,库可以静态的,也可以是动态的,由名为BUILD_SHARED_LIBS
的CMake变量的值决定。如果BUILD_SHARED_LIBS
设置为true
,库将构建为动态库,否则将构建为静态库。与变量的设置方法相同,可以通过在cmake命令行执行时,使用-D
选项进行定义:
也可以在CMakeLists.txt文件中直接设置,只要在add_library()
命令之前设置以下内容就可以了。但如果开发者想要改变它,就需要修改CMakeLists.txt(即灵活性较差):
与可执行文件一样,库目标也可以引用已有的二进制文件或目标,而不必是由项目构建出来的。另外还支持另一种类型的伪库,用于收集obj
文件,而非创建静态库。这些将在第16章目标类型中详细讨论。
4.3. 目标连接
考虑搭建项目的目标时,开发人员通常习惯于考虑A库依赖于B库,因此将A链接到B。这是处理库的传统方式,而在实际中,库之间存在几种不同类型的依赖关系:
PRIVATE
私有依赖项指定A库自己使用B库。其他链接到A库的目标都不需要知道B库,因为它只影响A库的内部实现。
PUBLIC
公共依赖关系指定A库不仅在内部使用B库,还在其接口中使用B库。这样,其他任何使用A库的目标也会依赖B库。例如,在A库中使用的函数,至少有一个在B库中定义和实现,因此代码需要依赖B库。
INTERFACE
接口依赖规定,为了使用A库,也必须使用B库的一部分。当依赖于纯头文件库时,在add_library()
中使用INTERFACE
就非常有有用了。
CMake通过target_link_libraries()
命令,可以获得更丰富的依赖关系,该命令的一般形式为:
允许项目定义一个库如何依赖于其他库。然后,CMake负责管理以这种方式的链接。
上例中,ui库PUBLIC
链接到collector,因此即使myApp只直接链接到collector,由于这种关系,myApp也将链接到ui。另一方面,algo和engine作为PRIVATE
链接到collector,所以myApp不会直接链接。第16.2节会讨论静态库的其他行为,这些行为可能会进行更多的链接,以满足依赖关系,包括循环依赖关系。
后面的章节将介绍其他target_…()
系列的命令,这些命令增强了目标之间的依赖。通过target_link_libraries()
连接时,允许编译器/链接器选项和头文件搜索路径从一个目标执行到另一个目标。这些特性从CMake 2.8.11到3.2渐进式添加,从而让CMakeLists.txt更加简单。
后面的章节还会讨论了更复杂的源目录层次。target_link_libraries()
所使用的targetName
,必须在add_executable()
或add_library()
命令之后调用,所连接的库目标需要在同一目录中进行定义。
4.4. 非目标连接
上一节中,所有的链接项都是当前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会警告,因为这些应该只用于PRIVATE
构建,因为如果定义为PUBLIC
或INTERFACE
,会携带其他目标,这可能并不安全。
除了上述内容之外,由于历史原因,任何配置项前面都可以有一个关键字debug
、optimized
或general
。这些关键字的作用是,根据构建确定是否配置为调试,从而进一步细化应该在什么时候包含后面的配置项(参见第13章)。如果某个项前面有debug
关键字,只有在生成为调试时才会添加该项。如果项前面有optimized
的关键字,则只有在构建时(不是Debug构建)才会添加。general
关键字为所有构建配置添加选项,如果没有使用任何关键字,general
就是默认行为。对于新项目,应该避免使用debug
、optimized
和general
的关键字,因为现代CMake有更清晰、更灵活和更健壮的特性可以实现同样的效果。
4.5. 老式CMake
target_link_libraries()
还有其他一些形式,这些形式早在2.8.11版本之前就已是CMake的一部分。这些选项有助于理解理解老式CMake项目,但不推荐在新项目中使用。应该在命令中使用PRIVATE
、PUBLIC
和INTERFACE
,从而更准确地表达依赖关系。
上述形式通常等价于使用了PUBLIC
,但在某些情况下,可能视为PRIVATE
。如果项目混合使用新旧命令形式定义库的依赖关系,那么老式的方式通常会视为PRIVATE
。
另一种不推荐的形式如下:
这是上面新形式INTERFACE
关键字的一个前导,但是CMake文档不推荐这样使用。它的行为可以影响不同的目标属性,并使用相应的策略设置控制其行为。对于开发人员来说,这会产生一些疑惑,使用新的参数形式可以避免这种疑惑。
与老式参数类似,参数表是新形式PRIVATE
和PUBLIC
关键字的前导。同样,老式参数对于目标属性同样会产生疑惑,PRIVATE
/PUBLIC
关键字会更适合于新项目。
4.6. 总结
目标名称与项目名称无关。常见的教程和例子使用同一个变量命名,也用于项目名称和可执行的目标,像这样:
这只适用于比较简单的项目。最好将项目名称和可执行文件名称分开,即使一开始是相同的。直接设置项目名称(而不是通过变量),根据目标所做的事情(而不是它所属的项目)选择目标名称,并假定项目将定义多个目标。这能有助于好习惯的养成,在处理复杂项目时会很重要。
命名库的目标时,不要用lib作为名称的开头或结尾。许多平台上(除了Windows之外的所有平台),构建实际的库名称时,lib会自动成为前缀,以使其符合平台的使用习惯。如果目标名称已经以lib开头,则生成的库文件名将以liblibsomething…
为名称(通常会认为这是一个错误)。
除非有硬性原因需要这样做,否则尽量避免直接将库指定STATIC
或SHARED
。这在选择静态库或动态库时有更大的灵活性,并可以作为整个项目范围的策略。可以通过BUILD_SHARED_LIBS
变量修改默认值,而不必修改对add_library()
的每次使用。
目标在调用target_link_libraries()
时需要指定PRIVATE
、PUBLIC
和/或INTERFACE
。随着项目越来越复杂,这三个关键字对如何处理目标间的依赖关系有更大的影响。项目开始时这样使用会让开发人员考虑目标之间的依赖关系,这有助于更早地暴露项目结构的问题。
Last updated
Was this helpful?