cmake:target_** 中的 PUBLIC,PRIVATE,INTERFACE

要回答这个问题,需要先理解target是怎么回事。然后再理解target之间的依赖关系。

  • CMake中由 add_executable() 、 add_library() 等定义 target。这些 target
    可以有很多属性。例如 target_sources() 可以为 target 指定源码。target_link_libraries()可以指定 target 需要链接的库。

  • 当存在多个 target 时,各个 target 之间可能存在一定的依赖关系。例如题主例子中的 linear-algebra 依赖于math 。现在,假设有第三个 target,需要依赖 linear-algebra 。这个时候,因为 math 是PRIVATE,所以在构建第三个 target 时,就不会链接 math。

  • target_include_directories():指定目标包含的头文件路径。
  • target_link_libraries():指定目标链接的库。
  • target_compile_options():指定目标的编译选项。

目标 由 add_library() 或 add_executable() 生成。

这三个指令类似,这里以 target_include_directories() 为例进行讲解。

测试用例

目录结构

cmake-test/                 工程主目录,main.c 调用 libhello-world.so
├── CMakeLists.txt
├── hello-world             生成 libhello-world.so,调用 libhello.so 和 libworld.so
│   ├── CMakeLists.txt
│   ├── hello               生成 libhello.so 
│   │   ├── CMakeLists.txt
│   │   ├── hello.c
│   │   └── hello.h         libhello.so 对外的头文件
│   ├── hello_world.c
│   ├── hello_world.h       libhello-world.so 对外的头文件
│   └── world               生成 libworld.so
│       ├── CMakeLists.txt
│       ├── world.c
│       └── world.h         libworld.so 对外的头文件
└── main.c

调用关系:

                                 ├────libhello.so
可执行文件────libhello-world.so
                                 ├────libworld.so

关键字用法说明:

PRIVATE:私有的。生成的libhello-world.so时,只在hello_world.c中包含了hello.h,libhello-world.so对外的头文件------hello_world.h中不喊了hello.h。而且main.c不会调用hello.c中的函数,或者说 main.c 不知道 hello.c 的存在,那么在 hello-world/CMakeLists.txt 中应该写入:

target_link_libraries(hello-world PRIVATE hello)
target_include_directories(hello-world PRIVATE hello)

INTERFACE:接口。生成 libhello-world.so 时,只在libhello-world.so 对外的头文件——hello_world.h 中包含 了 hello.h, hello_world.c 中不包含 hello.h,即 libhello-world.so 不使用 libhello.so 提供的功能,只使用 hello.h 中的某些信息,比如结构体。但是 main.c 需要使用 libhello.so 中的功能。那么在 hello-world/CMakeLists.txt 中应该写入:

target_link_libraries(hello-world INTERFACE hello)
target_include_directories(hello-world INTERFACE hello)

PUBLIC:公开的。PUBLIC = PRIVATE + INTERFACE。生成 libhello-world.so 时,在 hello_world.c 和 hello_world.h 中都包含了 hello.h。并且 main.c 中也需要使用 libhello.so 提供的功能。那么在 hello-world/CMakeLists.txt 中应该写入:

target_link_libraries(hello-world PUBLIC hello)
target_include_directories(hello-world PUBLIC hello)

实际上,这三个关键字指定的是目标文件依赖项的使用范围(scope)或者一种传递(propagate)

可执行文件依赖 libhello-world.so, libhello-world.so 依赖 libhello.so 和 libworld.so。

  • main.c 不使用 libhello.so 的任何功能,因此 libhello-world.so 不需要将其依赖—— libhello.so 传递给 main.c,hello-world/CMakeLists.txt 中使用 PRIVATE 关键字;
  • main.c 使用 libhello.so 的功能,但是libhello-world.so 不使用,hello-world/CMakeLists.txt 中使用 INTERFACE 关键字;
  • main.c 和 libhello-world.so 都使用 libhello.so 的功能,hello-world/CMakeLists.txt 中使用 PUBLIC 关键字;

