虽然有越来越多的包提供 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 …) 的故事。