用 CMake target 来管理依赖库

虽然有越来越多的包提供 CMake config 了,时常还是需要手写一个。但是如果你还活在 cmake 2.8 的时代, 你可能浪费了很多的精力在不必要的地方。

简单来说,如果你还在写 include_directories,link_directories 来给你的依赖添加找头文件和库的目录,那么你大概已经 OUT 了。

比较现代的一种方式是,用 Target 来管理依赖的库的一切。最早我是从 Qt5 的 cmake 那里学到的。简单来说,就是通过 add_library([Library] [TYPE] IMPORTED]) 来把你依赖的库变成一个 cmake target。同时这个 target 就自带了 include_directories 的属性和 library 自身路径。

那么可以来看看一个我写的新 libintl 的例子:https://github.com/fcitx/fcitx5/blob/master/cmake/FindLibIntl.cmake

精髓就是在最后这里:

if(LIBINTL_FOUND AND NOT TARGET LibIntl::LibIntl)
 if (LIBINTL_LIBRARY)
 add_library(LibIntl::LibIntl UNKNOWN IMPORTED)
 set_target_properties(LibIntl::LibIntl PROPERTIES
 IMPORTED_LOCATION "${LIBINTL_LIBRARY}")
 else()
 add_library(LibIntl::LibIntl INTERFACE IMPORTED )
 endif()
 set_target_properties(LibIntl::LibIntl PROPERTIES
 INTERFACE_INCLUDE_DIRECTORIES "${LIBINTL_INCLUDE_DIR}"
 )
endif()

也就是说,如果你在你的项目上 target_link_libraries([YourTarget] LibIntl::LibIntl),它就会自动在编译和链接时找到库和头文件,再也不用写 include_directories(…) 了。

上面还有用到了一个

 add_library(LibIntl::LibIntl INTERFACE IMPORTED )

通过这个来表示这个 library 是纯头文件的库。对于很多模板库,或者 libc 内置的情况来说是很有用的。

对于多 pc 文件的库,例如 cairo 来说,把它用 package + component 的方式来表示有时会很方便。这里可以请出 KDE 写的 extra-cmake-modules。里面自带的 FindXCB.cmake 可以作参考。

这里有一个我写的 Cairo 的实例:https://github.com/fcitx/fcitx5/blob/master/cmake/FindCairo.cmake

find_package(Cairo COMPONENTS Cairo XCB EGL)

使用时可以这样,是不是看起来就很高端大气上档次。实际 link 时就只要 target_link_libraries([Target] Cairo::XCB) 就可以了。

实际上,这样引入的 Tagert 也并不只有这一些属性,甚至可以直接指定依赖时需要使用的 C++ 标准之类。

另一方面,如果你想要在编译时支持 third_party 可以使用系统库,或者使用 bundle 的 source,这个方式就可以在不改变你自己 target 的 CMakeLists.txt 的前提下,更加方便的自动变换。

希望有更多的库使用这种方式直接提供 Find[Package].cmake 呢。

另外实际上,如果你采用 cmake 自动 export 的方式,也可以直接获得这样的效果。暂且挖一个坑,下一次再来说一下 install(EXPORT …) 的故事。

This entry was posted in cmake and tagged . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

Note: Commenter is allowed to use '@User+blank' to automatically notify your reply to other commenter. e.g, if ABC is one of commenter of this post, then write '@ABC '(exclude ') will automatically send your comment to ABC. Using '@all ' to notify all previous commenters. Be sure that the value of User should exactly match with commenter's name (case sensitive).