用 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).

This site uses Akismet to reduce spam. Learn how your comment data is processed.