# 第4章：构建目标

如前一章所示，在CMake中构建可执行文件不会很难。给出的例子需要为可执行文件定义目标名称，并列出要编译的源文件:

```
add_executable(myApp main.cpp)
```

假设开发者想要构建一个控制台可执行文件(当然CMake也允许开发者定义其他类型的可执行文件)，比如苹果平台上的应用程序包和Windows GUI应用程序。本章只讨论`add_executable()`提供的其他选项。

除了可执行文件之外，开发人员还需要构建和链接库。CMake支持几种不同类型的库，包括静态、动态、模块和框架。CMake提供了强大的特性来管理目标间的依赖关系，以及如何链接库，以及如何在CMake中使用它们。还会介绍了一些基本变量和属性的使用，以说明这些CMake特性是如何与库和目标相关联的。

## 4.1. 可执行文件

`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还会生成一个 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_library(targetName [STATIC | SHARED | MODULE]
            [EXCLUDE_FROM_ALL]
            source1 [source2 ...]
)
```

使用`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`选项进行定义:

```
cmake -DBUILD_SHARED_LIBS=YES /path/to/source
```

也可以在CMakeLists.txt文件中直接设置，只要在`add_library()`命令之前设置以下内容就可以了。但如果开发者想要改变它，就需要修改CMakeLists.txt(即灵活性较差):

```
set(BUILD_SHARED_LIBS YES)
```

与可执行文件一样，库目标也可以引用已有的二进制文件或目标，而不必是由项目构建出来的。另外还支持另一种类型的伪库，用于收集`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()`命令，可以获得更丰富的依赖关系，该命令的一般形式为:

```
target_link_libraries(targetName
     <PRIVATE|PUBLIC|INTERFACE> item1 [item2 ...]
     [<PRIVATE|PUBLIC|INTERFACE> 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`链接到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`，从而更准确地表达依赖关系。

```
target_link_libraries(targetName item [item...])
```

上述形式通常等价于使用了`PUBLIC`，但在某些情况下，可能视为`PRIVATE`。如果项目混合使用新旧命令形式定义库的依赖关系，那么老式的方式通常会视为`PRIVATE`。

另一种不推荐的形式如下:

```
target_link_libraries(targetName
 LINK_INTERFACE_LIBRARIES item [item...]
)
```

这是上面新形式`INTERFACE`关键字的一个前导，但是CMake文档不推荐这样使用。它的行为可以影响不同的目标属性，并使用相应的策略设置控制其行为。对于开发人员来说，这会产生一些疑惑，使用新的参数形式可以避免这种疑惑。

```
target_link_libraries(targetName
 <LINK_PRIVATE|LINK_PUBLIC> lib [lib...]
 [<LINK_PRIVATE|LINK_PUBLIC> lib [lib...]]
)
```

与老式参数类似，参数表是新形式`PRIVATE`和`PUBLIC`关键字的前导。同样，老式参数对于目标属性同样会产生疑惑，`PRIVATE`/`PUBLIC`关键字会更适合于新项目。

## 4.6. 总结

目标名称与项目名称无关。常见的教程和例子使用同一个变量命名，也用于项目名称和可执行的目标，像这样:

```
# Poor practice, but very common
set(projectName MyExample)
project(${projectName})
add_executable(${projectName} ...)
```

这只适用于比较简单的项目。最好将项目名称和可执行文件名称分开，即使一开始是相同的。直接设置项目名称(而不是通过变量)，根据目标所做的事情(而不是它所属的项目)选择目标名称，并假定项目将定义多个目标。这能有助于好习惯的养成，在处理复杂项目时会很重要。

命名库的目标时，不要用lib作为名称的开头或结尾。许多平台上(除了Windows之外的所有平台)，构建实际的库名称时，lib会自动成为前缀，以使其符合平台的使用习惯。如果目标名称已经以lib开头，则生成的库文件名将以`liblibsomething…`为名称(通常会认为这是一个错误)。

除非有硬性原因需要这样做，否则尽量避免直接将库指定`STATIC`或`SHARED`。这在选择静态库或动态库时有更大的灵活性，并可以作为整个项目范围的策略。可以通过`BUILD_SHARED_LIBS`变量修改默认值，而不必修改对`add_library()`的每次使用。

目标在调用`target_link_libraries()`时需要指定`PRIVATE`、`PUBLIC`和/或`INTERFACE`。随着项目越来越复杂，这三个关键字对如何处理目标间的依赖关系有更大的影响。项目开始时这样使用会让开发人员考虑目标之间的依赖关系，这有助于更早地暴露项目结构的问题。
