从 `cmake` 使用 `pkg-config` 的正确方法是什么?

What is the proper way to use `pkg-config` from `cmake`?

网上找了很多这样的代码:

include(FindPkgConfig)
pkg_search_module(SDL2 REQUIRED sdl2)

target_include_directories(app SYSTEM PUBLIC ${SDL2_INCLUDE_DIRS})
target_link_libraries(app ${SDL2_LIBRARIES})

然而,这似乎是错误的做法,因为它只使用包含目录和库,但忽略了定义、库路径和其他可能由 pkg-config 返回的标志。

执行此操作并确保编译的 app 使用 pkg-config 返回的所有编译和 link 标志的正确方法是什么?是否有一个命令可以完成此操作,即 target_use(app SDL2)?

参考:

  1. 没有target_use这样的命令。但我知道有几个项目已经编写了这样的命令供内部使用。但是每个项目都想传递额外的标志或定义,因此在一般的 CMake 中拥有它是没有意义的。没有它的另一个原因是像 Eigen 这样的 C++ 模板化库,没有库,但你只有一堆包含文件。

  2. 描述的方式往往是正确的。某些库可能会有所不同,那么您必须添加 _LDFLAGS_CFLAGS。没有 target_use 的另一个原因。如果它对您不起作用,请提出一个关于 SDL2 或您要使用的任何库的新问题。

首先,电话:

include(FindPkgConfig)

应替换为:

find_package(PkgConfig)

find_package() 调用更加灵活,并允许使用 REQUIRED 等选项,这些选项可以自动执行必须手动执行 include() 的操作。

其次,应尽可能避免手动调用pkg-config。 CMake 带有一组丰富的包定义,可在 /usr/share/cmake-3.0/Modules/Find*cmake 下的 Linux 中找到。与对 pkg_search_module().

的原始调用相比,这些为用户提供了更多选项和选择。

至于提到的假设 target_use() 命令,CMake 已经以某种方式内置了 PUBLIC|PRIVATE|INTERFACE。像 target_include_directories(mytarget PUBLIC ...) 这样的调用会导致 include 目录在每个使用 mytarget 的目标中自动使用,例如target_link_libraries(myapp mytarget)。然而,这种机制似乎只适用于在 CMakeLists.txt 文件中创建的库,不适用于使用 pkg_search_module() 获取的库。调用 add_library(bar SHARED IMPORTED) 可能用于此目的,但我还没有研究过。

至于主要问题,这在大多数情况下都有效:

find_package(PkgConfig REQUIRED)
pkg_check_modules(SDL2 REQUIRED sdl2)
...
target_link_libraries(testapp ${SDL2_LIBRARIES})
target_include_directories(testapp PUBLIC ${SDL2_INCLUDE_DIRS})
target_compile_options(testapp PUBLIC ${SDL2_CFLAGS_OTHER})

SDL2_CFLAGS_OTHER 包含成功编译所需的定义和其他标志。然而,标志 SDL2_LIBRARY_DIRSSDL2_LDFLAGS_OTHER 仍然被忽略,不知道这会成为一个问题的频率。

这里有更多文档http://www.cmake.org/cmake/help/v3.0/module/FindPkgConfig.html

如果您还想从库中添加定义,可以使用 add_definitions 指令。可以找到文档 here,以及更多添加编译器标志的方法。

以下代码片段使用此指令将 GTKGL 添加到项目中:

pkg_check_modules(GTKGL REQUIRED gtkglext-1.0)
include_directories(${GTKGL_INCLUDE_DIRS})
link_directories(${GTKGL_LIBRARY_DIRS})
add_definitions(${GTKGL_CFLAGS_OTHER})
set(LIBS ${LIBS} ${GTKGL_LIBRARIES})

target_link_libraries([insert name of program] ${LIBS})

很少有人只需要 link 使用 SDL2。当前流行的答案使用 pkg_search_module() 检查给定模块并使用第一个工作模块。

您更有可能希望 link 使用 SDL2 和 SDL2_Mixer 以及 SDL2_TTF,等等... pkg_check_modules() 检查所有给定的模块。

# sdl2 linking variables
find_package(PkgConfig REQUIRED)
pkg_check_modules(SDL2 REQUIRED sdl2 SDL2_ttf SDL2_mixer SDL2_image)

# your app
file(GLOB SRC "my_app/*.c")
add_executable(my_app ${SRC})
target_link_libraries(my_app ${SDL2_LIBRARIES})
target_include_directories(my_app PUBLIC ${SDL2_INCLUDE_DIRS})
target_compile_options(my_app PUBLIC ${SDL2_CFLAGS_OTHER})

免责声明:如果我有足够的 Whosebug 街头信誉,我会简单地评论 Grumbel 的自我回答。

大多数可用答案无法为 pkg-config 库配置 headers。在思考 Documentation for FindPkgConfig 之后,我想出了一个解决方案,它还提供了这些:

include(FindPkgConfig)
if(NOT PKG_CONFIG_FOUND)
  message(FATAL_ERROR "pkg-config not found!" )
endif()
 
pkg_check_modules(<some-lib> REQUIRED IMPORTED_TARGET <some-lib>)
 
target_link_libraries(<my-target> PkgConfig::<some-lib>)

(相应地用你的目标代替 <my-target> 并用任何库代替 <some-lib>)

IMPORTED_TARGET 选项似乎是关键,它使 PkgConfig:: 命名空间下的所有内容都可用。这就是所需要的,也是应该所需要的。

如果您以非常正常的方式使用 cmake 和 pkg-config,则此解决方案有效。

但是,如果您有一个库存在于某个开发目录中(例如 /home/me/hack/lib),则使用此处看到的其他方法无法配置链接器路径。在典型安装位置下找不到的库会导致链接器错误,如 /usr/bin/ld: cannot find -lmy-hacking-library-1.0。此解决方案修复了该情况下的链接器错误。

另一个问题可能是pkg-config文件没有安装到正常位置,需要使用PKG_CONFIG_PATH环境变量添加项目的pkg-config路径,而cmake是运行(请参阅与此相关的其他 Stack Overflow 问题)。当您使用正确的 pkg-config 路径时,此解决方案也能正常工作。

使用IMPORTED_TARGET是解决上述问题的关键。此解决方案是对 的改进,归结为工作 CMakeLists.txt:

的最终版本
cmake_minimum_required(VERSION 3.14)
project(ya-project C)

# the `pkg_check_modules` function is created with this call
find_package(PkgConfig REQUIRED) 

# these calls create special `PkgConfig::<MODULE>` variables
pkg_check_modules(MY_PKG REQUIRED IMPORTED_TARGET any-package)
pkg_check_modules(YOUR_PKG REQUIRED IMPORTED_TARGET ya-package)

add_executable(program-name file.c ya.c)

target_link_libraries(program-name PUBLIC
        PkgConfig::MY_PKG
        PkgConfig::YOUR_PKG)

请注意,target_link_libraries 不仅仅更改链接器命令。它还会传播指定目标的其他 PUBLIC 属性,例如编译器标志、编译器定义、包含路径等,因此请谨慎使用 PUBLIC 关键字。