近期重学CMake,为后续的开发做点准备。遇到PUBLIC|PRIVATE和INTERFACE
三个关键字不是很理解,搜索了一些资料,最后发现这篇文章说得比较浅显易懂。
CMake: Public VS Private VS Interface
本文在他的基础上,加上了一些实际验证的例子。
使用到这三个关键字的地方基本在:target_include_directories
和 target_link_libraries
两个命令。为了方便理解,我们以一个实际的例子来先说说 target_include_directories
。
建立文件目录如下:
hello.h 和 hello.cpp 组成 hello lib.
main依赖hello lib。
下面看一下每个文件的定义是如何的:
hello.h:
1 |
|
hello.cpp:
1 |
|
main.cpp:
1 |
|
ok, 下面重点看看CMakeLists.txt
1 | # create a lib target |
很简单,就是定义了hello lib target, 给hello lib指定 include dir, 定义main driver target, 给main driver target 添加链接到hello的依赖。
至于这里:target_include_directories(hello PUBLIC ${PROJECT_SOURCE_DIR}/include)
为什么使用public。 我马上就会解释。
想一个问题,在build一个target时,target是如何绑定include dir(也就是上面的的${PROJECT_SOURCE_DIR}/include
)的?。
这是因为每个target都有两个用来装include dir的变量,分别叫做INCLUDE_DIRECTORIES
和INTERFACE_INCLUDE_DIRECTORIES
。 前者用于存放专给自己用的INCLUDE DIR, 后者则用于存放暴露给外部程序的INCLUDE DIR。你可以把他想象成两个放食物的篮子,INCLUDE_DIRECTORIES
篮子放的东西给自己吃,INTERFACE_INCLUDE_DIRECTORIES
篮子放的东西给别人吃。
以hello lib和main driver为例, 这里的“自己”说的是hello lib, 这里的“别人”说的是main driver. 更泛化了的说,“自己”指的是要build的库, 而“别人”指的是依赖库的程序,而所谓的食物,指的是库的 include dir。
PUBLIC|PRIVATE|INTERFACE
就是在指定将定义的INCLUDE DIR放置到哪个篮子,或者两个都放。
- PUBLIC: 将include dir放到两个篮子中,自己用,也给别人用。
- PRIVATE: 将include dir只放到自己的篮子中。
- INTERFACE: 将include dir只放到别人的篮子中。
ok,记住这三点。我们再来看target_include_directories(hello PUBLIC ${PROJECT_SOURCE_DIR}/include)
的public如何解释:将${PROJECT_SOURCE_DIR}/include
同时放到自己和别人的篮子中。
target_link_libraries(main hello)
命令不仅链接了hello库。 同时还将hello lib的INTERFACE_INCLUDE_DIRECTORIES
篮子中的东西也装到了自己的篮子(和自己向外暴露的篮子,这一点晚点解释,可忽略)。于是我们可以成功编译并运行。
如下图,clion并没有报错。
现在将target_include_directories(hello PUBLIC ${PROJECT_SOURCE_DIR}/include)
的PUBLIC,改为PRIVATE.
1 | target_include_directories(hello PRIVATE ${PROJECT_SOURCE_DIR}/include) |
也就相当于 hello lib中给别人吃的篮子中没有放任何东西。所以main driver找不到hello.h。也就会报错了。
同理如果改为target_include_directories(hello INTERFACE ${PROJECT_SOURCE_DIR}/include)
main.cpp是不会报错,但是hello.cpp会报错。因为自己吃的篮子中没有东西,只有给别人的篮子中才有东西。
另外,细心的读者可能会发现,在CMakeLists.txt的最后一句
target_link_libraries(main hello)
没有指定关键字,这里补充一下target_link_libraries
默认为``PUBLIC, 为了避免和
target_include_directories`混淆,所以没写。
理解了 target_include_directories
中的 PUBLIC,PRIVATE, INTERFACE
, 再来理解 target_link_libraries
就很简单了。只用记住有两个篮子,一个装给自己用,一个装给别人用即可。
依然以一个例子来说明,目录结构如下:
各文件内容:
1 | // hello.h |
CMakeLists.txt:
1 | # create hello lib |
注意到所有地方都是用的``PUBLIC`,现在解释一下这几个target的篮子存放状况。
hello:
自己和暴露给别人的篮子都存放了:${PROJECT_SOURCE_DIR}/include/hello
world:
自己和暴露给别人的篮子都存放了: ${PROJECT_SOURCE_DIR}/include/world
以及来自hello的${PROJECT_SOURCE_DIR}/include/hello
。 因为world依赖了hello,而hello采用public暴露include dir. 所有world dir中的两个篮子都有 来自hello的include dir.
main:
和world一样。
现在去执行:
1 | in build dir |
是能成功执行的。
如果将
1 | target_link_libraries(world PUBLIC hello) |
则:
原因在于,main依赖于world,而world private依赖于hello,也就意味着,world的篮子变为了:
自己有:${PROJECT_SOURCE_DIR}/include/world
以及来自hello的${PROJECT_SOURCE_DIR}/include/hello
, 但是暴露给别人的只有${PROJECT_SOURCE_DIR}/include/world
。
所以main找不到hello.h
。