Target是个好东西[1]:从编译一个动态库说起
1 旧世界规则
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/include
)
add_library(my_library_shared SHARED
${CMAKE_CURRENT_SOURCE_DIR}/src/my_library.cpp
)
add_executable(main main.cpp)
target_link_libraries(main my_library_shared)
我们经常看到或用过上面的写法,对于实验性质的代码这并无不可,但这并非最佳实践。因为,形如include_directories
的命令实则将包含目录添加到了全局空间,这使它们会对所有存在于这个工程的构建目标可见。
通俗讲,工程内有a、b两个构建目标,原本header_a
仅用于lib_a
,但是现在完全不相干的lib_b
也可以发现并使用header_a
,一旦工程庞大起来,很容易引起头文件污染或冲突。笔者就因这种用法,在同时使用PCL及OpenCV时发生过Flann库头文件冲突的问题。
另一方面,我们有时候也需要细粒度地控制头文件或库在被后续目标使用时的可见性。通俗地说也就是header_a1
和header_a2
都用于lib_a
,其中header_a1
是库的对外接口,而header_a2
则是内部使用的头文件,当lib_b
使用lib_a
时,我们仅希望接口部分可见而其他部分不可见。后续介绍的用法将同时解决这样的问题,这和OOD中所说的“最少知识原则”是相通的。
最少知识原则:又称迪米特法则,对于OOD来说,又被解释为下面几种方式:一个软件实体应当尽可能少的与其他实体发生相互作用。每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。(百度百科)
注意:在Modern CMake中所有的构建目标都可认为是Target,既包含最终会实际编译的动静态库或可执行文件,也包含不会实际编译的INTERFACE
类型的目标(如add_library(lib_a INTERFACE IMPORTED)
),私以为基于面向对象的思想理解Target是对Modern CMake核心内容的诠释。
2 推荐用法
工程结构:
├── CMakeLists.txt
├── include
│ └── my_library.h
└── src
└── my_library.cpp
CMakeLists:
add_library(my_library_shared SHARED "")
target_sources(my_library_shared
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src/my_library.cpp
)
target_include_directories(my_library_shared
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
)
target_link_libraries(my_library_shared
PUBLIC
${SOME_OTHER_LIBRARIES}
)
3 作用
add_library(my_library_shared SHARED "")
:声明了一个名为my_library_shared
的动态库Target,这里我们没有指定源文件而是用""
代替,可以理解为一个空对象target_sources
为Target指定了源文件target_include_directories
为Target指定了包含目录target_link_libraries
为Target指定了需要链接的库
3 要点
- 在声明一个构建目标之后我们用
target_
开头的命令来添加构建该目标所需的资源和属性,诸如:源文件、包含目录、链接库、属性、编译标志等 - 这些命令中使用了
PUBLIC
、PRIVATE
这样的标记,除此之外还有INTERFACE
,它们细粒度的规定了所添加的资源或属性的可见性范围,其中: - PRIVATE:只用于该Target的构建,不用于使用该Target的其他对象
- INTERFACE:只用于使用该Target的其他对象
- PUBLIC:既用于该Target的构建,也用于使用该Target的其他对象
- 举个例子:
header_a1
和header_a2
都用于lib_a
,其中header_a1
是库的对外接口设为PUBLIC
,而header_a2
则是内部使用的头文件设为PRIVATE
,当lib_b
使用lib_a
时,header_a1
作为接口应该被lib_b
可见,而header_a2
是lib_a
的内部头文件不应可见
4 更多
除源文件、包含目录、链接库以外,我们还需要为Target设置属性和编译标志等,举例如下:
# 为Target设置属性
set_target_properties(my_library_shared
PROPERTIES
# -fPIC 生成位置无关代码
POSITION_INDEPENDENT_CODE 1
# 设置库版本号为工程主版本号
SOVERSION ${PROJECT_VERSION_MAJOR}
# 重设动态库输出的名称,my_library_shared → my_library
OUTPUT_NAME "my_library"
# 为Debug模式下生成的库添加后缀
DEBUG_POSTFIX "_d"
# 为该库指定PUBLIC可见的头文件,一般用于指定包含外部接口的头文件
PUBLIC_HEADER
${CMAKE_CURRENT_SOURCE_DIR}/include/my_library.h
)
# 为Target设置编译标志,编译标志的可见性同上所属
target_compile_options(my_library_shared
PRIVATE
-std=c++11
)
参考文献
- CMake Cookbook
版权声明见README