include_directories(dir)

target_include_directories() 的功能完全可以使用 include_directories() 实现。但是我还是建议使用 target_include_directories()。为什么?保持清晰!

include_directories(header-dir) 是一个全局包含,向下传递。什么意思呢?就是说如果某个目录的 CMakeLists.txt 中使用了该指令,其下所有的子目录默认也包含了header-dir 目录。

上述例子中,如果在顶层的 cmake-test/CMakeLists.txt 中加入:

include_directories(hello-world)
include_directories(hello-world/hello)
include_directories(hello-world/world)

那么整个工程的源文件在编译时都会增加:

-I hello-world -I hello-world/hello -I hello-world/world ...

各级子目录中无需使用 target_include_directories() 或者 include_directories()了。如果此时查看详细的编译过程(make VERBOSE=1)就会发现编译过程是一大坨,很不舒服。

当然了,在最终子目录的 CMakeLists.txt 文件中,使用 include_directories() 和 target_include_directories() 的效果是相同的。

目录划分

每一个目录都是一个模块,目录内部应将对外和对内的头文件进行区分,由模块的调用者决定模块是否被传递(PRIVATE,INTERFACE,PUBLIC)。

target_link_libraries

target_link_libraries(<target> ... <item>... ...)

< target >必须是由add_executable()或add_library()等命令创建的,并且不能是ALIAS目标。

每个< item >可以是:

  • 库目标名称:
    • 生成的链接行将具有与目标关联的可链接库文件的完整路径。如果库文件发生更改,buildsystem将依赖于重新链接< target>。
    • 命名目标必须由项目中的add_library()创建,或作为导入的库创建。
    • 如果是在项目中创建的,则会在生成系统中自动添加排序依赖项,以确保指定的库目标在< target>链接之前是最新的。
    • 如果一个导入的库有IMPORTED_NO_SONAME目标属性,CMake可能会要求链接器搜索这个库,而不是使用完整的路径(例如/usr/lib/libfoo. dll 变成- lfoo)。
  • 库文件的完整路径: 生成的链接行通常会保留文件的完整路径。如果库文件发生变化,构建系统将依赖于重新链接< target >。
  • 普通库名:生成的链接行将要求链接器搜索库(例如,foo变成-lfoo或foo.lib)。
  • 链接标志:
    • 以-而不是-l或-framework开头的项目名称被视为链接标志。请注意,对于传递依赖项,此类标志将与任何其他库链接项一样处理,因此通常只将其指定为不会传播到依赖项的私有链接项是安全的。
    • 这里指定的链接标志被插入到Link命令中与链接库相同的位置。这可能是不正确的,这取决于链接器。使用LINK_OPTIONS目标属性或target_link_options()命令显式添加链接标志。然后,标记将被放置在link命令中工具链定义的标记位置。
  • 生成器表达式
  • 紧跟着另一个的debug、optimized(优化)或通用关键字。此类关键字后面的项将仅用于相应的构建配置。
    • debug关键字对应于debug配置(或者如果设置了DEBUG_CONFIGURATIONS全局属性中命名的配置)。
    • optimized后的关键字对应其他所有配置。
    • general关键字对应所有配置,并且是完全可选的。

包含::的项,如Foo::Bar,被假定被导入或ALIAS库目标名称,如果没有这样的目标存在,将导致错误。看到CMP0028政策。

参见cmake-buildsystem(7)手册来定义更多的buildsystem属性。

使用示例

target_link_libraries(myProject hello)       # 连接libhello.so库,默认优先链接动态库
target_link_libraries(myProject libhello.a)  # 显示指定链接静态库
target_link_libraries(myProject libhello.so) # 显示指定链接动态库

参考

  • CMake中的属性PRIVATE?
  • cmake 实践和学习

你可能感兴趣的:(C++,cmake,c++)