如何优雅的使用CMake中的FindPkgConfig模块
背景
如果你遇到下面的场景,那么FindPkgConfig模块可以用来解决我们引用上游库的问题。
- 上游库没有提供CMake的配置文件。
- CMake没有提供相应的查找模块,即
Find<PackageName>.cmake
的文件。 - 上游库提供了pkg-config使用的
.pc
文件。
如果上面三个条件都不满足,不用担心,可以参考《Linux中使用CMake导入第三方开发库》导入上游库。
FindPkgConfig模块介绍
FindPkgConfig模块提供了pkg_get_variable()
, pkg_check_modules()
和pkg_search_module()
三个命令。普通用户还是接触后两个命令最多。
pkg_check_modules()
和pkg_search_module()
的签名一样。不同之处在于pkg_check_modules()
要求每个<moduleSpec>
都满足要求,才认为命令执行成功,而pkg_search_module()
要求只要有一个<moduleSpec>
满足要求,命令就算执行成功。
pkg_check_modules(<prefix>[REQUIRED] [QUIET][NO_CMAKE_PATH][NO_CMAKE_ENVIRONMENT_PATH][IMPORTED_TARGET [GLOBAL]]<moduleSpec> [<moduleSpec>...])pkg_search_module(<prefix>[REQUIRED] [QUIET][NO_CMAKE_PATH][NO_CMAKE_ENVIRONMENT_PATH][IMPORTED_TARGET [GLOBAL]]<moduleSpec> [<moduleSpec>...])
举一个实际例子,说明pkg_check_modules()
和pkg_search_module()
之间的差异。
include(FindPkgConfig)
pkg_check_modules(CHECK_DEPS REQUIRED gtk+-3.0 glib-2.0)# 必须gtk+-3.0和glib-2.0都存在,CHECK_DEPS_FOUND的值才为TRUE
if(CHECK_DEPS_FOUND)message(STATUS "找到 GTK3 和 Glib2")
else()message(WARNING "未找到依赖库!")
endif()# 只要gtk+-3.0或glib-2.0存在,SEARCH_DEPS_FOUND的值为TRUE
pkg_search_modules(SEARCH_DEPS REQUIRED gtk+-3.0 glib-2.0)if(SEARCH_DEPS_FOUND)message(STATUS "找到 GTK3 或 Glib2")
else()message(WARNING "未找到依赖库!")
endif()
如果需要检测的<moduleSpec>
只有一个,那这两个命令的实际使用效果是一样的。
基本用法
以pkg_check_modules()
为例,大部分的用法如下所示。借助该模块提供的变量<XXX>_LIBRARY_DIRS
,<XXX>_LIBRARIES
和<XXX>_INCLUDE_DIRS
,在项目中使用上游库。
cmake_minimum_required(VERSION 3.10)
project(MyGTKApp)include(FindPkgConfig)
pkg_check_modules(GTK3 REQUIRED gtk+-3.0>=3.18)add_executable(my_app main.c)
# 包含头文件路径
target_include_directories(my_app PRIVATE ${GTK3_INCLUDE_DIRS})
# 链接库路径和库文件
target_link_directories(my_app PRIVATE ${GTK3_LIBRARY_DIRS})
target_link_libraries(my_app PRIVATE ${GTK3_LIBRARIES})
进阶用法
其实,pkg_check_modules()
提供了符合CMake风格的用法,使用IMPORTED_TARGET
参数将上游库以PkgConfig::<prefix>
的名称,作为一个导入目标(imported target),直接使用target_link_libraries()
命令链接PkgConfig::<prefix>
即可。可以与上面的基本用法对比,写法更加简洁、专业。
如果我们需要这个导入的上游库在整个项目中可见(可用),可以在IMPORTED_TARGET
后追加一个参数GLOBAL
cmake_minimum_required(VERSION 3.13)
project(MyGTKApp)include(FindPkgConfig)
pkg_check_modules(GTK3 REQUIRED IMPORTED_TARGET [GLOBAL] gtk+-3.0>=3.18)add_executable(my_app main.c)
target_link_libraries(my_app PRIVATE PkgConfig::GTK3)
IMPORTED_TARGET的内部实现
查看/usr/share/cmake-3.22/Modules/FindPkgConfig.cmake
这个文件的_pkg_create_imp_target
函数,我们可以看到使用IMPORTED_TARGET
时,CMake到底做了什么。源码一看,原来也是简单命令的组合,只是CMake帮我们写了很多。
# create an imported target from all the information returned by pkg-config
function(_pkg_create_imp_target _prefix _imp_target_global)if (NOT TARGET PkgConfig::${_prefix})if(${_imp_target_global})set(_global_opt "GLOBAL")else()unset(_global_opt)endif()add_library(PkgConfig::${_prefix} INTERFACE IMPORTED ${_global_opt})if(${_prefix}_INCLUDE_DIRS)set_property(TARGET PkgConfig::${_prefix} PROPERTYINTERFACE_INCLUDE_DIRECTORIES "${${_prefix}_INCLUDE_DIRS}")endif()if(${_prefix}_LINK_LIBRARIES)set_property(TARGET PkgConfig::${_prefix} PROPERTYINTERFACE_LINK_LIBRARIES "${${_prefix}_LINK_LIBRARIES}")endif()if(${_prefix}_LDFLAGS_OTHER)set_property(TARGET PkgConfig::${_prefix} PROPERTYINTERFACE_LINK_OPTIONS "${${_prefix}_LDFLAGS_OTHER}")endif()if(${_prefix}_CFLAGS_OTHER)set_property(TARGET PkgConfig::${_prefix} PROPERTYINTERFACE_COMPILE_OPTIONS "${${_prefix}_CFLAGS_OTHER}")endif()endif()
endfunction()
参考资料
https://cmake.org/cmake/help/latest/module/FindPkgConfig.html
最后
分享CMake使用中的小技巧。如果文章对您有帮助,不妨关注、收藏和转发,感谢。